Fix having picker & inspector active at the same time

Related feedback:
https://github.com/uBlockOrigin/uBlock-issues/issues/3004#issuecomment-1863610146
This commit is contained in:
Raymond Hill 2023-12-20 11:23:25 -05:00
parent 698bec4f5b
commit 9a8dd66517
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
2 changed files with 91 additions and 87 deletions

View file

@ -31,28 +31,26 @@
/******************************************************************************/ /******************************************************************************/
if ( typeof vAPI !== 'object' ) { return; } if ( typeof vAPI !== 'object' ) { return; }
if ( typeof vAPI === null ) { return; }
if ( vAPI.domFilterer instanceof Object === false ) { return; } if ( vAPI.domFilterer instanceof Object === false ) { return; }
if ( document.querySelector(`iframe[${vAPI.sessionId}]`) !== null ) { return; }
/******************************************************************************/ if ( vAPI.inspectorFrame ) { return; }
/******************************************************************************/ vAPI.inspectorFrame = true;
const inspectorUniqueId = vAPI.randomToken();
const nodeToIdMap = new WeakMap(); // No need to iterate const nodeToIdMap = new WeakMap(); // No need to iterate
let blueNodes = []; let blueNodes = [];
const roRedNodes = new Map(); // node => current cosmetic filter const roRedNodes = new Map(); // node => current cosmetic filter
const rwRedNodes = new Set(); // node => new cosmetic filter (toggle node) const rwRedNodes = new Set(); // node => new cosmetic filter (toggle node)
//var roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle)
const rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter) const rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter)
//const roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle)
const reHasCSSCombinators = /[ >+~]/; const reHasCSSCombinators = /[ >+~]/;
/******************************************************************************/ /******************************************************************************/
//const getNodeId = node => nodeToIdMap.get(node) || 0;
/******************************************************************************/
const domLayout = (( ) => { const domLayout = (( ) => {
const skipTagNames = new Set([ const skipTagNames = new Set([
'br', 'head', 'link', 'meta', 'script', 'style', 'title' 'br', 'head', 'link', 'meta', 'script', 'style', 'title'
@ -670,11 +668,13 @@ const shutdownInspector = ( ) => {
passive: true, passive: true,
}); });
contentInspectorChannel.shutdown(); contentInspectorChannel.shutdown();
vAPI.userStylesheet.remove(inspectorCSS); if ( inspectorFrame ) {
vAPI.userStylesheet.apply();
if ( inspectorFrame === null ) { return; }
inspectorFrame.remove(); inspectorFrame.remove();
inspectorFrame = null; inspectorFrame = null;
}
vAPI.userStylesheet.remove(inspectorCSS);
vAPI.userStylesheet.apply();
vAPI.inspectorFrame = false;
}; };
/******************************************************************************/ /******************************************************************************/
@ -826,22 +826,22 @@ const contentInspectorChannel = (( ) => {
if ( inspectorArgs === null ) { return; } if ( inspectorArgs === null ) { return; }
return new Promise(resolve => { return new Promise(resolve => {
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.setAttribute(vAPI.sessionId, ''); iframe.setAttribute(inspectorUniqueId, '');
document.documentElement.append(iframe); document.documentElement.append(iframe);
iframe.addEventListener('load', ( ) => { iframe.addEventListener('load', ( ) => {
iframe.setAttribute(`${vAPI.sessionId}-loaded`, ''); iframe.setAttribute(`${inspectorUniqueId}-loaded`, '');
const channel = new MessageChannel(); const channel = new MessageChannel();
toFramePort = channel.port1; toFramePort = channel.port1;
toFramePort.onmessage = ev => { toFramePort.onmessage = ev => {
const msg = ev.data || {}; const msg = ev.data || {};
if ( msg.what !== 'startInspector' ) { return; } if ( msg.what !== 'startInspector' ) { return; }
resolve(iframe);
}; };
iframe.contentWindow.postMessage( iframe.contentWindow.postMessage(
{ what: 'startInspector' }, { what: 'startInspector' },
inspectorArgs.inspectorURL, inspectorArgs.inspectorURL,
[ channel.port2 ] [ channel.port2 ]
); );
resolve(iframe);
}, { once: true }); }, { once: true });
iframe.contentWindow.location = inspectorArgs.inspectorURL; iframe.contentWindow.location = inspectorArgs.inspectorURL;
}); });
@ -859,26 +859,32 @@ const inspectorCSSStyle = [
'box-shadow: none', 'box-shadow: none',
'color-scheme: light dark', 'color-scheme: light dark',
'display: block', 'display: block',
'filter: none',
'height: 100%', 'height: 100%',
'left: 0', 'left: 0',
'margin: 0', 'margin: 0',
'max-height: none',
'max-width: none',
'min-height: unset',
'min-width: unset',
'opacity: 1', 'opacity: 1',
'outline: 0', 'outline: 0',
'padding: 0', 'padding: 0',
'pointer-events: none', 'pointer-events: none',
'position: fixed', 'position: fixed',
'top: 0', 'top: 0',
'visibility: visible', 'transform: none',
'visibility: hidden',
'width: 100%', 'width: 100%',
'z-index: 2147483647', 'z-index: 2147483647',
'' ''
].join(' !important;\n'); ].join(' !important;\n');
const inspectorCSS = ` const inspectorCSS = `
:root > [${vAPI.sessionId}] { :root > [${inspectorUniqueId}] {
${inspectorCSSStyle} ${inspectorCSSStyle}
} }
:root > [${vAPI.sessionId}-loaded] { :root > [${inspectorUniqueId}-loaded] {
visibility: visible !important; visibility: visible !important;
} }
`; `;

View file

@ -30,18 +30,13 @@
/******************************************************************************/ /******************************************************************************/
if ( typeof vAPI !== 'object' || vAPI === null ) { if ( typeof vAPI !== 'object' ) { return; }
return; if ( typeof vAPI === null ) { return; }
}
/******************************************************************************/ if ( vAPI.pickerFrame ) { return; }
vAPI.pickerFrame = true;
const epickerId = vAPI.randomToken(); const pickerUniqueId = vAPI.randomToken();
let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`);
if ( pickerRoot !== null ) { return; }
let pickerBootArgs;
const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/; const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/;
@ -128,7 +123,7 @@ const highlightElements = function(elems, force) {
const islands = []; const islands = [];
for ( const elem of elems ) { for ( const elem of elems ) {
if ( elem === pickerRoot ) { continue; } if ( elem === pickerFrame ) { continue; }
targetElements.push(elem); targetElements.push(elem);
const rect = getElementBoundingClientRect(elem); const rect = getElementBoundingClientRect(elem);
// Ignore offscreen areas // Ignore offscreen areas
@ -554,10 +549,10 @@ const filtersFrom = function(x, y) {
// https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/ // https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/
// Extract network candidates first. // Extract network candidates first.
if ( typeof x === 'number' ) { if ( typeof x === 'number' ) {
const magicAttr = `${vAPI.sessionId}-clickblind`; const magicAttr = `${pickerUniqueId}-clickblind`;
pickerRoot.setAttribute(magicAttr, ''); pickerFrame.setAttribute(magicAttr, '');
const elems = document.elementsFromPoint(x, y); const elems = document.elementsFromPoint(x, y);
pickerRoot.removeAttribute(magicAttr); pickerFrame.removeAttribute(magicAttr);
for ( const elem of elems ) { for ( const elem of elems ) {
netFilterFromElement(elem); netFilterFromElement(elem);
} }
@ -737,7 +732,7 @@ const filterToDOMInterface = (( ) => {
} }
const out = []; const out = [];
for ( const elem of elems ) { for ( const elem of elems ) {
if ( elem === pickerRoot ) { continue; } if ( elem === pickerFrame ) { continue; }
out.push({ elem, raw, style: vAPI.hideStyle }); out.push({ elem, raw, style: vAPI.hideStyle });
} }
return out; return out;
@ -815,7 +810,7 @@ const filterToDOMInterface = (( ) => {
if ( Array.isArray(lastResultset) === false ) { return; } if ( Array.isArray(lastResultset) === false ) { return; }
const rootElem = document.documentElement; const rootElem = document.documentElement;
for ( const { elem, style } of lastResultset ) { for ( const { elem, style } of lastResultset ) {
if ( elem === pickerRoot ) { continue; } if ( elem === pickerFrame ) { continue; }
if ( style === undefined ) { continue; } if ( style === undefined ) { continue; }
if ( elem === rootElem && style === vAPI.hideStyle ) { continue; } if ( elem === rootElem && style === vAPI.hideStyle ) { continue; }
let styleToken = vAPI.epickerStyleProxies.get(style); let styleToken = vAPI.epickerStyleProxies.get(style);
@ -932,9 +927,9 @@ const elementFromPoint = (( ) => {
} else { } else {
return null; return null;
} }
if ( !pickerRoot ) { return null; } if ( !pickerFrame ) { return null; }
const magicAttr = `${vAPI.sessionId}-clickblind`; const magicAttr = `${pickerUniqueId}-clickblind`;
pickerRoot.setAttribute(magicAttr, ''); pickerFrame.setAttribute(magicAttr, '');
let elem = document.elementFromPoint(x, y); let elem = document.elementFromPoint(x, y);
if ( if (
elem === null || /* to skip following tests */ elem === null || /* to skip following tests */
@ -948,7 +943,7 @@ const elementFromPoint = (( ) => {
elem = null; elem = null;
} }
// https://github.com/uBlockOrigin/uBlock-issues/issues/380 // https://github.com/uBlockOrigin/uBlock-issues/issues/380
pickerRoot.removeAttribute(magicAttr); pickerFrame.removeAttribute(magicAttr);
return elem; return elem;
}; };
})(); })();
@ -1064,7 +1059,7 @@ const onViewportChanged = function() {
// Auto-select a specific target, if any, and if possible // Auto-select a specific target, if any, and if possible
const startPicker = function() { const startPicker = function() {
pickerRoot.focus(); pickerFrame.focus();
self.addEventListener('scroll', onViewportChanged, { passive: true }); self.addEventListener('scroll', onViewportChanged, { passive: true });
self.addEventListener('resize', onViewportChanged, { passive: true }); self.addEventListener('resize', onViewportChanged, { passive: true });
@ -1101,7 +1096,7 @@ const startPicker = function() {
if ( attr === undefined ) { return; } if ( attr === undefined ) { return; }
const elems = document.getElementsByTagName(tagName); const elems = document.getElementsByTagName(tagName);
for ( const elem of elems ) { for ( const elem of elems ) {
if ( elem === pickerRoot ) { continue; } if ( elem === pickerFrame ) { continue; }
const srcs = resourceURLsFromElement(elem); const srcs = resourceURLsFromElement(elem);
if ( if (
(srcs.length !== 0 && srcs.includes(url) === false) || (srcs.length !== 0 && srcs.includes(url) === false) ||
@ -1140,19 +1135,22 @@ const quitPicker = function() {
self.removeEventListener('resize', onViewportChanged, { passive: true }); self.removeEventListener('resize', onViewportChanged, { passive: true });
self.removeEventListener('keydown', onKeyPressed, true); self.removeEventListener('keydown', onKeyPressed, true);
vAPI.shutdown.remove(quitPicker); vAPI.shutdown.remove(quitPicker);
if ( pickerFramePort !== null ) { if ( pickerFramePort ) {
pickerFramePort.close(); pickerFramePort.close();
pickerFramePort = null; pickerFramePort = null;
} }
if ( pickerRoot !== null ) { if ( pickerFrame ) {
pickerRoot.remove(); pickerFrame.remove();
pickerRoot = null; pickerFrame = null;
} }
vAPI.userStylesheet.remove(pickerCSS); vAPI.userStylesheet.remove(pickerCSS);
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();
vAPI.pickerFrame = false;
self.focus(); self.focus();
}; };
vAPI.shutdown.add(quitPicker);
/******************************************************************************/ /******************************************************************************/
const onDialogMessage = function(msg) { const onDialogMessage = function(msg) {
@ -1232,21 +1230,6 @@ const onDialogMessage = function(msg) {
// of the iframe, and cannot interfere with its style properties. However the // of the iframe, and cannot interfere with its style properties. However the
// page can remove the iframe. // page can remove the iframe.
// fetch/process picker arguments.
{
pickerBootArgs = await vAPI.messaging.send('elementPicker', {
what: 'elementPickerArguments',
});
if ( typeof pickerBootArgs !== 'object' ) { return; }
if ( pickerBootArgs === null ) { return; }
// Restore net filter union data if origin is the same.
const eprom = pickerBootArgs.eprom || null;
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
}
}
// The DOM filterer will not be present when cosmetic filtering is disabled. // The DOM filterer will not be present when cosmetic filtering is disabled.
const noCosmeticFiltering = const noCosmeticFiltering =
vAPI.domFilterer instanceof Object === false || vAPI.domFilterer instanceof Object === false ||
@ -1285,13 +1268,13 @@ const pickerCSSStyle = [
const pickerCSS = ` const pickerCSS = `
:root > [${vAPI.sessionId}] { :root > [${pickerUniqueId}] {
${pickerCSSStyle} ${pickerCSSStyle}
} }
:root > [${vAPI.sessionId}-loaded] { :root > [${pickerUniqueId}-loaded] {
visibility: visible !important; visibility: visible !important;
} }
:root [${vAPI.sessionId}-clickblind] { :root [${pickerUniqueId}-clickblind] {
pointer-events: none !important; pointer-events: none !important;
} }
`; `;
@ -1299,38 +1282,53 @@ const pickerCSS = `
vAPI.userStylesheet.add(pickerCSS); vAPI.userStylesheet.add(pickerCSS);
vAPI.userStylesheet.apply(); vAPI.userStylesheet.apply();
pickerRoot = document.createElement('iframe'); let pickerBootArgs;
pickerRoot.setAttribute(vAPI.sessionId, '');
document.documentElement.append(pickerRoot);
vAPI.shutdown.add(quitPicker);
let pickerFramePort = null; let pickerFramePort = null;
{ const bootstrap = async ( ) => {
pickerBootArgs = await vAPI.messaging.send('elementPicker', {
what: 'elementPickerArguments',
});
if ( typeof pickerBootArgs !== 'object' ) { return; }
if ( pickerBootArgs === null ) { return; }
// Restore net filter union data if origin is the same.
const eprom = pickerBootArgs.eprom || null;
if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) {
lastNetFilterHostname = eprom.lastNetFilterHostname || '';
lastNetFilterUnion = eprom.lastNetFilterUnion || '';
}
const url = new URL(pickerBootArgs.pickerURL); const url = new URL(pickerBootArgs.pickerURL);
url.searchParams.set('epid', epickerId);
if ( pickerBootArgs.zap ) { if ( pickerBootArgs.zap ) {
url.searchParams.set('zap', '1'); url.searchParams.set('zap', '1');
} }
pickerRoot.addEventListener('load', ( ) => { return new Promise(resolve => {
const iframe = document.createElement('iframe');
iframe.setAttribute(pickerUniqueId, '');
document.documentElement.append(iframe);
iframe.addEventListener('load', ( ) => {
iframe.setAttribute(`${pickerUniqueId}-loaded`, '');
const channel = new MessageChannel(); const channel = new MessageChannel();
pickerFramePort = channel.port1; pickerFramePort = channel.port1;
pickerFramePort.onmessage = ev => { pickerFramePort.onmessage = ev => {
const msg = ev.data || {}; onDialogMessage(ev.data || {});
onDialogMessage(msg);
}; };
pickerFramePort.onmessageerror = ( ) => { pickerFramePort.onmessageerror = ( ) => {
quitPicker(); quitPicker();
}; };
pickerRoot.setAttribute(`${vAPI.sessionId}-loaded`, ''); iframe.contentWindow.postMessage(
pickerRoot.contentWindow.postMessage(
{ what: 'epickerStart' }, { what: 'epickerStart' },
url.href, url.href,
[ channel.port2 ] [ channel.port2 ]
); );
resolve(iframe);
}, { once: true }); }, { once: true });
pickerRoot.contentWindow.location = url.href; iframe.contentWindow.location = url.href;
});
};
let pickerFrame = await bootstrap();
if ( Boolean(pickerFrame) === false ) {
quitPicker();
} }
/******************************************************************************/ /******************************************************************************/