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.