news 2026/9/21 3:37:48

chrome-extensions-samples 实战:基于 chrome.downloads API 构建完整的 Download Manager 扩展

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
chrome-extensions-samples 实战:基于 chrome.downloads API 构建完整的 Download Manager 扩展
  • 示例工程

【免费下载链接】chrome-extensions-samples

Chrome Extensions Samples

项目地址:https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
点击查看免费下载

本文以chrome-extensions-samples仓库中的 api-samples/downloads/download_manager 示例为核心,系统讲解如何基于 Manifest V3(MV3)的chrome.downloadsAPI 家族实现一个功能完整的下载管理器扩展:包括下载项的查询与实时渲染、暂停/恢复/取消/删除等操作、危险文件处理、搜索过滤,以及 Service Worker 中基于 OffscreenCanvas 的动态工具栏图标与进度轮询。读完本文,你将掌握chrome.downloads核心 API 的调用模式,以及如何在一个 MV3 扩展中组织 popup 页面与后台 Service Worker 的分工协作。

示例概览:一个 "Download Manager Button" 扩展

该示例位于 api-samples/downloads/download_manager,是一个完整的 MV3 扩展,官方 README 用一句话概括了它的定位:

该示例使用了多个chrome.downloadsAPI 来实现一个简单的下载管理器(Download Manager)。

实际代码远比"简单"丰富。它通过扩展的 action 按钮(popup)展示当前浏览器的下载列表,并支持对每一条下载记录执行一系列操作,同时把工具栏图标变成一枚"实时状态徽章"——正在下载时显示进度弧、存在危险文件时显示红色危险角标、暂停时显示暂停符号、有新完成项时显示绿色对勾。

扩展的文件结构如下:

api-samples/downloads/download_manager/ ├── _locales/en/messages.json # i18n 本地化文案 ├── manifest.json # MV3 清单 ├── popup.html / popup.css # 弹出层界面与样式 ├── popup.js # 弹出层核心逻辑(约 767 行) ├── service-worker.js # 后台 Service Worker(约 233 行) ├── icons.html / icons.js # 辅助工具页:重新生成 Manifest 图标 ├── icon128.png / icon19.png / icon38.png └── README.md

其中 popup 与后台的分工非常清晰:popup.js 负责下载列表的展示与用户交互,service-worker.js 负责需要持续运行的状态监控与动态图标绘制。这种"UI 密集逻辑放 popup、后台只做轻量监控"的架构,正是 MV3 扩展的推荐实践,因为 popup 只在用户点击工具栏按钮时短暂存在,而 Service Worker 会因事件被唤醒。

运行与调试:三步加载一个未打包扩展

官方 README 给出的运行步骤非常简洁,共三步:

  1. 克隆本仓库(chrome-extensions-samples)。
  2. 在 Chrome 中以"加载已解压的扩展程序"(Load unpacked)方式加载api-samples/downloads/download_manager目录。
  3. 点击扩展的工具栏按钮,打开 popup。

补充几个实际运行中的细节:

  • 加载入口:打开chrome://extensions,开启"开发者模式",点击"加载已解压的扩展程序",选择api-samples/downloads/download_manager目录即可。加载后浏览器会读取 manifest.json 完成注册。
  • 触发下载:由于chrome.downloads的事件监听器(onCreated/onChanged/onErased)会唤醒 Service Worker,即使 popup 未打开,工具栏图标也会被动态更新;点击图标后 popup 打开,会立即执行一次全量查询并渲染列表。
  • 辅助图标生成页:目录内的 icons.html 是一个独立的小工具页("Generate Manifest Icons"),点击按钮会通过chrome.runtime.sendMessage('icons')通知 Service Worker,用 OffscreenCanvas 重新绘制icon16/19/38/128.png等图标并触发下载(见 service-worker.js),便于扩展作者重新生成自己的图标资源。

manifest.json 解析:downloads 权限族与可选权限

扩展的核心配置在 manifest.json,完整内容如下:

