diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index c5892d2f6..257503afa 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -224,9 +224,9 @@ function runAt(fn, when) { builtinScriptlets.push({ name: 'run-at-html-element.fn', - fn: runAtHtmlElement, + fn: runAtHtmlElementFn, }); -function runAtHtmlElement(fn) { +function runAtHtmlElementFn(fn) { if ( document.documentElement ) { fn(); return; @@ -1160,7 +1160,7 @@ builtinScriptlets.push({ // Issues to mind before changing anything: // https://github.com/uBlockOrigin/uBlock-issues/issues/2154 function abortCurrentScript(...args) { - runAtHtmlElement(( ) => { + runAtHtmlElementFn(( ) => { abortCurrentScriptCore(...args); }); } @@ -3882,4 +3882,113 @@ function trustedReplaceXhrResponse( }; } +/******************************************************************************* + * + * trusted-click-element.js + * + * Reference API: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-click-element.js + * + **/ + +builtinScriptlets.push({ + name: 'trusted-click-element.js', + requiresTrust: true, + fn: trustedClickElement, + world: 'ISOLATED', + dependencies: [ + 'run-at-html-element.fn', + 'safe-self.fn', + ], +}); +function trustedClickElement( + selectors = '', + extraMatch = '', // not yet supported + delay = '' +) { + if ( extraMatch !== '' ) { return; } + + const selectorList = selectors.split(/\s*,\s*/) + .filter(s => { + try { + void document.querySelector(s); + } catch(_) { + return false; + } + return true; + }); + if ( selectorList.length === 0 ) { return; } + + const clickDelay = parseInt(delay, 10) || 1; + const t0 = Date.now(); + const tbye = t0 + 10000; + let tnext = t0; + + const terminate = ( ) => { + selectorList.length = 0; + next.stop(); + observe.stop(); + }; + + const next = notFound => { + if ( selectorList.length === 0 ) { return terminate(); } + const tnow = Date.now(); + if ( tnow >= tbye ) { return terminate(); } + if ( notFound ) { observe(); } + const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1); + next.timer = setTimeout(( ) => { + next.timer = undefined; + process(); + }, delay); + }; + next.stop = ( ) => { + if ( next.timer === undefined ) { return; } + clearTimeout(next.timer); + next.timer = undefined; + }; + + const observe = ( ) => { + if ( observe.observer !== undefined ) { return; } + observe.observer = new MutationObserver(( ) => { + if ( observe.timer !== undefined ) { return; } + observe.timer = setTimeout(( ) => { + observe.timer = undefined; + process(); + }, 20); + }); + observe.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); + }; + observe.stop = ( ) => { + if ( observe.timer !== undefined ) { + clearTimeout(observe.timer); + observe.timer = undefined; + } + if ( observe.observer ) { + observe.observer.disconnect(); + observe.observer = undefined; + } + }; + + const process = ( ) => { + next.stop(); + if ( Date.now() < tnext ) { return next(); } + const selector = selectorList.shift(); + if ( selector === undefined ) { return terminate(); } + const elem = document.querySelector(selector); + if ( elem === null ) { + selectorList.unshift(selector); + return next(true); + } + elem.click(); + tnext += clickDelay; + next(); + }; + + runAtHtmlElementFn(process); +} + /******************************************************************************/