I am creating a chrome extension for blocking ads. Everything worked pretty fine so far. My goal is to:
WebSocket
console.log
alert
window.open
)onbeforeunload
Of course, it is supposed to block these functions only on specified websites. I searched on how to block a function. What I found is that the only way to disable a function using extension is to
manifest.json
add a content script init.js
which will run at document_start
(this will prevent any other code from being executed before my extension's code, so nobody can steal and save reference to these functions before I disable them)init.js
inject code in webpage, so that my extension can access non-sandboxed environmentI did everything, but the problem was how to override functions. I found on EcmaScript7 specification that there is no way to detect if a function is proxy instance or not. So, I proxyfied every of these functions and it works as expected. Websites couldn't notice that I've overriden all of these functions.
However, it works for all functions expect addEventListener
. In order to block onbeforeunload
, I should add a proxy handler to it and track calls to check if listener name is onbeforeunload
. I wrote the following script:
addEventListener = new Proxy(addEventListener, {
apply: (f, t, args) => {
if(args[0] !== 'onbeforeunload'){
f.apply(t, args);
}
}
});
This should filter calls and allow only calls which doesn't set onbeforeunload
event. However, it throws ReferenceError: addEventListener is not defined. It surprised me a lot. I supposed that it is maybe because addEventListener
is not yet initialized at document_start
, but console.log(typeof addEventListener) logs function
to console. How is that even possible?
I couldn't explain it. However, I tried to put it in a function and then to requestAnimationFrame
to that function until addEventListener
become accessible (I ecapsulated the above code in try...catch
too). But, it turns out that modifying addEventListener
always throws ReferenceError, while just accessing it never throws error.
I also tried to access it from developer console and from javascript:...
method, but it behaves normally, I can modify it without any errors.
For sure, the problem isn't like this one, becuase I properly injected script into non-sandboxed (real) page environment and I have access to real window
object.
Actually, it is, indirectly. It's a property of addEventListener
isn't a globalEventTarget.prototype
(at least in Chrome). window
has EventTarget.prototype
in its prototype chain, which makes addEventListener
a global.
So you'd need to proxy that instead, roughly (without your event name check, just for demonstration):
const save = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = new Proxy(save, {
apply: function(target, thisArg, args) {
console.log("Foo!");
return save.apply(thisArg, args);
}
});
document.body.addEventListener("click", function() {
console.log("Clicked");
});
<p>....</p>
Other notes:
beforeunload
, not onbeforeunload
.window.onbeforeunload
property to prevent assignments to it, probably via Object.defineProperty
.