mirror of
https://github.com/gorhill/uBlock.git
synced 2025-10-05 21:32:39 +02:00
Improve trusted-click-element
scriptlet
Related discussion/issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/2917 - https://github.com/uBlockOrigin/uAssets/discussions/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.
This commit is contained in:
@@ -2186,92 +2186,103 @@ function trustedClickElement(
|
||||
return shadowRoot && querySelectorEx(inside, shadowRoot);
|
||||
};
|
||||
|
||||
const selectorList = safe.String_split.call(selectors, /\s*,\s*/)
|
||||
.filter(s => {
|
||||
try {
|
||||
void querySelectorEx(s);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if ( selectorList.length === 0 ) { return; }
|
||||
|
||||
const steps = safe.String_split.call(selectors, /\s*,\s*/).map(a => {
|
||||
if ( /^\d+$/.test(a) ) { return parseInt(a, 10); }
|
||||
return a;
|
||||
});
|
||||
if ( steps.length === 0 ) { return; }
|
||||
const clickDelay = parseInt(delay, 10) || 1;
|
||||
const t0 = Date.now();
|
||||
const tbye = t0 + 10000;
|
||||
let tnext = selectorList.length !== 1 ? t0 : t0 + clickDelay;
|
||||
for ( let i = steps.length-1; i > 0; i-- ) {
|
||||
if ( typeof steps[i] !== 'string' ) { continue; }
|
||||
if ( typeof steps[i-1] !== 'string' ) { continue; }
|
||||
steps.splice(i, 0, clickDelay);
|
||||
}
|
||||
if ( typeof steps.at(-1) !== 'number' ) {
|
||||
steps.push(10000);
|
||||
}
|
||||
|
||||
const waitForTime = ms => {
|
||||
return new Promise(resolve => {
|
||||
safe.uboLog(logPrefix, `Waiting for ${ms} ms`);
|
||||
waitForTime.timer = setTimeout(( ) => {
|
||||
waitForTime.timer = undefined;
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
};
|
||||
waitForTime.cancel = ( ) => {
|
||||
const { timer } = waitForTime;
|
||||
if ( timer === undefined ) { return; }
|
||||
clearTimeout(timer);
|
||||
waitForTime.timer = undefined;
|
||||
};
|
||||
|
||||
const waitForElement = selector => {
|
||||
return new Promise(resolve => {
|
||||
const elem = querySelectorEx(selector);
|
||||
if ( elem !== null ) {
|
||||
elem.click();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
safe.uboLog(logPrefix, `Waiting for ${selector}`);
|
||||
const observer = new MutationObserver(( ) => {
|
||||
const elem = querySelectorEx(selector);
|
||||
if ( elem === null ) { return; }
|
||||
waitForElement.cancel();
|
||||
elem.click();
|
||||
resolve();
|
||||
});
|
||||
observer.observe(document, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
waitForElement.observer = observer;
|
||||
});
|
||||
};
|
||||
waitForElement.cancel = ( ) => {
|
||||
const { observer } = waitForElement;
|
||||
if ( observer === undefined ) { return; }
|
||||
waitForElement.observer = undefined;
|
||||
observer.disconnect();
|
||||
};
|
||||
|
||||
const waitForTimeout = ms => {
|
||||
waitForTimeout.cancel();
|
||||
waitForTimeout.timer = setTimeout(( ) => {
|
||||
waitForTimeout.timer = undefined;
|
||||
terminate();
|
||||
safe.uboLog(logPrefix, `Timed out after ${ms} ms`);
|
||||
}, ms);
|
||||
};
|
||||
waitForTimeout.cancel = ( ) => {
|
||||
if ( waitForTimeout.timer === undefined ) { return; }
|
||||
clearTimeout(waitForTimeout.timer);
|
||||
waitForTimeout.timer = undefined;
|
||||
};
|
||||
|
||||
const terminate = ( ) => {
|
||||
selectorList.length = 0;
|
||||
next.stop();
|
||||
observe.stop();
|
||||
waitForTime.cancel();
|
||||
waitForElement.cancel();
|
||||
waitForTimeout.cancel();
|
||||
};
|
||||
|
||||
const next = notFound => {
|
||||
if ( selectorList.length === 0 ) {
|
||||
safe.uboLog(logPrefix, 'Completed');
|
||||
return terminate();
|
||||
const process = async ( ) => {
|
||||
waitForTimeout(steps.pop());
|
||||
while ( steps.length !== 0 ) {
|
||||
const step = steps.shift();
|
||||
if ( step === undefined ) { break; }
|
||||
if ( typeof step === 'number' ) {
|
||||
await waitForTime(step);
|
||||
if ( step === 1 ) { continue; }
|
||||
continue;
|
||||
}
|
||||
if ( step.startsWith('!') ) { continue; }
|
||||
await waitForElement(step);
|
||||
safe.uboLog(logPrefix, `Clicked ${step}`);
|
||||
}
|
||||
const tnow = Date.now();
|
||||
if ( tnow >= tbye ) {
|
||||
safe.uboLog(logPrefix, 'Timed out');
|
||||
return terminate();
|
||||
}
|
||||
if ( notFound ) { observe(); }
|
||||
const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1);
|
||||
next.timer = setTimeout(( ) => {
|
||||
next.timer = undefined;
|
||||
process();
|
||||
}, delay);
|
||||
safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`);
|
||||
};
|
||||
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 = querySelectorEx(selector);
|
||||
if ( elem === null ) {
|
||||
selectorList.unshift(selector);
|
||||
return next(true);
|
||||
}
|
||||
safe.uboLog(logPrefix, `Clicked ${selector}`);
|
||||
elem.click();
|
||||
tnext += clickDelay;
|
||||
next();
|
||||
terminate();
|
||||
};
|
||||
|
||||
runAtHtmlElementFn(process);
|
||||
|
Reference in New Issue
Block a user