Skip to content

Commit 049d9cb

Browse files
committed
AG-20096 Improve 'prevent-addEventListener' — save reference to 'toString()' method. #292
Squashed commit of the following: commit 9cc48ae Author: Adam Wróblewski <adam@adguard.com> Date: Tue Jun 9 15:09:24 2026 +0200 Fix changelog Lint error commit 3c3bd27 Author: Adam Wróblewski <adam@adguard.com> Date: Tue Jun 9 14:53:26 2026 +0200 Improve 'prevent-addEventListener' — save reference to 'toString()' method
1 parent fa41a27 commit 049d9cb

5 files changed

Lines changed: 46 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
2020

2121
### Changed
2222

23+
- `listenerToString` helper now uses native `Function.prototype.toString`
24+
to prevent issues where websites redefine it [#292].
2325
- `prevent-fetch` now supports a structured `responseConfig` argument for overriding synthetic response fields such as
2426
`ok`, `redirected`, `status`, `statusText`, and extended `type` values [#529].
2527
- Redirect resources with a `.ts` extension are now converted to `.js`.
@@ -34,6 +36,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
3436
[#560]: https://github.com/AdguardTeam/Scriptlets/issues/560
3537
[#529]: https://github.com/AdguardTeam/Scriptlets/issues/529
3638
[#467]: https://github.com/AdguardTeam/Scriptlets/issues/467
39+
[#292]: https://github.com/AdguardTeam/Scriptlets/issues/292
3740

3841
## [2.4.2] - 2026-04-24
3942

src/helpers/add-event-listener-utils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,17 @@ export const validateListener = (listener: unknown): boolean => {
3333
* https://developer.mozilla.org/en-US/docs/Web/API/EventListener
3434
*
3535
* @param listener valid listener
36+
* @param nativeToString native Function.prototype.toString method
3637
* @returns listener string
3738
*/
38-
export const listenerToString = (listener: EventListener | EventListenerObject): string => {
39+
export const listenerToString = (
40+
listener: EventListener | EventListenerObject,
41+
nativeToString: typeof Function.prototype.toString,
42+
): string => {
3943
return typeof listener === 'function'
40-
? listener.toString()
41-
: listener.handleEvent.toString();
44+
// Using native toString() is required to fix issues
45+
// where websites redefine Function.prototype.toString
46+
// https://github.com/AdguardTeam/Scriptlets/issues/292
47+
? nativeToString.call(listener)
48+
: nativeToString.call(listener.handleEvent);
4249
};

src/scriptlets/log-addEventListener.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { type Source } from './scriptlets';
3636
*/
3737
export function logAddEventListener(source: Source, noProtect?: string) {
3838
const nativeAddEventListener = window.EventTarget.prototype.addEventListener;
39+
const nativeToString = Function.prototype.toString;
3940

4041
function addEventListenerWrapper(
4142
this: EventTarget | null | undefined,
@@ -46,7 +47,7 @@ export function logAddEventListener(source: Source, noProtect?: string) {
4647
if (validateType(type) && validateListener(listener)) {
4748
let targetElement: Element | undefined;
4849
let targetElementInfo: string | undefined;
49-
const listenerInfo = listenerToString(listener as EventListener | EventListenerObject);
50+
const listenerInfo = listenerToString(listener as EventListener | EventListenerObject, nativeToString);
5051

5152
if (this) {
5253
if (this instanceof Window) {

src/scriptlets/prevent-addEventListener.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,13 @@ export function preventAddEventListener(
152152
};
153153

154154
const nativeAddEventListener = window.EventTarget.prototype.addEventListener;
155+
const nativeToString = Function.prototype.toString;
155156

156157
function addEventListenerWrapper(type, listener, ...args) {
157158
let shouldPrevent = false;
158159
if (validateType(type) && validateListener(listener)) {
159160
shouldPrevent = typeSearchRegexp.test(type.toString())
160-
&& listenerSearchRegexp.test(listenerToString(listener))
161+
&& listenerSearchRegexp.test(listenerToString(listener, nativeToString))
161162
&& elementMatches(this);
162163
}
163164

tests/scriptlets/prevent-addEventListener.test.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { test, module } = QUnit;
55
const name = 'prevent-addEventListener';
66

77
const nativeDescriptor = Object.getOwnPropertyDescriptor(window.EventTarget.prototype, 'addEventListener');
8+
const nativeToString = Function.prototype.toString;
89

910
const beforeEach = () => {
1011
window.__debug = () => {
@@ -17,6 +18,8 @@ const afterEach = () => {
1718
Object.defineProperty(window.EventTarget.prototype, 'addEventListener', nativeDescriptor);
1819
Object.defineProperty(window, 'addEventListener', nativeDescriptor);
1920
Object.defineProperty(document, 'addEventListener', nativeDescriptor);
21+
// eslint-disable-next-line no-extend-native
22+
Function.prototype.toString = nativeToString;
2023
};
2124

2225
module(name, { beforeEach, afterEach });
@@ -92,7 +95,7 @@ test('should not throw error when event type is null', (assert) => {
9295
// This should not throw an error
9396
assert.expect(2);
9497
try {
95-
document.addEventListener(null, () => {});
98+
document.addEventListener(null, () => { });
9699
window[testProp] = 'end';
97100
} catch (e) {
98101
assert.ok(false, `Should not throw error: ${e.message}`);
@@ -119,6 +122,30 @@ test('does not allow to add event listener', (assert) => {
119122
clearGlobalProps(testProp);
120123
});
121124

125+
test('does not allow to add event listener - native toString mocked', (assert) => {
126+
const scriptletArgs = ['click', 'clicked'];
127+
runScriptlet(name, scriptletArgs);
128+
129+
const nativeToString = Function.prototype.toString;
130+
// eslint-disable-next-line no-extend-native
131+
Function.prototype.toString = function mockToString() {
132+
return nativeToString.call(this).includes('clicked')
133+
? ''
134+
: nativeToString.call(this);
135+
};
136+
137+
const testProp = 'testProp';
138+
const element = document.createElement('div');
139+
element.addEventListener('click', () => {
140+
window[testProp] = 'clicked';
141+
});
142+
element.click();
143+
144+
assert.strictEqual(window.hit, 'FIRED', 'hit function fired');
145+
assert.strictEqual(window[testProp], undefined, 'property should be undefined');
146+
clearGlobalProps(testProp);
147+
});
148+
122149
test('event listeners not corresponding to scriptlet arguments should be added correctly', (assert) => {
123150
const scriptletArgs = ['click', undefined];
124151
runScriptlet(name, scriptletArgs);
@@ -384,7 +411,7 @@ test('noProtect parameter allows subsequent override of addEventListener', (asse
384411
};
385412

386413
const element2 = document.createElement('div');
387-
element2.addEventListener('click', () => {});
414+
element2.addEventListener('click', () => { });
388415

389416
assert.strictEqual(overrideWorked, true, 'addEventListener should be overridable with noProtect');
390417
clearGlobalProps(testProp);

0 commit comments

Comments
 (0)