summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js920
1 files changed, 920 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js b/data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js
new file mode 100644
index 0000000..1536d8a
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/scriptlets/dom-inspector.js
@@ -0,0 +1,920 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2015-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/******************************************************************************/
+/******************************************************************************/
+
+(async ( ) => {
+
+/******************************************************************************/
+
+if ( typeof vAPI !== 'object' ) { return; }
+if ( vAPI === null ) { return; }
+if ( vAPI.domFilterer instanceof Object === false ) { 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)
+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 domLayout = (( ) => {
+ const skipTagNames = new Set([
+ 'br', 'head', 'link', 'meta', 'script', 'style', 'title'
+ ]);
+ const resourceAttrNames = new Map([
+ [ 'a', 'href' ],
+ [ 'iframe', 'src' ],
+ [ 'img', 'src' ],
+ [ 'object', 'data' ]
+ ]);
+
+ let idGenerator = 1;
+
+ // This will be used to uniquely identify nodes across process.
+
+ const newNodeId = node => {
+ const nid = `n${(idGenerator++).toString(36)}`;
+ nodeToIdMap.set(node, nid);
+ return nid;
+ };
+
+ const selectorFromNode = node => {
+ const tag = node.localName;
+ let selector = CSS.escape(tag);
+ // Id
+ if ( typeof node.id === 'string' ) {
+ let str = node.id.trim();
+ if ( str !== '' ) {
+ selector += `#${CSS.escape(str)}`;
+ }
+ }
+ // Class
+ const cl = node.classList;
+ if ( cl ) {
+ for ( let i = 0; i < cl.length; i++ ) {
+ selector += `.${CSS.escape(cl[i])}`;
+ }
+ }
+ // Tag-specific attributes
+ const attr = resourceAttrNames.get(tag);
+ if ( attr !== undefined ) {
+ let str = node.getAttribute(attr) || '';
+ str = str.trim();
+ const pos = str.startsWith('data:') ? 5 : str.search(/[#?]/);
+ let sw = '';
+ if ( pos !== -1 ) {
+ str = str.slice(0, pos);
+ sw = '^';
+ }
+ if ( str !== '' ) {
+ selector += `[${attr}${sw}="${CSS.escape(str, true)}"]`;
+ }
+ }
+ return selector;
+ };
+
+ function DomRoot() {
+ this.nid = newNodeId(document.body);
+ this.lvl = 0;
+ this.sel = 'body';
+ this.cnt = 0;
+ this.filter = roRedNodes.get(document.body);
+ }
+
+ function DomNode(node, level) {
+ this.nid = newNodeId(node);
+ this.lvl = level;
+ this.sel = selectorFromNode(node);
+ this.cnt = 0;
+ this.filter = roRedNodes.get(node);
+ }
+
+ const domNodeFactory = (level, node) => {
+ const localName = node.localName;
+ if ( skipTagNames.has(localName) ) { return null; }
+ // skip uBlock's own nodes
+ if ( node === inspectorFrame ) { return null; }
+ if ( level === 0 && localName === 'body' ) {
+ return new DomRoot();
+ }
+ return new DomNode(node, level);
+ };
+
+ // Collect layout data
+
+ const getLayoutData = ( ) => {
+ const layout = [];
+ const stack = [];
+ let lvl = 0;
+ let node = document.documentElement;
+ if ( node === null ) { return layout; }
+
+ for (;;) {
+ const domNode = domNodeFactory(lvl, node);
+ if ( domNode !== null ) {
+ layout.push(domNode);
+ }
+ // children
+ if ( domNode !== null && node.firstElementChild !== null ) {
+ stack.push(node);
+ lvl += 1;
+ node = node.firstElementChild;
+ continue;
+ }
+ // sibling
+ if ( node instanceof Element ) {
+ if ( node.nextElementSibling === null ) {
+ do {
+ node = stack.pop();
+ if ( !node ) { break; }
+ lvl -= 1;
+ } while ( node.nextElementSibling === null );
+ if ( !node ) { break; }
+ }
+ node = node.nextElementSibling;
+ }
+ }
+
+ return layout;
+ };
+
+ // Descendant count for each node.
+
+ const patchLayoutData = layout => {
+ const stack = [];
+ let ptr;
+ let lvl = 0;
+ let i = layout.length;
+
+ while ( i-- ) {
+ const domNode = layout[i];
+ if ( domNode.lvl === lvl ) {
+ stack[ptr] += 1;
+ continue;
+ }
+ if ( domNode.lvl > lvl ) {
+ while ( lvl < domNode.lvl ) {
+ stack.push(0);
+ lvl += 1;
+ }
+ ptr = lvl - 1;
+ stack[ptr] += 1;
+ continue;
+ }
+ // domNode.lvl < lvl
+ const cnt = stack.pop();
+ domNode.cnt = cnt;
+ lvl -= 1;
+ ptr = lvl - 1;
+ stack[ptr] += cnt + 1;
+ }
+ return layout;
+ };
+
+ // Track and report mutations of the DOM
+
+ let mutationObserver = null;
+ let mutationTimer;
+ let addedNodelists = [];
+ let removedNodelist = [];
+
+ const previousElementSiblingId = node => {
+ let sibling = node;
+ for (;;) {
+ sibling = sibling.previousElementSibling;
+ if ( sibling === null ) { return null; }
+ if ( skipTagNames.has(sibling.localName) ) { continue; }
+ return nodeToIdMap.get(sibling);
+ }
+ };
+
+ const journalFromBranch = (root, newNodes, newNodeToIdMap) => {
+ let node = root.firstElementChild;
+ while ( node !== null ) {
+ const domNode = domNodeFactory(undefined, node);
+ if ( domNode !== null ) {
+ newNodeToIdMap.set(domNode.nid, domNode);
+ newNodes.push(node);
+ }
+ // down
+ if ( node.firstElementChild !== null ) {
+ node = node.firstElementChild;
+ continue;
+ }
+ // right
+ if ( node.nextElementSibling !== null ) {
+ node = node.nextElementSibling;
+ continue;
+ }
+ // up then right
+ for (;;) {
+ if ( node.parentElement === root ) { return; }
+ node = node.parentElement;
+ if ( node.nextElementSibling !== null ) {
+ node = node.nextElementSibling;
+ break;
+ }
+ }
+ }
+ };
+
+ const journalFromMutations = ( ) => {
+ mutationTimer = undefined;
+
+ // This is used to temporarily hold all added nodes, before resolving
+ // their node id and relative position.
+ const newNodes = [];
+ const journalEntries = [];
+ const newNodeToIdMap = new Map();
+
+ for ( const nodelist of addedNodelists ) {
+ for ( const node of nodelist ) {
+ if ( node.nodeType !== 1 ) { continue; }
+ if ( node.parentElement === null ) { continue; }
+ cosmeticFilterMapper.incremental(node);
+ const domNode = domNodeFactory(undefined, node);
+ if ( domNode !== null ) {
+ newNodeToIdMap.set(domNode.nid, domNode);
+ newNodes.push(node);
+ }
+ journalFromBranch(node, newNodes, newNodeToIdMap);
+ }
+ }
+ addedNodelists = [];
+ for ( const nodelist of removedNodelist ) {
+ for ( const node of nodelist ) {
+ if ( node.nodeType !== 1 ) { continue; }
+ const nid = nodeToIdMap.get(node);
+ if ( nid === undefined ) { continue; }
+ journalEntries.push({ what: -1, nid });
+ }
+ }
+ removedNodelist = [];
+ for ( const node of newNodes ) {
+ journalEntries.push({
+ what: 1,
+ nid: nodeToIdMap.get(node),
+ u: nodeToIdMap.get(node.parentElement),
+ l: previousElementSiblingId(node)
+ });
+ }
+
+ if ( journalEntries.length === 0 ) { return; }
+
+ contentInspectorChannel.toLogger({
+ what: 'domLayoutIncremental',
+ url: window.location.href,
+ hostname: window.location.hostname,
+ journal: journalEntries,
+ nodes: Array.from(newNodeToIdMap)
+ });
+ };
+
+ const onMutationObserved = mutationRecords => {
+ for ( const record of mutationRecords ) {
+ if ( record.addedNodes.length !== 0 ) {
+ addedNodelists.push(record.addedNodes);
+ }
+ if ( record.removedNodes.length !== 0 ) {
+ removedNodelist.push(record.removedNodes);
+ }
+ }
+ if ( mutationTimer === undefined ) {
+ mutationTimer = vAPI.setTimeout(journalFromMutations, 1000);
+ }
+ };
+
+ // API
+
+ const getLayout = ( ) => {
+ cosmeticFilterMapper.reset();
+ mutationObserver = new MutationObserver(onMutationObserved);
+ mutationObserver.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+
+ return {
+ what: 'domLayoutFull',
+ url: window.location.href,
+ hostname: window.location.hostname,
+ layout: patchLayoutData(getLayoutData())
+ };
+ };
+
+ const reset = ( ) => {
+ shutdown();
+ };
+
+ const shutdown = ( ) => {
+ if ( mutationTimer !== undefined ) {
+ clearTimeout(mutationTimer);
+ mutationTimer = undefined;
+ }
+ if ( mutationObserver !== null ) {
+ mutationObserver.disconnect();
+ mutationObserver = null;
+ }
+ addedNodelists = [];
+ removedNodelist = [];
+ };
+
+ return {
+ get: getLayout,
+ reset,
+ shutdown,
+ };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
+
+const cosmeticFilterMapper = (( ) => {
+ const nodesFromStyleTag = rootNode => {
+ const filterMap = roRedNodes;
+ const details = vAPI.domFilterer.getAllSelectors();
+
+ // Declarative selectors.
+ for ( const block of (details.declarative || []) ) {
+ for ( const selector of block.split(',\n') ) {
+ let nodes;
+ if ( reHasCSSCombinators.test(selector) ) {
+ nodes = document.querySelectorAll(selector);
+ } else {
+ if (
+ filterMap.has(rootNode) === false &&
+ rootNode.matches(selector)
+ ) {
+ filterMap.set(rootNode, selector);
+ }
+ nodes = rootNode.querySelectorAll(selector);
+ }
+ for ( const node of nodes ) {
+ if ( filterMap.has(node) ) { continue; }
+ filterMap.set(node, selector);
+ }
+ }
+ }
+
+ // Procedural selectors.
+ for ( const entry of (details.procedural || []) ) {
+ const nodes = entry.exec();
+ for ( const node of nodes ) {
+ // Upgrade declarative selector to procedural one
+ filterMap.set(node, entry.raw);
+ }
+ }
+ };
+
+ const incremental = rootNode => {
+ nodesFromStyleTag(rootNode);
+ };
+
+ const reset = ( ) => {
+ roRedNodes.clear();
+ if ( document.documentElement !== null ) {
+ incremental(document.documentElement);
+ }
+ };
+
+ const shutdown = ( ) => {
+ vAPI.domFilterer.toggle(true);
+ };
+
+ return {
+ incremental,
+ reset,
+ shutdown,
+ };
+})();
+
+/******************************************************************************/
+
+const elementsFromSelector = function(selector, context) {
+ if ( !context ) {
+ context = document;
+ }
+ if ( selector.indexOf(':') !== -1 ) {
+ const out = elementsFromSpecialSelector(selector);
+ if ( out !== undefined ) { return out; }
+ }
+ // plain CSS selector
+ try {
+ return context.querySelectorAll(selector);
+ } catch {
+ }
+ return [];
+};
+
+const elementsFromSpecialSelector = function(selector) {
+ const out = [];
+ let matches = /^(.+?):has\((.+?)\)$/.exec(selector);
+ if ( matches !== null ) {
+ let nodes;
+ try {
+ nodes = document.querySelectorAll(matches[1]);
+ } catch {
+ nodes = [];
+ }
+ for ( const node of nodes ) {
+ if ( node.querySelector(matches[2]) === null ) { continue; }
+ out.push(node);
+ }
+ return out;
+ }
+
+ matches = /^:xpath\((.+?)\)$/.exec(selector);
+ if ( matches === null ) { return; }
+ const xpr = document.evaluate(
+ matches[1],
+ document,
+ null,
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
+ null
+ );
+ let i = xpr.snapshotLength;
+ while ( i-- ) {
+ out.push(xpr.snapshotItem(i));
+ }
+ return out;
+};
+
+/******************************************************************************/
+
+const highlightElements = ( ) => {
+ const paths = [];
+
+ const path = [];
+ for ( const elem of rwRedNodes.keys() ) {
+ if ( elem === inspectorFrame ) { continue; }
+ if ( rwGreenNodes.has(elem) ) { continue; }
+ if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
+ const rect = elem.getBoundingClientRect();
+ const xl = rect.left;
+ const w = rect.width;
+ const yt = rect.top;
+ const h = rect.height;
+ const ws = w.toFixed(1);
+ const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
+ 'h' + ws +
+ 'v' + h.toFixed(1) +
+ 'h-' + ws +
+ 'z';
+ path.push(poly);
+ }
+ paths.push(path.join('') || 'M0 0');
+
+ path.length = 0;
+ for ( const elem of rwGreenNodes ) {
+ if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
+ const rect = elem.getBoundingClientRect();
+ const xl = rect.left;
+ const w = rect.width;
+ const yt = rect.top;
+ const h = rect.height;
+ const ws = w.toFixed(1);
+ const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
+ 'h' + ws +
+ 'v' + h.toFixed(1) +
+ 'h-' + ws +
+ 'z';
+ path.push(poly);
+ }
+ paths.push(path.join('') || 'M0 0');
+
+ path.length = 0;
+ for ( const elem of roRedNodes.keys() ) {
+ if ( elem === inspectorFrame ) { continue; }
+ if ( rwGreenNodes.has(elem) ) { continue; }
+ if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
+ const rect = elem.getBoundingClientRect();
+ const xl = rect.left;
+ const w = rect.width;
+ const yt = rect.top;
+ const h = rect.height;
+ const ws = w.toFixed(1);
+ const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
+ 'h' + ws +
+ 'v' + h.toFixed(1) +
+ 'h-' + ws +
+ 'z';
+ path.push(poly);
+ }
+ paths.push(path.join('') || 'M0 0');
+
+ path.length = 0;
+ for ( const elem of blueNodes ) {
+ if ( elem === inspectorFrame ) { continue; }
+ if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; }
+ const rect = elem.getBoundingClientRect();
+ const xl = rect.left;
+ const w = rect.width;
+ const yt = rect.top;
+ const h = rect.height;
+ const ws = w.toFixed(1);
+ const poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) +
+ 'h' + ws +
+ 'v' + h.toFixed(1) +
+ 'h-' + ws +
+ 'z';
+ path.push(poly);
+ }
+ paths.push(path.join('') || 'M0 0');
+
+ contentInspectorChannel.toFrame({
+ what: 'svgPaths',
+ paths,
+ });
+};
+
+/******************************************************************************/
+
+const onScrolled = (( ) => {
+ let timer;
+ return ( ) => {
+ if ( timer ) { return; }
+ timer = window.requestAnimationFrame(( ) => {
+ timer = undefined;
+ highlightElements();
+ });
+ };
+})();
+
+const onMouseOver = ( ) => {
+ if ( blueNodes.length === 0 ) { return; }
+ blueNodes = [];
+ highlightElements();
+};
+
+/******************************************************************************/
+
+const selectNodes = (selector, nid) => {
+ const nodes = elementsFromSelector(selector);
+ if ( nid === '' ) { return nodes; }
+ for ( const node of nodes ) {
+ if ( nodeToIdMap.get(node) === nid ) {
+ return [ node ];
+ }
+ }
+ return [];
+};
+
+/******************************************************************************/
+
+const nodesFromFilter = selector => {
+ const out = [];
+ for ( const entry of roRedNodes ) {
+ if ( entry[1] === selector ) {
+ out.push(entry[0]);
+ }
+ }
+ return out;
+};
+
+/******************************************************************************/
+
+const toggleExceptions = (nodes, targetState) => {
+ for ( const node of nodes ) {
+ if ( targetState ) {
+ rwGreenNodes.add(node);
+ } else {
+ rwGreenNodes.delete(node);
+ }
+ }
+};
+
+const toggleFilter = (nodes, targetState) => {
+ for ( const node of nodes ) {
+ if ( targetState ) {
+ rwRedNodes.delete(node);
+ } else {
+ rwRedNodes.add(node);
+ }
+ }
+};
+
+const resetToggledNodes = ( ) => {
+ rwGreenNodes.clear();
+ rwRedNodes.clear();
+};
+
+/******************************************************************************/
+
+const startInspector = ( ) => {
+ const onReady = ( ) => {
+ window.addEventListener('scroll', onScrolled, {
+ capture: true,
+ passive: true,
+ });
+ window.addEventListener('mouseover', onMouseOver, {
+ capture: true,
+ passive: true,
+ });
+ contentInspectorChannel.toLogger(domLayout.get());
+ vAPI.domFilterer.toggle(false, highlightElements);
+ };
+ if ( document.readyState === 'loading' ) {
+ document.addEventListener('DOMContentLoaded', onReady, { once: true });
+ } else {
+ onReady();
+ }
+};
+
+/******************************************************************************/
+
+const shutdownInspector = ( ) => {
+ cosmeticFilterMapper.shutdown();
+ domLayout.shutdown();
+ window.removeEventListener('scroll', onScrolled, {
+ capture: true,
+ passive: true,
+ });
+ window.removeEventListener('mouseover', onMouseOver, {
+ capture: true,
+ passive: true,
+ });
+ contentInspectorChannel.shutdown();
+ if ( inspectorFrame ) {
+ inspectorFrame.remove();
+ inspectorFrame = null;
+ }
+ vAPI.userStylesheet.remove(inspectorCSS);
+ vAPI.userStylesheet.apply();
+ vAPI.inspectorFrame = false;
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
+const onMessage = request => {
+ switch ( request.what ) {
+ case 'startInspector':
+ startInspector();
+ break;
+
+ case 'quitInspector':
+ shutdownInspector();
+ break;
+
+ case 'commitFilters':
+ highlightElements();
+ break;
+
+ case 'domLayout':
+ domLayout.get();
+ highlightElements();
+ break;
+
+ case 'highlightMode':
+ break;
+
+ case 'highlightOne':
+ blueNodes = selectNodes(request.selector, request.nid);
+ if ( blueNodes.length !== 0 ) {
+ blueNodes[0].scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ inline: 'nearest',
+ });
+ }
+ highlightElements();
+ break;
+
+ case 'resetToggledNodes':
+ resetToggledNodes();
+ highlightElements();
+ break;
+
+ case 'showCommitted':
+ blueNodes = [];
+ // TODO: show only the new filters and exceptions.
+ highlightElements();
+ break;
+
+ case 'showInteractive':
+ blueNodes = [];
+ highlightElements();
+ break;
+
+ case 'toggleFilter': {
+ const nodes = selectNodes(request.selector, request.nid);
+ if ( nodes.length !== 0 ) {
+ nodes[0].scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ inline: 'nearest',
+ });
+ }
+ toggleExceptions(nodesFromFilter(request.filter), request.target);
+ highlightElements();
+ break;
+ }
+ case 'toggleNodes': {
+ const nodes = selectNodes(request.selector, request.nid);
+ if ( nodes.length !== 0 ) {
+ nodes[0].scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ inline: 'nearest',
+ });
+ }
+ toggleFilter(nodes, request.target);
+ highlightElements();
+ break;
+ }
+ default:
+ break;
+ }
+};
+
+/*******************************************************************************
+ *
+ * Establish two-way communication with logger/inspector window and
+ * inspector frame
+ *
+ * */
+
+const contentInspectorChannel = (( ) => {
+ let toLoggerPort;
+ let toFramePort;
+
+ const toLogger = msg => {
+ if ( toLoggerPort === undefined ) { return; }
+ try {
+ toLoggerPort.postMessage(msg);
+ } catch {
+ shutdownInspector();
+ }
+ };
+
+ const onLoggerMessage = msg => {
+ onMessage(msg);
+ };
+
+ const onLoggerDisconnect = ( ) => {
+ shutdownInspector();
+ };
+
+ const onLoggerConnect = port => {
+ browser.runtime.onConnect.removeListener(onLoggerConnect);
+ toLoggerPort = port;
+ port.onMessage.addListener(onLoggerMessage);
+ port.onDisconnect.addListener(onLoggerDisconnect);
+ };
+
+ const toFrame = msg => {
+ if ( toFramePort === undefined ) { return; }
+ toFramePort.postMessage(msg);
+ };
+
+ const shutdown = ( ) => {
+ if ( toFramePort !== undefined ) {
+ toFrame({ what: 'quitInspector' });
+ toFramePort.onmessage = null;
+ toFramePort.close();
+ toFramePort = undefined;
+ }
+ if ( toLoggerPort !== undefined ) {
+ toLoggerPort.onMessage.removeListener(onLoggerMessage);
+ toLoggerPort.onDisconnect.removeListener(onLoggerDisconnect);
+ toLoggerPort.disconnect();
+ toLoggerPort = undefined;
+ }
+ browser.runtime.onConnect.removeListener(onLoggerConnect);
+ };
+
+ const start = async ( ) => {
+ browser.runtime.onConnect.addListener(onLoggerConnect);
+ const inspectorArgs = await vAPI.messaging.send('domInspectorContent', {
+ what: 'getInspectorArgs',
+ });
+ if ( typeof inspectorArgs !== 'object' ) { return; }
+ if ( inspectorArgs === null ) { return; }
+ return new Promise(resolve => {
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute(inspectorUniqueId, '');
+ document.documentElement.append(iframe);
+ iframe.addEventListener('load', ( ) => {
+ iframe.setAttribute(`${inspectorUniqueId}-loaded`, '');
+ const channel = new MessageChannel();
+ toFramePort = channel.port1;
+ toFramePort.onmessage = ev => {
+ const msg = ev.data || {};
+ if ( msg.what !== 'startInspector' ) { return; }
+ };
+ iframe.contentWindow.postMessage(
+ { what: 'startInspector' },
+ inspectorArgs.inspectorURL,
+ [ channel.port2 ]
+ );
+ resolve(iframe);
+ }, { once: true });
+ iframe.contentWindow.location = inspectorArgs.inspectorURL;
+ });
+ };
+
+ return { start, toLogger, toFrame, shutdown };
+})();
+
+
+// Install DOM inspector widget
+const inspectorCSSStyle = [
+ 'background: transparent',
+ 'border: 0',
+ 'border-radius: 0',
+ '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',
+ 'transform: none',
+ 'visibility: hidden',
+ 'width: 100%',
+ 'z-index: 2147483647',
+ ''
+].join(' !important;\n');
+
+const inspectorCSS = `
+:root > [${inspectorUniqueId}] {
+ ${inspectorCSSStyle}
+}
+:root > [${inspectorUniqueId}-loaded] {
+ visibility: visible !important;
+}
+`;
+
+vAPI.userStylesheet.add(inspectorCSS);
+vAPI.userStylesheet.apply();
+
+let inspectorFrame = await contentInspectorChannel.start();
+if ( inspectorFrame instanceof HTMLIFrameElement === false ) {
+ return shutdownInspector();
+}
+
+startInspector();
+
+/******************************************************************************/
+
+})();
+
+
+
+
+
+
+
+
+/*******************************************************************************
+
+ DO NOT:
+ - Remove the following code
+ - Add code beyond the following code
+ Reason:
+ - https://github.com/gorhill/uBlock/pull/3721
+ - uBO never uses the return value from injected content scripts
+
+**/
+
+void 0;