Content scripts

A content script is a part of your extension that runs in the context of a particular web page (as opposed to background scripts which are part of the extension, or scripts which are part of the web site itself, such as those loaded using the <script> element).

Background scripts can access all the  WebExtension JavaScript APIs , but they can't directly access the content of web pages. So if your extension needs to do that, you need content scripts.

Just like the scripts loaded by normal web pages, content scripts can read and modify the content of their pages using the standard DOM APIs.

Content scripts can only access a small subset of the WebExtension APIs , but they can communicate with background scripts using a messaging system, and thereby indirectly access the WebExtension APIs.

注意: Content scripts are blocked on the following domains:

  • accounts-static.cdn.mozilla.net
  • accounts.firefox.com
  • addons.cdn.mozilla.net
  • addons.mozilla.org
  • api.accounts.firefox.com
  • content.cdn.mozilla.net
  • discovery.addons.mozilla.org
  • input.mozilla.org
  • install.mozilla.org
  • oauth.accounts.firefox.com
  • profile.accounts.firefox.com
  • support.mozilla.org
  • sync.services.mozilla.com
  • testpilot.firefox.com

If you try to inject a content script into a page in these domains, it fails and the page logs a CSP 错误。

Because these restrictions include addons.mozilla.org, users may attempt to use your extension immediately after installation—only to find that it doesn't work! You may want to add an appropriate warning, or an onboarding page to move users away from addons.mozilla.org .

注意: Values added to the global scope of a content script with let foo or window.foo = "bar" may disappear due to bug 1408996 .

Loading content scripts

You can load a content script into a web page in one of three ways:

  1. At install time, into pages that match URL patterns.

    使用 content_scripts key in your manifest.json , you can ask the browser to load a content script whenever the browser loads a page whose URL matches a given pattern .

  2. At runtime, into pages that match URL patterns.

    使用 contentScripts API, you can ask the browser to load a content script whenever the browser loads a page whose URL matches a given pattern . (This is similar to method 1, except that you can add and remove content scripts at runtime.)

  3. At runtime, into specific tabs.

    使用 tabs.executeScript() API, you can load a content script into a specific tab whenever you want. (For example, in response to the user clicking on a browser action )。

There is only one global scope per frame, per extension . This means that variables from one content script can directly be accessed by another content script, regardless of how the content script was loaded.

Using methods (1) and (2), you can only load scripts into pages whose URLs can be represented using a match pattern .

Using method (3), you can also load scripts into pages packaged with your extension, but you can't load scripts into privileged browser pages (like " about:debugging " or " about:addons ").

注意: Dynamic JS module imports are now working in content scripts. For more details, see bug 1536094 . Only URLs with the moz-extension scheme are allowed, which excludes data URLs ( bug 1587336 ).

Content script environment

DOM access

Content scripts can access and modify the page's DOM, just like normal page scripts can. They can also see any changes that were made to the DOM by page scripts.

However, content scripts get a "clean" view of the DOM. This means:

  • Content scripts cannot see JavaScript variables defined by page scripts.
  • If a page script redefines a built-in DOM property, the content script sees the original version of the property, not the redefined version.

In Firefox, this behavior is called Xray vision .

Consider a web page like this:

										<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  </head>
  <body>
    <script src="page-scripts/page-script.js"></script>
  </body>
</html>
										
					

脚本 page-script.js  does this:

										// page-script.js
// add a new element to the DOM
let p = document.createElement("p");
p.textContent = "This paragraph was added by a page script.";
p.setAttribute("id", "page-script-para");
document.body.appendChild(p);
// define a new property on the window
window.foo = "This global variable was added by a page script";
// redefine the built-in window.confirm() function
window.confirm = function() {
  alert("The page script has also redefined 'confirm'");
}
										
					

Now an extension injects a content script into the page:

										// content-script.js
// can access and modify the DOM
let pageScriptPara = document.getElementById("page-script-para");
pageScriptPara.style.backgroundColor = "blue";
// can't see properties added by page-script.js
console.log(window.foo);  // undefined
// sees the original form of redefined properties
window.confirm("Are you sure?"); // calls the original window.confirm()
										
					

