修改网页
扩展最常见的用例之一就是修改网页。例如,一个扩展可能希望更改应用于页面的样式、隐藏特定的 DOM 节点或将额外的 DOM 节点注入页面。
使用 WebExtensions API 可以通过两种方式实现此目的:
- 声明式地:定义一个匹配一组 URL 的模式,并将一组脚本加载到 URL 与该模式匹配的页面中。
- 编程式地:使用 JavaScript API,将脚本加载到由特定标签页托管的页面中。
无论哪种方式,这些脚本都称为内容脚本,并且与构成扩展的其他脚本不同。
- 它们只能访问 WebExtension API 的一小部分。
- 它们可以直接访问加载它们的网页。
- 它们使用消息传递 API 与扩展的其余部分进行通信。
在本文中,我们将介绍加载脚本的这两种方法。
修改匹配 URL 模式的页面
首先,创建一个名为“modify-page”的新目录。在该目录中,创建一个名为“manifest.json”的文件,内容如下:
{
"manifest_version": 2,
"name": "modify-page",
"version": "1.0",
"content_scripts": [
{
"matches": ["https://mdn.org.cn/*"],
"js": ["page-eater.js"]
}
]
}
content_scripts 键是如何将脚本加载到匹配 URL 模式的页面中。在这种情况下,content_scripts 指示浏览器将名为“page-eater.js”的脚本加载到 https://mdn.org.cn/ 下的所有页面中。
注意:由于 content_scripts 的 "js" 属性是一个数组,因此您可以使用它将多个脚本注入到匹配的页面中。如果这样做,页面将共享相同的范围,就像页面加载的多个脚本一样,并且它们会按照在数组中列出的顺序加载。
注意:content_scripts 键还有一个 "css" 属性,您可以使用它来注入 CSS 样式表。
接下来,在“modify-page”目录中创建一个名为“page-eater.js”的文件,并为其添加以下内容:
document.body.textContent = "";
let header = document.createElement("h1");
header.textContent = "This page has been eaten";
document.body.appendChild(header);
现在 安装扩展,然后访问 https://mdn.org.cn/。页面应该如下所示:

以编程方式修改页面
如果您仍然想“吃掉”页面,但只在用户请求时才这样做,该怎么办?让我们更新此示例,以便在用户单击上下文菜单项时注入内容脚本。
首先,将“manifest.json”更新为包含以下内容:
{
"manifest_version": 2,
"name": "modify-page",
"version": "1.0",
"permissions": ["activeTab", "contextMenus"],
"background": {
"scripts": ["background.js"]
}
}
在这里,我们删除了 content_scripts 键,并添加了两个新键:
permissions:要将脚本注入页面,我们需要修改页面的权限。activeTab权限是一种临时获取当前活动标签页权限的方法。我们还需要contextMenus权限才能添加上下文菜单项。background:我们使用它来加载一个持久的 “后台脚本”,名为background.js,我们将在其中设置上下文菜单并注入内容脚本。
让我们创建这个文件。在 modify-page 目录中创建一个名为 background.js 的新文件,并为其添加以下内容:
browser.contextMenus.create({
id: "eat-page",
title: "Eat this page",
});
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "eat-page") {
browser.tabs.executeScript({
file: "page-eater.js",
});
}
});
在此脚本中,我们创建了一个 上下文菜单项,并为其指定了一个特定的 id 和 title(将在上下文菜单中显示的文本)。然后,我们设置了一个事件监听器,以便当用户单击上下文菜单项时,我们检查它是否是我们 eat-page 项。如果是,我们使用 tabs.executeScript() API 将“page-eater.js”注入当前标签页。此 API 可选地接受标签页 ID 作为参数:我们省略了标签页 ID,这意味着脚本被注入到当前活动标签页中。
此时,扩展应该如下所示:
modify-page/
background.js
manifest.json
page-eater.js
现在 重新加载扩展,打开一个页面(这次是任何页面),激活上下文菜单,然后选择“Eat this page”。

消息
内容脚本和后台脚本无法直接访问彼此的状态。但是,它们可以通过发送消息进行通信。一方设置消息监听器,另一方可以向其发送消息。下表总结了双方涉及的 API:
| 在内容脚本中 | 在后台脚本中 | |
|---|---|---|
| 发送消息 |
browser.runtime.sendMessage()
|
browser.tabs.sendMessage()
|
| 接收消息 |
browser.runtime.onMessage
|
browser.runtime.onMessage
|
注意:除了这种发送一次性消息的通信方法外,您还可以使用 基于连接的方法来交换消息。有关选择建议,请参阅 选择一次性消息还是基于连接的消息。
让我们更新我们的示例,以展示如何从后台脚本发送消息。
首先,编辑 background.js,使其包含以下内容:
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((info, tab) => {
if (info.menuItemId === "eat-page") {
let executing = browser.tabs.executeScript({
file: "page-eater.js",
});
executing.then(onExecuted);
}
});
现在,在注入 page-eater.js 后,我们使用 tabs.query() 来获取当前活动标签页,然后使用 tabs.sendMessage() 将消息发送到该标签页中加载的内容脚本。消息的有效负载为 {replacement: "Message from the extension!"}。
接下来,将 page-eater.js 更新如下:
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);
现在,内容脚本不再仅仅立即“吃掉”页面,而是使用 runtime.onMessage 监听消息。当消息到达时,内容脚本运行与之前基本相同的代码,只是替换文本取自 request.replacement。
由于 tabs.executeScript() 是一个异步函数,为了确保在 page-eater.js 中添加监听器后才发送消息,我们使用 onExecuted(),它将在 page-eater.js 执行后被调用。
注意:按 Ctrl+Shift+J(macOS 上为 Cmd+Shift+J)或 web-ext run --bc 打开 浏览器控制台以查看后台脚本中的 console.log。
或者,使用 附加组件调试器,它可以让您设置断点。目前没有办法 直接从 web-ext 启动附加组件调试器。
如果我们想从内容脚本向后台页面发送消息,我们将使用 runtime.sendMessage() 而不是 tabs.sendMessage(),例如:
browser.runtime.sendMessage({
title: "from page-eater.js",
});
注意:这些示例都注入了 JavaScript;您也可以使用 tabs.insertCSS() 函数以编程方式注入 CSS。
了解更多
-
内容脚本指南
-
content_scriptsmanifest 键 -
permissionsmanifest 键 -
使用
content_scripts的示例 -
使用
tabs.executeScript()的示例