From 101f5e372d719f7f6730a6b6996849c20a93a2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Wr=C3=B3blewski?= Date: Thu, 8 Feb 2024 16:35:56 +0300 Subject: [PATCH 1/2] AG-23455 Add call-nothrow scriptlet. #333 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 319b7e9e8e35be578edef4d0eeb786b4499c1de0 Author: Adam Wróblewski Date: Tue Feb 6 18:55:28 2024 +0100 Add call-nothrow scriptlet --- CHANGELOG.md | 1 + scripts/compatibility-table.json | 1 + src/scriptlets/call-nothrow.js | 102 ++++++++++++++++++++++++++ src/scriptlets/scriptlets-list.js | 1 + tests/scriptlets/call-nothrow.test.js | 72 ++++++++++++++++++ wiki/compatibility-table.md | 2 +- 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/scriptlets/call-nothrow.js create mode 100644 tests/scriptlets/call-nothrow.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 05879851..f99661c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic ### Added +- `call-nothrow` scriptlet [#333](https://github.com/AdguardTeam/Scriptlets/issues/333) - `spoof-css` scriptlet [#317](https://github.com/AdguardTeam/Scriptlets/issues/317) - New values `t`, `f`, `necessary`, `required` for `set-cookie` and `set-cookie-reload` [#379](https://github.com/AdguardTeam/Scriptlets/issues/379) diff --git a/scripts/compatibility-table.json b/scripts/compatibility-table.json index 82001a85..ccce7b0f 100644 --- a/scripts/compatibility-table.json +++ b/scripts/compatibility-table.json @@ -201,6 +201,7 @@ "ubo": "href-sanitizer.js" }, { + "adg": "call-nothrow", "ubo": "call-nothrow.js" }, { diff --git a/src/scriptlets/call-nothrow.js b/src/scriptlets/call-nothrow.js new file mode 100644 index 00000000..13433a63 --- /dev/null +++ b/src/scriptlets/call-nothrow.js @@ -0,0 +1,102 @@ +import { + hit, + getPropertyInChain, + logMessage, + // following helpers are needed for helpers above + isEmptyObject, +} from '../helpers/index'; + +/* eslint-disable max-len */ +/** + * @scriptlet call-nothrow + * + * @description + * Prevents an exception from being thrown and returns undefined when a specific function is called. + * + * Related UBO scriptlet: + * https://github.com/gorhill/uBlock/wiki/Resources-Library#call-nothrowjs- + * + * ### Syntax + * + * ```text + * example.org#%#//scriptlet('call-nothrow', functionName) + * ``` + * + * - `functionName` — required, the name of the function to trap + * + * ### Examples + * + * 1. Prevents an exception from being thrown when `Object.defineProperty` is called: + * + * ```adblock + * example.org#%#//scriptlet('call-nothrow', 'Object.defineProperty') + * ``` + * + * For instance, the following call normally throws an error, but the scriptlet catches it and returns undefined: + * + * ```javascript + * Object.defineProperty(window, 'foo', { value: true }); + * Object.defineProperty(window, 'foo', { value: false }); + * ``` + * + * 2. Prevents an exception from being thrown when `JSON.parse` is called: + * + * ```adblock + * example.org#%#//scriptlet('call-nothrow', 'JSON.parse') + * ``` + * + * For instance, the following call normally throws an error, but the scriptlet catches it and returns undefined: + * + * ```javascript + * JSON.parse('foo'); + * ``` + * + * @added unknown. + */ +/* eslint-enable max-len */ +export function callNoThrow(source, functionName) { + if (!functionName) { + return; + } + + const { base, prop } = getPropertyInChain(window, functionName); + if (!base || !prop || typeof base[prop] !== 'function') { + const message = `${functionName} is not a function`; + logMessage(source, message); + return; + } + + const objectWrapper = (...args) => { + let result; + try { + result = Reflect.apply(...args); + } catch (e) { + const message = `Error calling ${functionName}: ${e.message}`; + logMessage(source, message); + } + hit(source); + return result; + }; + + const objectHandler = { + apply: objectWrapper, + }; + + base[prop] = new Proxy(base[prop], objectHandler); +} + +callNoThrow.names = [ + 'call-nothrow', + // aliases are needed for matching the related scriptlet converted into our syntax + 'call-nothrow.js', + 'ubo-call-nothrow.js', + 'ubo-call-nothrow', +]; + +callNoThrow.injections = [ + hit, + getPropertyInChain, + logMessage, + // following helpers are needed for helpers above + isEmptyObject, +]; diff --git a/src/scriptlets/scriptlets-list.js b/src/scriptlets/scriptlets-list.js index 380cb896..a9595046 100644 --- a/src/scriptlets/scriptlets-list.js +++ b/src/scriptlets/scriptlets-list.js @@ -62,3 +62,4 @@ export * from './trusted-replace-node-text'; export * from './evaldata-prune'; export * from './trusted-prune-inbound-object'; export * from './spoof-css'; +export * from './call-nothrow'; diff --git a/tests/scriptlets/call-nothrow.test.js b/tests/scriptlets/call-nothrow.test.js new file mode 100644 index 00000000..d6279f15 --- /dev/null +++ b/tests/scriptlets/call-nothrow.test.js @@ -0,0 +1,72 @@ +/* eslint-disable no-underscore-dangle, no-console */ +import { runScriptlet, clearGlobalProps } from '../helpers'; + +const { test, module } = QUnit; +const name = 'call-nothrow'; + +const beforeEach = () => { + window.__debug = () => { + window.hit = 'FIRED'; + }; +}; + +const afterEach = () => { + clearGlobalProps('hit', '__debug'); +}; + +module(name, { beforeEach, afterEach }); + +test('Checking if alias name works', (assert) => { + const adgParams = { + name, + engine: 'test', + verbose: true, + }; + const uboParams = { + name: 'ubo-call-nothrow.js', + engine: 'test', + verbose: true, + }; + + const codeByAdgParams = window.scriptlets.invoke(adgParams); + const codeByUboParams = window.scriptlets.invoke(uboParams); + + assert.strictEqual(codeByAdgParams, codeByUboParams, 'ubo name - ok'); +}); + +test('call-nothrow - JSON.parse', (assert) => { + let testPassed; + + runScriptlet(name, ['JSON.parse']); + + // JSON.parse('foo') throws an error, + // so scriptlet should catch it and testPassed should be true + try { + JSON.parse('foo'); + testPassed = true; + } catch (e) { + testPassed = false; + } + assert.strictEqual(testPassed, true, 'testPassed set to true'); + assert.strictEqual(window.hit, 'FIRED', 'hit function fired'); +}); + +test('call-nothrow - Object.defineProperty', (assert) => { + let testPassed; + const foo = {}; + Object.defineProperty(foo, 'bar', { value: true }); + + runScriptlet(name, ['Object.defineProperty']); + + // Redefining foo.bar should throw an error, + // so scriptlet should catch it and testPassed should be true + try { + Object.defineProperty(foo, 'bar', { value: false }); + testPassed = true; + } catch (e) { + testPassed = false; + } + assert.strictEqual(testPassed, true, 'testPassed set to true'); + assert.strictEqual(foo.bar, true, 'foo.bar set to true'); + assert.strictEqual(window.hit, 'FIRED', 'hit function fired'); +}); diff --git a/wiki/compatibility-table.md b/wiki/compatibility-table.md index 3cca37d0..0b7e9eb4 100644 --- a/wiki/compatibility-table.md +++ b/wiki/compatibility-table.md @@ -61,7 +61,7 @@ | | alert-buster.js | | | | golem.de.js (removed) | | | | href-sanitizer.js | | -| | call-nothrow.js | | +| [call-nothrow](../wiki/about-scriptlets.md#call-nothrow) | call-nothrow.js | | | | | abort-on-iframe-property-read | | | | abort-on-iframe-property-write | | | | freeze-element | From 7446aa35bc90e098c4bc47d074d98141ff96a771 Mon Sep 17 00:00:00 2001 From: Atlassian Bamboo Date: Thu, 8 Feb 2024 16:36:16 +0300 Subject: [PATCH 2/2] skipci: Automatic increment build number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b18e3575..a155f564 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adguard/scriptlets", - "version": "1.9.109", + "version": "1.9.110", "description": "AdGuard's JavaScript library of Scriptlets and Redirect resources", "scripts": { "build": "babel-node -x .js,.ts scripts/build.js",