- 示例工程
【免费下载链接】chrome-extensions-samples
Chrome Extensions Samples
导读
本文基于 chrome-extensions-samples 仓库中的 api-samples/alarms 示例,系统讲解 Manifest V3 下chrome.alarmsAPI 的完整用法。示例通过扩展页面让用户自由创建、查看、取消闹钟,并实时记录触发日志,覆盖了create、clear、clearAll、getAll与onAlarm监听器的全部核心场景。读完本文,你将掌握定时任务扩展从「初始化默认闹钟」到「用户交互创建闹钟」的完整实现路径,并能直接复用仓库中的代码搭建自己的闹钟应用。
示例概览:一个完整的闹钟管理 Demo
api-samples/alarms是一个轻量但功能完整的演示扩展:安装扩展后,后台 Service Worker 会自动创建一个初始闹钟,同时打开 index.html 演示页面;用户可以在页面上填写表单创建自定义闹钟,实时查看"当前闹钟列表"与"闹钟事件日志",并逐个或全部取消闹钟。
从仓库结构看,该示例由以下文件组成:
| 文件 | 职责 |
|---|---|
| manifest.json | MV3 清单,声明alarms权限与后台 Service Worker |
| bg-wrapper.js | Service Worker 入口,通过importScripts加载业务逻辑 |
| background.js | 安装初始化、创建默认闹钟、打开演示页 |
| index.html | 演示页面结构:创建表单、闹钟列表、日志区 |
| index.css | 页面样式 |
| index.js | 页面逻辑:AlarmManager类封装全部闹钟操作 |
| README.md | 官方说明文档 |
运行扩展的四个步骤
按照 README.md 的指引,运行该示例只需四步:
- 克隆仓库,获取
chrome-extensions-samples的完整代码。 - 以"加载已解压的扩展程序"方式加载
api-samples/alarms目录:打开chrome://extensions,开启"开发者模式",点击"加载已解压的扩展程序",选择该目录即可。 - 将扩展固定到工具栏,以便访问其操作按钮(action button)。
- 点击操作按钮打开扩展弹窗/页面并与 UI 交互:本示例中点击 action 会通过
chrome.tabs.create打开index.html演示页,而不是弹出一个 popup。
注意:本示例的
manifest.json中action字段为空对象{},未声明默认弹窗(default_popup),因此点击按钮触发的是 background.js 中注册的chrome.action.onClicked监听器,逻辑是打开演示标签页。
清单配置:声明 alarms 权限与后台脚本
manifest.json 是 MV3 扩展的入口配置,内容如下:
{ "name": "Alarms API Demo", "version": "1.0", "description": "Uses the chrome.alarms API to allow the user to set alarms using an extension page.", "manifest_version": 3, "background": { "service_worker": "bg-wrapper.js" }, "permissions": ["alarms"], "action": {} }关键点有三:
"permissions": ["alarms"]:使用chrome.alarmsAPI 必须在清单中显式声明alarms权限,否则 API 不可用。"background": { "service_worker": "bg-wrapper.js" }:MV3 不再支持持久化的背景页,取而代之的是事件驱动的 Service Worker。本示例采用"wrapper + 业务文件"的双文件结构,让业务逻辑可以像传统脚本一样被加载。"action": {}:声明操作按钮但未配置默认弹窗,配合chrome.action.onClicked实现"点击打开演示页"的行为。
bg-wrapper.js的全部内容只是一个容错加载器:
try { importScripts('background.js'); } catch (error) { console.error(error); }将importScripts包裹在try/catch中,即使业务脚本抛错,Service Worker 也能继续存活,便于在扩展调试页面排查问题。
安装初始化:自动创建默认闹钟
background.js 承担了安装初始化职责。扩展首次安装时,会打开演示页并创建一个"占位"闹钟,保证用户进入演示页时"有东西可看":
// Initialize the demo on install chrome.runtime.onInstalled.addListener(({ reason }) => { if (reason !== chrome.runtime.OnInstalledReason.INSTALL) { return; } openDemoTab(); // Create an alarm so we have something to look at in the demo chrome.alarms.create('demo-default-alarm', { delayInMinutes: 1, periodInMinutes: 1 }); }); chrome.action.onClicked.addListener(openDemoTab); function openDemoTab() { chrome.tabs.create({ url: 'index.html' }); }代码中有几个值得学习的工程细节:
- 区分安装原因:
onInstalled的reason只有等于INSTALL(首次安装)时才执行初始化逻辑,更新(UPDATE)或浏览器更新(BROWSER_UPDATE)时不重复创建闹钟,避免重复副作用。 chrome.alarms.create(name, alarmInfo)的两种入参形式:create的第一个参数是可选的闹钟名称,省略时使用默认名''。第二个参数alarmInfo必须提供when或delayInMinutes之一(详见下文)。- 周期闹钟:同时指定
delayInMinutes与periodInMinutes,闹钟会在 1 分钟后首次触发,之后每 1 分钟触发一次。 - 打开页面复用同一函数:
openDemoTab同时被安装监听器和 action 点击监听器调用,保证"安装后"与"手动点击"的体验一致。
表单交互:理解 when 与 delayInMinutes 两种计时方式
演示页 index.html 中的创建表单包含三个输入:闹钟名称(alarm-name)、初始延迟(time-value+time-format)、重复周期(period,单位分钟)。其中延迟时间支持两种单位——分钟(minutes,默认)与毫秒(milliseconds),这正好对应chrome.alarms.create的两种计时参数。
页面脚本 index.js 在表单提交时解析用户输入并组装alarmInfo:
form.addEventListener('submit', (event) => { event.preventDefault(); const formData = new FormData(form); const data = Object.fromEntries(formData); const name = data['alarm-name']; const delay = Number.parseFloat(data['time-value']); const delayFormat = data['time-format']; const period = Number.parseFloat(data['period']); const alarmInfo = {}; if (delayFormat === 'ms') { // Specified in milliseconds, use `when` property alarmInfo.when = Date.now() + delay; } else if (delayFormat === 'min') { // specified in minutes, use `delayInMinutes` property alarmInfo.delayInMinutes = delay; } if (period) { alarmInfo.periodInMinutes = period; } // Create the alarm – this uses the same signature as chrome.alarms.create manager.createAlarm(name, alarmInfo); });这段代码完整展示了alarmInfo的组装逻辑,其背后对应chrome.alarms.create的参数约定:
| alarmInfo 字段 | 作用 | 说明 |
|---|---|---|
when | 绝对触发时间 | 毫秒级时间戳,例如Date.now() + 5000表示 5 秒后触发;when与delayInMinutes二者必须提供其一,若同时提供,when优先 |
delayInMinutes | 相对延迟 | 指定后闹钟在约delayInMinutes分钟后触发(打包成 CRX 分发时最小值限制为 1 分钟,详见下文) |
periodInMinutes | 重复周期 | 若提供,闹钟会按此周期循环触发;与when或delayInMinutes组合使用,不提供则为一次性闹钟 |
两个值得注意的点:
- 最小值限制:表单下方特意标注了
*提示——"Can be set to < 1 min in an unpacked extension, but not in a distributed CRX file."(在未打包的扩展中可设置为小于 1 分钟,但在分发的 CRX 文件中不可以)。也就是说,开发调试阶段(unpacked 加载)你可以用delayInMinutes: 0.1这类亚分钟值快速验证,但如果要发布到 Chrome Web Store,闹钟触发间隔(至少首次触发)必须不小于 1 分钟。这与when形式(毫秒级绝对时间)并不冲突,后者在调试时同样能实现秒级触发。 - 周期为 0 即一次性闹钟:表单中
period默认值为 0,代码通过if (period)判断,只有非零值才会写入periodInMinutes,从而保证一次性闹钟与周期闹钟的行为正确切换。
AlarmManager:封装闹钟 CRUD 与日志记录
页面逻辑的核心是AlarmManager类(定义于 index.js),它把chrome.alarms的所有操作封装为带日志输出的方法,并将触发日志渲染到页面的"Alarm log"区。这类"薄封装 + 日志"的做法非常适合学习:每一行 API 调用都有对应的可读日志,闹钟生命周期一目了然。
创建闹钟:createAlarm
// Thin wrapper around alarms.create to log creation event createAlarm(name, alarmInfo) { chrome.alarms.create(name, alarmInfo); const json = JSON.stringify(alarmInfo, null, 2).replace(/\s+/g, ' '); this.logMessage(`Created "${name}"\n${json}`); this.refreshDisplay(); }create为异步操作,同名的闹钟会被新闹钟覆盖;重复创建同名闹钟时后创建的会替换先前的。创建后立即刷新显示区,让新闹钟立刻出现在列表中。
监听触发:onAlarm
构造函数中注册了chrome.alarms.onAlarm监听器:
constructor(display, log) { this.displayElement = display; this.logElement = log; this.logMessage('Manager: initializing demo'); this.displayElement.addEventListener('click', this.handleCancelAlarm); chrome.alarms.onAlarm.addListener(this.handleAlarm); }handleAlarm是async箭头函数(this绑定到实例),收到alarm对象后把name与序列化后的完整 JSON 写入日志,并刷新闹钟列表:
handleAlarm = async (alarm) => { const json = JSON.stringify(alarm); this.logMessage(`Alarm "${alarm.name}" fired\n${json}}`); await this.refreshDisplay(); };需要特别说明的是:在 MV3 中,chrome.alarms的主要使用场景正是 Service Worker 的定时唤醒——闹钟触发事件会唤醒休眠的 Service Worker,让扩展能在不常驻后台的情况下周期性地执行任务(例如清理缓存、轮询数据、发送通知)。本示例把onAlarm放在扩展页面中监听,是刻意为之的"页面内演示"方式,方便初学者直接观察触发过程;生产环境中的典型做法请参考下文"仓库中的更多实践"。
取消与清空:clear / clearAll
cancelAlarm与cancelAllAlarms分别对应chrome.alarms.clear(name, callback)和chrome.alarms.clearAll(callback)。它们的回调参数wasCleared(布尔值)用于区分"成功清除"与"闹钟不存在/已触发"两种结果,示例据此输出不同的日志:
async cancelAlarm(name) { return chrome.alarms.clear(name, (wasCleared) => { if (wasCleared) { this.logMessage(`Manager: canceled alarm "${name}"`); } else { this.logMessage(`Manager: could not canceled alarm "${name}"`); } }); } async cancelAllAlarms() { return chrome.alarms.clearAll((wasCleared) => { if (wasCleared) { this.logMessage(`Manager: canceled all alarms"`); } else { this.logMessage(`Manager: could not canceled all alarms`); } }); }列表渲染与防抖刷新:getAll
populateDisplay调用chrome.alarms.getAll(callback)获取全部闹钟并逐个渲染成带"cancel"按钮的行:
async populateDisplay() { return chrome.alarms.getAll((alarms) => { for (const [index, alarm] of alarms.entries()) { const isLast = index === alarms.length - 1; this.renderAlarm(alarm, isLast); } }); }refreshDisplay则用一个私有字段#refreshing实现简单的锁机制,防止并发刷新导致列表重复渲染:
#refreshing = false; async refreshDisplay() { if (this.#refreshing) { return; } // refresh in progress, bail this.#refreshing = true; // acquire lock try { await this.clearDisplay(); await this.populateDisplay(); } finally { this.#refreshing = false; // release lock } }这里的#前缀是 ES2022 的私有类字段语法,finally确保锁在任何情况下都会释放。闹钟触发、创建、取消三个异步入口都可能触发refreshDisplay,这个锁有效避免了竞态条件下列表出现重复条目。
仓库中的更多实践:闹钟与通知、徽章联动
除了api-samples/alarms这个教学示例,仓库里还有多个以chrome.alarms为核心的实战样例,可作为扩展学习的第二站:
- sample.water_alarm_notification:一个"喝水提醒"扩展。其 background.js 展示了纯 Service Worker 场景下的完整闭环——用户点击通知按钮后,从
chrome.storage.sync读取提醒间隔并创建周期闹钟;闹钟触发时(onAlarm)清除 action 徽章并创建系统通知:
chrome.alarms.onAlarm.addListener(() => { chrome.action.setBadgeText({ text: '' }); chrome.notifications.create({ type: 'basic', iconUrl: 'stay_hydrated.png', title: 'Time to Hydrate', message: "Everyday I'm Guzzlin'!", buttons: [{ title: 'Keep it Flowing.' }], priority: 0 }); }); chrome.notifications.onButtonClicked.addListener(async () => { const item = await chrome.storage.sync.get(['minutes']); chrome.action.setBadgeText({ text: 'ON' }); chrome.alarms.create({ delayInMinutes: item.minutes }); });注意这里的chrome.alarms.create({ delayInMinutes: item.minutes })省略了闹钟名称参数——此时闹钟使用默认名'',属于 API 的合法用法。
- tutorial.mole-game 与 tutorial.open-api-reference、tutorial.quick-api-reference:分别在游戏控制器与 API 参考工具中通过
onAlarm驱动 Service Worker 的周期性行为(如生成提示、刷新内容),印证了"alarms + Service Worker"是 MV3 定时任务的标准组合。
对比可见,api-samples/alarms的页面化展示更利于理解 API 每次调用的即时效果,而 water_alarm_notification 等样例更贴近真实产品形态,两者结合能帮你从"会用 API"进阶到"会设计定时任务架构"。
常见问题与注意事项
结合本示例的代码与 Chrome 的 API 约束,总结如下实践要点:
- 权限必须声明:忘记在 manifest.json 中添加
"permissions": ["alarms"]是新手最常见的报错来源。 when与delayInMinutes必须提供其一,否则chrome.alarms.create会报错;两者同时提供时以when为准。- 分发的扩展闹钟间隔不能小于 1 分钟(CRX / Web Store 场景),unpacked 调试不受限;用
when: Date.now() + delay可在调试时实现毫秒级触发。 - 同名闹钟会被覆盖:再次调用
create时若名称相同,新配置会替换旧配置。 - 清除回调的
wasCleared值得检查:它帮助区分"清除成功"与"该闹钟早已触发完(一次性闹钟触发后自动消失)"。 - MV3 中闹钟是 Service Worker 的唤醒机制:不要依赖页面存活来维持定时任务,
onAlarm应注册在后台脚本中;本示例的页面内监听是教学演示,真实产品请参照 water_alarm_notification 的写法。
总结
api-samples/alarms用不到百行页面脚本完整呈现了chrome.alarms的五个核心 API(create、clear、clearAll、getAll、onAlarm)与两种计时模型(when绝对时间 /delayInMinutes相对延迟),并通过AlarmManager类示范了如何将异步回调 API 封装为带日志、带防抖刷新、可测试的界面逻辑。结合仓库中 water_alarm_notification 等实战样例,你可以快速掌握"闹钟 + Service Worker + 通知"这一 MV3 定时任务最佳实践,并将其迁移到自己的扩展中。
- 示例工程
【免费下载链接】chrome-extensions-samples
Chrome Extensions Samples
相关推荐
JavaScript变量提升和作用域:通过JavaScript Challenges Book掌握闭包和条件语句
JavaScript变量提升和作用域:通过JavaScript Challenges Book掌握闭包和条件语句 JavaScript Challenges B
示例工程如何快速上手 WrenAI:从安装到第一次跑通自然语言查询
如何快速上手 WrenAI:从安装到第一次跑通自然语言查询 WrenAI 是一个开源的生成式 BI(GenBI)引擎:它让 AI 智能体通过受治理的 text
示例工程chrome-extensions-samples 实战:基于 chrome.tabs.captureVisibleTab() 实现标签页截图扩展
chrome extensions samples 实战:基于 chrome.tabs.captureVisibleTab 实现标签页截图扩展 导读 本文以 c
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考