From 0f330c7359567587df6c35e9108b75c339533a56 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 8 Jan 2021 10:45:35 -0500 Subject: [PATCH] Add ability to linger for remove-attr scriplet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1445 A third (optional) argument has been added to `remove-attr` scriptlet, which can be one or more space-separated tokens dictating the behavior of the scriptlet: `stay`: This tells the scriplet to stay and act on DOM changes, whiĺe the default behavior is to act only once when the document becomes interactive. `complete`: This tells the scriplet to start acting only when the document is complete, i.e. once all secondary resources have been loaded, while the default is to start acting when the document is interactive -- which is earlier than when the document is complete. Example: ...##+js(remove-attr, class, .j-mini-player, stay) --- assets/resources/scriptlets.js | 40 ++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index 286394cb2..309323e24 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -665,7 +665,10 @@ if ( selector === '' || selector === '{{2}}' ) { selector = `[${tokens.join('],[')}]`; } - const rmattr = function() { + let behavior = '{{3}}'; + let timer; + const rmattr = ( ) => { + timer = undefined; try { const nodes = document.querySelectorAll(selector); for ( const node of nodes ) { @@ -676,10 +679,39 @@ } catch(ex) { } }; - if ( document.readyState === 'loading' ) { - window.addEventListener('DOMContentLoaded', rmattr, { once: true }); - } else { + const mutationHandler = mutations => { + if ( timer !== undefined ) { return; } + let skip = true; + for ( let i = 0; i < mutations.length && skip; i++ ) { + const { type, addedNodes, removedNodes } = mutations[i]; + if ( type === 'attributes' ) { skip = false; } + for ( let j = 0; j < addedNodes.length && skip; j++ ) { + if ( addedNodes[j].nodeType === 1 ) { skip = false; break; } + } + for ( let j = 0; j < removedNodes.length && skip; j++ ) { + if ( removedNodes[j].nodeType === 1 ) { skip = false; break; } + } + } + if ( skip ) { return; } + timer = self.requestIdleCallback(rmattr, { timeout: 67 }); + }; + const start = ( ) => { rmattr(); + if ( /\bstay\b/.test(behavior) === false ) { return; } + const observer = new MutationObserver(mutationHandler); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: tokens, + childList: true, + subtree: true, + }); + }; + if ( document.readyState !== 'complete' && /\bcomplete\b/.test(behavior) ) { + document.addEventListener('load', start, { once: true }); + } else if ( document.readyState === 'loading' ) { + document.addEventListener('DOMContentLoaded', start, { once: true }); + } else { + start(); } })();