GitZip Pro 源码解析:一个 GitHub 文件/文件夹下载扩展是如何工作的(五)后台脚本与右键菜单

1785879195063

项目地址:fthux/GitZipPro。本文继续分析 GitZip Pro 的 background service worker 和右键菜单实现。

前两篇围绕 downloader.js 讲完了下载核心:解析 GitHub URL、请求 API、递归收集文件、并发下载并生成 zip。生成 zip 之后,下载器并没有直接完成浏览器下载,而是把 base64 zip 发送给 background.js

这一篇来看 GitZip Pro 的后台脚本:source/background.js

background.js 的职责不是操作 GitHub 页面,也不是遍历文件树,而是提供扩展级能力:右键菜单、下载 API、系统通知、下载完成监听和历史记录保存。

background 在扩展中的位置

manifest.json 中声明:

"background": {
  "service_worker": "background.js",
  "scripts": ["background.js"]
}

在 Manifest V3 中,background 以 service worker 形式运行。它可以响应消息、监听事件、调用扩展 API。GitZip Pro 把必须由扩展后台执行的工作都集中到这里。

整体关系可以画成这样:

flowchart TD A["content.js<br/>页面交互"] --> B["downloader.js<br/>生成 base64 zip"] B --> C["background.js<br/>接收下载消息"] C --> D["chrome.downloads.download()"] C --> E["chrome.notifications"] C --> F["chrome.contextMenus"] C --> G["chrome.storage.local<br/>保存历史记录"] F --> A

这个图里有一个双向关系:content script 会把右键行信息发送给 background;background 的右键菜单点击后,又会把下载命令发回 content script。

菜单 ID 与后台状态

background.js 开头定义了右键菜单 ID:

const MENU_IDS = {
  ROOT: 'gitzip-pro-download',
  CHECKED: 'gitzip-pro-checked-items',
  SEPARATOR: 'gitzip-pro-separator',
  SELECTED: 'gitzip-pro-selected-item'
};

同时还有几个后台状态:

const activeDownloads = new Map();
let selectedItemHref = null;
let backgroundTranslations = {};
let backgroundLocale = 'en';

activeDownloads 用来记录当前由 GitZip Pro 触发的下载,便于在下载完成时显示通知、打开下载项或保存历史记录。selectedItemHref 则保存最近一次右键点击的 GitHub 文件或目录链接。

后台国际化初始化

background 没有页面 DOM,但右键菜单和通知也需要国际化。启动时会调用:

async function initBackgroundI18n() {
  const locale = await GZP_I18N.init();
  backgroundLocale = locale;
  backgroundTranslations = await GZP_I18N.loadLocale(locale);
  ensureContextMenus();
}

这里复用了 i18n.js 暴露的 GZP_I18N。加载语言后,调用 ensureContextMenus() 创建或更新菜单。

同时,background 还监听 storage 变化。当用户在 options 页面切换语言时,background.js 会重新加载语言包并更新菜单文案。

创建和更新右键菜单

右键菜单由两个函数管理:

ensureContextMenus()
createContextMenus()

ensureContextMenus() 会先尝试更新已有菜单。如果更新失败,说明菜单还不存在,就调用 createContextMenus() 创建。

菜单结构大致是:

GitZip Pro
  下载已勾选项目
  --------
  下载当前右键项目

createContextMenus() 使用 chrome.contextMenus.create() 创建根菜单和子菜单。菜单文案通过 t('context_menu.xxx') 获取。

这里的设计让菜单可以随着语言切换动态更新,而不是只在安装时固定一次。

首次安装事件

background.js 监听安装事件:

chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    chrome.storage.local.set({
      'gitzip-pro-show-welcome': true
    }, () => {
      chrome.runtime.openOptionsPage();
    });
  }
});

首次安装时,它会写入一个 welcome 标记,然后打开 options 页面。后续 options.js 会读取这个标记,跳转到 token 页面并展示欢迎弹窗。

这说明首次体验不是 popup 负责的,而是 background 触发 options 页面完成的。

接收下载消息

下载器生成 zip 后,会发送:

