6

I'm working on a Chrome extension with many content scripts. Most of them use the same function(s), so I'd like to share those functions between the content scripts; however I can't seem to figure out how to do it.
Content scripts are sandboxed, so don't have access to the same window object. It seems there should be an obvious solution, but I haven't been able to find it.

Protector one
  • 6,926
  • 5
  • 62
  • 86
  • They're all in the same isolated world. They can interact with each other just fine. Have you been having trouble getting that to work? – Teepeemm Oct 07 '14 at 16:59
  • 1
    From https://developer.chrome.com/extensions/content_scripts: "isolated worlds allow each content script to make changes to its JavaScript environment without worrying about conflicting with the page or with other content scripts." This implies the content scripts don't share a single isolated world. This is in line with my own findings, but I'd prefer to be wrong! – Protector one Oct 07 '14 at 17:35
  • possible duplicate of [Can I share code between different parts of Chrome Extension?](http://stackoverflow.com/questions/24227874/can-i-share-code-between-different-parts-of-chrome-extension) – Xan Oct 07 '14 at 18:15
  • 2
    @Protectorone That only applies to content scripts from different extensions. If you inject several scripts from one extension, they share the context. – Xan Oct 07 '14 at 18:16

1 Answers1

16

Content scripts are sandboxed, so don't have access to the same window object.

That's not entirely true: although content scripts by default cannot interact with the window object of the page where they are loaded, they share the same hidden window object of the "isolated world". So if you load two different content scripts into the same page, you'll be using the same window for both content scripts.

Here is an example, try it by yourself, and do something like this:

  • A sample of manifest.json:

     {
         "name": "My extension",
    
         ...
    
         "content_scripts": [
             {
               "matches": ["*://*/*"],
               "js": ["one.js", "two.js"]
             }
         ]
     }
    
  • The script one.js with the function:

     function sayHello(name) {
         alert("Hello " + name + "!");
     }
    
  • The script two.js that uses that function:

     sayHello('John');
    

What happens here?

That's pretty easy to tell:

  1. When a page is loaded, the content scripts are injected
  2. The one.js script is injected first and defines the sayHello function
  3. Now two.js and any other content script injected after one.js can use that function
  4. Calling sayHello('John'); will alert "Hello John!" as expected.

That's why most of the developers (me too) love doing something like this:

"content_scripts": [
    {
      "matches": ["*://*/*"]
      "js": ["jquery.min.js", "script.js"]
    }
]

because that's an easy way to include jQuery and use it with your content scripts.

Obviously content scripts will share the same window object even if they are injected from the background page with the chrome.scripting.executeScript() (MV3) or chrome.tabs.executeScript() (MV2) function.

To answer your question:

You can easily create a script containing all your utilities and most used functions, so you'll always be ready to use them in any other content script injected after the first one.

So, basically, doing something like this:

"content_scripts": [
    {
      "matches": ["*://*/*"]
      "js": ["script1.js", "script2.js", "script3.js", ...]
    }
]

means that:

  • script1.js is injected
  • script2.js is injected and inherits the namespace of script1.js
  • script3.js is injected and inherits the namespace of script2.js and script1.js
  • and so on...

Clarifications

When the Google documentation says:

Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.

This means that:

  • Content scripts cannot interact with the namespace of the page they are injected to.
  • Content scripts cannot interact with the namespace of content scripts injected in the same page by another extension.

But:

  • They can interact with the page's DOM.
  • They can interact with the namespace of other content scripts injected on the same page by the same extension.

In ManifestV3 content scripts can be loaded in the window of the page by specifying "world": "MAIN" in their content_scripts declaration (Chrome 111 and newer) or when injecting/registering via chrome.scripting API (Chrome 95/102 and newer).

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • 2
    To clarify: what documentation meant was "content scripts from different extensions are isolated". – Xan Oct 07 '14 at 18:24
  • Thanks I'll add it to the answer – Marco Bonelli Oct 07 '14 at 18:25
  • This does not really work for me. I can see both scripts being injected and executed in the right order but when the second calls a method defined in the first, i get a method not defined error. – gtato Apr 18 '21 at 15:04
  • Confirmed that this also works in manifest v3 – Alexander May 05 '22 at 13:36
  • I know this is an old question, but how do you keep your namespace clean when doing that? – btonasse Jul 13 '22 at 06:40
  • 2
    @btonasse that's up to you really. The simplest way is probably to isolate stuff wrapping it in an anonymous function like: `(function() { /* stuff here */ })()`. – Marco Bonelli Jul 13 '22 at 12:24