Can the window object be modified from a Chrome extension?
You can't, not directly. From the content scripts documentation:
However, content scripts have some limitations. They cannot:
- Use chrome.* APIs (except for parts of chrome.extension)
- Use variables or functions defined by their extension's pages
- Use variables or functions defined by web pages or by other content scripts
(emphasis added)
The window
object the content script sees is not the same window
object that the page sees.
You can pass messages via the DOM, however, by using the window.postMessage
method. Both your page and content script listen to the message event, and whenever you call window.postMessage
from one of those places, the other will receive it. There's an example of this on the "Content Scripts" documentation page.
edit:
You could potentially add some methods to the page by injecting a script from the content script. It still wouldn't be able to communicate back with the rest of the extension though, without using something like postMessage
, but you could at least add some things to the page's window
var elt = document.createElement("script");
elt.innerHTML = "window.foo = {bar:function(){/*whatever*/}};"
document.head.appendChild(elt);
After hours trying different attempts and facing security issues like CORS, I found ways to edit the window
object on Chrome
, Firefox
and Safari
. You need to use different strategies for each one:
Chrome
- Add your script to
content_scripts
. - Inside your script file, append a
script
to the page and make it run your custom code inline. Like this:
;(function() {
function script() {
// your main code here
window.foo = 'bar'
}
function inject(fn) {
const script = document.createElement('script')
script.text = `(${fn.toString()})();`
document.documentElement.appendChild(script)
}
inject(script)
})()
Firefox
On Firefox, the solution above doesn't work due to a Content-Security-Policy
error. But the following workaround is currently working, at least for now:
- Add 2 scripts to
content_scripts
, e.g.inject.js
andscript.js
- The
inject
script will get the full absolute url of thescript.js
file and load it:
;(function() {
const b = typeof browser !== 'undefined' ? browser : chrome
const script = document.createElement('script')
script.src = b.runtime.getURL('script.js')
document.documentElement.appendChild(script)
})()
- Your
script.js
will contain your main code:
;(function() {
// your main code here
window.foo = 'bar'
})()
Safari
It's very similar to Firefox.
- Create 2 javascript files, e.g.
inject.js
andscript.js
- The
inject
script will get the full absolute url of thescript.js
file and load it:
;(function() {
const script = document.createElement('script')
script.src = safari.extension.baseURI + 'script.js'
document.documentElement.appendChild(script)
})()
- Your
script.js
will contain your main code:
;(function() {
// your main code here
window.foo = 'bar'
})()
Source code
See full code here: https://github.com/brunolemos/simplified-twitter
As others have pointed out, context scripts do not run in the same context as the page's, so, to access the correct window
, you need to inject code into the page.
Here's my take at it:
function codeToInject() {
// Do here whatever your script requires. For example:
window.foo = "bar";
}
function embed(fn) {
const script = document.createElement("script");
script.text = `(${fn.toString()})();`;
document.documentElement.appendChild(script);
}
embed(codeToInject);
Clean and easy to use. Whatever you need to run in the page's context, put it in codeToInject()
(you may call it whatever you prefer). The embed()
function takes care of packaging your function and sending it to run in the page.
What the embed()
function does is to create a script
tag in the page and embed the function codeToInject()
into it as an IIFE. The browser will immediately execute the new script
tag as soon as it's appended to the document and your injected code will run in the context of the page, as intended.
A chrome extension's content_script
runs within its own context which is separate from the window. You can inject a script into the page though so it runs in the same context as the page's window, like this: Chrome extension - retrieving global variable from webpage
I was able to call methods on the window object and modify window properties by essentially adding a script.js
to the page's DOM:
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
s.remove();
};
and creating custom event listeners in that injected script file:
document.addEventListener('_my_custom_event', function(e) {
// do whatever you'd like! Like access the window obj
window.myData = e.detail.my_event_data;
})
and dispatching that event in the content_script:
var foo = 'bar'
document.dispatchEvent(new CustomEvent('_save_OG_Editor', {
'detail': {
'my_event_data': foo
}
}))
or vice versa; dispatch events in script.js
and listen for them in your extension's content_script (like the above link illustrates well).
Just be sure to add your injected script within your extension's files, and add the script file's path to your manifest within "web_accessible_resources"
or you'll get an error.
Hope that helps someone \ (•◡•) /