开发第一个插件
为了让大家更快速、更直观地了解插件开发的入门过程,我们将参考官方的Demo案例作为指导。这些Demo案例不仅为你展示了插件的基本结构和功能,还提供了清晰的开发步骤和代码示例。无论你是初学者还是有一定经验的开发者,这个案例都能帮助你迅速掌握插件开发的核心概念和技术。
Hello World 扩展程序
概览
您将创建一个"Hello World"示例,在本地加载扩展程序,查找日志。
当用户点击扩展程序工具栏图标时,此扩展程序会显示"Hello Extensions"。

构建
- 创建
manifest.json
文件。
json
{
"manifest_version": 3,
"name": "Hello Extensions",
"description": "Base Level Extension",
"version": "1.0",
"action": {
"default_popup": "hello.html"
}
}
- 创建
hello.html
文件。
css
<html>
<body>
<h1>Hello Extensions</h1>
</body>
</html>
- 加载已解压的扩展程序。
-
在新标签页中输入
chrome://extensions
,前往"扩展程序"页面。- 或者,您也可以点击"扩展程序"菜单谜题按钮,然后选择菜单底部的管理扩展程序。
- 或者,点击 Chrome 菜单,将光标悬停在更多工具 上,然后选择扩展程序。

- 点击开发者模式旁边的切换开关以启用开发者模式。
- 点击 Load unpacked(加载解压缩)按钮,然后选择扩展程序目录。

- 默认情况下,在本地加载扩展程序时,它会显示在扩展程序菜单中。将扩展程序固定到工具栏,以便在开发过程中快速访问扩展程序。

- 点击扩展程序的操作图标(工具栏图标);您应该会看到一个弹出式窗口。

重新加载
返回代码,在清单中将扩展程序的名称更改为"Hello Extensions of the world!"。
json
{
"manifest_version": 3,
"name": "Hello Extensions of the world!",
...
}
保存文件后,要在浏览器中看到此更改,您还需要刷新扩展程序。前往"扩展程序"页面,然后点击开启 /关闭切换开关旁边的刷新图标。

下表显示了需要重新加载哪些组件才能看到更改:
扩展程序组件 | 需要重新加载扩展程序 |
---|---|
清单 | 是 |
Service Worker | 是 |
内容脚本 | 是 |
弹出式窗口 | 否 |
选项页面 | 否 |
其他扩展程序 HTML 网页 | 否 |
查看控制台日志与错误
在开发期间,您可以通过访问浏览器控制台日志来调试代码。在本例中,我们将找到该弹出式窗口的日志。首先,向 hello.html
添加脚本标记。
xml
<html>
<body>
<h1>Hello Extensions</h1>
<script src="popup.js"></script>
</body>
</html>
创建一个popup.js
文件,并添加以下代码。
arduino
console.log("This is a popup!")
要在控制台中查看这条消息的记录,请按以下步骤操作:
- 打开弹出式窗口。
- 右键点击弹出式窗口。
- 选择检查。

- 在 DevTools 中,前往控制台面板。

在每个网页上运行脚本
概览
创建您的首个用于在网页上插入新元素的扩展程序。每次打开一个新页面时,都将弹出一个对话框。
构建
- 创建
manifest.json
文件,并声明注册content.js
脚本。其中"matches"
字段可以有一种或多种匹配模式。这些变量可让浏览器识别要将内容脚本注入到哪些网站。匹配模式由三部分组成:<scheme>://<host><path>
。它们可以包含"*
"字符。
json
{
"manifest_version": 3,
"name": "Hello Extensions",
"description": "Base Level Extension",
"version": "1.0",
"action": {
"default_popup": "hello.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"], //这里我们匹配所有路径
"js": ["content.js"]
}
]
}
- 编写 content.js 内容。
scss
alert('hello world')
- 加载扩展程序。
- 随便找到一个网页打开,当页面加载完成时会弹出一个 hello world 的弹窗。

将脚本注入当前标签页中
概览
点击扩展程序工具栏图标,简化当前页面的样式设置。去除当前页面中所有的图片元素。
构建
- 创建
manifest.json
文件。
json
{
"manifest_version": 3,
"name": "Focus Mode",
"description": "Enable focus mode on Chrome's official Extensions and Chrome Web Store documentation.",
"version": "1.0"
}
- 初始化扩展程序。
扩展程序可以使用扩展程序的 Service Worker 在后台监控浏览器事件。Service Worker 是特殊的 JavaScript 环境,用于处理事件,并会在不需要时终止。(Service Worker 其实就是上文中讲到的 Background Scripts ,在 Manifest V2 版本中,因为 Background Scripts 会长期存在于后台中,会占用资源。因此在 V3中将后台上下文移至仅在需要时运行的 Service Worker。Service Worker 用于处理事件,并会在不需要时终止。)
css
{
...
"background": {
"service_worker": "background.js"
},
...
}
创建 background.js 文件。
arduino
chrome.runtime.onInstalled.addListener(() => {
chrome.action.setBadgeText({
text: "OFF",
});
});
这里我们利用 Service Worker 监听 runtime.onInstalled()
。此方法可让扩展程序在安装时设置初始状态或完成一些任务。我们通过 setBadgeText 去设置扩展图标上的徽章(badge)文本。

