Protector one
Protector one

Reputation: 7271

How do I re-use code between content scripts in a Chrome extension?

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.

Upvotes: 7

Views: 1927

Answers (1)

Marco Bonelli
Marco Bonelli

Reputation: 69367

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

Upvotes: 18

Related Questions