diff --git a/src/helpers/get-descriptor-addon.js b/src/helpers/get-descriptor-addon.js index 2740826f..ebd23bcb 100644 --- a/src/helpers/get-descriptor-addon.js +++ b/src/helpers/get-descriptor-addon.js @@ -1,7 +1,9 @@ +import { randomId } from './random-id'; /** * Prevent infinite loops when trapping props that could be used by scriptlet's own helpers * Example: window.RegExp, that is used by matchStackTrace > toRegExp * + * https://github.com/AdguardTeam/Scriptlets/issues/251 * https://github.com/AdguardTeam/Scriptlets/issues/226 * https://github.com/AdguardTeam/Scriptlets/issues/232 * @@ -12,9 +14,19 @@ export function getDescriptorAddon() { isAbortingSuspended: false, isolateCallback(cb, ...args) { this.isAbortingSuspended = true; - const result = cb(...args); - this.isAbortingSuspended = false; - return result; + // try...catch is required in case if there are more than one inline scripts + // which should be aborted + try { + const result = cb(...args); + this.isAbortingSuspended = false; + return result; + } catch { + this.isAbortingSuspended = false; + const rid = randomId(); + // It's necessary to throw error + // otherwise scriptt will be not aborted + throw new ReferenceError(rid); + } }, }; } diff --git a/tests/scriptlets/abort-current-inline-script.test.js b/tests/scriptlets/abort-current-inline-script.test.js index 05d16053..b83b8fa5 100644 --- a/tests/scriptlets/abort-current-inline-script.test.js +++ b/tests/scriptlets/abort-current-inline-script.test.js @@ -20,9 +20,9 @@ module(name, { beforeEach, afterEach }); const onError = (assert) => (message) => { const browserErrorMessage = 'Script error.'; - const nodePuppeteerErrorMessageRgx = /Reference error/g; + const nodePuppeteerErrorMessageRgx = /Reference error|ReferenceError/g; const checkResult = message === browserErrorMessage - || message.test(nodePuppeteerErrorMessageRgx); + || nodePuppeteerErrorMessageRgx.test(message); assert.ok(checkResult); }; @@ -246,3 +246,24 @@ test('Protected from infinite loop when prop is used in a helper', (assert) => { assert.strictEqual(window.hit, undefined, 'hit should NOT fire'); }); + +test('searches script by regexp - abort few inline scripts', (assert) => { + window.onerror = onError(assert); + window.shouldBeAborted = true; + window.shouldNotBeAborted = false; + const property = 'console.log'; + const shouldBeAborted = 'shouldBeAborted'; + const shouldNotBeAborted = 'shouldNotBeAborted'; + const search = '/test|abcd|1234|qwerty/'; + const scriptletArgs = [property, search]; + runScriptlet(name, scriptletArgs); + addAndRemoveInlineScript(`window.${property}('test'); window.${shouldBeAborted} = false;`); + addAndRemoveInlineScript(`window.${property}('abcd'); window.${shouldBeAborted} = false;`); + addAndRemoveInlineScript(`window.${property}('1234'); window.${shouldBeAborted} = false;`); + addAndRemoveInlineScript(`window.${property}('should not be aborted'); window.${shouldNotBeAborted} = true;`); + addAndRemoveInlineScript(`window.${property}('qwerty'); window.${shouldBeAborted} = false;`); + + assert.strictEqual(window.shouldBeAborted, true); + assert.strictEqual(window.shouldNotBeAborted, true); + assert.strictEqual(window.hit, 'FIRED', 'hit fired'); +}); diff --git a/wiki/about-scriptlets.md b/wiki/about-scriptlets.md index ac43183b..34dc266e 100644 --- a/wiki/about-scriptlets.md +++ b/wiki/about-scriptlets.md @@ -45,6 +45,7 @@ * [set-local-storage-item](#set-local-storage-item) * [set-popads-dummy](#set-popads-dummy) * [set-session-storage-item](#set-session-storage-item) +* [trusted-click-element](#trusted-click-element) * [xml-prune](#xml-prune) * * * ### ⚡️ abort-current-inline-script @@ -1695,6 +1696,61 @@ example.org#%#//scriptlet('set-session-storage-item', 'exit-intent-marketing', ' [Scriptlet source](../src/scriptlets/set-session-storage-item.js) * * * +### ⚡️ trusted-click-element + +Clicks selected elements in a strict sequence, ordered by selectors passed, and waiting for them to render in the DOM first. +Deactivates after all elements have been clicked or by 10s timeout. + +**Syntax** +``` +example.com#%#//scriptlet('trusted-click-element', selectors[, extraMatch[, delay]]) +``` + +- `selectors` — required, string with query selectors delimited by comma +- `extraMatch` — optional, extra condition to check on a page; allows to match `cookie` and `localStorage`; can be set as `name:key[=value]` where `value` is optional. +Multiple conditions are allowed inside one `extraMatch` but they should be delimited by comma and each of them should match the syntax. Possible `name`s: + - `cookie` - test string or regex against cookies on a page + - `localStorage` - check if localStorage item is present +- 'delay' - optional, time in ms to delay scriptlet execution, defaults to instant execution. +**Examples** +1. Click single element by selector +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]') +``` + +2. Delay click execution by 500ms +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', '', '500') +``` + +3. Click multiple elements by selector with a delay +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], button[name='check"], input[type="submit"][value="akkoord"]', '', '500') +``` + +4. Match cookies by keys using regex and string +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', 'cookie:userConsentCommunity, cookie:/cmpconsent|cmp/') +``` + +5. Match by cookie key=value pairs using regex and string +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', 'cookie:userConsentCommunity=true, cookie:/cmpconsent|cmp/=/[a-z]{1,5}/') +``` + +6. Match by localStorage item 'promo' key +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"]', 'localStorage:promo') +``` + +7. Click multiple elements with delay and matching by both cookie string and localStorage item +``` +example.com#%#//scriptlet('trusted-click-element', 'button[name="agree"], input[type="submit"][value="akkoord"]', 'cookie:cmpconsent, localStorage:promo', '250') +``` + +[Scriptlet source](../src/scriptlets/trusted-click-element.js) +* * * + ### ⚡️ xml-prune Removes an element from the specified XML.