你能用插件做什么
丰富的 Chrome 插件能显著提升效率,比如广告屏蔽、长截图、网页收藏等。大多数插件的核心原理可以概括为一句话:
把 JavaScript 注入到网页里,对页面进行处理(改样式、加 UI、提取信息、自动化操作等),再配合插件自身的 UI 与后台能力,完成闭环。
插件由哪些部分组成
Chrome 插件通常由以下几部分组成(除 manifest.json 外其余都可选,按需求取用):
manifest.json:插件的"入口与权限说明",包含名称、版本、图标、页面入口、脚本声明、权限等- Service Worker(后台脚本):可以调用更多扩展 API,适合做消息中转、统一网络请求、任务调度、统一存储等(MV3 下不常驻)
- 功能页面(扩展自己的页面):
- Popup:点击工具栏图标弹出的页面
- Options:插件配置页
- Content Script(内容脚本):注入到目标网页的脚本,可操作 DOM,但与网页自身脚本相互隔离,能调用的扩展 API 也有限
你可以把它们理解成 3 类"进程/页面":
csharp
用户点击
|
v
[Popup / Options] <--- 扩展自己的页面(有独立 DOM)
|
| 消息(runtime.sendMessage / tabs.sendMessage)
v
[Service Worker] <--- 后台中枢(事件驱动,非长驻)
|
| 注入 / 消息
v
[Content Script] <--- 运行在目标网页(能改 DOM)
运行环境差异
如果你只记住两点,会少走很多弯路:
-
Popup/Options 不能直接操作目标网页 DOM 它们是扩展自己的页面,跟目标网页是两套 DOM。
-
真正"改网页"的代码应该放在 Content Script Content Script 运行在目标网页里,天然适合做 DOM 操作与网页增强。
因此常见模式是:
- Popup 负责 UI 与触发动作
- Content Script 负责对当前页面做实际处理
- Service Worker 负责更"系统级"的能力(统一请求、存储、调度、跨页面协作)
manifest.json:重点字段与读法
manifest.json 相当于插件的"声明式配置"。入门阶段重点关注这些字段:
manifest_version:现在都用 3name / version / description:基本信息action:工具栏图标与 popuppermissions:扩展 API 权限(例如storage、scripting等)host_permissions:网站访问范围(尽量最小化)content_scripts:声明式注入内容脚本background.service_worker:后台入口options_page:配置页入口
一个"结构示意"式的 MV3 manifest 片段如下:
json
{
"manifest_version": 3,
"name": "Your Extension",
"version": "1.0.0",
"action": { "default_popup": "popup.html" },
"background": {
"service_worker": "sw.js",
"type": "module"
},
"options_page": "options.html",
"permissions": ["storage", "scripting", "alarms"],
"host_permissions": ["*://example.com/*"],
"content_scripts": [
{
"matches": ["*://example.com/*"],
"run_at": "document_idle",
"js": ["content.js"],
"css": ["style.css"]
}
],
"web_accessible_resources": [
{
"resources": ["logo.png", "inject.html"],
"matches": ["*://example.com/*"]
}
]
}
(清单字段、示例与约束):developer.chrome.com/docs/extens...
Content Script 注入:声明式 vs 程序式
内容脚本的注入常见有两种方式,可以组合使用:
1)声明式注入(manifest 里写 content_scripts)
特点:
- 优点:稳定,浏览器按规则自动注入
- 缺点:如果用户在安装插件前已经打开了一堆网页,这些已打开页面通常不会自动注入,需要刷新或重新打开页面才生效
关键字段含义:
matches:哪些 URL 才注入run_at:注入时机(常用document_idle,减少对页面加载的影响)
2)程序式注入(用户触发时动态注入)
特点:
- 优点:不用要求用户刷新页面;可以"按需注入",权限更可控
- 缺点:需要处理"重复注入"与"注入时机"问题;MV3 推荐用
chrome.scripting.executeScript
需要注意的坑:
- 动态注入如果每次点击都注一次,可能会导致重复执行带来的副作用(例如重复绑定事件、重复插入 UI)
- 可以在 content script 的作用域内做"是否已初始化"的标记,避免重复初始化(标记建议挂在页面侧或脚本侧可控位置,避免污染页面逻辑)
三者如何通信
插件开发的"工程感"大多来自通信:Popup、Content Script、Service Worker 之间需要消息传递。
你可以把消息理解为前端里的"事件总线 + RPC"的简化版:
- Popup → Content Script:对当前页面发指令
- Content Script → Service Worker:请求后台能力(存储、网络、跨页面协作)
- Service Worker → Popup:推送状态或响应结果(必要时)
消息的形态建议遵循两个原则:
- 有明确的
type(最好是namespace:action形式,避免冲突) - 有明确的请求/响应结构(避免用"随便塞一个对象"的方式扩展到后期无法维护)
示意(仅展示结构,不作为完整例子):
js
// request
{ type: 'devtools:requestList', payload: { ... } }
// response
{ ok: true, data: { ... } }
权限与安全
权限是插件的"安全边界",也是上架审核与用户信任的关键。
建议:
- 能不用
"<all_urls>"就不用,优先指定域名或路径 - 能用
activeTab的场景不要扩大 host 权限 - 不要记录或上报敏感信息(URL query、cookie、token、响应体默认都应视为敏感)
- 权限变化要在文档/更新日志里说清楚"为什么需要"
常见权限分工理解:
permissions:你要调用哪些扩展 API(比如storage、scripting)host_permissions:你要访问哪些站点(尽量最小化) (permissions / host_permissions / optional_*):developer.chrome.com/docs/extens...
调试指南:你应该打开哪一个 DevTools
"我看不到 console"是入门阶段最常见的问题。按运行环境分别打开:
- Popup:在弹窗里右键 → 检查(Inspect)
- Content Script:在目标网页按 F12 → Console/Sources
- Service Worker:
chrome://extensions/→ 找到扩展 → Service Worker 的 Inspect
定位顺序建议:
- 先确认 manifest 是否生效(扩展是否加载成功、权限是否正确)
- 再确认 content script 是否注入(在目标网页 Console 看是否有日志/断点)
- 再确认消息是否到达(发送/接收两端分别打印)
- 最后再看是否是权限/注入时机/重复执行导致的问题
常见坑
- MV3 后台不常驻:不要把关键状态只放内存,需要持久化用
chrome.storage - 已打开页面没注入:声明式注入下,安装/更新后往往要刷新页面才生效
- 重复注入/重复初始化:动态注入时要做好"只初始化一次"的机制
- Content Script 与页面脚本隔离:访问不到网页脚本里定义的变量/函数(别用"我在页面 console 能跑"为标准)
- 网络请求同源/权限问题:需要弄清请求发生在 content script 还是后台,以及对应的权限策略
实战演练:写一个"网页背景色修改器"
光说不练假把式。我们来写一个最小闭环的 MV3 插件:Focus Mode 。 功能:点击插件图标,弹窗选择背景色,点击按钮将当前网页背景变色,并记住这个颜色。
1. 目录结构
创建一个文件夹 my-focus-extension,放入以下 4 个文件:
text
my-focus-extension/
├── manifest.json
├── popup.html
├── popup.js
└── content.js
2. manifest.json (核心配置)
json
{
"manifest_version": 3,
"name": "Focus Mode",
"version": "1.0",
"description": "Change page background color",
"action": {
"default_popup": "popup.html"
},
"permissions": ["storage", "activeTab"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}
注意:这里为了演示方便用了 <all_urls>,实际发布时请尽量缩小范围。
3. popup.html (界面)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
width: 200px;
padding: 10px;
font-family: sans-serif;
}
button {
width: 100%;
margin-top: 10px;
padding: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<h3>选择背景色</h3>
<!-- 颜色选择器 -->
<input type="color" id="colorPicker" value="#f0f0f0" style="width: 100%;">
<button id="btnApply">应用背景色</button>
<script src="popup.js"></script>
</body>
</html>
4. popup.js
js
document.addEventListener('DOMContentLoaded', async () => {
const btn = document.getElementById('btnApply')
const picker = document.getElementById('colorPicker')
// 1. 回显上次保存的颜色 (Storage API)
// MV3 支持 Promise,不需要回调地狱
const data = await chrome.storage.sync.get('focusColor')
if (data.focusColor) {
picker.value = data.focusColor
}
btn.addEventListener('click', async () => {
const color = picker.value
// 2. 保存颜色
await chrome.storage.sync.set({ focusColor: color })
// 3. 获取当前标签页
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
// 4. 发送消息给 Content Script
if (tab.id) {
// 这里的 type 建议加上命名空间防止冲突
chrome.tabs.sendMessage(tab.id, {
type: 'FOCUS_MODE:CHANGE_COLOR',
color
})
}
})
})
5. content.js (网页操作)
js
// 监听来自 Popup 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'FOCUS_MODE:CHANGE_COLOR') {
document.body.style.backgroundColor = request.color
console.log('背景色已修改为:', request.color)
// 可选:给发送方回执
sendResponse({ status: 'done' })
}
})
总结
入门 Chrome 插件,不要先写功能,而是先建立结构:
- 你有哪些运行环境(Popup/Content/Service Worker)
- 它们分别能做什么、不能做什么
- 你的代码是如何注入网页的(声明式/程序式)
- 你的数据与指令如何在它们之间流动(消息)
- 你的权限如何做到"够用但克制"
有了这套骨架,再回头做任何具体功能,都会更顺畅也更好维护。