Skip to content

Commit cdf6ab9

Browse files
committed
AG-44329 Fixed issue with assigning window to property which was not set in few scriptlets. #513
Squashed commit of the following: commit e8c3eeb Author: slvvko <v.leleka@adguard.com> Date: Thu Feb 26 19:47:27 2026 -0500 set PUPPETEER_CACHE_DIR commit f63a880 Author: slvvko <v.leleka@adguard.com> Date: Thu Feb 26 19:41:57 2026 -0500 revert chrome installation in dockerfile commit 4946291 Author: Adam Wróblewski <adam@adguard.com> Date: Thu Feb 26 10:20:05 2026 +0100 Add tests to check if chained properties are undefined commit 33efbae Author: Adam Wróblewski <adam@adguard.com> Date: Thu Feb 26 09:47:07 2026 +0100 Refactor destructuring of chainInfo in property access functions commit da90198 Author: slvvko <v.leleka@adguard.com> Date: Wed Feb 25 23:23:42 2026 -0500 install chrome for puppeteer in docker image commit b58830c Author: slvvko <v.leleka@adguard.com> Date: Wed Feb 25 23:23:10 2026 -0500 revert executablePath commit 3bded14 Author: slvvko <v.leleka@adguard.com> Date: Wed Feb 25 23:16:01 2026 -0500 add executablePath to puppeteer.launch options commit 8f5fa0b Author: Adam Wróblewski <adam@adguard.com> Date: Wed Feb 25 12:22:20 2026 +0100 Fixed issue with assigning `window` to property which was not set in `abort-on-property-read`, `abort-on-property-write`, `abort-on-stack-trace`, `abort-current-inline-script`, `debug-on-property-write`, `debug-on-property-read`, `debug-current-inline-script` and `log-on-stack-trace` scriptlets
1 parent 2d46ab6 commit cdf6ab9

20 files changed

Lines changed: 264 additions & 111 deletions

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
1010
<!-- TODO: change `@added unknown` tag due to the actual version -->
1111
<!-- during new scriptlets or redirects releasing -->
1212

