修改网页
扩展最常见的用例之一是修改网页。例如,扩展可能希望更改应用于页面的样式、隐藏特定的 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 和标题(要在上下文菜单中显示的文本)。然后,我们设置一个事件侦听器,以便当用户点击上下文菜单项时,我们检查它是否是我们 eat-page
项。如果是,我们使用 tabs.executeScript()
API 将“page-eater.js”注入到当前选项卡中。此 API 可选择将选项卡 ID 作为参数:我们省略了选项卡 ID,这意味着脚本将注入到当前活动选项卡中。
此时,扩展应如下所示
modify-page/ background.js manifest.json page-eater.js
现在 重新加载扩展,打开一个页面(这次是任何页面),激活上下文菜单,然后选择“吃掉此页面”
消息传递
内容脚本和后台脚本无法直接访问彼此的状态。但是,它们可以通过发送消息来通信。下表总结了每一方涉及的 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_scripts
清单键permissions
清单键tabs.executeScript()
tabs.insertCSS()
tabs.sendMessage()
runtime.sendMessage()
runtime.onMessage
- 使用
content_scripts
的示例 - 使用
tabs.executeScript()
的示例