Skip to content

Commit 1977196

Browse files
committed
Improve prevent-addEventListener scriptlet
1 parent af7b975 commit 1977196

File tree

2 files changed

+159
-96
lines changed

2 files changed

+159
-96
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
*/
22+
23+
import { proxyApplyFn } from './proxy-apply.js';
24+
import { registerScriptlet } from './base.js';
25+
import { runAt } from './run-at.js';
26+
import { safeSelf } from './safe-self.js';
27+
28+
/******************************************************************************/
29+
30+
/**
31+
* @scriptlet prevent-addEventListener
32+
*
33+
* @description
34+
* Conditionally prevent execution of the callback function passed to native
35+
* addEventListener method. With no parameters, all calls to addEventListener
36+
* will be shown in the logger.
37+
*
38+
* @param [type]
39+
* The type of the event to prevent. The pattern can be a plain string, or a
40+
* regex to specify more than one event type.
41+
*
42+
* @param [pattern]
43+
* A pattern to match against the stringified callback. The pattern can be a
44+
* plain string, or a regex.
45+
*
46+
* @param [runat, value]
47+
* An optional vararg which tell the scriptlet when the prevention should
48+
* start. Values correspond to `readyState`: loading, interactive, complete.
49+
*
50+
* @param [protect, 1]
51+
* An optional vararg which tells the scriptlet whether the prevention should
52+
* be protected, i.e. not overwritten by other code.
53+
*
54+
* */
55+
56+
function preventAddEventListener(
57+
type = '',
58+
pattern = ''
59+
) {
60+
const safe = safeSelf();
61+
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
62+
const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
63+
const reType = safe.patternToRegex(type, undefined, true);
64+
const rePattern = safe.patternToRegex(pattern);
65+
const targetSelector = extraArgs.elements || undefined;
66+
const elementMatches = elem => {
67+
if ( targetSelector === 'window' ) { return elem === window; }
68+
if ( targetSelector === 'document' ) { return elem === document; }
69+
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
70+
const elems = Array.from(document.querySelectorAll(targetSelector));
71+
return elems.includes(elem);
72+
};
73+
const elementDetails = elem => {
74+
if ( elem instanceof Window ) { return 'window'; }
75+
if ( elem instanceof Document ) { return 'document'; }
76+
if ( elem instanceof Element === false ) { return '?'; }
77+
const parts = [];
78+
// https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079
79+
const id = String(elem.id);
80+
if ( id !== '' ) { parts.push(`#${CSS.escape(id)}`); }
81+
for ( let i = 0; i < elem.classList.length; i++ ) {
82+
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
83+
}
84+
for ( let i = 0; i < elem.attributes.length; i++ ) {
85+
const attr = elem.attributes.item(i);
86+
if ( attr.name === 'id' ) { continue; }
87+
if ( attr.name === 'class' ) { continue; }
88+
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
89+
}
90+
return parts.join('');
91+
};
92+
const shouldPrevent = (thisArg, type, handler) => {
93+
const matchesType = safe.RegExp_test.call(reType, type);
94+
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
95+
const matchesEither = matchesType || matchesHandler;
96+
const matchesBoth = matchesType && matchesHandler;
97+
if ( safe.logLevel > 1 && matchesEither ) {
98+
debugger; // eslint-disable-line no-debugger
99+
}
100+
if ( matchesBoth && targetSelector !== undefined ) {
101+
if ( elementMatches(thisArg) === false ) { return false; }
102+
}
103+
return matchesBoth;
104+
};
105+
const proxyFn = function(context) {
106+
const { callArgs, thisArg } = context;
107+
let t, h;
108+
try {
109+
t = String(callArgs[0]);
110+
if ( typeof callArgs[1] === 'function' ) {
111+
h = String(safe.Function_toString(callArgs[1]));
112+
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
113+
if ( typeof callArgs[1].handleEvent === 'function' ) {
114+
h = String(safe.Function_toString(callArgs[1].handleEvent));
115+
}
116+
} else {
117+
h = String(callArgs[1]);
118+
}
119+
} catch {
120+
}
121+
if ( type === '' && pattern === '' ) {
122+
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
123+
} else if ( shouldPrevent(thisArg, t, h) ) {
124+
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
125+
}
126+
return context.reflect();
127+
};
128+
runAt(( ) => {
129+
proxyApplyFn('EventTarget.prototype.addEventListener', proxyFn);
130+
if ( extraArgs.protect ) {
131+
const { addEventListener } = EventTarget.prototype;
132+
Object.defineProperty(EventTarget.prototype, 'addEventListener', {
133+
set() { },
134+
get() { return addEventListener; }
135+
});
136+
}
137+
proxyApplyFn('document.addEventListener', proxyFn);
138+
if ( extraArgs.protect ) {
139+
const { addEventListener } = document;
140+
Object.defineProperty(document, 'addEventListener', {
141+
set() { },
142+
get() { return addEventListener; }
143+
});
144+
}
145+
}, extraArgs.runAt);
146+
}
147+
registerScriptlet(preventAddEventListener , {
148+
name: 'prevent-addEventListener.js',
149+
aliases: [
150+
'addEventListener-defuser.js',
151+
'aeld.js',
152+
],
153+
dependencies: [
154+
proxyApplyFn,
155+
runAt,
156+
safeSelf,
157+
],
158+
});

