From 9a8dd6651762c5557f5f944e34de6001a0c211a4 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 20 Dec 2023 11:23:25 -0500 Subject: [PATCH] Fix having picker & inspector active at the same time Related feedback: https://github.com/uBlockOrigin/uBlock-issues/issues/3004#issuecomment-1863610146 --- src/js/scriptlets/dom-inspector.js | 40 +++++---- src/js/scriptlets/epicker.js | 138 ++++++++++++++--------------- 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index 469a7a4e4..b5317d536 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -31,28 +31,26 @@ /******************************************************************************/ if ( typeof vAPI !== 'object' ) { return; } +if ( typeof vAPI === null ) { 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 let blueNodes = []; const roRedNodes = new Map(); // node => current cosmetic filter 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 roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle) const reHasCSSCombinators = /[ >+~]/; /******************************************************************************/ -//const getNodeId = node => nodeToIdMap.get(node) || 0; - -/******************************************************************************/ - const domLayout = (( ) => { const skipTagNames = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style', 'title' @@ -670,11 +668,13 @@ const shutdownInspector = ( ) => { passive: true, }); contentInspectorChannel.shutdown(); + if ( inspectorFrame ) { + inspectorFrame.remove(); + inspectorFrame = null; + } vAPI.userStylesheet.remove(inspectorCSS); vAPI.userStylesheet.apply(); - if ( inspectorFrame === null ) { return; } - inspectorFrame.remove(); - inspectorFrame = null; + vAPI.inspectorFrame = false; }; /******************************************************************************/ @@ -826,22 +826,22 @@ const contentInspectorChannel = (( ) => { if ( inspectorArgs === null ) { return; } return new Promise(resolve => { const iframe = document.createElement('iframe'); - iframe.setAttribute(vAPI.sessionId, ''); + iframe.setAttribute(inspectorUniqueId, ''); document.documentElement.append(iframe); iframe.addEventListener('load', ( ) => { - iframe.setAttribute(`${vAPI.sessionId}-loaded`, ''); + iframe.setAttribute(`${inspectorUniqueId}-loaded`, ''); const channel = new MessageChannel(); toFramePort = channel.port1; toFramePort.onmessage = ev => { const msg = ev.data || {}; if ( msg.what !== 'startInspector' ) { return; } - resolve(iframe); }; iframe.contentWindow.postMessage( { what: 'startInspector' }, inspectorArgs.inspectorURL, [ channel.port2 ] ); + resolve(iframe); }, { once: true }); iframe.contentWindow.location = inspectorArgs.inspectorURL; }); @@ -859,26 +859,32 @@ const inspectorCSSStyle = [ 'box-shadow: none', 'color-scheme: light dark', 'display: block', + 'filter: none', 'height: 100%', 'left: 0', 'margin: 0', + 'max-height: none', + 'max-width: none', + 'min-height: unset', + 'min-width: unset', 'opacity: 1', 'outline: 0', 'padding: 0', 'pointer-events: none', 'position: fixed', 'top: 0', - 'visibility: visible', + 'transform: none', + 'visibility: hidden', 'width: 100%', 'z-index: 2147483647', '' ].join(' !important;\n'); const inspectorCSS = ` -:root > [${vAPI.sessionId}] { +:root > [${inspectorUniqueId}] { ${inspectorCSSStyle} } -:root > [${vAPI.sessionId}-loaded] { +:root > [${inspectorUniqueId}-loaded] { visibility: visible !important; } `; diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index f49332135..80489e81d 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -30,18 +30,13 @@ /******************************************************************************/ -if ( typeof vAPI !== 'object' || vAPI === null ) { - return; -} +if ( typeof vAPI !== 'object' ) { return; } +if ( typeof vAPI === null ) { return; } -/******************************************************************************/ +if ( vAPI.pickerFrame ) { return; } +vAPI.pickerFrame = true; -const epickerId = vAPI.randomToken(); - -let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`); -if ( pickerRoot !== null ) { return; } - -let pickerBootArgs; +const pickerUniqueId = vAPI.randomToken(); const reCosmeticAnchor = /^#(\$|\?|\$\?)?#/; @@ -128,7 +123,7 @@ const highlightElements = function(elems, force) { const islands = []; for ( const elem of elems ) { - if ( elem === pickerRoot ) { continue; } + if ( elem === pickerFrame ) { continue; } targetElements.push(elem); const rect = getElementBoundingClientRect(elem); // Ignore offscreen areas @@ -554,10 +549,10 @@ const filtersFrom = function(x, y) { // https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/ // Extract network candidates first. if ( typeof x === 'number' ) { - const magicAttr = `${vAPI.sessionId}-clickblind`; - pickerRoot.setAttribute(magicAttr, ''); + const magicAttr = `${pickerUniqueId}-clickblind`; + pickerFrame.setAttribute(magicAttr, ''); const elems = document.elementsFromPoint(x, y); - pickerRoot.removeAttribute(magicAttr); + pickerFrame.removeAttribute(magicAttr); for ( const elem of elems ) { netFilterFromElement(elem); } @@ -737,7 +732,7 @@ const filterToDOMInterface = (( ) => { } const out = []; for ( const elem of elems ) { - if ( elem === pickerRoot ) { continue; } + if ( elem === pickerFrame ) { continue; } out.push({ elem, raw, style: vAPI.hideStyle }); } return out; @@ -815,7 +810,7 @@ const filterToDOMInterface = (( ) => { if ( Array.isArray(lastResultset) === false ) { return; } const rootElem = document.documentElement; for ( const { elem, style } of lastResultset ) { - if ( elem === pickerRoot ) { continue; } + if ( elem === pickerFrame ) { continue; } if ( style === undefined ) { continue; } if ( elem === rootElem && style === vAPI.hideStyle ) { continue; } let styleToken = vAPI.epickerStyleProxies.get(style); @@ -932,9 +927,9 @@ const elementFromPoint = (( ) => { } else { return null; } - if ( !pickerRoot ) { return null; } - const magicAttr = `${vAPI.sessionId}-clickblind`; - pickerRoot.setAttribute(magicAttr, ''); + if ( !pickerFrame ) { return null; } + const magicAttr = `${pickerUniqueId}-clickblind`; + pickerFrame.setAttribute(magicAttr, ''); let elem = document.elementFromPoint(x, y); if ( elem === null || /* to skip following tests */ @@ -948,7 +943,7 @@ const elementFromPoint = (( ) => { elem = null; } // https://github.com/uBlockOrigin/uBlock-issues/issues/380 - pickerRoot.removeAttribute(magicAttr); + pickerFrame.removeAttribute(magicAttr); return elem; }; })(); @@ -1064,7 +1059,7 @@ const onViewportChanged = function() { // Auto-select a specific target, if any, and if possible const startPicker = function() { - pickerRoot.focus(); + pickerFrame.focus(); self.addEventListener('scroll', onViewportChanged, { passive: true }); self.addEventListener('resize', onViewportChanged, { passive: true }); @@ -1101,7 +1096,7 @@ const startPicker = function() { if ( attr === undefined ) { return; } const elems = document.getElementsByTagName(tagName); for ( const elem of elems ) { - if ( elem === pickerRoot ) { continue; } + if ( elem === pickerFrame ) { continue; } const srcs = resourceURLsFromElement(elem); if ( (srcs.length !== 0 && srcs.includes(url) === false) || @@ -1140,19 +1135,22 @@ const quitPicker = function() { self.removeEventListener('resize', onViewportChanged, { passive: true }); self.removeEventListener('keydown', onKeyPressed, true); vAPI.shutdown.remove(quitPicker); - if ( pickerFramePort !== null ) { + if ( pickerFramePort ) { pickerFramePort.close(); pickerFramePort = null; } - if ( pickerRoot !== null ) { - pickerRoot.remove(); - pickerRoot = null; + if ( pickerFrame ) { + pickerFrame.remove(); + pickerFrame = null; } vAPI.userStylesheet.remove(pickerCSS); vAPI.userStylesheet.apply(); + vAPI.pickerFrame = false; self.focus(); }; +vAPI.shutdown.add(quitPicker); + /******************************************************************************/ const onDialogMessage = function(msg) { @@ -1232,21 +1230,6 @@ const onDialogMessage = function(msg) { // of the iframe, and cannot interfere with its style properties. However the // 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. const noCosmeticFiltering = vAPI.domFilterer instanceof Object === false || @@ -1285,13 +1268,13 @@ const pickerCSSStyle = [ const pickerCSS = ` -:root > [${vAPI.sessionId}] { +:root > [${pickerUniqueId}] { ${pickerCSSStyle} } -:root > [${vAPI.sessionId}-loaded] { +:root > [${pickerUniqueId}-loaded] { visibility: visible !important; } -:root [${vAPI.sessionId}-clickblind] { +:root [${pickerUniqueId}-clickblind] { pointer-events: none !important; } `; @@ -1299,38 +1282,53 @@ const pickerCSS = ` vAPI.userStylesheet.add(pickerCSS); vAPI.userStylesheet.apply(); -pickerRoot = document.createElement('iframe'); -pickerRoot.setAttribute(vAPI.sessionId, ''); -document.documentElement.append(pickerRoot); - -vAPI.shutdown.add(quitPicker); - +let pickerBootArgs; 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); - url.searchParams.set('epid', epickerId); if ( pickerBootArgs.zap ) { url.searchParams.set('zap', '1'); } - pickerRoot.addEventListener('load', ( ) => { - const channel = new MessageChannel(); - pickerFramePort = channel.port1; - pickerFramePort.onmessage = ev => { - const msg = ev.data || {}; - onDialogMessage(msg); - }; - pickerFramePort.onmessageerror = ( ) => { - quitPicker(); - }; - pickerRoot.setAttribute(`${vAPI.sessionId}-loaded`, ''); - pickerRoot.contentWindow.postMessage( - { what: 'epickerStart' }, - url.href, - [ channel.port2 ] - ); - }, { once: true }); - pickerRoot.contentWindow.location = url.href; + 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(); + pickerFramePort = channel.port1; + pickerFramePort.onmessage = ev => { + onDialogMessage(ev.data || {}); + }; + pickerFramePort.onmessageerror = ( ) => { + quitPicker(); + }; + iframe.contentWindow.postMessage( + { what: 'epickerStart' }, + url.href, + [ channel.port2 ] + ); + resolve(iframe); + }, { once: true }); + iframe.contentWindow.location = url.href; + }); +}; + +let pickerFrame = await bootstrap(); +if ( Boolean(pickerFrame) === false ) { + quitPicker(); } /******************************************************************************/