Skip to content

Commit

Permalink
AG-16616 add decimal delay matching for prevent-setTimeout/-setInterval
Browse files Browse the repository at this point in the history
#247

Merge in ADGUARD-FILTERS/scriptlets from fix/AG-16616 to release/v1.8

Squashed commit of the following:

commit 6313c0e
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Mon Dec 26 15:57:57 2022 +0300

    improve helper

commit 47b9202
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 22 20:40:17 2022 +0300

    tweak buggy test

commit 55c72f6
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 22 17:29:25 2022 +0300

    fix typo

commit 72946ad
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 22 12:16:27 2022 +0300

    fix parseRawDelay helper

commit 5350f36
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Wed Dec 21 20:06:15 2022 +0300

    fix parseRawDelay helper

commit 369c327
Merge: dd79e29 6191100
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Wed Dec 21 16:32:20 2022 +0300

    merge release/v1.8

commit dd79e29
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Mon Dec 19 19:49:08 2022 +0300

    improve helper

commit 8d52c1b
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Mon Dec 19 18:49:34 2022 +0300

    fix helper description

commit 28b374d
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Mon Dec 19 18:34:22 2022 +0300

    improve descriptions

commit 6bf00c1
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Mon Dec 19 18:17:55 2022 +0300

    imporve parseRawDelay and move to prevent-utils

commit 491b35f
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Fri Dec 16 19:34:43 2022 +0300

    fix changelog indents

commit e35248d
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Fri Dec 16 15:00:53 2022 +0300

    improve and test helper

commit baca1d8
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 15 21:21:10 2022 +0300

    improve raw delay parsing

commit e0e6698
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 15 19:24:40 2022 +0300

    add typecheck for delay + tests

commit 60505e9
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 15 19:06:15 2022 +0300

    update scriptlets descriptions, fix typo in changelog

commit f4b48ce
Author: Stanislav A <s.atroschenko@adguard.com>
Date:   Thu Dec 15 17:03:37 2022 +0300

    add decimal delay matching for prevent-setTimeout/-setInterval
  • Loading branch information
stanislav-atr committed Dec 27, 2022
1 parent 6191100 commit 22e41f8
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 25 deletions.
35 changes: 19 additions & 16 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased 1.8.x

### Changed

