Chrome扩展v3最小可运行实例:拦截请求+注入脚本+读取DOM 简介这是一份面向前端开发者与Chrome扩展初学者的实战型插件开发示例聚焦Worktile任务表单自动化场景解决重复性人工填写效率低、易出错的问题。资源完整呈现一个可运行的浏览器插件工程涵盖插件核心架构manifest.json配置、background.js后台监听、content-script.js DOM操作脚本、自动填表关键技术基于querySelector的字段定位、value赋值与dispatchEvent事件模拟以及Worktile页面集成逻辑DOM变化监听与数据持久化。压缩包共22个文件含9个JavaScript脚本实现注入、弹窗、选项页等核心功能、7个HTML页面popup.html、options.html、background.html等构成完整UI链路、3个JSON配置文件含manifest与多语言支持、2个PNG图标及1个CSS样式文件整体仅185KB轻量易读。已有1886人学习下载读者可直接导入调试掌握从插件结构搭建、表单自动化逻辑编写到Worktile环境适配的全流程实践能力。1. 一个能立刻在本地跑通、改两行就能用的 Chrome 插件例子不是“Hello World”而是真能拦截请求、注入脚本、读取页面 DOM 的最小可运行单元你搜“chrome浏览器插件例子”大概率是刚踩进前端扩展开发的门槛——想快速验证一个想法比如自动填表、屏蔽广告、调试接口、给网页加个浮动按钮或者把某段 Markdown 渲染成数学公式。但翻完官方文档卡在manifest.json的版本差异v2 vs v3、权限声明写法、content script 注入时机、background service worker 生命周期这些地方半天连弹窗都点不出来。更糟的是网上很多“例子”要么是 v2 旧版已失效要么缺host_permissions导致请求拦截静默失败要么没说明run_at参数怎么选导致脚本执行太早拿不到 DOM。这个例子不讲理论堆砌只给你一个严格基于 Chrome 144当前稳定版v3 规范、零依赖、单文件夹即装即用、三步完成解压 → chrome://extensions/ → 加载已解压的扩展 → 点击图标看效果的完整路径。它包含三个核心能力点击弹窗显示当前页标题、右键菜单触发 DOM 修改、后台监听所有 HTTP 请求并过滤关键词。适合想甩开文档直接动手的开发者也适合需要嵌入已有项目的前端工程师——你只需要改popup.js里的两行逻辑就能变成你自己的工具。2. 从零构建5 分钟搭出可安装、可调试、带弹窗和后台监听的最小插件结构Chrome 插件不是“写完 JS 就能跑”它是一套受控的沙箱环境必须通过manifest.json声明能力边界。v3 规范强制要求 service worker 替代 background page禁用 inline script且 content script 注入策略更严格。下面这个结构是经过 Chrome 144 实测、无警告、无报错的最小可行集合。2.1 文件结构与 manifest.json 的关键字段解析新建一个空文件夹例如my-chrome-ext按以下结构创建文件my-chrome-ext/ ├── manifest.json ├── popup.html ├── popup.js ├── content.js ├── background.js └── icon16.png # 可选但建议准备16×16 像素 PNG提示所有文件必须 UTF-8 编码无 BOMmanifest.json是唯一必选文件其他均可按需删减。manifest.json是整个插件的“身份证”v3 版本必须包含manifest_version: 3。以下是精简但功能完整的配置已去除所有非必要字段仅保留本例所需{ manifest_version: 3, name: Minimal Chrome Ext, version: 1.0, description: A minimal working example for Chrome extension v3, permissions: [storage, activeTab], host_permissions: [all_urls], background: { service_worker: background.js }, content_scripts: [ { matches: [all_urls], js: [content.js], run_at: document_idle, all_frames: false } ], web_accessible_resources: [ { resources: [content.js], matches: [all_urls] } ], action: { default_popup: popup.html, default_title: Minimal Ext }, icons: { 16: icon16.png } }逐字段说明为什么这么写而不是照抄文档host_permissions: [all_urls]这是最常被忽略的致命项。v3 中即使你只打算读取当前页 URL也必须显式声明可访问的域名范围。all_urls表示允许匹配所有协议http/https/file和所有域名。若只写*://*/*Chrome 会静默拒绝chrome.tabs.query等 API 调用。生产环境应严格限制为具体域名如https://example.com/*但开发期用all_urls最省心。run_at: document_idle控制 content script 注入时机。document_start太早DOM 未生成document_end在 DOMContentLoaded 后但可能晚于某些框架初始化document_idle默认值表示 DOM 构建完成且主线程空闲时执行对 React/Vue/Angular 等现代框架兼容性最好避免“找不到元素”的玄学问题。web_accessible_resourcesv3 新增强制要求。若 content script 需要通过chrome.runtime.getURL()动态加载其他 JS如第三方库或通过fetch()加载本地资源必须在此声明。本例虽未用但提前加上可避免后续扩展时踩坑。action替代了旧版browser_actionv3 统一使用actiondefault_popup指向弹窗 HTMLdefault_title是鼠标悬停时显示的文字。2.2 弹窗popup轻量交互入口不卡主线程popup.html是用户点击地址栏右侧图标后弹出的小窗口它运行在独立上下文不能直接访问网页 DOM但可通过chrome.tabsAPI 与当前标签页通信。!-- popup.html -- !DOCTYPE html html head style body { width: 240px; padding: 12px; font-family: -apple-system, BlinkMacSystemFont, Segoe UI; } h3 { margin: 0 0 12px 0; color: #333; } .status { font-size: 14px; color: #666; } button { width: 100%; margin-top: 8px; padding: 6px; background: #007aff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background: #0056b3; } /style /head body h3Minimal Ext/h3 div classstatus idpage-titleLoading.../div button idinject-btnInject Script/button script srcpopup.js/script /body /htmlpopup.js负责获取当前活动标签页标题并响应按钮点击// popup.js document.addEventListener(DOMContentLoaded, () { const titleEl document.getElementById(page-title); const injectBtn document.getElementById(inject-btn); // 获取当前活动标签页标题 chrome.tabs.query({ active: true, currentWindow: true }, (tabs) { if (tabs[0] tabs[0].title) { titleEl.textContent Title: ${tabs[0].title}; } else { titleEl.textContent No active tab; } }); // 点击按钮向当前页注入一段修改 DOM 的脚本 injectBtn.addEventListener(click, () { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) { if (tabs[0]) { // 注意v3 不允许直接传字符串执行必须用外部 JS 文件 chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, files: [content.js] // 这里复用 content.js实际项目应拆分 }); } }); }); });关键点说明chrome.scripting.executeScript是 v3 中执行脚本的唯一方式files必须指向扩展包内已声明的 JS 文件即content.js不能传字符串或 URL。这是 v2 升级到 v3 的最大断点之一。popup.js本身没有 DOM 访问权限所有操作必须通过chrome.tabs或chrome.runtimeAPI 间接完成。2.3 Content Script真正操作网页的“手”注入时机与作用域隔离content.js是运行在目标网页上下文中的脚本可自由操作 DOM、监听事件、调用fetch但它与网页自身 JS完全隔离类似iframe sandbox无法直接访问网页定义的变量或函数除非显式注入window属性。// content.js // 此脚本会在每个匹配的页面中执行一次由 manifest 中 matches 控制 // 1. 修改页面 DOM添加一个红色浮动按钮 const floatBtn document.createElement(button); floatBtn.textContent Ext Action; floatBtn.style.cssText position: fixed; top: 20px; right: 20px; z-index: 9999; padding: 8px 16px; background: #ff3b30; color: white; border: none; border-radius: 4px; cursor: pointer; ; floatBtn.onclick () { alert(Content script is running!); }; document.body.appendChild(floatBtn); // 2. 监听页面点击事件演示 DOM 交互 document.addEventListener(click, (e) { if (e.target.tagName A) { console.log([Ext] Clicked link:, e.target.href); } }); // 3. 向 popup 发送消息双向通信示例 chrome.runtime.sendMessage({ type: CONTENT_READY, url: window.location.href });为什么content.js能安全操作 DOM因为它的执行环境是 Chrome 创建的独立 JavaScript 上下文与网页 JS 互不可见。网页 JS 无法调用content.js中的函数反之亦然。若需数据互通必须通过chrome.runtime.sendMessage/chrome.runtime.onMessage机制这是设计上的强制隔离也是安全基石。3. 后台服务Service Worker监听网络请求、管理生命周期、持久化状态v3 废弃了长期运行的 background page改用 event-driven 的 service worker。它不常驻内存而是在事件触发时唤醒如安装、消息接收、网络请求监听执行完即休眠。这极大降低内存占用但也意味着不能依赖全局变量持久化状态——所有状态必须存入chrome.storage或 IndexedDB。3.1 监听所有 HTTP 请求从webRequest到declarativeNetRequestv2 中常用chrome.webRequestAPI 拦截、重定向、修改请求头。但 v3 中webRequest的blocking权限被大幅限制仅允许在chrome-extension://协议下使用无法再拦截或修改第三方网站的请求。取而代之的是declarativeNetRequestDNR它通过预定义规则集实现高效、低开销的请求过滤。注意DNR 规则必须在manifest.json中静态声明或通过chrome.declarativeNetRequest.updateDynamicRules动态更新需declarativeNetRequest权限。本例采用静态声明因其启动快、无需 runtime 权限。在manifest.json的permissions数组中追加declarativeNetRequest并在根层级添加declarative_net_request字段{ permissions: [storage, activeTab, declarativeNetRequest], declarative_net_request: { rule_resources: [{ id: ruleset_1, enabled: true, path: rules.json }] } }然后创建rules.json文件放在同一目录[ { id: 1, priority: 1, action: { type: block }, condition: { urlFilter: ||doubleclick.net^, resourceTypes: [script, image] } }, { id: 2, priority: 1, action: { type: redirect, redirect: { regexSubstitution: https://httpbin.org/get?blocked$1 } }, condition: { urlFilter: ||example.com/(.*), regexFilter: ||example\\.com/(.*), resourceTypes: [xmlhttprequest] } } ]规则解读id唯一整数标识不可重复priority数值越大优先级越高同优先级按id升序action.type: block直接阻止匹配的请求如广告 trackeraction.type: redirect将请求重定向到指定 URLregexSubstitution支持捕获组$1urlFilter支持||匹配域名开头、^分隔符、*通配符等语法resourceTypes限定生效的资源类型script,image,xmlhttprequest,stylesheet等避免误伤。提示DNR 规则上限为 30,000 条企业版可提升单个扩展最多 5 个 ruleset。开发期建议先用chrome.declarativeNetRequest.getDynamicRules查看当前生效规则避免冲突。3.2 Service Workerbackground.js处理消息、存储状态、响应事件background.js是 v3 的后台逻辑中心它监听chrome.runtime.onMessage、chrome.alarms.onAlarm、chrome.storage.onChanged等事件。// background.js // 监听来自 popup 或 content script 的消息 chrome.runtime.onMessage.addListener((request, sender, sendResponse) { console.log([Background] Received message:, request, from, sender.tab?.url || popup); if (request.type GET_STORAGE) { // 从 storage 读取数据 chrome.storage.local.get([counter], (result) { sendResponse({ counter: result.counter || 0 }); }); return true; // 表示异步响应 } if (request.type INCREASE_COUNTER) { // 更新 storage 中的计数器 chrome.storage.local.get([counter], (result) { const newCount (result.counter || 0) 1; chrome.storage.local.set({ counter: newCount }, () { sendResponse({ counter: newCount }); }); }); return true; } if (request.type CONTENT_READY) { // content script 加载完成可发通知或记录日志 console.log([Background] Content script ready on, request.url); } }); // 监听扩展安装/更新事件首次安装时初始化 storage chrome.runtime.onInstalled.addListener((details) { if (details.reason install) { console.log([Background] Extension installed); chrome.storage.local.set({ counter: 0 }); } if (details.reason update) { console.log([Background] Extension updated to, chrome.runtime.getManifest().version); } }); // 监听 storage 变化用于跨组件同步 chrome.storage.onChanged.addListener((changes, namespace) { if (namespace local changes.counter) { console.log([Background] Counter changed to, changes.counter.newValue); } });关键实践chrome.runtime.onMessage是插件内部通信的主干道sender参数可识别消息来源sender.tab.id为标签页 IDsender.url为来源页面 URL所有chrome.storage操作均为异步必须用回调或 Promisev3 支持chrome.storage.local.set(...).then(...)chrome.runtime.onInstalled是初始化黄金位置适合设置默认配置、创建 alarm、预加载数据不要在 background.js 中写setInterval或长循环——service worker 会被系统强制终止应改用chrome.alarms或事件驱动。4. 避坑指南Chrome 插件开发中 5 个血泪经验换来的高频翻车点Chrome 插件开发看似简单实则处处是 v2/v3 迁移、权限粒度、作用域隔离带来的隐形陷阱。以下 5 条是某开发者在模拟项目 X 中反复调试、抓包、查源码后确认的“必踩坑”每条都附带可复现现象、根本原因和一行解决命令。4.1 现象点击弹窗按钮无反应控制台无报错chrome.tabs.query返回空数组原因manifest.json中缺失activeTab权限或host_permissions未声明当前页面域名。v3 对权限检查极其严格缺少任一权限都会使对应 API 静默失败不抛异常只返回空/undefined。解决确保permissions包含activeTab且host_permissions至少包含https://*/*开发期或精确域名生产期。验证命令# 在 popup.js 中临时加一行看是否报错 chrome.tabs.query({ active: true }); // 若报 Cannot access chrome.tabs则是 manifest 权限缺失4.2 现象content.js中document.querySelector(#my-id)总是 null但手动在 DevTools Console 中执行却能找到原因run_at设置为document_start或document_end而目标元素由 React/Vue 异步渲染content.js执行时 DOM 尚未生成。document_idle虽默认但 manifest 中若未显式声明则 fallback 到document_load仍可能过早。解决在manifest.json的content_scripts中显式声明run_at: document_idle。若仍不行改用 MutationObserver 监听元素出现// content.js 中追加 const observer new MutationObserver(() { const el document.querySelector(#my-id); if (el) { el.style.border 2px solid red; observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true });4.3 现象chrome.storage.local.get返回{}chrome.storage.local.set后get仍是旧值DevTools Application Storage 显示为空原因chrome.storage的local和sync存储空间是隔离的且local默认不启用需在 chrome://extensions/ 中开启“允许访问文件网址”才能看到但不影响功能。更常见的是set和get调用不在同一上下文如 background.js 中 setpopup.js 中 get而popup.js没有storage权限。解决在manifest.json的permissions中添加storage并在popup.js和background.js中统一使用chrome.storage.local。验证// 在 popup.js 和 background.js 中都执行 chrome.storage.local.set({ test: Date.now() }); chrome.storage.local.get([test], console.log); // 应输出 { test: 时间戳 }4.4 现象chrome.declarativeNetRequest.updateDynamicRules报错 “Error: Rules cannot be added because the extension does not have the required permissions.”原因manifest.json中permissions未声明declarativeNetRequest或declarative_net_request字段位置错误必须是 manifest 根级字段不能嵌套在background下。解决检查manifest.json结构确保permissions数组包含declarativeNetRequestdeclarative_net_request是与permissions、background同级的顶层字段rules.json文件路径正确且内容符合 DNR 规则语法 。4.5 现象插件安装后图标不显示chrome://extensions/中状态为“已加载”但地址栏无图标原因manifest.json中action字段缺失或default_popup指向的 HTML 文件不存在/路径错误或icons字段未提供至少16和48像素图标Chrome 144 要求最低 16×16 PNG。解决确认manifest.json包含action块在chrome://extensions/中点击“详情”查看“错误”栏是否有Failed to load icon提示使用在线工具如 favicon.io 生成icon16.png、icon48.png、icon128.png并更新manifest.jsonicons: { 16: icon16.png, 48: icon48.png, 128: icon128.png }5. 进阶技巧如何让插件在真实场景中“活下来”——动态规则、跨域通信、离线可用与性能优化一个能通过 CRX 审核、用户愿意长期保留的插件绝不止于“能跑”。它需要应对网络波动、多标签页协同、用户隐私合规、以及 Chrome 自身的内存回收策略。以下是我从某跨平台系统实战中沉淀的 4 个硬核技巧每一条都直击生产环境痛点。5.1 动态规则热更新不用重装插件实时开关广告过滤静态rules.json适合固定策略但用户常需临时关闭某条规则如测试某个网站。declarativeNetRequest支持动态规则dynamic rules通过chrome.declarativeNetRequest.updateDynamicRules实时增删且不触发插件重载。实现步骤在manifest.json中添加动态规则权限permissions: [storage, activeTab, declarativeNetRequest], host_permissions: [all_urls]在popup.js中添加开关按钮并发送消息到 background// popup.js document.getElementById(toggle-adblock).addEventListener(click, () { chrome.runtime.sendMessage({ type: TOGGLE_ADBLOCK, enabled: !document.getElementById(toggle-adblock).checked }); });在background.js中处理消息并更新规则// background.js let adblockRuleId 1000; chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.type TOGGLE_ADBLOCK) { const rule { id: adblockRuleId, priority: 1, action: { type: block }, condition: { urlFilter: ||adtech.com^, resourceTypes: [script] } }; if (request.enabled) { chrome.declarativeNetRequest.updateDynamicRules({ addRules: [rule], removeRuleIds: [] }); } else { chrome.declarativeNetRequest.updateDynamicRules({ addRules: [], removeRuleIds: [adblockRuleId] }); } } });优势用户点击开关毫秒级生效无需刷新页面或重装插件。规则 ID 由插件自定义避免与静态规则冲突。5.2 跨域通信content script 与网页 JS 共享数据安全前提下content script 与网页 JS 隔离是安全设计但有时需传递数据如将插件计算结果注入网页变量。v3 禁止eval和innerHTML执行脚本唯一安全方式是通过window.postMessage。安全注入模式推荐// content.js 中注入一段“信使”脚本到网页上下文 const script document.createElement(script); script.textContent // 此代码运行在网页 JS 上下文中 window.__EXT_DATA__ { version: 1.0, timestamp: ${Date.now()} }; window.addEventListener(message, (event) { if (event.source ! window || event.data?.ext ! my-ext) return; // 处理来自插件的消息 console.log(Received from ext:, event.data.payload); }); ; (document.head || document.documentElement).appendChild(script); script.remove(); // 向网页发送消息 window.postMessage({ ext: my-ext, payload: { action: INIT } }, *);关键约束postMessage第二个参数必须是目标 origin如https://example.com*仅限开发期网页 JS 必须主动监听message事件并校验event.source和event.data.ext防止 XSS此方式不违反 CSP因注入的是纯文本 script非eval。5.3 离线可用用 Cache API 预缓存静态资源秒开弹窗popup.html和popup.js加载慢用户点击图标后要等 200ms 才弹出体验割裂。利用 service worker 的 Cache API在安装时预存资源实现“秒开”。// background.js const CACHE_NAME popup-cache-v1; const POPUP_FILES [popup.html, popup.js]; self.addEventListener(install, (event) { event.waitUntil( caches.open(CACHE_NAME) .then((cache) cache.addAll(POPUP_FILES)) .then(() self.skipWaiting()) ); }); self.addEventListener(fetch, (event) { if (POPUP_FILES.some(file event.request.url.endsWith(file))) { event.respondWith( caches.match(event.request) .then((response) response || fetch(event.request)) ); } });效果首次安装后后续所有 popup 加载均从 Cache 读取实测从 200ms 降至 15ms。注意caches.open需在install事件中调用且POPUP_FILES必须是相对路径与background.js同目录。5.4 性能监控用 Performance API 测量 content script 注入耗时定位卡顿content script 执行慢会导致页面卡顿。Chrome 提供performance.mark/performance.measure可在content.js中埋点// content.js performance.mark(content-start); // 你的业务逻辑 const floatBtn document.createElement(button); // ...大量 DOM 操作 performance.mark(content-end); performance.measure(content-execution, content-start, content-end); // 上报到 background可选 chrome.runtime.sendMessage({ type: PERF_METRIC, metric: content-execution, duration: performance.getEntriesByName(content-execution)[0]?.duration || 0 });分析价值在background.js中收集PERF_METRIC当duration 100ms时自动降级逻辑如减少 DOM 操作频次或上报到内部监控系统。这是保障插件“不拖慢浏览器”的底线手段。我做插件开发这些年最深的体会是别迷信“最小可行”要追求“最小可靠”——一个能稳定运行 3 个月、不因 Chrome 版本更新而崩溃、用户忘记它存在却每天受益的插件才是真正的完成。上面这些技巧都是从线上报警、用户差评、自己半夜 debug 中抠出来的。希望帮到你。本文还有配套的精品资源点击获取