lucio
lucio

Reputation: 157

Hook window property in custom chrome extension

I'm trying to hook some window property in a custom chrome extension. I'd like to intercept get/set calls so in my content script I added some functions:

window.__defineGetter__('name', function() { ... })
window.__defineSetter__('name', function(v) { ... })

But it seems that it will not override the default behavior of window.name property. What could be the problem?

This is the manifest entry which inject the content script:

...
"content_scripts": [
    {
      "matches": [
        "*://*/*"
      ],
      "js": [
        "src/inject/inject.js"
      ]
    }
]
...

Upvotes: 2

Views: 2847

Answers (1)

Xan
Xan

Reputation: 77531

There are two problems at work here:

1) Your script is injected too late to affect the scripts on the page.

See the documentation regarding "run_at" property:

Optional. Controls when the files in js are injected. Can be "document_start", "document_end", or "document_idle". Defaults to "document_idle".

In the case of "document_start", the files are injected after any files from css, but before any other DOM is constructed or any other script is run.

In the case of "document_end", the files are injected immediately after the DOM is complete, but before subresources like images and frames have loaded.

In the case of "document_idle", the browser chooses a time to inject scripts between "document_end" and immediately after the window.onload event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.

So by default your script runs after the whole DOM is constructed (and consequently, scripts that are part of it are run) + some non-deterministic delay until the page is "idle".

To fix, you need to add "run_at" : "document_start" to your content script definition.


Note that this can be expensive in terms of page load performance. You should limit code that runs at "document_start" to only the code that requires it.

You can make a second content script that will inject later and will be using the same context. Just hooking into DOMContentLoaded is possible, but the script still needs to compile before it's run, so there still will be a performance impact.


2) Content scripts have an isolated context

What you do with this is change window.name for your content script only. Since the console by default executes in the page context (<top frame> in the selector), you don't see the effect.

This is a result of context isolation. However, it can be bypassed, by injecting a <script> tag into the document. Note that this bypasses the page's CSP, if any.

Code courtesy of Rob W:

var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('script.js');
s.onload = function() {
    this.parentNode.removeChild(this);
};
(document.head || document.documentElement).appendChild(s);

Note that document.head will be undefined at "document_start", but the fallback will succeed.

To read more about web_accessible_resources, see docs.
For page-level scripts in general, the canonical question.

Upvotes: 2

Related Questions