简介:这份资源是一套自制 Edge、Chrome 标签页扩展插件,面向具备一定前端基础、希望动手开发浏览器插件的开发者与学习者。内容围绕 Chromium 内核扩展体系展开,涵盖 manifest.json 配置、background.js 后台脚本、content scripts 注入、popup 弹窗页面以及图标资源等核心模块,并涉及 jQuery 简化 DOM 操作、CSS 定制弹窗与图标样式、chrome.tabs 与 chrome.storage 等 API 的调用方式,同时兼顾权限声明、安全实践、打包发布与跨浏览器兼容性等要点。压缩包为 zip 格式,整体约 14.64MB,文件类型以扩展源码与前端资源为主,便于直接阅读与二次修改。目前已有 1027 人学习下载,适合想从零梳理扩展结构、理解标签页管理逻辑或对照调试自己插件的读者参考借鉴。
1. 从一次标签页失控说起:这个扩展到底解决什么问题
你有没有遇到过这种情况:查资料时一口气开了三十多个标签页,Chrome 和 Edge 的标签栏被压缩得只剩图标,想找刚才那个页面得靠肉眼一个个扫;更糟的是浏览器崩溃重启后,标签页全没了,只能翻历史记录一条条捞。系统自带的标签页分组、垂直标签页功能各有用处,但定制化程度有限,比如你想让新标签页固定打开某个内网地址、想一键把当前窗口所有标签页导出成清单、想给标签页加个自定义右键菜单,原生功能都做不到。
这份「自制 Edge、Chrome 标签页扩展插件」资源,就是围绕这些真实痛点做的一套可复现的浏览器扩展源码。它基于 Manifest V3 规范,用前端三件套 HTML、CSS、JavaScript 配合 jQuery 完成界面与逻辑,覆盖标签页的读取、创建、更新、分组、导出等核心操作,同时演示了 popup 弹窗、options 配置页、content script 注入三种典型扩展形态。适合两类人:一是想入门浏览器扩展开发、需要一个能跑通全流程的练手项目的前端;二是手上有一堆重复性标签页操作、想自己改一个顺手的工具出来的从业者。下面我按「它由什么组成 → 怎么装怎么改 → 哪些地方容易翻车」的顺序拆一遍。
2. 拆开这份扩展:Manifest V3 结构与标签页 API 权限
2.1 目录结构与各文件职责
拿到源码包后,先别急着往浏览器里拖,花两分钟把目录结构看清楚,后面改代码时才知道该动哪个文件。一个典型的标签页扩展目录大致长这样:
tab-manager-extension/ ├── manifest.json # 扩展的配置入口,声明权限、入口文件、版本 ├── popup/ │ ├── popup.html # 点击扩展图标后弹出的小窗口 │ ├── popup.css # 弹窗样式 │ └── popup.js # 弹窗逻辑,调用 chrome.tabs API ├── options/ │ ├── options.html # 配置页,设置默认行为 │ └── options.js ├── background/ │ └── service-worker.js # MV3 的后台脚本,处理事件监听 ├── content/ │ └── content.js # 注入到页面的脚本(按需) ├── lib/ │ └── jquery.min.js # 本地引入的 jQuery └── icons/ ├── icon16.png ├── icon48.png └── icon128.pngmanifest.json是整个扩展的身份证,浏览器靠它识别你要什么权限、加载哪些文件。popup目录是用户交互的主入口,点击工具栏图标就弹出这里。background/service-worker.js在 MV3 里替代了原来的 background page,负责监听标签页变化、右键菜单等全局事件。options页给用户做持久化配置。content脚本只在需要操作页面 DOM 时才用得上,纯标签页管理其实可以不要。lib放本地 jQuery,避免依赖外部 CDN 导致加载失败。
2.2 manifest.json 关键字段与权限声明
Manifest V3 和 V2 最大的区别在于后台脚本从常驻页面变成了事件驱动的 service worker,权限声明也更严格。下面是一份可直接用的配置:
{ "manifest_version": 3, "name": "Tab Manager Lite", "version": "1.0.0", "description": "标签页批量管理与快速跳转", "permissions": [ "tabs", "storage", "contextMenus" ], "host_permissions": [ "<all_urls>" ], "action": { "default_popup": "popup/popup.html", "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } }, "background": { "service_worker": "background/service-worker.js" }, "options_page": "options/options.html", "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } }permissions里的tabs是核心,没有它你连当前窗口有几个标签页都读不到;storage用于保存用户配置;contextMenus用于给标签页加右键菜单。host_permissions在 MV3 里被单独拆出来,<all_urls>表示允许访问所有站点,如果你只操作标签页本身、不读取页面内容,其实可以去掉这一项,减少安装时的权限提示。action.default_popup指向弹窗页面,background.service_worker指向后台脚本。这里有个容易忽略的点:MV3 的 service worker 不是常驻的,空闲约 30 秒会被回收,所以不要把全局状态存在变量里,要用chrome.storage持久化。
2.3 标签页 API 的常用方法与参数
chrome.tabs是这套扩展的主力 API,下面几个方法覆盖了八成场景:
| 方法 | 作用 | 关键参数 |
|---|---|---|
chrome.tabs.query | 查询符合条件的标签页 | {currentWindow: true}、{active: true} |
chrome.tabs.create | 新建标签页 | {url, active, index} |
chrome.tabs.update | 更新标签页属性 | tabId, {url, active, pinned} |
chrome.tabs.remove | 关闭标签页 | tabId或[tabId1, tabId2] |
chrome.tabs.group | 将标签页加入分组 | {tabIds, groupId} |
chrome.tabs.move | 移动标签页位置 | tabId, {index, windowId} |
query的过滤条件写错是最常见的翻车点。比如{currentWindow: true}只返回当前窗口,{windowId: chrome.windows.WINDOW_ID_CURRENT}效果类似但语义不同;{active: true}配合{currentWindow: true}才能拿到当前激活的那个标签页。create的active默认是true,意味着新标签页会立刻抢焦点,如果你在批量创建,记得显式设成false,否则用户会看到标签页疯狂闪烁。
3. 从零跑通:加载扩展、写弹窗逻辑、做配置持久化
3.1 在 Edge 和 Chrome 里加载未打包扩展
源码改完后要验证,第一步是把它加载进浏览器。Chrome 和 Edge 都是 Chromium 内核,操作几乎一样,但入口地址不同:
- Chrome:地址栏输入
chrome://extensions/,回车。 - Edge:地址栏输入
edge://extensions/,回车。
进入扩展管理页后,做三件事:第一,打开右上角的「开发者模式」开关,否则「加载已解压的扩展程序」按钮不会出现;第二,点击「加载已解压的扩展程序」,选中你解压后的tab-manager-extension文件夹(注意是包含manifest.json的那一层,不是它的父目录);第三,加载成功后工具栏会出现扩展图标,如果没出现,点一下拼图图标把它固定到工具栏。
提示:每次修改
manifest.json或 service worker 后,必须回到扩展管理页点一次「刷新」按钮,改动才会生效。只改 popup 里的 HTML/CSS/JS 则关掉弹窗重开即可。
3.2 popup 弹窗:用 jQuery 渲染标签页列表
popup 是用户点开图标看到的第一屏,这里用 jQuery 把当前窗口的标签页列出来,并提供关闭和跳转按钮。先看 HTML 骨架:
<!-- popup/popup.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="popup.css"> </head> <body> <div class="header"> <span id="tab-count">0 个标签页</span> <button id="export-btn">导出清单</button> </div> <ul id="tab-list"></ul> <script src="../lib/jquery.min.js"></script> <script src="popup.js"></script> </body> </html>逻辑部分用chrome.tabs.query拿到列表,再用 jQuery 动态生成 DOM:
// popup/popup.js $(function () { // 查询当前窗口所有标签页 chrome.tabs.query({ currentWindow: true }, function (tabs) { $('#tab-count').text(tabs.length + ' 个标签页'); tabs.forEach(function (tab) { // 用 favIconUrl 做图标,缺失时用默认图 var icon = tab.favIconUrl || '../icons/icon16.png'; var $li = $('<li>').attr('data-tab-id', tab.id); $('<img>').attr('src', icon).addClass('favicon').appendTo($li); $('<span>').text(tab.title || tab.url).addClass('title').appendTo($li); // 点击标题跳转到该标签页 $li.on('click', function () { chrome.tabs.update(tab.id, { active: true }); window.close(); }); // 点击关闭按钮移除标签页 $('<button>').text('×').addClass('close-btn').on('click', function (e) { e.stopPropagation(); // 阻止冒泡触发跳转 chrome.tabs.remove(tab.id, function () { $li.remove(); $('#tab-count').text($('#tab-list li').length + ' 个标签页'); }); }).appendTo($li); $('#tab-list').append($li); }); }); });这段代码有几个参数值得说清楚。chrome.tabs.query的回调里tabs是一个数组,每个元素包含id、title、url、favIconUrl、active、pinned等字段。chrome.tabs.update(tab.id, {active: true})把目标标签页设为激活,同时window.close()关掉弹窗,体验上就是「点一下跳过去」。关闭按钮上的e.stopPropagation()不能省,否则点关闭会同时触发父级li的点击事件,导致先跳转再关闭的诡异行为。favIconUrl在某些页面(比如刚打开还没加载完)可能是空字符串,所以要有兜底图标。
3.3 options 配置页与 chrome.storage 持久化
如果想让用户自定义「新标签页默认打开哪个网址」「是否显示 favicon」这类选项,就需要 options 页配合chrome.storage。MV3 里推荐用chrome.storage.sync,它能跨设备同步(前提是用户登录了账号),容量上限约 100KB,存配置绰绰有余。
<!-- options/options.html --> <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><title>设置</title></head> <body> <label>新标签页默认地址: <input type="text" id="homepage" placeholder="https://example.com"> </label> <label>显示网站图标: <input type="checkbox" id="show-favicon"> </label> <button id="save">保存</button> <script src="../lib/jquery.min.js"></script> <script src="options.js"></script> </body> </html>// options/options.js $(function () { // 读取已保存的配置,回填到表单 chrome.storage.sync.get({ homepage: '', showFavicon: true }, function (items) { $('#homepage').val(items.homepage); $('#show-favicon').prop('checked', items.showFavicon); }); // 保存配置 $('#save').on('click', function () { chrome.storage.sync.set({ homepage: $('#homepage').val().trim(), showFavicon: $('#show-favicon').prop('checked') }, function () { alert('已保存'); }); }); });chrome.storage.sync.get的第一个参数是默认值对象,当存储里没有对应键时返回这些默认值,这样能避免首次使用时读到undefined。set的回调在写入完成后触发,用来给用户反馈。注意storage.sync有写入频率限制(每分钟约 120 次),别在循环里频繁调用。读取到的配置在 popup 或 service worker 里同样用chrome.storage.sync.get取,保持单一数据源。
3.4 用 service worker 监听标签页事件
MV3 的 service worker 适合处理「标签页创建/更新/关闭」这类全局事件。比如你想统计用户每天开了多少标签页,或者在新标签页打开时自动做点什么,都写在这里:
// background/service-worker.js // 监听标签页创建事件 chrome.tabs.onCreated.addListener(function (tab) { console.log('新标签页:', tab.id, tab.url || '(空白页)'); }); // 监听标签页更新(URL 变化、加载完成等) chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (changeInfo.status === 'complete') { console.log('加载完成:', tab.title); } }); // 安装时初始化默认配置 chrome.runtime.onInstalled.addListener(function () { chrome.storage.sync.get({ homepage: '' }, function (items) { if (!items.homepage) { chrome.storage.sync.set({ homepage: 'https://example.com' }); } }); });onUpdated的changeInfo对象里,status为'loading'或'complete',url在地址变化时出现。判断changeInfo.status === 'complete'才能确保页面标题已经可用。service worker 被回收后,这些监听器会在下次事件触发时重新注册,所以不用担心丢失,但也意味着你不能依赖内存里的全局变量跨事件保持状态。
4. 避坑与排查:权限、MV3 生命周期、跨浏览器差异
4.1 现象:扩展加载报「Manifest 文件缺失或无法读取」
原因通常是选错了文件夹层级。manifest.json必须位于你选中的目录根下,如果你选中了它的父目录,浏览器找不到这个文件。解决方法是确认选中目录里直接能看到manifest.json,而不是先看到一个同名子文件夹。另外 JSON 文件里多一个逗号、少一个引号也会导致解析失败,用编辑器的 JSON 校验功能过一遍。
4.2 现象:popup 里调用 chrome.tabs 报 undefined
原因是在普通网页里调试 popup 的 JS,而不是在扩展上下文里。chrome.tabs这类 API 只在扩展页面(popup、options、service worker)中可用。解决方法是右键点击扩展图标选「审查弹出内容」,或者到扩展管理页点击对应扩展的「检查视图」链接,在专属的 DevTools 里调试。直接在浏览器标签页打开popup.html是拿不到 API 的。
4.3 现象:service worker 里的变量过一会儿就丢了
这是 MV3 的设计使然,service worker 空闲约 30 秒会被终止,内存中的全局变量随之清空。血泪经验是不要把待处理队列、计数器这类状态存在变量里。解决办法是改用chrome.storage或chrome.storage.session(MV3 新增,仅内存、浏览器关闭即清)持久化,每次事件触发时重新读取。
4.4 现象:Edge 上能用,Chrome 上权限报错
虽然同为 Chromium 内核,但两家对某些 API 的支持节奏不同。比如chrome.tabs.group在较老的 Chrome 版本上可能不存在,需要做特性检测。解决办法是在调用前加判断:
if (chrome.tabs.group) { chrome.tabs.group({ tabIds: [tab.id] }); } else { console.warn('当前浏览器不支持标签页分组 API'); }另外 Edge 有自己的edge://协议页面,Chrome 是chrome://,扩展无法操作这些内部页面,tabs.query返回的结果里这类页面的url字段可能为空,遍历时要做好空值判断。
4.5 现象:修改代码后扩展行为没变化
九成是忘了刷新扩展。改manifest.json、service worker、content script 后,必须到扩展管理页点刷新按钮;只改 popup 的 HTML/CSS/JS 则关掉弹窗重新打开即可。如果还不生效,检查是不是装了同名的旧版本扩展,两个版本冲突会导致行为混乱,先禁用或删除旧的。
5. 进阶技巧:标签页分组、批量导出与性能取舍
把基础功能跑通后,可以往上加两个实用能力。第一个是标签页分组,chrome.tabs.group配合chrome.tabGroups.update能给分组命名和上色:
// 把当前窗口所有标签页归入一个分组 chrome.tabs.query({ currentWindow: true }, function (tabs) { var tabIds = tabs.map(function (t) { return t.id; }); chrome.tabs.group({ tabIds: tabIds }, function (groupId) { chrome.tabGroups.update(groupId, { title: '批量整理', color: 'blue' }); }); });color可选值有grey、blue、red、yellow、green、pink、purple、cyan、orange,传错会静默失败。分组 API 需要tabGroups权限,记得在 manifest 里补上。
第二个是批量导出标签页清单。思路是把tabs.query的结果拼成文本,用 Blob 触发下载:
function exportTabs() { chrome.tabs.query({ currentWindow: true }, function (tabs) { var lines = tabs.map(function (t) { return (t.title || '无标题') + '\t' + (t.url || ''); }); var blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'tabs-' + Date.now() + '.txt'; a.click(); URL.revokeObjectURL(url); }); }这里用\t分隔标题和 URL,方便粘进表格软件。URL.revokeObjectURL要在点击后调用,否则 Blob 占用的内存不会释放。如果标签页数量上百,tabs.query返回的数据量不小,建议加个 loading 状态,别让用户以为卡死了。
性能上有个取舍:popup 每次打开都会重新查询标签页,标签页多的时候渲染会慢。常见做法是只渲染前 50 条,剩下的用「加载更多」按钮分批展示,或者用虚拟滚动。我一般会先量一下实际标签页数量,超过 80 个再考虑优化,否则属于过度设计。从那以后我每次写完扩展,都会先在标签页数量拉满的情况下点一遍所有按钮,确认没有卡顿和报错才收工。希望这份拆解帮到你,源码包里的注释比正文还细,动手改起来不会迷路。
本文还有配套的精品资源,点击获取