The same is true in reverse; page scripts cannot see JavaScript properties added by content scripts.

This means that content scripts can rely on DOM properties behaving predictably, without worrying about its variables clashing with variables from the page script.

One practical consequence of this behavior is that a content script doesn't have access to any JavaScript libraries loaded by the page. So, for example, if the page includes jQuery, the content script can't see it.

If a content script needs to use a JavaScript library, then the library itself should be injected as a content script alongside the content script that wants to use it:

										"content_scripts": [
  {
    "matches": ["*://*.mozilla.org/*"],
    "js": ["jquery.js", "content-script.js"]
  }
]
										
					

注意:  Firefox does provide some APIs that enable content scripts to access JavaScript objects created by page scripts, and to expose their own JavaScript objects to page scripts.

Sharing objects with page scripts 了解更多细节。

WebExtension APIs

In addition to the standard DOM APIs, content scripts can use the following WebExtension APIs:

extension :

runtime :

i18n :

menus :

Everything from:

XHR and Fetch

Content scripts can make requests using the normal window.XMLHttpRequest and window.fetch() API。

注意: In Firefox, content script requests (for example, using fetch() ) happen in the context of an extension, so you must provide an absolute URL to reference page content.

In Chrome, these requests happen in context of the page, so they are made to a relative URL.  For example, /api is sent to https://«current page URL»/api .

Content scripts get the same cross-domain privileges as the rest of the extension: so if the extension has requested cross-domain access for a domain using the permissions key in manifest.json , then its content scripts get access that domain as well.

This is accomplished by exposing more privileged XHR and fetch instances in the content script, which has the side-effect of not setting the Origin and Referer headers like a request from the page itself would; this is often preferable to prevent the request from revealing its cross-origin nature.

注意: In Firefox, extensions that need to perform requests that behave as if they were sent by the content itself can use  content.XMLHttpRequest and content.fetch() 代替。

For cross-browser extensions, the presence of these methods must be feature-detected.

注意: In Chrome, starting with version 73, content scripts are subject to the same CORS policy as the page they are running within. Only backend scripts have elevated cross-domain privileges. See Changes to Cross-Origin Requests in Chrome Extension Content Scripts .

Communicating with background scripts

Although content scripts can't directly use most of the WebExtension APIs, they can communicate with the extension's background scripts using the messaging APIs, and can therefore indirectly access all the same APIs that the background scripts can.

There are two basic patterns for communicating between the background scripts and content scripts:

  • You can send one-off messages (with an optional response).
  • You can set up a longer-lived connection between the two sides , and use that connection to exchange messages.

One-off messages

To send one-off messages, with an optional response, you can use the following APIs:

In content script In background script
Send a message browser.runtime.sendMessage() browser.tabs.sendMessage()
Receive a message browser.runtime.onMessage browser.runtime.onMessage

For example, here's a content script that listens for click events in the web page.

If the click was on a link, it sends a message to the background page with the target URL:

										// content-script.js
window.addEventListener("click", notifyExtension);
function notifyExtension(e) {
  if (e.target.tagName != "A") {
    return;
  }
  browser.runtime.sendMessage({"url": e.target.href});
}
										
					

The background script listens for these messages and displays a notification using the notifications API:

										// background-script.js
browser.runtime.onMessage.addListener(notify);
function notify(message) {
  browser.notifications.create({
    "type": "basic",
    "iconUrl": browser.extension.getURL("link.png"),
    "title": "You clicked a link!",
    "message": message.url
  });
}
										
					

(This example code is lightly adapted from the notify-link-clicks-i18n example on GitHub.)

Connection-based messaging

Sending one-off messages can get cumbersome if you are exchanging a lot of messages between a background script and a content script. So an alternative pattern is to establish a longer-lived connection between the two contexts, and use this connection to exchange messages.

Both sides have a runtime.Port object, which they can use to exchange messages.

To create the connection:

This returns a runtime.Port 对象。

Once each side has a port, the two sides can:

  • Send messages using runtime.Port.postMessage()
  • Receive messages using runtime.Port.onMessage()

