GitHub 页面注入与文件选择)
项目地址fthux/GitZipPro。这一篇基于 GitZip Pro 的开源源码继续分析它如何增强 GitHub 文件列表页面。上一篇从manifest.json看了 GitZip Pro 的整体装配关系。这一篇进入第一个真正的业务入口source/content.js。content.js的职责可以概括成一句话在 GitHub 仓库文件页里找到文件列表给每一行加上可选择能力再把用户选中的文件或文件夹交给下载器。它不直接生成 zip也不直接保存下载历史。它更像一个页面适配层负责把 GitHub 原本的文件浏览界面改造成 GitZip Pro 可以操作的下载界面。注入入口content.js不是用户主动打开的页面而是由manifest.json注入到 GitHub 页面content_scripts:[{matches:[https://github.com/*],js:[constants.js,i18n.js,jszip.min.js,downloader.js,content.js],css:[content.css],run_at:document_idle}]这里的顺序决定了content.js可以直接使用几个前置能力GZP_CONSTANTS提供配置GZP_I18N提供翻译JSZip提供压缩能力window.GZPDownloader提供下载入口。也就是说content.js并不是孤立脚本它是在前面几个模块准备完成后最后接管 GitHub 页面交互。页面注入总流程init()loadSettings()读取主题、按钮位置、文件大小显示等配置isRepoFilePage()判断是否为仓库文件页getFileRows()查找文件/目录行attachRowBehavior(row)给每一行绑定选择行为createCheckbox(row)插入复选框setupRowContextMenu(row)绑定右键菜单信息updateDownloadButton()更新浮动下载按钮ensureDownloadButton()创建或复用下载按钮GZPDownloader.start()启动下载这条链路可以帮助我们把content.js里的函数分成几类页面识别函数、DOM 查询函数、行增强函数、按钮函数、导航监听函数和设置应用函数。判断当前页面是否可处理GitHub 页面很多仓库首页、issues、pull requests、actions、releases 都在github.com下。GitZip Pro 只应该在仓库文件列表页工作所以第一步是判断页面类型。isRepoFilePage()的作用就是做这个过滤。它会根据当前location.pathname判断路径是否像仓库文件页。典型路径包括/{owner}/{repo} /{owner}/{repo}/tree/{branch} /{owner}/{repo}/tree/{branch}/{path}而 issues、pull、wiki、settings、actions 等页面不属于文件列表页不应该注入选择框和下载按钮。这种判断很重要。content script 虽然会在所有https://github.com/*页面加载但加载不等于每个页面都要执行增强逻辑。先判断页面类型可以避免对非仓库文件页造成 DOM 干扰。查找 GitHub 文件列表行页面确认之后下一步是找到 GitHub 的文件/目录行。对应函数是getFileRows()。GitHub 的前端 DOM 并不是固定不变的项目中并没有只依赖一个选择器而是为不同结构准备了多种匹配方式。content.js里还有getTargetCell(row)这样的辅助函数用来找到每一行里最适合插入复选框的单元格。这种写法说明content.js面对的不是一个由自己完全控制的页面而是 GitHub 官方页面。扩展只能“贴”在别人已有的 DOM 上因此需要兼容不同页面结构、响应式布局以及 GitHub 后续可能调整过的 class。从一行中提取路径行被找到之后还需要知道这一行对应哪个文件或文件夹。这个工作由getPathFromRow(row)完成。GitHub 文件列表中的链接通常包含/tree/或/blob//owner/repo/tree/main/src /owner/repo/blob/main/README.mdtree代表目录blob代表文件。content.js在页面层不需要完整解析 owner、repo、branch、path它只要把 href 收集起来即可。真正的结构化解析会交给downloader.js的parseGitHubUrl()。这也是一个明显的分层页面层只收集 GitHub 链接下载层再把链接解析成下载任务。给文件行插入复选框核心增强逻辑在attachRowBehavior(row)和createCheckbox(row)。attachRowBehavior(row)会先判断这一行是否已经初始化过if(row.classList.contains(gzp-row-initialized))return;这是为了避免重复插入。GitHub 页面会因为导航、局部刷新、滚动等原因反复触发 DOM 处理如果没有这个标记同一行可能被插入多个复选框。接着它会跳过父目录行也就是..或 “Go to parent directory” 这种返回上一级的行。因为这类行不是实际下载目标不应该出现在选择列表里。真正创建复选框的是createCheckbox(row)。它会创建一个span.gzp-cb-wrapper包裹input.gzp-checkbox。当复选框状态变化时代码会更新一个核心状态selectedItems.set(row,getPathFromRow(row));如果取消选择则从selectedItems中删除。这里的selectedItems是Mapkey 是当前 DOM 行value 是对应的 GitHub href。这个结构同时保留了页面元素和下载路径后续既可以更新 UI也可以把路径传给下载器。双击选择与选中态除了直接点击复选框attachRowBehavior(row)还支持双击选择。它通过记录lastClickTime判断两次点击是否发生在 500ms 内。如果设置项doubleClickSelect开启就切换复选框状态并主动派发change事件让状态更新逻辑复用复选框的处理流程。选中后行会添加样式类row.classList.add(gzp-row--selected);取消后再移除。视觉样式由content.css负责这样 JS 只控制状态具体呈现交给 CSS。浮动下载按钮当用户选择了至少一个文件或目录页面需要出现下载按钮。这部分由ensureDownloadButton()和updateDownloadButton()负责。updateDownloadButton()会读取selectedItems.size。如果数量大于 0就显示按钮并更新 badge如果数量为 0并且按钮处于 idle 状态就隐藏按钮。点击下载按钮时content.js会调用window.GZPDownloader.start(selectedItems,callbacks)注意这里传进去的不是数组而是前面维护的Map。下载器会遍历其中的 href逐个解析成 GitHub 下载任务。右键菜单信息同步content.js中还有setupRowContextMenu(row)。当用户在某一行上右键时它会读取这一行的 href 和展示名称然后向 background 发送消息chrome.runtime.sendMessage({type:GZP_UPDATE_CONTEXT_MENU,href,itemName});background 收到后会更新右键菜单标题。这样浏览器右键菜单里的“下载当前项目”就能知道用户刚刚右键的是哪一个文件或文件夹。适配 GitHub 的 SPA 导航GitHub 页面不是传统的每次导航都完整刷新。仓库内部切换目录时页面可能发生局部更新。content.js里有interceptLinkClicks()、onNavigate()、attachAllRows()等函数用来在导航后重新扫描文件行、清理旧状态并重新注入 UI。可以把这一层理解为 GitHub 页面适配器GitHub URL 或 DOM 变化onNavigate()清理旧选择和文件大小请求attachAllRows()重新扫描文件行重新插入复选框和右键逻辑如果没有这层处理扩展可能只在首次打开页面时有效进入子目录后就失效。本篇小结content.js是 GitZip Pro 的页面入口。它的核心工作不是下载而是把 GitHub 文件列表改造成一个可交互的选择界面。这一篇可以得到一条清晰的源码主线init() - loadSettings() - isRepoFilePage() - getFileRows() - attachRowBehavior(row) - createCheckbox(row) - updateDownloadButton() - GZPDownloader.start(selectedItems)下一篇进入downloader.js的前半部分看 GitZip Pro 如何把页面中收集到的 GitHub 链接解析成 owner、repo、branch、path并通过 GitHub API 请求文件和目录数据。完整源码见 fthux/GitZipPro。如果这篇对你理解浏览器扩展 content script 有帮助也欢迎给 GitZip Pro 一个 Star。如果你也好奇这个 Pro 版为什么会做起来可以接着看这篇项目缘起用了 GitZip 这么多年我动手做了一个「Pro」版。