Can the window object be modified from a Chrome extension? [duplicate]
Asked Answered
F

7

66

I would like to make a Chrome extension that provides a new object inside window. When a web page is viewed in a browser with the extension loaded, I would like window.mything to be available via Javascript. The window.mything object will have some functions that I will define in the extension, and these functions should be callable from console.log or any Javascript file when the page is viewed in a browser with the extension enabled.

I was able to successfully inject a Javascript file into the page by using a Content Script:

var s = document.createElement("script"); 
s.src = chrome.extension.getURL("mything.js");
document.getElementsByTagName("head")[0].appendChild(s);

mything.js looks like this:

window.mything = {thing: true};
console.log(window);

Whenever a page loads, I see the entire window object as I expect it to be in the console. However, I can't interact with the window.mything object from the console. It seems at if the injected script hasn't really modified the global window object.

How can I modify the global window object from a Chrome extension?

Folio answered 12/9, 2012 at 20:26 Comment(1)
That's impossible. Are you viewing the right console, is the script really executed (inspect document and look for script tag / check for syntax errors)? The script certainly runs in the context of the page, and therefore, mything must be existent.Ass
F
70

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);
Frau answered 12/9, 2012 at 21:8 Comment(5)
Thanks, I didn't use your suggestion verbatim, but the concept helped me zero in on the real issue.Folio
U can also use eval("window.foo = {bar:function(){/*whatever*/}};");Hypophosphate
The <script> element hack worked perfectly. I'm curious, are these Javascript tags executed as soon as they are added to <head>, even if the page is already loaded?Lamasery
If it's inline script, ie <script>//do something</script> then yes, it's executed immediately (synchronously). If it's a remote script <script src="foo.js"></script> then it will be executed after the script is downloaded. Prior to the addition of the async tag this would be a common way to prevent a script download from blocking the page rendering.Frau
You can modify the window object using it's own mutators like addEventListener. No need for Document element injection.Easeful
A
29

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

  1. Add your script to content_scripts.
  2. 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:

  1. Add 2 scripts to content_scripts, e.g. inject.js and script.js
  2. The inject script will get the full absolute url of the script.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)
})()
  1. Your script.js will contain your main code:
;(function() {
  // your main code here
  window.foo = 'bar'
})()

Safari

It's very similar to Firefox.

  1. Create 2 javascript files, e.g. inject.js and script.js
  2. The inject script will get the full absolute url of the script.js file and load it:
;(function() {
  const script = document.createElement('script')
  script.src = safari.extension.baseURI + 'script.js'
  document.documentElement.appendChild(script)
})()
  1. 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

Apollonius answered 2/8, 2019 at 0:36 Comment(3)
I managed in firefox using the cloneInto command: developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/… Sadly this doesn't work in Chrome...Commons
thanks fro the solution, is this secure? or is there any other secure way?Barre
this is fail with Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-2hqy/01Xj3kkdA60wVjY8N6m2WXv5URv1rhk3jCDyHY='), or a nonce ('nonce-...') is required to enable inline execution. Denysedenzil
P
16

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.

Puttyroot answered 12/10, 2018 at 1:54 Comment(0)
R
10

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('_my_custom_event', {
  '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 \ (•◡•) /

Recapture answered 25/1, 2018 at 6:50 Comment(0)
S
6

I've been playing around with this. I found that I can interact with the window object of the browser by wrapping my javascript into a window.location= call.

var myInjectedJs = "window.foo='This exists in the \'real\' window object';"
window.location = "javascript:" + myInjectedJs;

var myInjectedJs2 = "window.bar='So does this.';"
window.location = "javascript:" + myInjectedJs2;

It works, but only for the last instance of window.location being set. If you access the document's window object, it will have a variable "bar" but not "foo"

Saxena answered 12/4, 2016 at 1:40 Comment(0)
P
1

Thanks to the other answers here, this is what I'm using:

((source)=>{
  const script = document.createElement("script");
  script.text = `(${source.toString()})();`;
  document.documentElement.appendChild(script);
})(function (){

  // Your code here
  // ...

})

Works great, no issues.

Pfister answered 2/10, 2020 at 15:29 Comment(0)
E
-2

Content Scripts can call window methods which can then be used to mutate the window object. This is easier than <script> tag injection and works even when the <head> and <body> haven't yet been parsed (e.g. when using run_at: document_start).

// In Content Script
window.addEventListener('load', loadEvent => {
    let window = loadEvent.currentTarget;
    window.document.title='You changed me!';
});
Easeful answered 23/10, 2017 at 11:54 Comment(3)
This seems to only work for existing variables not for new ones, can someone verify this?Driest
this window object that is referred in this script is part of the content script and not the embedding page so is not the correct solution to the questionOphthalmology
Extension and page use different contexts, this answer is misleading.Fiona

© 2022 - 2024 — McMap. All rights reserved.