- add decimal delay matching for `prevent-setInterval` and `prevent-setTimeout` [#247](https://github.com/AdguardTeam/Scriptlets/issues/247)

### Fixed

- `prevent-xhr` and `trusted-replace-xhr-response` closure bug on multiple requests [#261](https://github.com/AdguardTeam/Scriptlets/issues/261)
Expand Down Expand Up @@ -38,30 +42,29 @@
- spread of args bug at `getXhrData` call for `trusted-replace-xhr-response`
- request properties array not being served to `getRequestData` and `parseMatchProps` helpers


## v1.7.3

### Added

- [Trusted scriptlets](./README.md#trusted-scriptlets) with extended capabilities:
- trusted-click-element [#23](https://github.com/AdguardTeam/Scriptlets/issues/23)
- trusted-replace-xhr-response [#202](https://github.com/AdguardTeam/Scriptlets/issues/202)
- trusted-replace-fetch-response
- trusted-set-local-storage-item
- trusted-set-cookie
- `trusted-click-element` [#23](https://github.com/AdguardTeam/Scriptlets/issues/23)
- `trusted-replace-xhr-response` [#202](https://github.com/AdguardTeam/Scriptlets/issues/202)
- `trusted-replace-fetch-response`
- `trusted-set-local-storage-item`
- `trusted-set-cookie`

- Scriptlets:
- xml-prune [#249](https://github.com/AdguardTeam/Scriptlets/issues/249)
- `xml-prune` [#249](https://github.com/AdguardTeam/Scriptlets/issues/249)

### Improved
### Changed

- Scriptlets:
- prevent-element-src-loading [#228](https://github.com/AdguardTeam/Scriptlets/issues/228)
- prevent-fetch [#216](https://github.com/AdguardTeam/Scriptlets/issues/216)
- abort-on-stack-trace [#201](https://github.com/AdguardTeam/Scriptlets/issues/201)
- abort-current-inline-script [#251](https://github.com/AdguardTeam/Scriptlets/issues/251)
- set-cookie & set-cookie-reload
- `prevent-element-src-loading` [#228](https://github.com/AdguardTeam/Scriptlets/issues/228)
- `prevent-fetch` [#216](https://github.com/AdguardTeam/Scriptlets/issues/216)
- `abort-on-stack-trace` [#201](https://github.com/AdguardTeam/Scriptlets/issues/201)
- `abort-current-inline-script` [#251](https://github.com/AdguardTeam/Scriptlets/issues/251)
- `set-cookie` & `set-cookie-reload`
- Redirects:
- google-ima3 [#255](https://github.com/AdguardTeam/Scriptlets/issues/255)
- metrika-yandex-tag [#254](https://github.com/AdguardTeam/Scriptlets/issues/254)
- googlesyndication-adsbygoogle [#252](https://github.com/AdguardTeam/Scriptlets/issues/252)
- `google-ima3` [#255](https://github.com/AdguardTeam/Scriptlets/issues/255)
- `metrika-yandex-tag` [#254](https://github.com/AdguardTeam/Scriptlets/issues/254)
- `googlesyndication-adsbygoogle` [#252](https://github.com/AdguardTeam/Scriptlets/issues/252)
21 changes: 19 additions & 2 deletions src/helpers/prevent-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isValidMatchNumber,
isValidMatchStr,
} from './string-utils';
import { nativeIsNaN } from './number-utils';

/**
* Checks whether the passed arg is proper callback
Expand All @@ -18,6 +19,18 @@ export const isValidCallback = (callback) => {
|| typeof callback === 'string';
};

/**
* Parses delay argument of setTimeout / setInterval methods into
* rounded down number for number/string values or passes on for other types.
* Needed for prevent-setTimeout and prevent-setInterval
* @param {any} delay
* @returns {any} number as parsed delay or any input type if `delay` is not parsable
*/
export const parseRawDelay = (delay) => {
const parsedDelay = Math.floor(parseInt(delay, 10));
return typeof parsedDelay === 'number' && !nativeIsNaN(parsedDelay) ? parsedDelay : delay;
};

/**
* Checks whether 'callback' and 'delay' are matching
* by given parameters 'matchCallback' and 'matchDelay'.
Expand Down Expand Up @@ -45,16 +58,20 @@ export const isPreventionNeeded = ({
const { isInvertedMatch, matchRegexp } = parseMatchArg(matchCallback);
const { isInvertedDelayMatch, delayMatch } = parseDelayArg(matchDelay);

// Parse delay for decimal, string and non-number values
// https://github.com/AdguardTeam/Scriptlets/issues/247
const parsedDelay = parseRawDelay(delay);

let shouldPrevent = false;
// https://github.com/AdguardTeam/Scriptlets/issues/105
const callbackStr = String(callback);
if (delayMatch === null) {
shouldPrevent = matchRegexp.test(callbackStr) !== isInvertedMatch;
} else if (!matchCallback) {
shouldPrevent = (delay === delayMatch) !== isInvertedDelayMatch;
shouldPrevent = (parsedDelay === delayMatch) !== isInvertedDelayMatch;
} else {
shouldPrevent = matchRegexp.test(callbackStr) !== isInvertedMatch
&& (delay === delayMatch) !== isInvertedDelayMatch;
&& (parsedDelay === delayMatch) !== isInvertedDelayMatch;
}
return shouldPrevent;
};
18 changes: 18 additions & 0 deletions src/scriptlets/prevent-setInterval.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
escapeRegExp,
nativeIsFinite,
isValidMatchNumber,
parseRawDelay,
} from '../helpers/index';

/* eslint-disable max-len */
Expand Down Expand Up @@ -44,6 +45,7 @@ import {
* - `matchDelay` - optional, must be an integer.
* If starts with `!`, scriptlet will not match the delay but all other will be defused.
* If do not start with `!`, the delay passed to the `setInterval` call will be matched.
* Decimal delay values will be rounded down, e.g `10.95` will be matched by `matchDelay` with value `10`.
*
* > If `prevent-setInterval` log looks like `setInterval(undefined, 1000)`,
* it means that no callback was passed to setInterval() and that's not scriptlet issue
Expand Down Expand Up @@ -118,6 +120,21 @@ import {
* window.value = "test -- executed";
* }, 500);
* ```
*
* 5. Prevents `setInterval` calls if the callback contains `value` and delay is a decimal.
* ```
* example.org#%#//scriptlet('prevent-setInterval', 'value', '300')
* ```
*
* For instance, the following calls will be prevented:
* ```javascript
* setInterval(function () {
* window.test = "value";
* }, 300);
* setInterval(function () {
* window.test = "value";
* }, 300 + Math.random());
* ```
*/
/* eslint-enable max-len */
export function preventSetInterval(source, matchCallback, matchDelay) {
Expand Down Expand Up @@ -218,4 +235,5 @@ preventSetInterval.injections = [
escapeRegExp,
nativeIsFinite,
isValidMatchNumber,
parseRawDelay,
];
18 changes: 18 additions & 0 deletions src/scriptlets/prevent-setTimeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isValidStrPattern,
nativeIsFinite,
isValidMatchNumber,
parseRawDelay,
} from '../helpers/index';

/* eslint-disable max-len */
Expand Down Expand Up @@ -44,6 +45,7 @@ import {
* - `matchDelay` - optional, must be an integer.
* If starts with `!`, scriptlet will not match the delay but all other will be defused.
* If do not start with `!`, the delay passed to the `setTimeout` call will be matched.
* Decimal delay values will be rounded down, e.g `10.95` will be matched by `matchDelay` with value `10`.
*
* > If `prevent-setTimeout` log looks like `setTimeout(undefined, 1000)`,
* it means that no callback was passed to setTimeout() and that's not scriptlet issue
Expand Down Expand Up @@ -118,6 +120,21 @@ import {
* window.value = "test -- executed";
* }, 500);
* ```
*
* 5. Prevents `setTimeout` calls if the callback contains `value` and delay is a decimal.
* ```
* example.org#%#//scriptlet('prevent-setTimeout', 'value', '300')
* ```
*
* For instance, the following calls will be prevented:
* ```javascript
* setTimeout(function () {
* window.test = "value";
* }, 300);
* setTimeout(function () {
* window.test = "value";
* }, 300 + Math.random());
* ```
*/
/* eslint-enable max-len */
export function preventSetTimeout(source, matchCallback, matchDelay) {
Expand Down Expand Up @@ -221,4 +238,5 @@ preventSetTimeout.injections = [
isValidStrPattern,
nativeIsFinite,
isValidMatchNumber,
parseRawDelay,
];
23 changes: 23 additions & 0 deletions tests/helpers/prevent-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { parseRawDelay } from '../../src/helpers';

const { test, module } = QUnit;
const name = 'scriptlets-redirects helpers';

module(name);
test('Test parseRawDelay', (assert) => {
assert.strictEqual(parseRawDelay(0), 0, 'parsing number ok');
assert.strictEqual(parseRawDelay(10), 10, 'parsing number ok');
assert.strictEqual(parseRawDelay(10.123), 10, 'parsing number ok');

assert.strictEqual(parseRawDelay('0'), 0, 'parsing number in string ok');
assert.strictEqual(parseRawDelay('10'), 10, 'parsing number in string ok');
assert.strictEqual(parseRawDelay('10.123'), 10, 'parsing number in string ok');

assert.strictEqual(parseRawDelay('string'), 'string', 'parsing string ok');

assert.strictEqual(parseRawDelay(null), null, 'parsing other types ok');
assert.strictEqual(parseRawDelay(undefined), undefined, 'parsing other types ok');
assert.strictEqual(parseRawDelay(false), false, 'parsing other types ok');
// as NaN !== NaN
assert.strictEqual(parseRawDelay(NaN).toString(), 'NaN', 'parsing other types ok');
});
119 changes: 117 additions & 2 deletions tests/scriptlets/prevent-setInterval.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const beforeEach = () => {
const afterEach = () => {
window.setInterval = nativeSetInterval;
testIntervals.forEach((i) => (clearInterval(i)));
clearGlobalProps('hit', '__debug', 'aaa', 'one', 'two', 'three', 'four');
clearGlobalProps('hit', '__debug', 'aaa', 'one', 'two', 'three', 'four', 'five');
console.log = nativeConsole; // eslint-disable-line no-console
};

Expand Down Expand Up @@ -67,7 +67,11 @@ test('no args -- logging', (assert) => {
// We need to run our assertion after all timeouts
setTimeout(() => {
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
assert.strictEqual(loggedMessage, `prevent-setInterval: setInterval(${callback.toString()}, ${timeout})`, 'console.hit input ok');
assert.strictEqual(
loggedMessage,
`prevent-setInterval: setInterval(${callback.toString()}, ${timeout})`,
'console.hit input ok',
);
assert.strictEqual(window[agLogSetInterval], 'changed', 'property changed');
clearGlobalProps(agLogSetInterval);
done();
Expand Down Expand Up @@ -368,3 +372,114 @@ test('prevent-setInterval: single square bracket in matchCallback', (assert) =>
const testInterval = setInterval(callback, 10);
testIntervals.push(testInterval);
});

test('match any callback + decimal delay', (assert) => {
const done = assert.async();
window.one = 'old one';
window.two = 'old two';
window.three = 'old three';
// We need to run our assertion after all timeouts
setTimeout(() => {
assert.equal(window.one, 'NEW ONE', 'property \'one\' is changed due to non-matched delay');
assert.equal(window.two, 'old two', 'property \'two\' should NOT be changed');
assert.equal(window.three, 'old three', 'property \'three\' should NOT be changed');
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
done();
}, 100);

// run scriptlet code
const scriptletArgs = ['', '10'];
runScriptlet(name, scriptletArgs);

// only this one SHOULD NOT be prevented because of delay mismatch
const one = () => { window.one = 'NEW ONE'; };
const intervalTest1 = setInterval(one, 25);
testIntervals.push(intervalTest1);

const second = () => { window.two = 'NEW TWO'; };
const intervalTest2 = setInterval(second, 10.05);
testIntervals.push(intervalTest2);

const third = () => { window.three = 'NEW THREE'; };
const intervalTest3 = setInterval(third, 10.95);
testIntervals.push(intervalTest3);
});

test('match any callback + non-number, decimal and string delays', (assert) => {
const done = assert.async();
window.one = 'old one';
window.two = 'old two';
window.three = 'old three';
window.four = 'old four';
window.five = 'old five';
// We need to run our assertion after all timeouts
setTimeout(() => {
assert.equal(window.one, 'old one', 'property \'one\' should NOT be changed');
assert.equal(window.two, 'NEW TWO', 'property \'two\' should be changed');
assert.equal(window.three, 'NEW THREE', 'property \'three\' should be changed');

assert.equal(window.four, 'old four', 'property \'four\' should NOT be changed');
assert.equal(window.five, 'NEW FIVE', 'property \'five\' should be changed');

assert.strictEqual(window.hit, 'FIRED', 'hit fired');
done();
}, 100);

// run scriptlet code
const scriptletArgs = ['', '25'];
runScriptlet(name, scriptletArgs);

// only this one SHOULD NOT be prevented because of delay mismatch
const one = () => { window.one = 'NEW ONE'; };
const intervalTest1 = setInterval(one, 25.123);
testIntervals.push(intervalTest1);

const second = () => { window.two = 'NEW TWO'; };
const intervalTest2 = setInterval(second, null);
testIntervals.push(intervalTest2);

const third = () => { window.three = 'NEW THREE'; };
const intervalTest3 = setInterval(third, false);
testIntervals.push(intervalTest3);

// test with string delays
const fourth = () => { window.four = 'NEW FOUR'; };
const intervalTest4 = setInterval(fourth, '25.123');
testIntervals.push(intervalTest4);

const fifth = () => { window.five = 'NEW FIVE'; };
const intervalTest5 = setInterval(fifth, '10');
testIntervals.push(intervalTest5);
});

test('match any callback, falsy non-numbers delays dont collide with 0 ', (assert) => {
const done = assert.async();
window.one = 'one';
window.two = 'two';
window.three = 'three';
// We need to run our assertion after all timeouts
setTimeout(() => {
assert.equal(window.one, 'one', 'property \'one\' should NOT be changed');
assert.equal(window.two, 'NEW TWO', 'property \'two\' should be changed');
assert.equal(window.three, 'NEW THREE', 'property \'three\' should be changed');

assert.strictEqual(window.hit, 'FIRED', 'hit fired');
done();
}, 100);

// run scriptlet code
const scriptletArgs = ['', '0'];
runScriptlet(name, scriptletArgs);

const first = () => { window.one = 'NEW ONE'; };
const intervalTest1 = setInterval(first, 0);
testIntervals.push(intervalTest1);

const second = () => { window.two = 'NEW TWO'; };
const intervalTest2 = setInterval(second, null);
testIntervals.push(intervalTest2);

const third = () => { window.three = 'NEW THREE'; };
const intervalTest3 = setInterval(third, undefined);
testIntervals.push(intervalTest3);
});
Loading

0 comments on commit 22e41f8

Please sign in to comment.