For example, as soon as it loads, the following content script:

  • Connects to the background script
  • Stores the Port in a variable myPort
  • Listens for messages on myPort   (and logs them)
  • 使用 myPort to sends messages to the background script when the user clicks the document
										// content-script.js
let myPort = browser.runtime.connect({name:"port-from-cs"});
myPort.postMessage({greeting: "hello from content script"});
myPort.onMessage.addListener(function(m) {
  console.log("In content script, received message from background script: ");
  console.log(m.greeting);
});
document.body.addEventListener("click", function() {
  myPort.postMessage({greeting: "they clicked the page!"});
});
										
					

The corresponding background script:

  • Listens for connection attempts from the content script
  • When receiving a connection attempt:
    • Stores the port in a variable named portFromCS
    • Sends the content script a message using the port
    • Starts listening to messages received on the port, and logs them
  • Sends messages to the content script, using portFromCS , when the user clicks the extension's browser action
										// background-script.js
let portFromCS;
function connected(p) {
  portFromCS = p;
  portFromCS.postMessage({greeting: "hi there content script!"});
  portFromCS.onMessage.addListener(function(m) {
    portFromCS.postMessage({greeting: "In background script, received message from content script:" + m.greeting});
  });
}
browser.runtime.onConnect.addListener(connected);
browser.browserAction.onClicked.addListener(function() {
  portFromCS.postMessage({greeting: "they clicked the button!"});
});
										
					

Multiple content scripts

If you have multiple content scripts communicating at the same time, you might want to store connections to them in an array.

										// background-script.js
let ports = []
function connected(p) {
  ports[p.sender.tab.id] = p
  //...
}
browser.runtime.onConnect.addListener(connected)
browser.browserAction.onClicked.addListener(function() {
  ports.forEach( p => {
        p.postMessage({greeting: "they clicked the button!"})
    })
});
										
					

Choosing between one-off messages and connection-based messaging

The choice between one-off and connection-based messaging depends on how your extension expects to make use of messaging.

The recommended best practices are:

Use one-off messages when…

  • Only one response is expected to a message.
  • A small number of scripts listen to receive messages ( runtime.onMessage calls).

Use connection-based messaging when…

  • Scripts engage in sessions where multiple messages are exchanged.
  • The extension needs to know about task progress or if a task is interrupted, or wants to interrupt a task initiated using messaging.

Communicating with the web page

By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM window.postMessage and window.addEventListener API。

例如:

										// page-script.js
let messenger = document.getElementById("from-page-script");
messenger.addEventListener("click", messageContentScript);
function messageContentScript() {
  window.postMessage({
    direction: "from-page-script",
    message: "Message from the page"
  }, "*");
}
										
					
										// content-script.js
window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data &&
      event.data.direction == "from-page-script") {
    alert("Content script received message: \"" + event.data.message + "\"");
  }
});
										
					

For a complete working example of this, visit the demo page on GitHub and follow the instructions.

警告: Be very careful when interacting with untrusted web content in this manner! Extensions are privileged code which can have powerful capabilities and hostile web pages can easily trick them into accessing those capabilities.

To give a trivial example, suppose the content script code that receives the message does something like this:

										// content-script.js
window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data.direction   &&
      event.data.direction == "from-page-script") {
    eval(event.data.message);
  }
});
										
					

Now the page script can run any code with all the privileges of the content script.

使用 eval() in content scripts

In Chrome

eval always runs code in the context of the content script , not in the context of the page.

In Firefox

若调用 eval() , it runs code in the context of the content script .

若调用 window.eval() , it runs code in the context of the page .

For example, consider a content script like this:

										// content-script.js
window.eval('window.x = 1;');
eval('window.y = 2');
console.log(`In content script, window.x: ${window.x}`);
console.log(`In content script, window.y: ${window.y}`);
window.postMessage({
  message: "check"
}, "*");
										
					

This code just creates some variables x and y 使用 window.eval() and eval() ,  logs their values, and then messages the page.

