Skip to content

Commit 9aa91ba

Browse files
committed
Improve trusted-click-element scriptlet
Related discussion/issue: - uBlockOrigin/uBlock-issues#2917 - uBlockOrigin/uAssets#30124 The "list of selectors" parameter is now a "list of steps". A step can be: - A selector, which tells the scriptlet to click a matching element. If no matching element is found, the scriptlet will wait for a matching element to become available. - An integer, which tells the scriptlet to wait n ms before processing the next step - A directive, which is a string starting with `!` (not implemented yet) If the last item in the list is an integer, this tells the scriplet to override the built-in timeout value of 10s, such that the life time of the scriptlet can now be extended beyond 10s. Example: ..##+js(trusted-click-element, '1000, a, 500, b, c, 15000') The scriptlet filter above will perform the following steps, in order: - Prepare the scriptlet to timeout at 15s from now - Wait 1000 ms - Wait for element `a` to become available then click on it - Wait 500 ms - Wait for element `b` to become available then click on it - Wait for element `c` to become available then click on it - Abort if all the steps cannot be completed before 15s The changes keep compatiblity with older syntax or with AdGuard syntax.
1 parent 705e632 commit 9aa91ba

File tree

1 file changed

+87
-76
lines changed

1 file changed

+87
-76
lines changed

src/js/resources/scriptlets.js

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,92 +2186,103 @@ function trustedClickElement(
21862186
return shadowRoot && querySelectorEx(inside, shadowRoot);
21872187
};
21882188

2189-
const selectorList = safe.String_split.call(selectors, /\s*,\s*/)
2190-
.filter(s => {
2191-
try {
2192-
void querySelectorEx(s);
2193-
} catch {
2194-
return false;
2195-
}
2196-
return true;
2197-
});
2198-
if ( selectorList.length === 0 ) { return; }
2199-
2189+
const steps = safe.String_split.call(selectors, /\s*,\s*/).map(a => {
2190+
if ( /^\d+$/.test(a) ) { return parseInt(a, 10); }
2191+
return a;
2192+
});
2193+
if ( steps.length === 0 ) { return; }
22002194
const clickDelay = parseInt(delay, 10) || 1;
2201-
const t0 = Date.now();
2202-
const tbye = t0 + 10000;
2203-
let tnext = selectorList.length !== 1 ? t0 : t0 + clickDelay;
2195+
for ( let i = steps.length-1; i > 0; i-- ) {
2196+
if ( typeof steps[i] !== 'string' ) { continue; }
2197+
if ( typeof steps[i-1] !== 'string' ) { continue; }
2198+
steps.splice(i, 0, clickDelay);
2199+
}
2200+
if ( typeof steps.at(-1) !== 'number' ) {
2201+
steps.push(10000);
2202+
}
22042203