{ "name": "Download Manager Button", "version": "0.3", "manifest_version": 3, "description": "Uses multiple chrome.downloads APIs to implement a simple download manager.", "icons": { "128": "icon128.png" }, "action": { "default_icon": { "19": "icon19.png", "38": "icon38.png" }, "default_title": "__MSG_extName__", "default_popup": "popup.html" }, "background": { "service_worker": "service-worker.js" }, "default_locale": "en", "optional_permissions": ["management"], "permissions": ["downloads", "downloads.open", "downloads.ui", "storage"] }

几个关键点值得逐一说明:

配置项作用
manifest_version3使用 Manifest V3,后台逻辑运行在 Service Worker 中(对应background.service_worker字段)
permissionsdownloadschrome.downloadsAPI 的查询/管理权限,本示例几乎所有功能都依赖它
permissionsdownloads.open允许调用chrome.downloads.open()打开已下载文件
permissionsdownloads.ui允许调用chrome.downloads.setUiOptions()关闭浏览器原生的下载 UI(下载托盘),由本扩展自绘状态
permissionsstorage使用chrome.storage.localchrome.storage.session记录 popup 打开时间、图标缓存、权限拒绝状态等
optional_permissionsmanagement可选权限,仅在用户点击"Show links to extensions that download files"并授权后,才通过chrome.permissions.request()申请,用于显示"由哪个扩展发起的下载"链接
action.default_popuppopup.html点击工具栏图标时打开的弹出层
default_localeen声明默认语言为英文,配合_locales/en/messages.json使用

关于downloads.ui权限有一个非常重要的细节:获得该权限的扩展(通常是下载管理器类扩展)调用chrome.downloads.setUiOptions({ enabled: false })后,Chrome 会隐藏自带的下载托盘/下载条,将下载状态的呈现完全交给扩展。这正是 service-worker.js 的第一行代码所做的事情:

chrome.downloads.setUiOptions({ enabled: false });

从源码结构看,本扩展正是借助这一能力"接管"了下载进度的可视化,用工具栏动态图标替代系统下载托盘。这也是为什么它需要同时申请downloadsdownloads.opendownloads.ui三个权限的原因。

弹出层架构:DownloadItem 类与下载列表渲染

