Skip to content

Commit

Permalink
Add trusted-click-element scriptlet
Browse files Browse the repository at this point in the history
Implemented as per AdGuard API documentation:
https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#trusted-click-element

The current implementation in uBO does not support the `extraMatch`
argument. If the extraMatch argument is not empty, the scriptlet
will abort and do nothing.

As for the rest, it complies with the documentation.
  • Loading branch information
gorhill committed Oct 15, 2023
1 parent 9a809a8 commit 7af88b0
Showing 1 changed file with 112 additions and 3 deletions.
115 changes: 112 additions & 3 deletions assets/resources/scriptlets.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
}
Expand Down Expand Up @@ -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);
}

/******************************************************************************/

0 comments on commit 7af88b0

Please sign in to comment.