mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
Improve prevent-addEventListener scriptlet
This commit is contained in:
parent
af7b975840
commit
1977196abe
2 changed files with 159 additions and 96 deletions
158
src/js/resources/prevent-addeventlistener.js
Normal file
158
src/js/resources/prevent-addeventlistener.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a comprehensive, efficient content blocker
|
||||
Copyright (C) 2019-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
|
||||
*/
|
||||
|
||||
import { proxyApplyFn } from './proxy-apply.js';
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
* @scriptlet prevent-addEventListener
|
||||
*
|
||||
* @description
|
||||
* Conditionally prevent execution of the callback function passed to native
|
||||
* addEventListener method. With no parameters, all calls to addEventListener
|
||||
* will be shown in the logger.
|
||||
*
|
||||
* @param [type]
|
||||
* The type of the event to prevent. The pattern can be a plain string, or a
|
||||
* regex to specify more than one event type.
|
||||
*
|
||||
* @param [pattern]
|
||||
* A pattern to match against the stringified callback. The pattern can be a
|
||||
* plain string, or a regex.
|
||||
*
|
||||
* @param [runat, value]
|
||||
* An optional vararg which tell the scriptlet when the prevention should
|
||||
* start. Values correspond to `readyState`: loading, interactive, complete.
|
||||
*
|
||||
* @param [protect, 1]
|
||||
* An optional vararg which tells the scriptlet whether the prevention should
|
||||
* be protected, i.e. not overwritten by other code.
|
||||
*
|
||||
* */
|
||||
|
||||
function preventAddEventListener(
|
||||
type = '',
|
||||
pattern = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
||||
const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
|
||||
const reType = safe.patternToRegex(type, undefined, true);
|
||||
const rePattern = safe.patternToRegex(pattern);
|
||||
const targetSelector = extraArgs.elements || undefined;
|
||||
const elementMatches = elem => {
|
||||
if ( targetSelector === 'window' ) { return elem === window; }
|
||||
if ( targetSelector === 'document' ) { return elem === document; }
|
||||
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
|
||||
const elems = Array.from(document.querySelectorAll(targetSelector));
|
||||
return elems.includes(elem);
|
||||
};
|
||||
const elementDetails = elem => {
|
||||
if ( elem instanceof Window ) { return 'window'; }
|
||||
if ( elem instanceof Document ) { return 'document'; }
|
||||
if ( elem instanceof Element === false ) { return '?'; }
|
||||
const parts = [];
|
||||
// https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079
|
||||
const id = String(elem.id);
|
||||
if ( id !== '' ) { parts.push(`#${CSS.escape(id)}`); }
|
||||
for ( let i = 0; i < elem.classList.length; i++ ) {
|
||||
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
|
||||
}
|
||||
for ( let i = 0; i < elem.attributes.length; i++ ) {
|
||||
const attr = elem.attributes.item(i);
|
||||
if ( attr.name === 'id' ) { continue; }
|
||||
if ( attr.name === 'class' ) { continue; }
|
||||
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
||||
const shouldPrevent = (thisArg, type, handler) => {
|
||||
const matchesType = safe.RegExp_test.call(reType, type);
|
||||
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
|
||||
const matchesEither = matchesType || matchesHandler;
|
||||
const matchesBoth = matchesType && matchesHandler;
|
||||
if ( safe.logLevel > 1 && matchesEither ) {
|
||||
debugger; // eslint-disable-line no-debugger
|
||||
}
|
||||
if ( matchesBoth && targetSelector !== undefined ) {
|
||||
if ( elementMatches(thisArg) === false ) { return false; }
|
||||
}
|
||||
return matchesBoth;
|
||||
};
|
||||
const proxyFn = function(context) {
|
||||
const { callArgs, thisArg } = context;
|
||||
let t, h;
|
||||
try {
|
||||
t = String(callArgs[0]);
|
||||
if ( typeof callArgs[1] === 'function' ) {
|
||||
h = String(safe.Function_toString(callArgs[1]));
|
||||
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
|
||||
if ( typeof callArgs[1].handleEvent === 'function' ) {
|
||||
h = String(safe.Function_toString(callArgs[1].handleEvent));
|
||||
}
|
||||
} else {
|
||||
h = String(callArgs[1]);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
if ( type === '' && pattern === '' ) {
|
||||
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
||||
} else if ( shouldPrevent(thisArg, t, h) ) {
|
||||
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
||||
}
|
||||
return context.reflect();
|
||||
};
|
||||
runAt(( ) => {
|
||||
proxyApplyFn('EventTarget.prototype.addEventListener', proxyFn);
|
||||
if ( extraArgs.protect ) {
|
||||
const { addEventListener } = EventTarget.prototype;
|
||||
Object.defineProperty(EventTarget.prototype, 'addEventListener', {
|
||||
set() { },
|
||||
get() { return addEventListener; }
|
||||
});
|
||||
}
|
||||
proxyApplyFn('document.addEventListener', proxyFn);
|
||||
if ( extraArgs.protect ) {
|
||||
const { addEventListener } = document;
|
||||
Object.defineProperty(document, 'addEventListener', {
|
||||
set() { },
|
||||
get() { return addEventListener; }
|
||||
});
|
||||
}
|
||||
}, extraArgs.runAt);
|
||||
}
|
||||
registerScriptlet(preventAddEventListener , {
|
||||
name: 'prevent-addEventListener.js',
|
||||
aliases: [
|
||||
'addEventListener-defuser.js',
|
||||
'aeld.js',
|
||||
],
|
||||
dependencies: [
|
||||
proxyApplyFn,
|
||||
runAt,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
|
@ -27,6 +27,7 @@ import './json-edit.js';
|
|||
import './json-prune.js';
|
||||
import './noeval.js';
|
||||
import './object-prune.js';
|
||||
import './prevent-addeventlistener.js';
|
||||
import './prevent-dialog.js';
|
||||
import './prevent-fetch.js';
|
||||
import './prevent-innerHTML.js';
|
||||
|
|
@ -701,102 +702,6 @@ function abortOnPropertyWrite(
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'addEventListener-defuser.js',
|
||||
aliases: [
|
||||
'aeld.js',
|
||||
'prevent-addEventListener.js',
|
||||
],
|
||||
fn: addEventListenerDefuser,
|
||||
dependencies: [
|
||||
'proxy-apply.fn',
|
||||
'run-at.fn',
|
||||
'safe-self.fn',
|
||||
'should-debug.fn',
|
||||
],
|
||||
});
|
||||
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
|
||||
function addEventListenerDefuser(
|
||||
type = '',
|
||||
pattern = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
|
||||
const logPrefix = safe.makeLogPrefix('prevent-addEventListener', type, pattern);
|
||||
const reType = safe.patternToRegex(type, undefined, true);
|
||||
const rePattern = safe.patternToRegex(pattern);
|
||||
const debug = shouldDebug(extraArgs);
|
||||
const targetSelector = extraArgs.elements || undefined;
|
||||
const elementMatches = elem => {
|
||||
if ( targetSelector === 'window' ) { return elem === window; }
|
||||
if ( targetSelector === 'document' ) { return elem === document; }
|
||||
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
|
||||
const elems = Array.from(document.querySelectorAll(targetSelector));
|
||||
return elems.includes(elem);
|
||||
};
|
||||
const elementDetails = elem => {
|
||||
if ( elem instanceof Window ) { return 'window'; }
|
||||
if ( elem instanceof Document ) { return 'document'; }
|
||||
if ( elem instanceof Element === false ) { return '?'; }
|
||||
const parts = [];
|
||||
// https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079
|
||||
const id = String(elem.id);
|
||||
if ( id !== '' ) { parts.push(`#${CSS.escape(id)}`); }
|
||||
for ( let i = 0; i < elem.classList.length; i++ ) {
|
||||
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
|
||||
}
|
||||
for ( let i = 0; i < elem.attributes.length; i++ ) {
|
||||
const attr = elem.attributes.item(i);
|
||||
if ( attr.name === 'id' ) { continue; }
|
||||
if ( attr.name === 'class' ) { continue; }
|
||||
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
||||
const shouldPrevent = (thisArg, type, handler) => {
|
||||
const matchesType = safe.RegExp_test.call(reType, type);
|
||||
const matchesHandler = safe.RegExp_test.call(rePattern, handler);
|
||||
const matchesEither = matchesType || matchesHandler;
|
||||
const matchesBoth = matchesType && matchesHandler;
|
||||
if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) {
|
||||
debugger; // eslint-disable-line no-debugger
|
||||
}
|
||||
if ( matchesBoth && targetSelector !== undefined ) {
|
||||
if ( elementMatches(thisArg) === false ) { return false; }
|
||||
}
|
||||
return matchesBoth;
|
||||
};
|
||||
const proxyFn = function(context) {
|
||||
const { callArgs, thisArg } = context;
|
||||
let t, h;
|
||||
try {
|
||||
t = String(callArgs[0]);
|
||||
if ( typeof callArgs[1] === 'function' ) {
|
||||
h = String(safe.Function_toString(callArgs[1]));
|
||||
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
|
||||
if ( typeof callArgs[1].handleEvent === 'function' ) {
|
||||
h = String(safe.Function_toString(callArgs[1].handleEvent));
|
||||
}
|
||||
} else {
|
||||
h = String(callArgs[1]);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
if ( type === '' && pattern === '' ) {
|
||||
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
||||
} else if ( shouldPrevent(thisArg, t, h) ) {
|
||||
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
|
||||
}
|
||||
return context.reflect();
|
||||
};
|
||||
runAt(( ) => {
|
||||
proxyApplyFn('EventTarget.prototype.addEventListener', proxyFn);
|
||||
proxyApplyFn('document.addEventListener', proxyFn);
|
||||
}, extraArgs.runAt);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'adjust-setInterval.js',
|
||||
aliases: [
|
||||
|
|
|
|||
Loading…
Reference in a new issue