diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 7ed08c7d2..a78624b61 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -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() { diff --git a/src/js/messaging.js b/src/js/messaging.js index 9d6df2189..b29140cc6 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -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 }; diff --git a/src/js/scriptlets/dom-survey.js b/src/js/scriptlets/dom-survey.js index 4a9791c3c..3640ae236 100644 --- a/src/js/scriptlets/dom-survey.js +++ b/src/js/scriptlets/dom-survey.js @@ -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, }; })();