13+
## [Unreleased]
14+
15+
### Changed
16+
17+
- Added `interceptChainProp` helper to share intermediate chain property access logic
18+
across `abort-on-property-read`, `abort-on-property-write`, `abort-on-stack-trace`,
19+
`abort-current-inline-script`, `debug-on-property-write`, `debug-on-property-read`,
20+
`debug-current-inline-script` and `log-on-stack-trace` scriptlets [#513].
21+
22+
### Fixed
23+
24+
- issue with assigning `window` to property which was not set in
25+
`abort-on-property-read`, `abort-on-property-write`, `abort-on-stack-trace`,
26+
`abort-current-inline-script`, `debug-on-property-write`, `debug-on-property-read`,
27+
`debug-current-inline-script` and `log-on-stack-trace` scriptlets [#513].
28+
29+
[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v2.2.16...HEAD
30+
[#513]: https://github.com/AdguardTeam/Scriptlets/issues/513
31+
1332
## [v2.2.16] - 2026-02-19
1433

1534
### Added

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ RUN npm install -g pnpm@10.7.1 && \
4040
WORKDIR /scriptlets
4141

4242
ENV PNPM_STORE=/pnpm-store
43+
# Point puppeteer to the cache directory where Chrome is pre-installed in the Docker image
44+
ENV PUPPETEER_CACHE_DIR=/home/pptruser/.cache/puppeteer
4345

4446
# Configure pnpm store globally so it doesn't need to be set in each stage
4547
RUN pnpm config set store-dir /pnpm-store

src/helpers/chain-prop-utils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { type ChainBase } from '../../types/types';
2+
3+
/**
4+
* Defines an intermediate getter/setter trap on `owner[prop]` to intercept
5+
* chain property assignment and allow recursive re-application of chain access.
6+
*
7+
* Reads the current value of `owner[prop]` safely (using try/catch) so that an
8+
* already-existing intermediate property keeps its value after the trap is set.
9+
*
10+
* @param owner Object on which to define the intercepting property.
11+
* @param prop Intermediate property name to intercept.
12+
* @param chain Remaining property chain string.
13+
* @param setChainPropAccess Callback invoked when the intercepted property is
14+
* assigned a new Object value.
15+
*/
16+
export const interceptChainProp = (
17+
owner: ChainBase,
18+
prop: string,
19+
chain: string,
20+
setChainPropAccess: (base: ChainBase, chain: string) => void,
21+
): void => {
22+
let base: unknown;
23+
try {
24+
base = owner[prop];
25+
} catch (e) {
26+
base = undefined;
27+
}
28+
const setter = (a: unknown): void => {
29+
base = a;
30+
if (a instanceof Object) {
31+
setChainPropAccess(a as ChainBase, chain);
32+
}
33+
};
34+
Object.defineProperty(owner, prop, {
35+
get: () => base,
36+
set: setter,
37+
});
38+
};

src/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ export * from './trusted-types-utils';
4141
export * from './value-matchers';
4242
export * from './click-utils';
4343
export * from './set-constant-utils';
44+
export * from './chain-prop-utils';

src/scriptlets/abort-current-inline-script.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
logMessage,
99
isEmptyObject,
1010
getDescriptorAddon,
11+
interceptChainProp,
1112
} from '../helpers';
1213

1314
/* eslint-disable max-len */
@@ -135,8 +136,7 @@ export function abortCurrentInlineScript(source, property, search) {
135136

136137
const setChainPropAccess = (owner, property) => {
137138
const chainInfo = getPropertyInChain(owner, property);
138-
let { base } = chainInfo;
139-
const { prop, chain } = chainInfo;
139+
const { base, prop, chain } = chainInfo;
140140

141141
// The scriptlet might be executed before the chain property has been created
142142
// (for instance, document.body before the HTML body was loaded).
@@ -154,16 +154,7 @@ export function abortCurrentInlineScript(source, property, search) {
154154
}
155155

156156
if (chain) {
157-
const setter = (a) => {
158-
base = a;
159-
if (a instanceof Object) {
160-
setChainPropAccess(a, chain);
161-
}
162-
};
163-
Object.defineProperty(owner, prop, {
164-
get: () => base,
165-
set: setter,
166-
});
157+
interceptChainProp(owner, prop, chain, setChainPropAccess);
167158
return;
168159
}
169160

@@ -247,4 +238,5 @@ abortCurrentInlineScript.injections = [
247238
logMessage,
248239
isEmptyObject,
249240
getDescriptorAddon,
241+
interceptChainProp,
250242
];

src/scriptlets/abort-on-property-read.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createOnErrorHandler,
66
hit,
77
isEmptyObject,
8+
interceptChainProp,
89
} from '../helpers';
910

1011
/* eslint-disable max-len */
@@ -53,19 +54,9 @@ export function abortOnPropertyRead(source, property) {
5354
};
5455
const setChainPropAccess = (owner, property) => {
5556
const chainInfo = getPropertyInChain(owner, property);
56-
let { base } = chainInfo;
57-
const { prop, chain } = chainInfo;
57+
const { base, prop, chain } = chainInfo;
5858
if (chain) {
59-
const setter = (a) => {
60-
base = a;
61-
if (a instanceof Object) {
62-
setChainPropAccess(a, chain);
63-
}
64-
};
65-
Object.defineProperty(owner, prop, {
66-
get: () => base,
67-
set: setter,
68-
});
59+
interceptChainProp(owner, prop, chain, setChainPropAccess);
6960
return;
7061
}
7162

@@ -103,4 +94,5 @@ abortOnPropertyRead.injections = [
10394
createOnErrorHandler,
10495
hit,
10596
isEmptyObject,
97+
interceptChainProp,
10698
];

src/scriptlets/abort-on-property-write.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createOnErrorHandler,
66
hit,
77
isEmptyObject,
8+
interceptChainProp,
89
} from '../helpers';
910

1011
/* eslint-disable max-len */
@@ -51,19 +52,9 @@ export function abortOnPropertyWrite(source, property) {
5152
};
5253
const setChainPropAccess = (owner, property) => {
5354
const chainInfo = getPropertyInChain(owner, property);
54-
let { base } = chainInfo;
55-
const { prop, chain } = chainInfo;
55+
const { base, prop, chain } = chainInfo;
5656
if (chain) {
57-
const setter = (a) => {
58-
base = a;
59-
if (a instanceof Object) {
60-
setChainPropAccess(a, chain);
61-
}
62-
};
63-
Object.defineProperty(owner, prop, {
64-
get: () => base,
65-
set: setter,
66-
});
57+
interceptChainProp(owner, prop, chain, setChainPropAccess);
6758
return;
6859
}
6960

@@ -97,4 +88,5 @@ abortOnPropertyWrite.injections = [
9788
createOnErrorHandler,
9889
hit,
9990
isEmptyObject,
91+
interceptChainProp,
10092
];

src/scriptlets/abort-on-stack-trace.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getNativeRegexpTest,
1616
backupRegExpValues,
1717
restoreRegExpValues,
18+
interceptChainProp,
1819
} from '../helpers';
1920

2021
/* eslint-disable max-len */
@@ -89,19 +90,9 @@ export function abortOnStackTrace(source, property, stack) {
8990

9091
const setChainPropAccess = (owner, property) => {
9192
const chainInfo = getPropertyInChain(owner, property);
92-
let { base } = chainInfo;
93-
const { prop, chain } = chainInfo;
93+
const { base, prop, chain } = chainInfo;
9494
if (chain) {
95-
const setter = (a) => {
96-
base = a;
97-
if (a instanceof Object) {
98-
setChainPropAccess(a, chain);
99-
}
100-
};
101-
Object.defineProperty(owner, prop, {
102-
get: () => base,
103-
set: setter,
104-
});
95+
interceptChainProp(owner, prop, chain, setChainPropAccess);
10596
return;
10697
}
10798

@@ -177,4 +168,5 @@ abortOnStackTrace.injections = [
177168
shouldAbortInlineOrInjectedScript,
178169
backupRegExpValues,
179170
restoreRegExpValues,
171+
interceptChainProp,
180172
];

src/scriptlets/debug-current-inline-script.js

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
hit,
88
logMessage,
99
isEmptyObject,
10+
interceptChainProp,
1011
} from '../helpers';
1112

1213
/* eslint-disable max-len */
@@ -21,10 +22,11 @@ import {
2122
*
2223
* ### Examples
2324
*
24-
* ```adblock
25-
* ! Aborts script when it tries to access `window.alert`
26-
* example.org#%#//scriptlet('debug-current-inline-script', 'alert')
27-
* ```
25+
* 1. Debug script when it tries to access `window.alert`
26+
*
27+
* ```adblock
28+
* example.org#%#//scriptlet('debug-current-inline-script', 'alert')
29+
* ```
2830
*
2931
* @added v1.0.4.
3032
*/
@@ -70,8 +72,7 @@ export function debugCurrentInlineScript(source, property, search) {
7072

7173
const setChainPropAccess = (owner, property) => {
7274
const chainInfo = getPropertyInChain(owner, property);
73-
let { base } = chainInfo;
74-
const { prop, chain } = chainInfo;
75+
const { base, prop, chain } = chainInfo;
7576

7677
// The scriptlet might be executed before the chain property has been created
7778
// (for instance, document.body before the HTML body was loaded).
@@ -89,16 +90,7 @@ export function debugCurrentInlineScript(source, property, search) {
8990
}
9091

9192
if (chain) {
92-
const setter = (a) => {
93-
base = a;
94-
if (a instanceof Object) {
95-
setChainPropAccess(a, chain);
96-
}
97-
};
98-
Object.defineProperty(owner, prop, {
99-
get: () => base,
100-
set: setter,
101-
});
93+
interceptChainProp(owner, prop, chain, setChainPropAccess);
10294
return;
10395
}
10496

@@ -136,4 +128,5 @@ debugCurrentInlineScript.injections = [
136128
hit,
137129
logMessage,
138130
isEmptyObject,
131+
interceptChainProp,
139132
];

src/scriptlets/debug-on-property-read.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
hit,
77
noopFunc,
88
isEmptyObject,
9+
interceptChainProp,
910
} from '../helpers';
1011

1112
/* eslint-disable max-len */
@@ -20,13 +21,17 @@ import {
2021
*
2122
* ### Examples
2223
*
23-
* ```adblock
24-
* ! Debug script if it tries to access `window.alert`
25-
* example.org#%#//scriptlet('debug-on-property-read', 'alert')
24+
* 1. Debug script if it tries to access `window.alert`
2625
*
27-
* ! or `window.open`
28-
* example.org#%#//scriptlet('debug-on-property-read', 'open')
29-
* ```
26+
* ```adblock
27+
* example.org#%#//scriptlet('debug-on-property-read', 'alert')
28+
* ```
29+
*
30+
* 1. Debug script if it tries to access `window.open`
31+
*
32+
* ```adblock
33+
* example.org#%#//scriptlet('debug-on-property-read', 'open')
34+
* ```
3035
*
3136
* @added v1.0.4.
3237
*/
@@ -42,19 +47,9 @@ export function debugOnPropertyRead(source, property) {
4247
};
4348
const setChainPropAccess = (owner, property) => {
4449
const chainInfo = getPropertyInChain(owner, property);
45-
let { base } = chainInfo;
46-
const { prop, chain } = chainInfo;
50+
const { base, prop, chain } = chainInfo;
4751
if (chain) {
48-
const setter = (a) => {
49-
base = a;
50-
if (a instanceof Object) {
51-
setChainPropAccess(a, chain);
52-
}
53-
};
54-
Object.defineProperty(owner, prop, {
55-
get: () => base,
56-
set: setter,
57-
});
52+
interceptChainProp(owner, prop, chain, setChainPropAccess);
5853
return;
5954
}
6055

@@ -84,4 +79,5 @@ debugOnPropertyRead.injections = [
8479
hit,
8580
noopFunc,
8681
isEmptyObject,
82+
interceptChainProp,
8783
];

0 commit comments

Comments
 (0)