On receiving the message, the page script logs the same variables:

										window.addEventListener("message", function(event) {
  if (event.source === window && event.data && event.data.message === "check") {
    console.log(`In page script, window.x: ${window.x}`);
    console.log(`In page script, window.y: ${window.y}`);
  }
});
										
					

In Chrome, this produces output like this:

In content script, window.x: 1
In content script, window.y: 2
In page script, window.x: undefined
In page script, window.y: undefined
					

In Firefox, this produces output like this:

In content script, window.x: undefined
In content script, window.y: 2
In page script, window.x: 1
In page script, window.y: undefined
					

The same applies to setTimeout() , setInterval() ,和 Function() .

警告: Be very careful when running code in the context of the page!

The page's environment is controlled by potentially malicious web pages, which can redefine objects you interact with to behave in unexpected ways:

										// page.js redefines console.log
let original = console.log;
console.log = function() {
  original(true);
}
										
					
										// content-script.js calls the redefined version
window.eval('console.log(false)');
										
					

Found a problem with this page?

最后修改: , 由 MDN 贡献者

  1. 浏览器扩展名
  2. 快速入门
    1. What are extensions?
    2. Your first extension
    3. Your second extension
    4. Anatomy of an extension
    5. Example extensions
    6. What next?
  3. 概念
    1. Using the JavaScript APIs
    2. Content scripts
    3. Match patterns
    4. Working with files
    5. 国际化
    6. Content Security Policy
    7. Native messaging
    8. Differences between API implementations
    9. Chrome incompatibilities
  4. 用户界面
    1. 用户界面
    2. Toolbar button
    3. Address bar button
    4. Sidebars
    5. Context menu items
    6. Options page
    7. Extension pages
    8. Notifications
    9. Address bar suggestions
    10. Developer tools panels
  5. 如何
    1. Intercept HTTP requests
    2. Modify a web page
    3. Insert external content
    4. Share objects with page scripts
    5. Add a button to the toolbar
    6. Implement a settings page
    7. Work with the Tabs API
    8. Work with the Bookmarks API
    9. Work with the Cookies API
    10. Work with contextual identities
    11. Interact with the clipboard
    12. Build a cross-browser extension
  6. Firefox differentiators
  7. JavaScript API
    1. Browser support for JavaScript APIs
    2. alarms
    3. bookmarks
    4. browserAction
    5. browserSettings
    6. browsingData
    7. captivePortal
    8. clipboard
    9. 命令
    10. contentScripts
    11. contextualIdentities
    12. Cookie
    13. devtools
    14. dns
    15. downloads
    16. events
    17. extension
    18. extensionTypes
    19. find
    20. history
    21. i18n
    22. identity
    23. idle
    24. management
    25. menus
    26. notifications
    27. omnibox
    28. pageAction
    29. permissions
    30. pkcs11
    31. privacy
    32. proxy
    33. runtime
    34. search
    35. sessions
    36. sidebarAction
    37. storage
    38. tabs
    39. theme
    40. topSites
    41. 类型
    42. userScripts
    43. webNavigation
    44. webRequest
    45. windows
  8. Manifest keys
    1. 介绍
    1. 作者
    2. background
    3. browser_action
    4. browser_specific_settings
    5. chrome_settings_overrides
    6. chrome_url_overrides
    7. 命令
    8. content_scripts
    9. content_security_policy
    10. default_locale
    11. description
    12. developer
    13. devtools_page
    14. dictionaries
    15. externally_connectable
    16. homepage_url
    17. icons
    18. incognito
    19. manifest_version
    20. name
    21. offline_enabled
    22. omnibox
    23. optional_permissions
    24. options_page
    25. options_ui
    26. page_action
    27. permissions
    28. protocol_handlers
    29. short_name
    30. sidebar_action
    31. storage
    32. theme
    33. theme_experiment
    34. user_scripts
    35. version
    36. version_name
    37. web_accessible_resources
  9. Extension Workshop
    1. Develop
    2. Publish
    3. Manage
    4. Enterprise
  10. Contact us
  11. Channels
    1. Add-ons blog
    2. Add-ons forum
    3. Add-ons chat