Skip to content

Commit

Permalink
AG-35842 Add new scriptlet — 'prevent-canvas'. #451
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 4f1f97e
Author: Adam Wróblewski <adam@adguard.com>
Date:   Fri Sep 27 10:19:05 2024 +0200

    Remove unnecessary line

commit 9486855
Author: Adam Wróblewski <adam@adguard.com>
Date:   Fri Sep 27 09:14:37 2024 +0200

    Add prevent-canvas to compatibility-table.json

commit a935c1e
Merge: 6ccf2cb 1838d7b
Author: Adam Wróblewski <adam@adguard.com>
Date:   Thu Sep 26 09:38:52 2024 +0200

    Merge branch 'master' into feature/AG-35842

commit 6ccf2cb
Author: Adam Wróblewski <adam@adguard.com>
Date:   Thu Sep 26 09:36:41 2024 +0200

    Fix description

commit 84e27b1
Author: Adam Wróblewski <adam@adguard.com>
Date:   Thu Sep 26 09:32:43 2024 +0200

    Add types

commit 7966487
Author: Adam Wróblewski <adam@adguard.com>
Date:   Wed Sep 25 14:20:34 2024 +0200

    Add prevent-canvas scriptlet
  • Loading branch information
AdamWr committed Sep 27, 2024
1 parent 1838d7b commit 3a49d36
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 0 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
<!-- TODO: change `@added unknown` tag due to the actual version -->
<!-- during new scriptlets or redirects releasing -->

## [Unreleased]

### Added

- `prevent-canvas` scriptlet [#451]

[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v1.12.1...HEAD
[#451]: https://github.com/AdguardTeam/Scriptlets/issues/451

## [v1.12.1] - 2024-09-20

### Added
Expand Down
1 change: 1 addition & 0 deletions scripts/compatibility-table.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@
"ubo": "multiup.js"
},
{
"adg": "prevent-canvas",
"ubo": "prevent-canvas.js"
},
{
Expand Down
104 changes: 104 additions & 0 deletions src/scriptlets/prevent-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
hit,
logMessage,
parseMatchArg,
isValidMatchStr,
// following helpers are needed for helpers above
toRegExp,
escapeRegExp,
isValidStrPattern,
} from '../helpers';

/**
* @scriptlet prevent-canvas
*
* @description
* Prevents calls to `HTMLCanvasElement.prototype.getContext` and returns `null`.
*
* Related UBO scriptlet:
* https://github.com/gorhill/uBlock/wiki/Resources-Library#prevent-canvasjs-
*
* ### Syntax
*
* ```adblock
* example.org#%#//scriptlet('prevent-canvas'[, contextType])
* ```
*
* - `contextType` — optional, string matching the context type (e.g., '2d', 'webgl');
* by default it matches all context types.
* It can be a string pattern or a regular expression pattern.
* If the pattern starts with `!`, it will be negated.
*
* ### Examples
*
* 1. Prevent all canvas contexts
*
* ```adblock
* example.org#%#//scriptlet('prevent-canvas')
* ```
*
* 1. Prevent only '2d' canvas contexts
*
* ```adblock
* example.org#%#//scriptlet('prevent-canvas', '2d')
* ```
*
* 1. Prevent all canvas contexts except '2d'
*
* ```adblock
* example.org#%#//scriptlet('prevent-canvas', '!2d')
* ```
*
* @added unknown.
*/
export function preventCanvas(source: Source, contextType?: string) {
const handlerWrapper = (
target: HTMLCanvasElement['getContext'],
thisArg: HTMLCanvasElement,
argumentsList: string[],
) => {
const type = argumentsList[0];
let shouldPrevent = false;
if (!contextType) {
shouldPrevent = true;
} else if (isValidMatchStr(contextType)) {
const { isInvertedMatch, matchRegexp } = parseMatchArg(contextType);
shouldPrevent = matchRegexp.test(type) !== isInvertedMatch;
} else {
logMessage(source, `Invalid contextType parameter: ${contextType}`);
shouldPrevent = false;
}
if (shouldPrevent) {
hit(source);
return null;
}
return Reflect.apply(target, thisArg, argumentsList);
};

const canvasHandler = {
apply: handlerWrapper,
};

window.HTMLCanvasElement.prototype.getContext = new Proxy(
window.HTMLCanvasElement.prototype.getContext,
canvasHandler,
);
}

preventCanvas.names = [
'prevent-canvas',
// aliases are needed for matching the related scriptlet converted into our syntax
'prevent-canvas.js',
'ubo-prevent-canvas.js',
'ubo-prevent-canvas',
];

preventCanvas.injections = [
hit,
logMessage,
parseMatchArg,
isValidMatchStr,
toRegExp,
escapeRegExp,
isValidStrPattern,
];
1 change: 1 addition & 0 deletions src/scriptlets/scriptlets-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export * from './trusted-suppress-native-method';
export * from './json-prune-xhr-response';
export * from './trusted-dispatch-event';
export * from './trusted-replace-outbound-text';
export * from './prevent-canvas';
// redirects as scriptlets
// https://github.com/AdguardTeam/Scriptlets/issues/300
export * from './amazon-apstag';
Expand Down
1 change: 1 addition & 0 deletions tests/scriptlets/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ import './remove-node-text.test';
import './trusted-replace-node-text.test';
import './trusted-prune-inbound-object.test';
import './trusted-suppress-native-method.test';
import './prevent-canvas.test';
106 changes: 106 additions & 0 deletions tests/scriptlets/prevent-canvas.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint-disable no-underscore-dangle */
import { runScriptlet, clearGlobalProps } from '../helpers';

const { test, module } = QUnit;
const name = 'prevent-canvas';

const nativeCanvas = window.HTMLCanvasElement.prototype.getContext;

const beforeEach = () => {
window.__debug = () => {
window.hit = 'FIRED';
};
};

const afterEach = () => {
window.HTMLCanvasElement.prototype.getContext = nativeCanvas;
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-prevent-canvas.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('should return null for any context type when no contextType is specified', (assert) => {
runScriptlet(name);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
assert.strictEqual(context, null);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('should return null for specified context type', (assert) => {
runScriptlet(name, ['2d']);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
assert.strictEqual(context, null);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('should return original context for non-matching context type', (assert) => {
runScriptlet(name, ['webgl']);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
assert.ok(context.direction);
});

test('should return null for context type matched by regexp', (assert) => {
const regexp = /2d|webgl/;
runScriptlet(name, [`${regexp}`]);

const canvas2d = document.createElement('canvas');
const context2d = canvas2d.getContext('2d');

const canvasWebgl = document.createElement('canvas');
const contextWebgl = canvasWebgl.getContext('webgl');

assert.strictEqual(context2d, null);
assert.strictEqual(contextWebgl, null);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('should return original context for negated context type', (assert) => {
const negated = '!2d';
runScriptlet(name, [negated]);

const canvas2d = document.createElement('canvas');
const context2d = canvas2d.getContext('2d');

const canvasWebgl = document.createElement('canvas');
const contextWebgl = canvasWebgl.getContext('webgl');

assert.ok(context2d.direction);
assert.strictEqual(contextWebgl, null);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

test('should return original context for negated context type regexp', (assert) => {
const negated = '!/2d/';
runScriptlet(name, [negated]);

const canvas2d = document.createElement('canvas');
const context2d = canvas2d.getContext('2d');

const canvasWebgl = document.createElement('canvas');
const contextWebgl = canvasWebgl.getContext('webgl');

assert.ok(context2d.direction);
assert.strictEqual(contextWebgl, null);
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
});

0 comments on commit 3a49d36

Please sign in to comment.