{
  type: 'GZP_DOWNLOAD_FILE',
  filename,
  base64,
  mimeType,
  notifyShow,
  notifyOpen,
  historyRecord
}

background.jschrome.runtime.onMessage 中处理这个消息。它先对文件名做一次安全处理:

safeFilename = safeFilename.replace(/(^|\/)[.~]/g, '$1_');

这是因为 chrome.downloads 不允许路径段以点号或波浪号开头。然后把 base64 组装成 data URL:

const dataUrl = `data:${mimeType};base64,${base64}`;

最后调用:

chrome.downloads.download({
  url: dataUrl,
  filename: safeFilename,
  saveAs: false
}, callback);

如果下载成功,并且用户开启了通知或打开下载项,background 会把下载 ID 记录到 activeDownloads

activeDownloads.set(downloadId, {
  notifyShow,
  notifyOpen,
  filename,
  historyRecord
});

后续下载状态变化时,就能根据这个 ID 找回对应上下文。

右键菜单通信

当用户在 GitHub 文件行上右键时,content.js 会发送:

GitZip Pro 右键菜单会根据当前行更新下载目标

{
  type: 'GZP_UPDATE_CONTEXT_MENU',
  href,
  itemName,
  itemType
}

background 收到后,把 href 保存到 selectedItemHref,并更新菜单标题。例如“下载当前选中文件 README.md”。

当用户真正点击右键菜单时,background 监听:

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === MENU_IDS.SELECTED && selectedItemHref) {
    chrome.tabs.sendMessage(tab.id, {
      type: 'GZP_DOWNLOAD_CONTEXT_ITEM',
      href: selectedItemHref
    });
  }
});

这条消息会回到 content script。content script 再构造单项 selectedItems,调用 GZPDownloader.start()

完整右键链路如下:

sequenceDiagram participant User as 用户 participant C as content.js participant B as background.js participant D as downloader.js User->>C: 在文件行右键 C->>B: GZP_UPDATE_CONTEXT_MENU B->>B: 更新菜单标题并保存 href User->>B: 点击右键菜单 B->>C: GZP_DOWNLOAD_CONTEXT_ITEM C->>D: start(单个 href)

下载完成监听与历史保存

background 还监听浏览器下载状态:

chrome.downloads.onChanged.addListener((delta) => {
  ...
});

当下载状态变成 complete,它会从 activeDownloads 中取出下载偏好:

  • 如果 notifyShow 为 true,就创建系统通知。
  • 如果 notifyOpen 为 true,就调用 chrome.downloads.show(delta.id)
  • 如果存在 historyRecord,就保存下载历史。

历史保存由 saveHistoryRecord(record) 完成。它读取 STORAGE_KEYS.DOWNLOAD_HISTORY,把新记录放到最前面,并限制最多保留 100 条。

保存完成后,background 还会尝试发送:

chrome.runtime.sendMessage({
  type: 'GZP_DOWNLOAD_COMPLETE',
  record
});

如果 options 页面正打开,它可以收到这条消息并实时刷新历史列表;如果没打开,也没关系,因为记录已经写入 storage。

本篇小结

background.js 是 GitZip Pro 的扩展后台层。它把下载器不能或不适合直接做的事情接了过来:

GZP_DOWNLOAD_FILE
  -> chrome.downloads.download()
  -> chrome.downloads.onChanged
  -> notification / open downloaded item / save history

同时它还负责右键菜单:

content.js 发送右键行信息
  -> background 更新菜单标题
  -> 用户点击菜单
  -> background 通知 content.js 启动下载

下一篇进入 options.js,看 GitZip Pro 的设置页如何管理主题、语言、token、命名规则、忽略规则、下载历史和统计数据。

项目源码可在 fthux/GitZipPro 查看。欢迎 Star 支持 GitZip Pro,也方便后续对照源码阅读本系列。

如果你也好奇这个 Pro 版为什么会做起来,可以接着看这篇项目缘起:用了 GitZip 这么多年,我动手做了一个「Pro」版

posted on 2026-08-05 09:55  fthux  阅读(5)  评论(0)    收藏  举报