src/js/resources/scriptlets.js

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import './json-edit.js';
2727
import './json-prune.js';
2828
import './noeval.js';
2929
import './object-prune.js';
30+
import './prevent-addeventlistener.js';
3031
import './prevent-dialog.js';
3132
import './prevent-fetch.js';
3233
import './prevent-innerHTML.js';
@@ -701,102 +702,6 @@ function abortOnPropertyWrite(
701702

702703
/******************************************************************************/
703704

704-
builtinScriptlets.push({
705-
name: 'addEventListener-defuser.js',
706-
aliases: [
707-
'aeld.js',
708-
'prevent-addEventListener.js',
709-
],
710-
fn: addEventListenerDefuser,
711-
dependencies: [
712-
'proxy-apply.fn',
713-
'run-at.fn',
714-
'safe-self.fn',
715-
'should-debug.fn',
716-
],
717-
});
718-
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
719-
function addEventListenerDefuser(
720-
type = '',
721-
pattern = ''
722-
) {
723-
const safe = safeSelf();
724-
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
725-
const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
726-
const reType = safe.patternToRegex(type, undefined, true);
727-
const rePattern = safe.patternToRegex(pattern);
728-
const debug = shouldDebug(extraArgs);
729-
const targetSelector = extraArgs.elements || undefined;
730-
const elementMatches = elem => {
731-
if ( targetSelector === 'window' ) { return elem === window; }
732-
if ( targetSelector === 'document' ) { return elem === document; }
733-
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
734-
const elems = Array.from(document.querySelectorAll(targetSelector));
735-
return elems.includes(elem);
736-
};
737-
const elementDetails = elem => {
738-
if ( elem instanceof Window ) { return 'window'; }
739-
if ( elem instanceof Document ) { return 'document'; }
740-
if ( elem instanceof Element === false ) { return '?'; }
741-
const parts = [];
742-
// https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079
743-
const id = String(elem.id);
744-
if ( id !== '' ) { parts.push(`#${CSS.escape(id)}`); }
745-
for ( let i = 0; i < elem.classList.length; i++ ) {
746-
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
747-
}
748-
for ( let i = 0; i < elem.attributes.length; i++ ) {
749-
const attr = elem.attributes.item(i);
750-
if ( attr.name === 'id' ) { continue; }
751-
if ( attr.name === 'class' ) { continue; }
752-
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
753-
}
754-
return parts.join('');
755-
};
756-
const shouldPrevent = (thisArg, type, handler) => {
757-
const matchesType = safe.RegExp_test.call(reType, type);
758-
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
759-
const matchesEither = matchesType || matchesHandler;
760-
const matchesBoth = matchesType && matchesHandler;
761-
if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
762-
debugger; // eslint-disable-line no-debugger
763-
}
764-
if ( matchesBoth && targetSelector !== undefined ) {
765-
if ( elementMatches(thisArg) === false ) { return false; }
766-
}
767-
return matchesBoth;
768-
};
769-
const proxyFn = function(context) {
770-
const { callArgs, thisArg } = context;
771-
let t, h;
772-
try {
773-
t = String(callArgs[0]);
774-
if ( typeof callArgs[1] === 'function' ) {
775-
h = String(safe.Function_toString(callArgs[1]));
776-
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
777-
if ( typeof callArgs[1].handleEvent === 'function' ) {
778-
h = String(safe.Function_toString(callArgs[1].handleEvent));
779-
}
780-
} else {
781-
h = String(callArgs[1]);
782-
}
783-
} catch {
784-
}
785-
if ( type === '' && pattern === '' ) {
786-
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
787-
} else if ( shouldPrevent(thisArg, t, h) ) {
788-
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
789-
}
790-
return context.reflect();
791-
};
792-
runAt(( ) => {
793-
proxyApplyFn('EventTarget.prototype.addEventListener', proxyFn);
794-
proxyApplyFn('document.addEventListener', proxyFn);
795-
}, extraArgs.runAt);
796-
}
797-
798-
/******************************************************************************/
799-
800705
builtinScriptlets.push({
801706
name: 'adjust-setInterval.js',
802707
aliases: [

0 commit comments

Comments
 (0)