从零开发Edge/Chrome标签页扩展:Manifest V3与核心API实战

发布时间:2026/10/29 9:35:42
从零开发Edge/Chrome标签页扩展:Manifest V3与核心API实战 简介这份资源是一套自制 Edge、Chrome 标签页扩展插件面向具备一定前端基础、希望入门浏览器扩展开发的开发者与学习者。内容围绕 Chromium 内核扩展体系展开涵盖 manifest.json 配置、background 后台脚本、content scripts 注入、popup 弹窗页面以及图标资源等核心模块并涉及 jQuery 简化 DOM 操作、CSS 样式设计、chrome.tabs 与 chrome.storage 等 API 的使用同时兼顾权限声明、安全实践、打包发布、调试技巧与跨浏览器兼容性等工程要点。压缩包为 zip 格式整体约 14.64MB文件类型以 JavaScript、HTML、CSS 及 JSON 配置为主分别承担逻辑处理、界面结构、样式渲染与扩展声明等职责。目前已有 1027 人学习下载适合想通过完整示例理解扩展目录结构、对照源码梳理开发流程、并在此基础上二次定制个性化插件的读者参考。1. 从「标签页太乱」到自制 Edge、Chrome 标签页扩展插件每天开几十个标签页找东西全靠肉眼扫Chrome 标签页分组怎么隐藏、Edge 设置启动打开新的标签页却还是恢复上次会话这些破事堆在一起逼得人想自己动手。自制 Edge、Chrome 标签页扩展插件本质就是用浏览器提供的扩展 API去读写、整理、增强标签页行为把「手动点」变成「自动管」。它适合两类人一类是被标签页淹没、想给自己做个顺手工具的开发者另一类是刚学完前端、想找个真实项目练手的同学。Chrome 和 Edge 现在都基于 Chromium扩展体系基本互通一份代码两边都能装这也是它值得投入的原因。下面按「先跑通最小插件 → 再吃透标签页 API → 最后处理权限和踩坑」的顺序讲能直接抄。2. 最小可运行插件manifest、popup 与加载流程2.1 先搞清楚 Manifest V3 的结构现在新建扩展默认走 Manifest V3MV3。它和老的 V2 最大区别是后台脚本从常驻的 background page 换成了按需唤醒的 service worker权限声明更细远程代码基本被禁。对标签页插件来说核心就三块manifest.json声明权限和入口popup 或 side panel 做界面service worker 或 popup 里的脚本调chrome.tabs系列 API。一个能跑的最小目录长这样tab-tool/ ├── manifest.json ├── popup.html ├── popup.js └── icons/ └── icon128.pngmanifest.json里必须声明tabs权限才能读取标签页的 url、title 等敏感字段如果只是操作当前标签页activeTab就够权限越小审核越顺。2.2 写一个能列出所有标签页的 popup先上manifest.json{ manifest_version: 3, name: Tab Tool, version: 1.0.0, description: 列出并管理当前窗口的标签页, permissions: [tabs], action: { default_popup: popup.html, default_icon: { 128: icons/icon128.png } }, icons: { 128: icons/icon128.png } }逻辑说明manifest_version固定 3permissions里放tabs这是后面能拿到tab.url的前提action.default_popup指定点扩展图标后弹出的页面。参数上version每次改动建议递增否则浏览器可能不刷新缓存。再写popup.html和popup.js!DOCTYPE html html headmeta charsetutf-8style body { width: 320px; font: 13px/1.5 sans-serif; margin: 0; padding: 8px; } li { display: flex; justify-content: space-between; padding: 4px 0; } button { cursor: pointer; } /style/head body ul idlist/ul script srcpopup.js/script /body /html// 查询当前窗口所有标签页并渲染 async function renderTabs() { // currentWindow: true 只取当前窗口避免把别的窗口也列出来 const tabs await chrome.tabs.query({ currentWindow: true }); const ul document.getElementById(list); ul.innerHTML ; for (const tab of tabs) { const li document.createElement(li); const span document.createElement(span); // tab.title 可能为空兜底显示 url span.textContent tab.title || tab.url || (无标题); const btn document.createElement(button); btn.textContent 关闭; btn.onclick async () { await chrome.tabs.remove(tab.id); // 按 id 关闭 renderTabs(); // 关完重渲染 }; li.append(span, btn); ul.appendChild(li); } } renderTabs();逻辑说明chrome.tabs.query返回 PromiseMV3 支持 Promise 写法currentWindow: true是关键参数不加会把所有窗口的标签页都拉进来。chrome.tabs.remove(tab.id)按 id 关闭关完必须重新查询否则列表和实际状态对不上。参数上query还支持active、pinned、url等过滤条件后面做「只关重复页」就靠它。2.3 在 Edge 和 Chrome 里加载调试打开chrome://extensions/或edge://extensions/右上角开启「开发者模式」点「加载已解压的扩展程序」选中tab-tool目录。改完代码后回到扩展页点刷新图标即可popup 不用重装。调试 popup 时右键扩展图标选「审查弹出内容」能直接开 DevToolsservice worker 的日志在扩展页点「Service Worker」链接查看。这一步是后面所有功能的地基先确认列表能正常显示再往下走。3. 标签页核心 API查询、分组、批量操作怎么落地3.1 tabs.query 的过滤参数与常见组合chrome.tabs.query是整个插件的入口参数组合决定了你能拿到什么。常用的几组参数作用典型场景currentWindow限定当前窗口只管理本窗口active只取激活标签获取当前页 urlpinned只取固定标签批量取消固定url按 url 模式匹配找同域名重复页groupId按分组 id 取操作某个标签组比如找当前窗口里所有同域名的重复标签// 找出当前窗口内 url 完全相同的标签保留第一个 async function findDuplicates() { const tabs await chrome.tabs.query({ currentWindow: true }); const seen new Map(); const dupIds []; for (const tab of tabs) { if (!tab.url) continue; // 扩展页等可能没有 url if (seen.has(tab.url)) { dupIds.push(tab.id); } else { seen.set(tab.url, tab.id); } } return dupIds; }逻辑说明用 Map 记录第一次出现的 url再次出现就归为重复。参数上注意tab.url在没tabs权限时是空的所以权限必须给到位。tab.id是后续关闭、移动、分组的唯一凭据务必保存。3.2 用 tabGroups 做自动分组Chromium 从 89 起支持标签组扩展侧对应chrome.tabs.group和chrome.tabGroups。自动按域名分组的最小实现// 按域名把当前窗口标签分组同域名归一组 async function groupByDomain() { const tabs await chrome.tabs.query({ currentWindow: true }); const byDomain {}; for (const tab of tabs) { if (!tab.url || !tab.url.startsWith(http)) continue; const domain new URL(tab.url).hostname; (byDomain[domain] || []).push(tab.id); } for (const [domain, ids] of Object.entries(byDomain)) { if (ids.length 2) continue; // 单个标签不分组 const groupId await chrome.tabs.group({ tabIds: ids }); await chrome.tabGroups.update(groupId, { title: domain, color: blue }); } }逻辑说明先按 hostname 聚合再对数量大于 1 的组调chrome.tabs.group它返回 groupId接着用chrome.tabGroups.update设置标题和颜色。参数上color只接受固定枚举grey/blue/red/yellow/green/pink/purple/cyan/orange传别的会报错。注意chrome.tabs.group需要tabGroups权限别漏声明。3.3 批量关闭、固定与移动批量操作的核心是「先 query 出 id 列表再循环调 API」。关一批// 关闭所有非固定、非激活的标签页 async function closeInactive() { const tabs await chrome.tabs.query({ currentWindow: true, pinned: false, active: false }); const ids tabs.map(t t.id); if (ids.length) await chrome.tabs.remove(ids); // remove 支持传数组 }逻辑说明chrome.tabs.remove既能传单个 id 也能传数组批量关一次调用即可比循环快。参数上pinned: false保证固定标签不被误关active: false保住当前页。移动标签用chrome.tabs.move(id, { index })固定用chrome.tabs.update(id, { pinned: true })。这些 API 都返回 Promise记得await否则连续操作会因状态未更新而错乱。4. 权限、跨浏览器兼容与界面选型4.1 权限最小化tabs、activeTab、host_permissions 的区别很多人一上来就把tabs、all_urls全勾上结果 Edge 加载时警告一堆Chrome 商店审核也慢。三者的边界activeTab用户点击扩展图标后临时获得当前标签页的访问权最轻量适合「只处理当前页」。tabs能读取所有标签页的 url、title、favIconUrl做标签管理基本绕不开。host_permissions访问页面 DOM 内容用比如注入脚本改页面。只做标签页管理不需要它。结论纯标签页管理给tabs就够要读页面内容再加具体域名别用all_urls一把梭。4.2 Chrome 与 Edge 的差异点两者同源 Chromiumchrome.*API 在 Edge 里同样可用Edge 也认browser.*。实际差异主要在Edge 有自己的edge://extensions/管理页和商店离线安装包分发方式不同部分 Edge 独有 API如侧边栏在 Chrome 里没有。写代码时用特性检测而不是硬判断浏览器// 优先用 Promise 风格兼容 chrome 与 browser 命名空间 const api globalThis.browser ?? globalThis.chrome; const tabs await api.tabs.query({ currentWindow: true });逻辑说明Firefox 用browserChromium 系用chrome这行做统一入口。参数上不用管版本号MV3 下两者都支持 Promise。这样一份代码在 Edge、Chrome 都能跑省得维护两套。4.3 popup、side panel 还是 options 页界面选型看使用频率popup 适合「点一下就用完」的轻操作缺点是失焦即关不能做长任务side panelChrome 114、Edge 支持常驻侧边适合标签页列表这种要一直看的场景options 页放设置项。标签管理插件我一般主界面用 side panel设置放 optionspopup 只留一个快捷入口。注意 side panel 需要在 manifest 里声明side_panel字段并调chrome.sidePanel.setPanelBehavior不是所有旧版本都支持做兼容时留好降级。5. 避坑与排查加载失败、API 不生效、状态错乱5.1 改了代码没生效现象改完popup.js刷新扩展界面还是旧的。原因popup 页面被缓存或 service worker 没重启。解决在扩展页点刷新后关掉已打开的 popup 重新点开service worker 在扩展页点「Service Worker」链接进 DevTools 后按 CtrlR 强制重启。macOS 上 Chrome 强制刷新用 CmdShiftR。5.2 tabs.query 拿不到 url现象tab.url是undefined。原因manifest 没声明tabs权限或该标签是chrome://、扩展页等受限页面。解决补上permissions: [tabs]对受限页面做判断跳过别硬读。这类页面本来也不允许扩展操作属于预期行为。5.3 批量操作后列表和实际对不上现象关了几个标签列表还显示旧的。原因chrome.tabs.remove是异步的没await就重渲染拿到的是旧状态。解决所有 tabs API 调用都await操作完再重新query一次。更稳的做法是监听chrome.tabs.onRemoved、onUpdated事件状态变了自动刷新界面而不是手动重渲染。5.4 分组颜色或标题报错现象tabGroups.update抛错。原因color传了枚举外的值或 groupId 已失效组被解散。解决颜色只用官方枚举操作前先chrome.tabGroups.get(groupId)确认存在捕获异常做兜底别让一个失败中断整批操作。5.5 Edge 开发者模式下的加载警告现象Edge 加载时提示权限过多或清单有问题。原因权限声明过宽或 manifest 字段拼写错误。解决按第 4 章做权限最小化用edge://extensions/里的「错误」按钮看具体报错行逐条对照 manifest 规范改。Edge 对 MV3 的校验和 Chrome 基本一致Chrome 能过的一般 Edge 也能过。6. 进阶用事件驱动替代轮询把插件做稳前面所有操作都是「用户点了才查」真正好用的标签页插件得会「自己感知变化」。核心是监听chrome.tabs的事件而不是定时轮询——轮询既费电又容易和实际状态错位。常用事件有onCreated、onRemoved、onUpdated、onActivated、onMoved以及chrome.tabGroups.onUpdated。一个自动维护「重复标签」提示的实现// 监听标签创建与更新实时统计重复 url 数量 const urlCount new Map(); async function refreshCount() { const tabs await chrome.tabs.query({ currentWindow: true }); urlCount.clear(); for (const tab of tabs) { if (!tab.url) continue; urlCount.set(tab.url, (urlCount.get(tab.url) || 0) 1); } const dup [...urlCount.values()].filter(n n 1).length; // 用 badge 显示重复组数量 await chrome.action.setBadgeText({ text: dup ? String(dup) : }); await chrome.action.setBadgeBackgroundColor({ color: #e00 }); } chrome.tabs.onCreated.addListener(refreshCount); chrome.tabs.onRemoved.addListener(refreshCount); chrome.tabs.onUpdated.addListener((id, info) { if (info.url) refreshCount(); // 只有 url 变了才重算 }); refreshCount();逻辑说明用 Map 统计每个 url 出现次数重复组数写进扩展图标的 badge。参数上onUpdated的changeInfo.url只在地址变化时存在用它做过滤能避免每次加载状态变化都重算。setBadgeText传空字符串即清空角标。这套事件驱动比setInterval干净得多也不会在后台空转。验证方法开几个相同网址的标签看 badge 数字是否实时变化关掉一个数字应立刻减。如果不动先查 service worker 是否被浏览器休眠——MV3 的 service worker 空闲会被挂起事件会重新唤醒它但全局变量会丢所以urlCount这种状态要么每次重算如上要么存进chrome.storage。这是 MV3 最容易翻车的地方我早期就栽在「以为全局变量一直在」上后来全部改成事件触发时重新 query稳了。再补一个实用技巧把常用操作绑到chrome.commands快捷键比如 CtrlShiftD 一键去重比点图标快。manifest 里声明commandsservice worker 里监听chrome.commands.onCommand分发即可。做插件这事别一上来追求功能全先把「查询—操作—事件刷新」这条链路跑顺再往上叠分组、去重、快捷键。我自己现在用的就是这套骨架从最初只能列标签到现在自动分组加去重提示迭代了七八个版本每次都是先保证状态同步不出错再加新功能。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询