Modify a web page

One of the most common use cases for an extension is to modify a web page. For example, an extension might want to change the style applied to a page, hide particular DOM nodes, or inject extra DOM nodes into the page.

There are two ways to do this with WebExtensions APIs:

  • Declaratively : Define a pattern that matches a set of URLs, and load a set of scripts into pages whose URL matches that pattern.
  • Programmatically : Using a JavaScript API, load a script into the page hosted by a particular tab.

Either way, these scripts are called content scripts , and are different from the other scripts that make up an extension:

  • They only get access to a small subset of the WebExtension APIs.
  • They get direct access to the web page in which they are loaded.
  • They communicate with the rest of the extension using a messaging API.

In this article we'll look at both methods of loading a script.

Modifying pages that match a URL pattern

First of all, create a new directory called "modify-page". In that directory, create a file called "manifest.json", with the following contents:

{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["https://developer.mozilla.org/*"],
      "js": ["page-eater.js"]
    }
  ]
}

					

content_scripts key is how you load scripts into pages that match URL patterns. In this case, content_scripts instructs the browser to load a script called "page-eater.js" into all pages under https://developer.mozilla.org/ .

注意: 由于 "js" property of content_scripts is an array, you can use it to inject more than one script into matching pages. If you do this the pages share the same scope, just like multiple scripts loaded by a page, and they are loaded in the order that they are listed in the array.

注意: content_scripts key also has a "css" property that you can use to inject CSS stylesheets.

Next, create a file called "page-eater.js" inside the "modify-page" directory, and give it the following contents:

document.body.textContent = "";
var header = document.createElement('h1');
header.textContent = "This page has been eaten";
document.body.appendChild(header);

					

现在 install the extension , and visit https://developer.mozilla.org/ . The page should look like this:

developer.mozilla.org page "eaten" by the script

Modifying pages programmatically

What if you still want to eat pages, but only when the user asks you to? Let's update this example so we inject the content script when the user clicks a context menu item.

First, update "manifest.json" so it has the following contents:

{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",
  "permissions": [
    "activeTab",
    "contextMenus"
  ],
  "background": {
    "scripts": ["background.js"]
  }
}

					

Here, we've removed the content_scripts key, and added two new keys:

  • permissions : To inject scripts into pages we need permissions for the page we're modifying. The activeTab permission is a way to get this temporarily for the currently active tab. We also need the contextMenus permission to be able to add context menu items.
  • background : We're using this to load a persistent "background script" called background.js , in which we'll set up the context menu and inject the content script.

Let's create this file. Create a new file called background.js  in the modify-page  directory, and give it the following contents:

browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page"
});
browser.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "eat-page") {
    browser.tabs.executeScript({
      file: "page-eater.js"
    });
  }
});

					

In this script we're creating a context menu item , giving it a specific id and title (the text to be displayed in the context menu). Then we set up an event listener so that when the user clicks a context menu item, we check to see if it is our eat-page item. If it is, we inject "page-eater.js" into the current tab using the tabs.executeScript() API. This API optionally takes a tab ID as an argument: we've omitted the tab ID, which means that the script is injected into the currently active tab.

At this point the extension should look like this:

modify-page/
    background.js
    manifest.json
    page-eater.js
					

现在 reload the extension , open a page (any page, this time) activate the context menu, and select "Eat this page":

Option to eat a page on the context menu

Messaging

Content scripts and background scripts can't directly access each other's state. However, they can communicate by sending messages. One end sets up a message listener, and the other end can then send it a message. The following table summarizes the APIs involved on each side:

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

注意: In addition to this method of communication, which sends one-off messages, you can also use a connection-based approach to exchange messages . For advice on choosing between the options, see Choosing between one-off messages and connection-based messaging .

Let's update our example to show how to send a message from the background script.

First, edit background.js  so that it has these contents:

browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page"
});
function messageTab(tabs) {
  browser.tabs.sendMessage(tabs[0].id, {
    replacement: "Message from the extension!"
  });
}
function onExecuted(result) {
    let querying = browser.tabs.query({
        active: true,
        currentWindow: true
    });
    querying.then(messageTab);
}
browser.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "eat-page") {
    let executing = browser.tabs.executeScript({
      file: "page-eater.js"
    });
    executing.then(onExecuted);
  }
});

					

Now, after injecting page-eater.js , we use tabs.query() to get the currently active tab, and then use tabs.sendMessage() to send a message to the content scripts loaded into that tab. The message has the payload {replacement: "Message from the extension!"} .

Next, update page-eater.js  like this:

function eatPageReceiver(request, sender, sendResponse) {
  document.body.textContent = "";
  let header = document.createElement('h1');
  header.textContent = request.replacement;
  document.body.appendChild(header);
}
browser.runtime.onMessage.addListener(eatPageReceiver);

					

Now, instead of just eating the page right away, the content script listens for a message using runtime.onMessage . When a message arrives, the content script runs essentially the same code as before, except that the replacement text is taken from request.replacement .

由于 tabs.executeScript() is an asynchronous function, and to ensure we send message only after listener has been added in page-eater.js , we use onExecuted() which will be called after page-eater.js  executed.

注意: Press Ctrl + Shift + J (或 Cmd + Shift + J on macOS) OR web-ext run --bc to open Browser Console to view console.log in background script.

Alternatively, use Add-on Debugger   which allows you set breakpoint. There is currently no way to start Add-on Debugger directly from web-ext .

If we want send messages back from the content script to the background page,  we would use runtime.sendMessage() 而不是 tabs.sendMessage() ,如:

browser.runtime.sendMessage({
    title: "from page-eater.js"
});

					

注意: These examples all inject JavaScript; you can also inject CSS programmatically using the tabs.insertCSS() 函数。

了解更多

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