mirror of
https://github.com/yeongpin/cursor-free-vip.git
synced 2025-08-03 13:07:35 +08:00
921 lines
26 KiB
JavaScript
921 lines
26 KiB
JavaScript
/*******************************************************************************
|
|
|
|
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 (ex) {
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const elementsFromSpecialSelector = function(selector) {
|
|
const out = [];
|
|
let matches = /^(.+?):has\((.+?)\)$/.exec(selector);
|
|
if ( matches !== null ) {
|
|
let nodes;
|
|
try {
|
|
nodes = document.querySelectorAll(matches[1]);
|
|
} catch(ex) {
|
|
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;
|