popup 的全部逻辑位于 popup.js,其核心是一个DownloadItem类(popup.js),它封装了"一条下载记录"的全部数据与操作:

  • 数据绑定:构造函数遍历chrome.downloads.search()返回的 DownloadItem 数据对象,把statebytesReceivedtotalBytesfilenamereferrerdangerpausedstartTimeestimatedEndTimeendTimebyExtensionId等字段全部复制到实例上,并把startTime解析为Date对象(popup.js)。
  • DOM 克隆:popup.html 中定义了一个隐藏的.item模板节点,DownloadItem通过cloneNode(true)克隆模板生成列表项,并以item{id}作为 DOM id(popup.js)。
  • 按开始时间排序插入:新项插入列表时,先用binarySearchstartTime时间戳二分定位插入位置,再插入到对应 DOM 节点前后,保证列表始终按"开始时间从新到旧"排列([popup.js](https://link.gitcode.com/i/f6afd7332af812c00784f4b3ab11588f#L151-L168, L185-L216))。
  • 事件绑定:每个操作按钮都绑定到对应 API 调用——openchrome.downloads.open(id)showchrome.downloads.show(id)removeFilechrome.downloads.removeFile(id)erasechrome.downloads.erase({id})pause/resume/cancel分别调chrome.downloads.pause/resume/cancel(id)(popup.js)。

render()方法(popup.js)负责把数据状态映射为界面表现,是理解整个列表渲染的关键:

  • 状态判定in_progress = state == 'in_progress'openable = state != 'interrupted' && exists && !deleted。文件仍可打开时显示"打开文件名"链接,否则显示灰化的"Removed"文本(popup.js)。
  • 文件图标:若已有filename且尚无icon_url,调用chrome.downloads.getFileIcon(id, { size: 32 })异步获取文件类型图标(popup.js)。
  • 按钮显隐矩阵:暂停/恢复/取消按钮仅在in_progress时出现;恢复按钮额外要求paused为真;remove-file仅当state == 'complete'且文件存在、未被删除、且chrome.downloads.removeFile可用时显示;erase在下载进行中隐藏(popup.js)。
  • 进度条与剩余时间:当in_progress || canResume时显示进度区;有totalBytes时显示bytesReceived/totalBytes及按百分比填充的 meter 进度条;estimatedEndTime存在且未暂停时,用formatTimeLeft()把剩余毫秒格式化为 "Xd Yh left / Xh Ym left / Xm Ys left / Xs left"([popup.js](https://link.gitcode.com/i/f6afd7332af812c00784f4b3ab11588f#L365-L447, L120-L139))。
  • 危险文件提示maybeAccept()是渲染的收尾动作(popup.js)——只要某条记录处于in_progressdanger既不是'safe'也不是'accepted',就调用chrome.downloads.acceptDanger(id)触发 Chrome 原生的危险下载确认对话框,并用一个类级标志accepting_danger防止并发重复弹窗。

数据驱动:search 查询、事件监听与进度轮询

popup 里的数据不是静态快照,而是由"事件驱动 + 定时轮询"两条链路共同维持的:

初次加载:chrome.downloads.search分页取数

DownloadManager.loadItems()在脚本加载时立即执行(不等待window.onload,见 popup.js),用orderBylimit做分页查询:

const kShowNewMax = 50; const kOldMs = 1000 * 60 * 60 * 24 * 7; // 7 天 const results = await chrome.downloads.search({ orderBy: ['-startTime'], limit: kShowNewMax + 1 });

这里有个精巧的"探针"设计(popup.js):查询kShowNewMax + 1(51)条,但只展示 50 条——多出来的第 51 条用于探测"是否存在更早的下载"。如果确实存在更早记录,就显示"Show Older Downloads"按钮。默认展示策略是:优先展示 7 天(kOldMs)内、最多 50 条新记录;如果一条新的都没有,则退而展示任意时间的最多 50 条(见showNew()的兜底逻辑,popup.js)。

点击"Show Older"后,showOlder()会执行一次不带任何过滤条件的全量search({}),把隐藏的旧记录全部显示出来,同时显示"Loading Older Downloads..."占位(popup.js)。

事件监听:onCreated / onChanged / onErased

popup 注册了三个事件监听器(popup.js):

  • chrome.downloads.onCreated:新下载创建时,getOrCreate()拿到或新建DownloadItem,刷新列表并启动进度轮询。
  • chrome.downloads.onChanged:任意字段变化时,把 delta 中的current值合并进实例并重新render();当状态变为in_progress且未暂停时,启动进度轮询(popup.js)。
  • chrome.downloads.onErased:下载从历史中删除时,从 DOM 移除对应列表项并重新加载。

值得强调的是代码注释里明确提到的一个坑:bytesReceived的变化永远不会触发onChanged事件(见 service-worker.js 的注释),所以实时进度只能靠轮询。popup 的进度轮询间隔为200ms

DownloadManager.startPollingProgress.MS = 200; DownloadManager.startPollingProgress.pollProgress = async function () { const results = await chrome.downloads.search({ state: 'in_progress', paused: false }); // 更新所有进行中记录并继续下一轮 };

轮询用setTimeout自续期(而非setInterval),且只在"存在进行中且未暂停的下载"时才启动,避免空闲时反复唤醒(popup.js)。

搜索:支持引号的 token 化查询

顶部搜索框通过onsearch事件触发DownloadManager.onSearch()(popup.js)。查询串会按"空格分词、引号内整体保留"的规则拆成多个 term:

let query = document.getElementById('q').value.match(/(?:[^\s"]+|"[^"]*")+/g); // 逐个 strip 掉成对的引号,然后调用: const results = await chrome.downloads.search({ query: query });

即搜索"foo bar" baz会被解析为foo bar(整体)与baz两个关键词,交给chrome.downloads.searchquery参数做匹配;无结果时显示"Zero matches"占位。搜索期间会隐藏"Show Older"入口并显示"Teleporting lots of goats..."(本地化后的searching文案)。

打开文件夹:showDefaultFolder

popup 顶部的文件夹图标调用chrome.downloads.showDefaultFolder()直接打开系统的默认下载目录(popup.js)。

可选权限实战:按需申请 management 权限

这是示例中非常值得学习的一个模式:下载记录里可能带有byExtensionId/byExtensionName(由哪个扩展发起的下载),但展示"来源扩展"的链接需要management权限。示例没有在安装时强要该权限,而是:

  1. 在 manifest 里声明为optional_permissions: ["management"]
  2. 渲染时用chrome.permissions.contains({ permissions: ['management'] })检查是否已授权(popup.js);
  3. 未授权时显示一条提示条与"Show links to extensions that download files"链接,点击后通过chrome.permissions.request({ permissions: ['management'] })弹出系统授权对话框;
  4. 若用户拒绝,则把managementPermissionDenied写入chrome.storage.local下次不再重复打扰用户(popup.js)。

授权成功后,每条由扩展发起的下载会显示来源扩展图标(通过chrome://extension-icon/{id}/48/1加载)和名称链接,指向chrome://extensions#{extensionId}(popup.js)。注意:show-folderreferrer(跳转到来源页面)、open等链接的目标 URL 均在render()中动态赋值。

Service Worker:setUiOptions、动态图标与 1 秒轮询

后台 service-worker.js 承担三类任务:

1. 关闭原生下载 UI

第一行即chrome.downloads.setUiOptions({ enabled: false })(需要downloads.ui权限),前文已述。

2. OffscreenCanvas 动态绘制工具栏图标

drawIcon(side, options)使用OffscreenCanvas在 Service Worker 中直接绘制图标(service-worker.js),绘制逻辑包括:

  • 进度弧:存在进行中下载时,按totalBytesReceived / totalTotalBytes的比例绘制绿色圆弧;若某些下载缺少totalBytes(服务器未给出总大小),则绘制 16 段的"未知进度"旋转条纹;
  • 下载箭头:中央固定的下载箭头;
  • 状态角标(优先级从高到低):存在danger非 safe/accepted 的记录 → 红色危险角标;否则存在暂停项 → 灰色暂停符号;否则存在自上次打开 popup 以来新完成的项 → 绿色完成对勾。

绘制完成后,通过chrome.action.setIcon({ imageData })一次性设置 19px 与 38px 两档图标(service-worker.js)。

3. 1 秒轮询驱动图标刷新

与 popup 的 200ms 轮询不同,后台的pollProgress()1000ms间隔运行(pollProgress.MS = 1000,service-worker.js),同样只在存在进行中下载时自续期。它执行一次全量chrome.downloads.search({}),聚合出上述图标状态,并用chrome.storage.session缓存上一次的图标 JSON 快照——只有状态真正变化时才调用setIcon,避免无意义的重复绘制(service-worker.js)。

后台的唤醒路径覆盖了所有相关事件:onCreatedonChanged(状态变化会唤醒已卸载的 Worker)、以及 popup 通过chrome.runtime.sendMessage('poll')发来的主动请求(service-worker.js)。popup 每次打开时(setLastOpened())会记录popupLastOpened时间戳并发送poll消息,从而让"新完成下载"角标的判定以上一次打开 popup 为时间基准。

界面与国际化:popup.html / popup.css / messages.json

popup.html 定义了完整的界面骨架:顶部固定搜索栏(#q)、清除全部按钮(#clear-all,遍历可见项逐个erase())、打开下载文件夹按钮、下载项列表容器#items、隐藏的.item模板、以及危险文件授权提示条。所有操作按钮均使用内联 SVG 绘制图标(暂停/恢复/取消/删除/擦除/显示目录/引用页/来源扩展),无需额外图片资源。

popup.css 实现了紧凑的单行布局(white-space: nowrap)、悬浮操作菜单(.more绝对定位)、以及带条纹动画的进度条(.meter > span:after+@keyframes move,2 秒线性循环)。

国际化方面,messages.json 定义了全部可见文案:按钮 title、搜索占位符、空列表/零结果提示、12 个月份缩写、以及带占位符的剩余时间模板(如$days$d $hours$h left)。popup.js 的loadI18nMessages()在窗口加载时统一把chrome.i18n.getMessage()的结果写入 DOM,并会根据本地化文案长度动态调整 popup 的最小宽度(ratchetWidth/ratchetHeight,保证不同语言下 UI 不换行、不截断)。formatDateTime()还根据日期距离当前时间的远近,智能显示"HH:MMam/pm"、"日 月"缩写或年份(popup.js)。

本示例覆盖的 chrome.downloads API 全景

将全部源码整理后可得到本示例实际使用到的 API 清单,这也是一个下载管理器类扩展的"最低完整能力集":

API用途出现位置
chrome.downloads.search()orderBy/limit/state/paused/query查询下载记录[popup.js](https://link.gitcode.com/i/f6afd7332af812c00784f4b3ab11588f#L573-L576, L691-L694, L636, L662)
chrome.downloads.pause()/resume()/cancel()暂停 / 恢复 / 取消下载popup.js
chrome.downloads.erase()从下载历史中移除记录popup.js
chrome.downloads.removeFile()删除已下载的文件popup.js
chrome.downloads.open()打开已完成的文件popup.js
chrome.downloads.show()在系统文件管理器中定位文件popup.js
chrome.downloads.showDefaultFolder()打开系统默认下载目录popup.js
chrome.downloads.getFileIcon()获取文件类型图标popup.js
chrome.downloads.acceptDanger()触发危险下载确认popup.js
chrome.downloads.setUiOptions()接管/关闭浏览器原生下载 UIservice-worker.js
chrome.downloads.download()发起新下载(生成图标场景)service-worker.js
chrome.downloads.onCreated/onChanged/onErased下载生命周期事件popup.js
chrome.action.setIcon()动态更新工具栏图标service-worker.js
chrome.permissions.request()/contains()按需申请可选权限popup.js
chrome.storage.local / session持久化与图标快照缓存[popup.js](https://link.gitcode.com/i/f6afd7332af812c00784f4b3ab11588f#L11, L394-L407)、service-worker.js
chrome.runtime.sendMessage / onMessagepopup 与 Worker 通信popup.js、service-worker.js

两个值得借鉴的实现细节

最后提炼示例中两个可复用的工程技巧:

  1. "少查询一条"的探针分页loadItems查询 51 条只展示 50 条,用多出的 1 条判断"是否有更早记录",从而决定是否显示"Show Older"按钮——这是避免额外一次 API 调用的低成本做法(popup.js)。
  2. 两个层级的差异化轮询:popup 需要高刷新率的进度条,用 200ms 轮询;Service Worker 只关心工具栏徽章的整体状态,用 1000ms 轮询,且仅在存在进行中下载时自续期、仅在状态快照变化时重绘。这种"按需唤醒、按变化绘制"的策略能显著减少后台 Worker 的空转与绘制开销。

参考同仓库其他 downloads 示例(downloads_overwrite、download_links、download_filename_controller)可以进一步看到chrome.downloads在文件命名、批量下载、下载触发等场景的更多用法;而本示例聚焦的是"下载完成后的全生命周期管理",二者互补。若要在自己的项目中复刻,直接以本示例的 manifest.json 为起点,按上文权限表裁剪权限,即可快速落地一个拥有完整下载管理能力的 MV3 扩展。

  • 示例工程

【免费下载链接】chrome-extensions-samples

Chrome Extensions Samples

项目地址:https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples
点击查看免费下载
上一篇:DeepSeek-LLM部署实战:7B/67B模型GPU配置完全指南
下一篇:从Electron迁移到PakePlus的终极指南:20倍体积缩减与性能提升

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 3:22:35

上千篇笔记一键整理:Foam标签、Query查询与智能文件夹进阶指南

上千篇笔记一键整理:Foam标签、Query查询与智能文件夹进阶指南 【免费下载链接】foam A personal knowledge management and sharing system for VSCode 项目地址: https://gitcode.com/gh_mirrors/fo/foam Foam 是基于 VSCode 的个人知识管理与笔记分享系统…

作者头像 李华
网站建设 2026/9/21 3:21:34

STM32外部中断实战:ITR9606红外对管转速测量方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华