- 启用扩展程序操作。
扩展程序操作 可控制扩展程序的工具栏图标。因此,只要用户点击该扩展程序图标,它就会运行一些代码(如此示例所示)或显示一个弹出式窗口。添加以下代码,以在 manifest.json
文件中声明扩展程序操作:
json
"action": {}
- 声明权限。
由于我们会修改用户访问页面的样式,因此也需要在 manifest
中声明 activeTab 权限。
json
"permissions": ["activeTab"]
- 完善代码
用户点击扩展程序操作后,检查当前标签页的状态并设置下一个状态。并注入 scripting 脚本,移除页面中所有的图片。
javascript
chrome.runtime.onInstalled.addListener(() => {
chrome.action.setBadgeText({
text: "OFF",
});
});
chrome.action.onClicked.addListener( (tab) => {
const prevState = await chrome.action.getBadgeText({ tabId: tab.id });
const nextState = prevState === 'ON' ? 'OFF' : 'ON'
await chrome.action.setBadgeText({
tabId: tab.id,
text: nextState,
});
// 如果状态为 'ON',则移除页面中的所有图片
if (nextState === 'ON') {
try {
// 在当前 tab 中执行脚本以移除所有图片
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
// 使用 JavaScript 代码移除页面中的所有 <img> 元素
const images = document.querySelectorAll('img');
images.forEach(img => img.remove());
}
});
} catch (error) {
console.error('Error executing script:', error);
}
}
})
上述代码中,我们利用 Scripting API 向当前标签页注入脚本,因此也需要在清单中声明 scripting 权限。
json
"permissions": ["activeTab","scripting"]
- 加载扩展程序。
- 打开一个有图片的网页,点击扩展按钮。触发程序。


使用 Service Worker 处理事件
概览
上面的几个Demo中,我们都有用到 Service Worker 去监听不同的时候,本次教大家如何注册 Service Worker 并导入模块。
构建
- 注册 Service Worker。
在项目的根目录中创建manifest.json
文件,并添加以下代码:
css
{
"manifest_version": 3,
"name": "Open extension API reference",
"version": "1.0.0",
"icons": {
"16": "images/icon-16.png",
"128": "images/icon-128.png"
},
"background": {
"service_worker": "service-worker.js",
},
}
代码中,扩展程序在清单中注册其 Service Worker,而清单中只接受一个 JavaScript 文件。
- 导入多个 Service Worker 模块。
我们的 Service Worker 实现了两项功能。为了提高可维护性,我们将在单独的模块中实现每项功能。首先,我们需要在清单中将 Service Worker 声明为一个 ES 模块,这样我们就可以将模块导入到 Service Worker 中:
css
{
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
}
创建 service-worker.js
文件
arduino
import './sw-omnibox.js';
import './sw-tips.js';
分别创建这两个 js 文件。
sw-omnibox.js
arduino
console.log("sw-omnibox.js")
sw-tips.js
arduino
console.log("sw-tips.js")
- 加载扩展程序。
- 打开 Service Worker。


在 service-worker.js 中,我们使用了 import 导入了多个模块。它们的执行顺序是根据你在代码中放置 import
语句的顺序来确定的。首先,'./sw-omnibox.js'
模块会被导入并执行,然后 './sw-tips.js'
模块会被导入并执行。
管理标签页
概览
此教程会构建一个标签页管理器,用于整理 Chrome 扩展程序和 Chrome 应用商店的文档标签页。

构建
- 创建
manifest.json
文件。
json
{
"manifest_version": 3,
"name": "Tab Manager for Chrome Dev Docs",
"version": "1.0"
}
- 创建弹出式窗口并设置样式
json
{
...
"action": {
"default_popup": "popup.html"
},
...
}
弹出式窗口与网页类似,但有一点例外:它无法运行内嵌 JavaScript。创建一个 popup.html
文件,并添加以下代码。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./popup.css" />
</head>
<body>
<template id="li_template">
<li>
<a>
<h3 class="title">Tab Title</h3>
<p class="pathname">Tab Pathname</p>
</a>
</li>
</template>
<h1>Google Dev Docs</h1>
<button>Group Tabs</button>
<ul></ul>
<script src="./popup.js" type="module"></script>
</body>
</html>
接下来,设置弹出式窗口的样式。创建一个 popup.css
文件,并添加以下代码。
css
body {
width: 20rem;
}
ul {
list-style-type: none;
padding-inline-start: 0;
margin: 1rem 0;
}
li {
padding: 0.25rem;
}
li:nth-child(odd) {
background: #80808030;
}
li:nth-child(even) {
background: #ffffff;
}
h3,
p {
margin: 0;
}
- 管理标签页
通过 Tabs API,扩展程序可以在浏览器中创建、查询、修改和重新排列标签页。Tabs API 中的许多方法无需请求任何权限即可使用。不过,这里我们需要访问标签页的 title
和 URL
;这些敏感属性需要权限。我们可以请求 "tabs"
权限,但这样做会获得对所有标签页的敏感属性的访问权限。由于我们只管理特定网站的标签页,因此会请求较小的主机权限。缩小主机权限范围可让我们向特定网站 授予更高的权限,从而保护用户隐私。这将授予对 title
和 URL
属性的访问权限以及其他功能。将突出显示的代码添加到 manifest.json
文件中:
json
{
...
"host_permissions": [
"https://developer.chrome.com/*"
],
...
}
host_permissions 在之前的内容中没有提到,它表示主机权限。专门用于声明扩展程序需要与哪些外部网站或服务进行交互。例如,如果你希望你的扩展可以读取或修改https://developer.chrome.com/
上的内容,你就需要在host_permissions
中声明这个URL。
host_permissions
是关于扩展可以与哪些网站交互的权限,而permissions
是关于扩展可以使用哪些Chrome API或功能的权限。
查询标签
我们可以使用 tabs.query()
方法从特定网址检索标签页。创建一个 popup.js
文件并添加以下代码:
csharp
const tabs = await chrome.tabs.query({
url: [
"https://developer.chrome.com/docs/webstore/*",
"https://developer.chrome.com/docs/extensions/*",
],
});
...
将焦点移至某个标签页
首先,该扩展程序会按字母顺序对标签页名称(所含 HTML 网页的标题)进行排序。然后,当用户点击某个列表项时,它会使用 tabs.update()
将焦点置于该标签页上,并使用 windows.update()
将窗口置于最前面。将以下代码添加到 popup.js
文件中
dart
...
const collator = new Intl.Collator();
tabs.sort((a, b) => collator.compare(a.title, b.title));
const template = document.getElementById("li_template");
const elements = new Set();
for (const tab of tabs) {
const element = template.content.firstElementChild.cloneNode(true);
const title = tab.title.split("-")[0].trim();
const pathname = new URL(tab.url).pathname.slice("/docs".length);
element.querySelector(".title").textContent = title;
element.querySelector(".pathname").textContent = pathname;
element.querySelector("a").addEventListener("click", async () => {
// need to focus window as well as the active tab
await chrome.tabs.update(tab.id, { active: true });
await chrome.windows.update(tab.windowId, { focused: true });
});
elements.add(element);
}
document.querySelector("ul").append(...elements);
...
为标签分组
TabGroups API 允许扩展程序为组命名并选择背景颜色。通过添加突出显示的代码,向清单中添加 "tabGroups"
权限:
json
{
...
"permissions": [
"tabGroups"
]
}
在 popup.js
中,添加以下代码以创建按钮,该按钮将使用 tabs.group()
对所有标签页进行分组,并将其移动到当前窗口中。
javascript
...
const button = document.querySelector("button");
button.addEventListener("click", async () => {
const tabIds = tabs.map(({ id }) => id);
const group = await chrome.tabs.group({ tabIds });
await chrome.tabGroups.update(group, { title: "DOCS" });
});
完整代码
ini
const tabs = await chrome.tabs.query({
url: [
"https://developer.chrome.com/docs/webstore/*",
"https://developer.chrome.com/docs/extensions/*",
],
});
const collator = new Intl.Collator();
tabs.sort((a, b) => collator.compare(a.title, b.title));
const template = document.getElementById("li_template");
const elements = new Set();
for (const tab of tabs) {
const element = template.content.firstElementChild.cloneNode(true);
const title = tab.title.split("-")[0].trim();
const pathname = new URL(tab.url).pathname.slice("/docs".length);
element.querySelector(".title").textContent = title;
element.querySelector(".pathname").textContent = pathname;
element.querySelector("a").addEventListener("click", async () => {
// need to focus window as well as the active tab
await chrome.tabs.update(tab.id, { active: true });
await chrome.windows.update(tab.windowId, { focused: true });
});
elements.add(element);
}
document.querySelector("ul").append(...elements);
const button = document.querySelector("button");
button.addEventListener("click", async () => {
const tabIds = tabs.map(({ id }) => id);
const group = await chrome.tabs.group({ tabIds });
await chrome.tabGroups.update(group, { title: "DOCS" });
});
-
加载扩展程序。
-
打开下面的网页。
-
点击扩展程序。我们可以在弹出的界面中看到我们打开的页签,并已经按照标题字母顺序排完序。当你点击 Group Tabs 按钮时,会将当前这些页签进行分组。

