Search code examples
javascriptgoogle-chromegoogle-chrome-extensionecmascript-6ecmascript-2016

JavaScript - addEventListener is not defined


I am creating a chrome extension for blocking ads. Everything worked pretty fine so far. My goal is to:

  1. Disable WebSocket
  2. Disable console.log
  3. Disable alert
  4. Disable popups (window.open)
  5. Disable 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

  1. In 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)
  2. From init.js inject code in webpage, so that my extension can access non-sandboxed environment
  3. Override blacklisted functions

I 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.

Edit

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.


Solution

  • addEventListener isn't a global Actually, it is, indirectly. It's a property of EventTarget.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:

    • The event name is beforeunload, not onbeforeunload.
    • You'll need to redefine the window.onbeforeunload property to prevent assignments to it, probably via Object.defineProperty.