2205-
const terminate = ( ) => {
2206-
selectorList.length = 0;
2207-
next.stop();
2208-
observe.stop();
2204+
const waitForTime = ms => {
2205+
return new Promise(resolve => {
2206+
safe.uboLog(logPrefix, `Waiting for ${ms} ms`);
2207+
waitForTime.timer = setTimeout(( ) => {
2208+
waitForTime.timer = undefined;
2209+
resolve();
2210+
}, ms);
2211+
});
2212+
};
2213+
waitForTime.cancel = ( ) => {
2214+
const { timer } = waitForTime;
2215+
if ( timer === undefined ) { return; }
2216+
clearTimeout(timer);
2217+
waitForTime.timer = undefined;
22092218
};
22102219

2211-
const next = notFound => {
2212-
if ( selectorList.length === 0 ) {
2213-
safe.uboLog(logPrefix, 'Completed');
2214-
return terminate();
2215-
}
2216-
const tnow = Date.now();
2217-
if ( tnow >= tbye ) {
2218-
safe.uboLog(logPrefix, 'Timed out');
2219-
return terminate();
2220-
}
2221-
if ( notFound ) { observe(); }
2222-
const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1);
2223-
next.timer = setTimeout(( ) => {
2224-
next.timer = undefined;
2225-
process();
2226-
}, delay);
2227-
safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`);
2220+
const waitForElement = selector => {
2221+
return new Promise(resolve => {
2222+
const elem = querySelectorEx(selector);
2223+
if ( elem !== null ) {
2224+
elem.click();
2225+
resolve();
2226+
return;
2227+
}
2228+
safe.uboLog(logPrefix, `Waiting for ${selector}`);
2229+
const observer = new MutationObserver(( ) => {
2230+
const elem = querySelectorEx(selector);
2231+
if ( elem === null ) { return; }
2232+
waitForElement.cancel();
2233+
elem.click();
2234+
resolve();
2235+
});
2236+
observer.observe(document, {
2237+
attributes: true,
2238+
childList: true,
2239+
subtree: true,
2240+
});
2241+
waitForElement.observer = observer;
2242+
});
22282243
};
2229-
next.stop = ( ) => {
2230-
if ( next.timer === undefined ) { return; }
2231-
clearTimeout(next.timer);
2232-
next.timer = undefined;
2244+
waitForElement.cancel = ( ) => {
2245+
const { observer } = waitForElement;
2246+
if ( observer === undefined ) { return; }
2247+
waitForElement.observer = undefined;
2248+
observer.disconnect();
22332249
};
22342250

2235-
const observe = ( ) => {
2236-
if ( observe.observer !== undefined ) { return; }
2237-
observe.observer = new MutationObserver(( ) => {
2238-
if ( observe.timer !== undefined ) { return; }
2239-
observe.timer = setTimeout(( ) => {
2240-
observe.timer = undefined;
2241-
process();
2242-
}, 20);
2243-
});
2244-
observe.observer.observe(document, {
2245-
attributes: true,
2246-
childList: true,
2247-
subtree: true,
2248-
});
2251+
const waitForTimeout = ms => {
2252+
waitForTimeout.cancel();
2253+
waitForTimeout.timer = setTimeout(( ) => {
2254+
waitForTimeout.timer = undefined;
2255+
terminate();
2256+
safe.uboLog(logPrefix, `Timed out after ${ms} ms`);
2257+
}, ms);
22492258
};
2250-
observe.stop = ( ) => {
2251-
if ( observe.timer !== undefined ) {
2252-
clearTimeout(observe.timer);
2253-
observe.timer = undefined;
2254-
}
2255-
if ( observe.observer ) {
2256-
observe.observer.disconnect();
2257-
observe.observer = undefined;
2258-
}
2259+
waitForTimeout.cancel = ( ) => {
2260+
if ( waitForTimeout.timer === undefined ) { return; }
2261+
clearTimeout(waitForTimeout.timer);
2262+
waitForTimeout.timer = undefined;
22592263
};
22602264

2261-
const process = ( ) => {
2262-
next.stop();
2263-
if ( Date.now() < tnext ) { return next(); }
2264-
const selector = selectorList.shift();
2265-
if ( selector === undefined ) { return terminate(); }
2266-
const elem = querySelectorEx(selector);
2267-
if ( elem === null ) {
2268-
selectorList.unshift(selector);
2269-
return next(true);
2270-
}
2271-
safe.uboLog(logPrefix, `Clicked ${selector}`);
2272-
elem.click();
2273-
tnext += clickDelay;
2274-
next();
2265+
const terminate = ( ) => {
2266+
waitForTime.cancel();
2267+
waitForElement.cancel();
2268+
waitForTimeout.cancel();
2269+
};
2270+
2271+
const process = async ( ) => {
2272+
waitForTimeout(steps.pop());
2273+
while ( steps.length !== 0 ) {
2274+
const step = steps.shift();
2275+
if ( step === undefined ) { break; }
2276+
if ( typeof step === 'number' ) {
2277+
await waitForTime(step);
2278+
if ( step === 1 ) { continue; }
2279+
continue;
2280+
}
2281+
if ( step.startsWith('!') ) { continue; }
2282+
await waitForElement(step);
2283+
safe.uboLog(logPrefix, `Clicked ${step}`);
2284+
}
2285+
terminate();
22752286
};
22762287

22772288
runAtHtmlElementFn(process);

0 commit comments

Comments
 (0)