mirror of https://github.com/gorhill/uBlock.git
Improve efficiency of per-site switches badge code
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/756 This is the code used to find out the count values displayed as badge on the cosmetic filtering and scripting per-site switches in the popup panel. The issue is that document.querySelector*() -- used to find out the number of hidden elements -- is unduly expensive on large DOM. The changes in this commit have focused on avoiding the use of document.querySelector*() as much as possible. Also, the results are cached for reuse unless DOM mutations are detected.
This commit is contained in:
parent
11da758d47
commit
d8975ee580
|
@ -268,6 +268,7 @@ vAPI.SafeAnimationFrame.prototype = {
|
|||
/******************************************************************************/
|
||||
|
||||
vAPI.domWatcher = (( ) => {
|
||||
vAPI.domMutationTime = Date.now();
|
||||
|
||||
const addedNodeLists = [];
|
||||
const removedNodeLists = [];
|
||||
|
@ -282,9 +283,7 @@ vAPI.domWatcher = (( ) => {
|
|||
safeObserverHandlerTimer;
|
||||
|
||||
const safeObserverHandler = function() {
|
||||
//console.time('dom watcher/safe observer handler');
|
||||
let i = addedNodeLists.length,
|
||||
j = addedNodes.length;
|
||||
let i = addedNodeLists.length;
|
||||
while ( i-- ) {
|
||||
const nodeList = addedNodeLists[i];
|
||||
let iNode = nodeList.length;
|
||||
|
@ -293,7 +292,7 @@ vAPI.domWatcher = (( ) => {
|
|||
if ( node.nodeType !== 1 ) { continue; }
|
||||
if ( ignoreTags.has(node.localName) ) { continue; }
|
||||
if ( node.parentElement === null ) { continue; }
|
||||
addedNodes[j++] = node;
|
||||
addedNodes.push(node);
|
||||
}
|
||||
}
|
||||
addedNodeLists.length = 0;
|
||||
|
@ -308,7 +307,6 @@ vAPI.domWatcher = (( ) => {
|
|||
}
|
||||
}
|
||||
removedNodeLists.length = 0;
|
||||
//console.timeEnd('dom watcher/safe observer handler');
|
||||
if ( addedNodes.length === 0 && removedNodes === false ) { return; }
|
||||
for ( const listener of getListenerIterator() ) {
|
||||
try { listener.onDOMChanged(addedNodes, removedNodes); }
|
||||
|
@ -316,12 +314,12 @@ vAPI.domWatcher = (( ) => {
|
|||
}
|
||||
addedNodes.length = 0;
|
||||
removedNodes = false;
|
||||
vAPI.domMutationTime = Date.now();
|
||||
};
|
||||
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/205
|
||||
// Do not handle added node directly from within mutation observer.
|
||||
const observerHandler = function(mutations) {
|
||||
//console.time('dom watcher/observer handler');
|
||||
let i = mutations.length;
|
||||
while ( i-- ) {
|
||||
const mutation = mutations[i];
|
||||
|
@ -339,7 +337,6 @@ vAPI.domWatcher = (( ) => {
|
|||
addedNodeLists.length < 100 ? 1 : undefined
|
||||
);
|
||||
}
|
||||
//console.timeEnd('dom watcher/observer handler');
|
||||
};
|
||||
|
||||
const startMutationObserver = function() {
|
||||
|
|
|
@ -377,8 +377,15 @@ const getDOMStats = async function(tabId) {
|
|||
let scriptCount = 0;
|
||||
results.forEach(result => {
|
||||
if ( result instanceof Object === false ) { return; }
|
||||
elementCount += result.elementCount;
|
||||
scriptCount += result.scriptCount;
|
||||
if ( result.hiddenElementCount > 0 ) {
|
||||
elementCount += result.hiddenElementCount;
|
||||
}
|
||||
if ( result.externalScriptCount > 0 ) {
|
||||
scriptCount += result.externalScriptCount;
|
||||
}
|
||||
if ( result.inlineScriptCount > 0 ) {
|
||||
scriptCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
return { elementCount, scriptCount };
|
||||
|
|
|
@ -23,41 +23,150 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/756
|
||||
// Keep in mind CPU usage witj large DOM and/or filterset.
|
||||
|
||||
(( ) => {
|
||||
if ( typeof vAPI !== 'object' ) { return; }
|
||||
|
||||
// https://github.com/gorhill/httpswitchboard/issues/25
|
||||
//
|
||||
// https://github.com/gorhill/httpswitchboard/issues/131
|
||||
// Looks for inline javascript also in at least one a[href] element.
|
||||
//
|
||||
// https://github.com/gorhill/uMatrix/issues/485
|
||||
// Mind "on..." attributes.
|
||||
//
|
||||
// https://github.com/gorhill/uMatrix/issues/924
|
||||
// Report inline styles.
|
||||
let inlineScriptCount = 0;
|
||||
if (
|
||||
document.querySelector('script:not([src])') !== null ||
|
||||
document.querySelector('script[src^="data:"]') !== null ||
|
||||
document.querySelector('script[src^="blob:"]') !== null ||
|
||||
document.querySelector('a[href^="javascript:"]') !== null ||
|
||||
document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') !== null
|
||||
) {
|
||||
inlineScriptCount = 1;
|
||||
const t0 = Date.now();
|
||||
const tMax = t0 + 60;
|
||||
|
||||
if ( vAPI.domSurveyResults instanceof Object === false ) {
|
||||
vAPI.domSurveyResults = {
|
||||
busy: false,
|
||||
hiddenElementCount: -1,
|
||||
inlineScriptCount: -1,
|
||||
externalScriptCount: -1,
|
||||
surveyTime: t0,
|
||||
};
|
||||
}
|
||||
const surveyResults = vAPI.domSurveyResults;
|
||||
|
||||
if ( surveyResults.busy ) { return; }
|
||||
surveyResults.busy = true;
|
||||
|
||||
if ( surveyResults.surveyTime < vAPI.domMutationTime ) {
|
||||
surveyResults.hiddenElementCount = -1;
|
||||
surveyResults.inlineScriptCount = -1;
|
||||
surveyResults.externalScriptCount = -1;
|
||||
}
|
||||
surveyResults.surveyTime = t0;
|
||||
|
||||
if ( surveyResults.externalScriptCount === -1 ) {
|
||||
const reInlineScript = /^(data:|blob:|$)/;
|
||||
let inlineScriptCount = 0;
|
||||
let externalScriptCount = 0;
|
||||
for ( const script of document.scripts ) {
|
||||
if ( reInlineScript.test(script.src) ) {
|
||||
inlineScriptCount = 1;
|
||||
continue;
|
||||
}
|
||||
externalScriptCount += 1;
|
||||
if ( externalScriptCount === 99 ) { break; }
|
||||
}
|
||||
if ( inlineScriptCount !== 0 || externalScriptCount === 99 ) {
|
||||
surveyResults.inlineScriptCount = inlineScriptCount;
|
||||
}
|
||||
surveyResults.externalScriptCount = externalScriptCount;
|
||||
}
|
||||
|
||||
const scriptTags = document.querySelectorAll('script[src]');
|
||||
|
||||
let elementCount = 0;
|
||||
if ( vAPI.domFilterer ) {
|
||||
elementCount = vAPI.domFilterer.getFilteredElementCount();
|
||||
if ( surveyResults.hiddenElementCount === -1 ) {
|
||||
surveyResults.hiddenElementCount = (( ) => {
|
||||
if ( vAPI.domFilterer instanceof Object === false ) { return 0; }
|
||||
const details = vAPI.domFilterer.getAllSelectors_(true);
|
||||
if ( Array.isArray(details.declarative) === false ) { return 0; }
|
||||
const selectors = details.declarative.map(entry => entry[0]);
|
||||
const simple = [], complex = [];
|
||||
for ( const selectorStr of selectors ) {
|
||||
for ( const selector of selectorStr.split(',\n') ) {
|
||||
if ( /[ +>~]/.test(selector) ) {
|
||||
complex.push(selector);
|
||||
} else {
|
||||
simple.push(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
const simpleStr = simple.join(',\n');
|
||||
const complexStr = complex.join(',\n');
|
||||
const nodeIter = document.createNodeIterator(
|
||||
document.body,
|
||||
NodeFilter.SHOW_ELEMENT
|
||||
);
|
||||
const matched = new Set();
|
||||
for (;;) {
|
||||
const node = nodeIter.nextNode();
|
||||
if ( node === null ) { break; }
|
||||
if ( node.offsetParent !== null ) { continue; }
|
||||
if (
|
||||
node.matches(simpleStr) === false &&
|
||||
node.closest(complexStr) !== node
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
matched.add(node);
|
||||
if ( matched.size === 99 ) { break; }
|
||||
}
|
||||
return matched.size;
|
||||
})();
|
||||
}
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/756
|
||||
// Keep trying to find inline script-like instances but only if we
|
||||
// have the time-budget to do so.
|
||||
if ( surveyResults.inlineScriptCount === -1 && Date.now() < tMax ) {
|
||||
if ( document.querySelector('a[href^="javascript:"]') !== null ) {
|
||||
surveyResults.inlineScriptCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( surveyResults.inlineScriptCount === -1 && Date.now() < tMax ) {
|
||||
surveyResults.inlineScriptCount = 0;
|
||||
const onHandlers = new Set([
|
||||
'onabort', 'onblur', 'oncancel', 'oncanplay',
|
||||
'oncanplaythrough', 'onchange', 'onclick', 'onclose',
|
||||
'oncontextmenu', 'oncuechange', 'ondblclick', 'ondrag',
|
||||
'ondragend', 'ondragenter', 'ondragexit', 'ondragleave',
|
||||
'ondragover', 'ondragstart', 'ondrop', 'ondurationchange',
|
||||
'onemptied', 'onended', 'onerror', 'onfocus',
|
||||
'oninput', 'oninvalid', 'onkeydown', 'onkeypress',
|
||||
'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata',
|
||||
'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave',
|
||||
'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup',
|
||||
'onwheel', 'onpause', 'onplay', 'onplaying',
|
||||
'onprogress', 'onratechange', 'onreset', 'onresize',
|
||||
'onscroll', 'onseeked', 'onseeking', 'onselect',
|
||||
'onshow', 'onstalled', 'onsubmit', 'onsuspend',
|
||||
'ontimeupdate', 'ontoggle', 'onvolumechange', 'onwaiting',
|
||||
'onafterprint', 'onbeforeprint', 'onbeforeunload', 'onhashchange',
|
||||
'onlanguagechange', 'onmessage', 'onoffline', 'ononline',
|
||||
'onpagehide', 'onpageshow', 'onrejectionhandled', 'onpopstate',
|
||||
'onstorage', 'onunhandledrejection', 'onunload',
|
||||
'oncopy', 'oncut', 'onpaste'
|
||||
]);
|
||||
const nodeIter = document.createNodeIterator(
|
||||
document.body,
|
||||
NodeFilter.SHOW_ELEMENT
|
||||
);
|
||||
for (;;) {
|
||||
const node = nodeIter.nextNode();
|
||||
if ( node === null ) { break; }
|
||||
if ( node.hasAttributes() === false ) { continue; }
|
||||
for ( const attr of node.getAttributeNames() ) {
|
||||
if ( onHandlers.has(attr) === false ) { continue; }
|
||||
surveyResults.inlineScriptCount = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
surveyResults.busy = false;
|
||||
|
||||
// IMPORTANT: This is returned to the injector, so this MUST be
|
||||
// the last statement.
|
||||
return {
|
||||
elementCount,
|
||||
scriptCount: inlineScriptCount + scriptTags.length,
|
||||
hiddenElementCount: surveyResults.hiddenElementCount,
|
||||
inlineScriptCount: surveyResults.inlineScriptCount,
|
||||
externalScriptCount: surveyResults.externalScriptCount,
|
||||
};
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue