介绍扩展程序 Service Worker 概念的教程
概览
本教程介绍了 Chrome 扩展程序 Service Worker。在本教程中,您将构建一个扩展程序,让用户可以使用多功能框快速导航到 Chrome API 参考文档页面。您将学习如何:
- 注册 Service Worker 并导入模块。
- 调试扩展程序 Service Worker。
- 管理状态和处理事件。
- 触发周期性事件。
- 与内容脚本通信。
前期准备
本指南假定您具备基本的 Web 开发经验。我们建议您查看 扩展程序 101 和 Hello World,了解 扩展程序开发简介。
构建扩展程序
首先,创建一个名为 quick-api-reference 的新目录来保存扩展程序文件,或者
从我们的 GitHub 示例 代码库下载源代码。
第 1 步:注册 Service Worker
在项目的根目录中创建 清单 文件,并添加以下代码:
manifest.json:
{
"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 文件。
无需像在网页中那样调用
navigator.serviceWorker.register()。
创建一个 images 文件夹,然后 将图标下载到该文件夹中。
如需详细了解清单中的扩展程序 元数据 和 图标,请查看阅读时间教程的第一个步骤。
第 2 步:导入多个 Service Worker 模块
我们的 Service Worker 实现了两个功能。为了提高可维护性,我们将在单独的模块中实现每个功能。首先,我们需要在清单中将 Service Worker 声明为 ES 模块,这样我们就可以在 Service Worker 中导入模块:
manifest.json:
{
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
}
创建 service-worker.js 文件并导入两个模块:
import './sw-omnibox.js';
import './sw-tips.js';
创建这些文件,并向每个文件添加控制台日志。
sw-omnibox.js:
console.log("sw-omnibox.js");
sw-tips.js:
console.log("sw-tips.js");
如需了解在 Service Worker 中导入多个文件的其他方法,请参阅导入脚本。
可选:调试 Service Worker
我将介绍如何查找 Service Worker 日志,以及如何了解 Service Worker 何时终止。首先,按照说明加载未封装的扩展程序。
30 秒后,您将看到“Service Worker(已停用)”,这意味着 Service Worker 已终止。点击“Service Worker(已停用)”链接以检查它。以下动画对此进行了演示。
您是否注意到,检查 Service Worker 会唤醒它?在开发者工具中打开 Service Worker 会使其保持活跃状态。为确保扩展程序在 Service Worker 终止时行为正确,请记得关闭开发者工具。
现在,中断扩展程序以了解在哪里查找错误。一种方法是从 './sw-omnibox.js' 导入中删除“.js”,该导入位于 service-worker.js 文件中。Chrome 将无法注册 Service Worker。
返回 chrome://extensions 并刷新扩展程序。您将看到两个错误:
Service worker registration failed. Status code: 3.
An unknown error occurred when fetching the script.
如需了解调试扩展程序 Service Worker 的更多方法,请参阅调试扩展程序。
第 4 步:初始化状态
如果不需要 Service Worker,Chrome 会将其关闭。我们使用 chrome.storage API 在 Service Worker 会话之间保留状态。如需存储访问权限,我们需要在清单中请求权限:
manifest.json:
{
...
"permissions": ["storage"],
}
首先,将默认建议保存到存储空间。我们可以在首次安装扩展程序时通过监听 runtime.onInstalled() 事件来初始化状态:
sw-omnibox.js:
...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
chrome.storage.local.set({
apiSuggestions: ['tabs', 'storage', 'scripting']
});
}
});
Service Worker 无法直接访问 window 对象,因此无法使用
window.localStorage 存储值。此外,Service
Worker 是生命周期较短的执行环境;它们会在用户的浏览器会话期间反复终止,这使得它们与全局变量不兼容。请改用chrome.storage.local,它会将数据存储在本地机器上。
如需了解扩展程序 Service Worker 的其他存储选项,请参阅保留数据,而不是使用全局变量。
第 5 步:注册事件
所有事件监听器都需要在 Service Worker 的全局范围内静态注册。换句话说,事件监听器不应嵌套在异步函数中。这样,Chrome 就可以确保在 Service Worker 重新启动时恢复所有事件处理脚本。
在此示例中,我们将使用 chrome.omnibox API,但首先我们必须在清单中声明多功能框关键字触发器:
manifest.json:
{
...
"minimum_chrome_version": "102",
"omnibox": {
"keyword": "api"
},
}
现在,在脚本的顶层注册多功能框事件监听器。当用户在地址栏中输入多功能框关键字 (api),然后按 Tab 键或空格键时,Chrome
会根据存储空间中的关键字显示建议列表。onInputChanged() 事件负责填充这些建议,该事件会接收当前用户输入和 suggestResult 对象。
sw-omnibox.js:
...
const URL_CHROME_EXTENSIONS_DOC =
'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;
// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
await chrome.omnibox.setDefaultSuggestion({
description: 'Enter a Chrome API or choose from past searches'
});
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
const suggestions = apiSuggestions.map((api) => {
return { content: api, description: `Open chrome.${api} API` };
});
suggest(suggestions);
});
用户选择建议后,onInputEntered() 将打开相应的 Chrome API 参考文档页面。
sw-omnibox.js:
...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
// Save the latest keyword
updateHistory(input);
});
updateHistory() 函数会接收多功能框输入并将其保存到 storage.local。这样,最近的搜索字词稍后可用作多功能框建议。
sw-omnibox.js:
...
async function updateHistory(input) {
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
apiSuggestions.unshift(input);
apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
return chrome.storage.local.set({ apiSuggestions });
}
第 6 步:设置周期性活动
setTimeout() 或 setInterval() 方法通常用于执行延迟或周期性任务。不过,这些 API
可能会失败,因为当 Service Worker 终止时,调度器会取消计时器。相反,扩展程序可以使用 chrome.alarms API。
首先,在清单中请求 "alarms" 权限:
manifest.json:
{
...
"permissions": ["storage"],
"permissions": ["storage", "alarms"],
}
扩展程序将提取所有提示,随机选择一个并将其保存到存储空间。我们将创建一个每天触发一次的闹钟,以更新提示。关闭 Chrome 时,闹钟不会保存。因此,我们需要检查闹钟是否存在,如果不存在则创建闹钟。
sw-tips.js:
// Fetch tip & save in storage
const updateTip = async () => {
const response = await fetch('https://chrome.dev/f/extension_tips/');
const tips = await response.json();
const randomIndex = Math.floor(Math.random() * tips.length);
return chrome.storage.local.set({ tip: tips[randomIndex] });
};
const ALARM_NAME = 'tip';
// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
const alarm = await chrome.alarms.get(ALARM_NAME);
if (typeof alarm === 'undefined') {
chrome.alarms.create(ALARM_NAME, {
delayInMinutes: 1,
periodInMinutes: 1440
});
updateTip();
}
}
createAlarm();
// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);
第 7 步:与其他上下文通信
扩展程序使用内容脚本来读取和修改页面的内容。当用户访问 Chrome API 参考文档页面时,扩展程序的内容脚本会使用当天的提示更新页面。它会发送消息以从 Service Worker 请求当天的提示。
首先,在清单中声明内容脚本,并添加与 Chrome API 参考文档对应的匹配模式。
manifest.json:
{
...
"content_scripts": [
{
"matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
"js": ["content.js"]
}
]
}
创建一个新的内容文件。以下代码会向 Service Worker 发送消息,请求提示。然后,添加一个按钮,该按钮将打开一个包含扩展程序提示的弹出式窗口。此代码使用新的 Web 平台 Popover API。
content.js:
(async () => {
// Sends a message to the service worker and receives a tip in response
const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });
const nav = document.querySelector('.upper-tabs > nav');
const tipWidget = createDomElement(`
<button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
<span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
</button>
`);
const popover = createDomElement(
`<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
);
document.body.append(popover);
nav.append(tipWidget);
})();
function createDomElement(html) {
const dom = new DOMParser().parseFromString(html, 'text/html');
return dom.body.firstElementChild;
}
最后一步是向我们的 Service Worker 添加一个消息处理程序,该处理程序会向内容脚本发送包含每日提示的回复。
sw-tips.js:
...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.greeting === 'tip') {
chrome.storage.local.get('tip').then(sendResponse);
return true;
}
});
测试其是否正常运行
验证项目的目录结构是否如下所示:

在本地加载扩展程序
如需在开发者模式下加载未封装的扩展程序,请按照 Hello World 中的步骤操作。
打开参考页面
- 在浏览器地址栏中输入关键字“api”。
- 按“Tab”键或“空格键”。
- 输入 API 的完整名称。
- 或者从过去的搜索列表中选择
- 系统会打开一个新页面,其中包含 Chrome API 参考文档页面。
它应如下所示:
打开当天的提示
点击导航栏上的“提示”按钮,打开扩展程序提示。
🎯 潜在增强功能
根据您今天所学的内容,尝试完成以下任一操作:
- 探索实现多功能框建议的其他方法。
- 创建您自己的自定义模态框,用于显示扩展程序提示。
- 打开一个额外的页面,其中包含 MDN 的 Web 扩展程序参考 API 页面。
继续构建!
恭喜您完成本教程 🎉。继续完成其他初学者教程,提升您的技能:
| 扩展程序 | 学习内容 |
|---|---|
| 阅读时间 | 在特定的一组页面上自动插入元素。 |
| 标签页管理器 | 创建用于管理浏览器标签页的弹出式窗口。 |
| 专注模式 | 在点击扩展程序操作后,在当前页面上运行代码。 |
继续探索
如需继续学习扩展程序 Service Worker 学习路线,我们建议您探索以下文章: