Chrome 插件开发
概述
Chrome 插件(Extension)是一种运行在 Chrome 浏览器中的小型软件程序,用于扩展浏览器功能、增强用户体验或与 Web 页面进行深度交互。截至 2024 年,Chrome Web Store 拥有超过 18 万个插件,覆盖了从开发工具、生产力助手到安全防护等各个领域。Chrome 插件的开发基于 Web 技术栈——HTML、CSS 和 JavaScript,这使得前端开发者能够以较低的学习成本进入插件开发领域。
插件架构概览
一个典型的 Chrome 插件由以下核心组件构成:
- Manifest 文件:插件的配置文件,声明插件的元数据、权限、组件入口等。
- Background Service Worker:后台运行的脚本,处理事件和生命周期逻辑。
- Content Script:注入到 Web 页面中执行的脚本,用于操作页面 DOM 或与页面交互。
- Popup / Side Panel / Options:插件提供的用户界面组件。
这些组件之间通过消息通信机制进行数据交换和协调工作。
Manifest V3
Manifest V3(简称 MV3)是 Chrome 插件的最新规范版本,于 2022 年底逐步推广,并在 2023 年成为 Chrome 插件开发的默认标准。MV3 在安全性、隐私性和性能方面进行了重大改进。
manifest.json 配置
MV3 的入口配置文件是 manifest.json,放置于插件根目录。以下是一个基本的配置示例:
{
"manifest_version": 3,
"name": "我的插件",
"version": "1.0.0",
"description": "一个功能强大的 Chrome 插件",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"action": {
"default_popup": "popup.html",
"default_icon": "icons/icon48.png",
"default_title": "我的插件"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"https://*/*",
"http://*/*"
],
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["content-script.js"],
"css": ["content-styles.css"],
"run_at": "document_idle"
}
],
"options_page": "options.html",
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+Y"
}
}
}
}权限模型
MV3 对权限模型进行了重大重构,引入了更精细的权限控制机制:
Permissions(安装时权限):在安装时向用户展示的权限声明,影响用户体验的通过率。常见的权限包括 storage(存储访问)、activeTab(当前标签页的临时权限)、scripting(脚本注入)、alarms(定时器)、notifications(通知)等。
Host Permissions(主机权限):在 MV3 中,主机权限从 permissions 字段中分离出来,成为独立的 host_permissions 字段。用户在安装时会看到插件需要访问哪些网站域名的提示。这一改动增加了透明度,让用户更清楚地了解插件的数据访问范围。
Optional Permissions(可选权限):插件可以在运行时通过 chrome.permissions.request API 向用户请求额外的权限,而不必在安装时就要求全部权限。这种方式降低了首次安装的门槛,提升了转化率。
Background Service Worker
MV3 最显著的变化之一是用 Service Worker 替代了 MV2 的 Background Page:
生命周期管理:Service Worker 在空闲时会被浏览器卸载,在需要时重新启动。这意味着开发者不能依赖全局变量来持久化状态。所有重要数据必须通过 storage API 或 IndexedDB 进行持久化。
模块化支持:MV3 的 Service Worker 支持 ES Module,可以使用 import / export 语法组织代码,这与现代前端开发实践保持一致。
事件驱动模型:Service Worker 基于事件驱动,只在需要处理事件时被唤醒。常见的监听事件包括 chrome.runtime.onInstalled、chrome.runtime.onMessage、chrome.alarms.onAlarm 等。
异步 API 支持:MV3 全面拥抱 Promise,大多数 Chrome API 都支持 Promise 调用方式,使得异步代码更易于编写和维护。
// MV3 Service Worker 示例
chrome.runtime.onInstalled.addListener(async () => {
const { version } = chrome.runtime.getManifest();
await chrome.storage.sync.set({ installedVersion: version });
console.log(`插件版本 ${version} 已安装`);
});
chrome.alarms.create('dailyTask', { periodInMinutes: 1440 });Host Permissions
Host Permissions 定义了插件可以访问的网站范围。在 MV3 中,主机权限的声明更为严格:
- 使用匹配模式声明,例如
<all_urls>、https://*/*、https://example.com/* - Content Script 的
matches字段不受host_permissions限制,但运行时动态注入脚本需要主机权限 activeTab权限提供了一种临时访问当前标签页的方式,用户交互时自动获得该标签页的权限
MV3 新特性
- Service Worker:替代 Background Page,更节省内存
chrome.scriptingAPI:替代旧的tabs.executeScript方法,功能更丰富actionAPI:替代browser_action和page_action,统一了工具栏按钮的 API- Net Request:
webRequestAPI 被declarativeNetRequest取代以实现请求拦截(更安全、性能更好) - 远程代码限制:禁止加载和执行远程托管的 JavaScript,所有代码必须打包在插件中
Manifest V2 与 V3 对比
| 对比维度 | Manifest V2 | Manifest V3 |
|---|---|---|
| 后台脚本 | Background Page(常驻) | Service Worker(按需唤醒) |
| 权限模型 | 统一 permissions | 分离 permissions + host_permissions |
| 脚本注入 | tabs.executeScript | scripting.executeScript |
| 工具栏 API | browser_action / page_action | action(统一 API) |
| 请求拦截 | webRequest(阻塞式) | declarativeNetRequest(声明式) |
| 远程代码 | 允许加载远程 JS | 禁止远程代码执行 |
| Promise 支持 | 部分支持 | 全面支持 |
| 模块化 | 不支持 ES Module | 支持 ES Module |
| 内存占用 | 较高(后台页面常驻) | 较低(按需启动) |
| 安全性 | 较低(远程代码 / 阻塞式网络请求) | 较高(声明式规则 / 无远程代码) |
Content Script
Content Script 是注入到 Web 页面中运行的 JavaScript 文件,可以读取和修改页面的 DOM,但与页面脚本运行在隔离的执行环境中。
注入时机
通过 content_scripts 字段声明的 Content Script 会在浏览器加载匹配页面时自动注入。注入时机的控制通过 run_at 字段实现:
document_start:在页面加载 CSS 之后、DOM 构建之前注入,适合需要尽早执行的操作(如添加页面级样式)document_end:在 DOM 加载完成但资源(图片、iframe)可能仍在加载时注入document_idle(默认):在 DOM 加载完成后、页面渲染空闲时注入,推荐时机,不影响页面加载性能
匹配模式
Content Script 通过 matches 字段声明需要注入的页面 URL 模式,语法与 Host Permissions 一致:
{
"content_scripts": [{
"matches": [
"https://developer.chrome.com/*",
"https://docs.github.com/*"
],
"exclude_matches": ["https://developer.chrome.com/docs/*"],
"js": ["content.js"],
"css": ["styles.css"]
}]
}exclude_matches 用于排除特定子路径,避免不必要的注入。
隔离环境
Content Script 运行在所谓的"隔离世界"(Isolated World)中:
- Content Script 中的 JavaScript 变量不会与页面脚本冲突
- 页面脚本也无法直接访问 Content Script 中定义的变量
- Content Script 可以访问部分 Chrome API(如
runtime、storage等),但无法访问页面脚本中定义的变量和函数 - 两个世界共享同一个 DOM,因此看到的 DOM 结构是一致的
这种隔离机制既保证了安全性,也避免了命名冲突。
与页面通信
Content Script 与页面脚本的通信有以下几种方式:
通过 DOM 事件通信:Content Script 可以在 DOM 上触发自定义事件,页面脚本通过监听这些事件来获取数据。反之亦然。
// Content Script 向页面发送数据
window.dispatchEvent(new CustomEvent('EXTENSION_DATA', { detail: { key: 'value' } }));
// 页面脚本接收
window.addEventListener('EXTENSION_DATA', (e) => {
console.log(e.detail);
});通过 window.postMessage 通信:虽然 Content Script 和页面脚本运行在不同环境,但它们共享同一个 window 对象。使用 postMessage 可以实现双向通信。需要注意的是,Content Script 发送消息时使用 window.postMessage,而在接收时也需要监听 window 上的 message 事件。
通过注入 <script> 标签通信:Content Script 可以动态创建 <script> 标签将其代码注入到页面主执行环境中,这种方式可以让注入的代码访问页面脚本的变量和函数。
操作 DOM
Content Script 可以像普通 JavaScript 一样操作 DOM:
- 使用
document.querySelector、document.createElement等标准 DOM API - 添加、修改或删除页面元素
- 监听页面事件(点击、滚动、输入等)
- 修改页面样式
需要注意的是,Content Script 获取的 DOM 元素与页面脚本看到的是同一个 DOM,但 JavaScript 对象引用是隔离的。
样式注入
Content Script 可以通过两种方式注入样式:
- 在
content_scripts配置中声明css字段,样式会自动注入 - 使用
chrome.scripting.insertCSS在运行时动态注入样式
// 动态注入样式
await chrome.scripting.insertCSS({
target: { tabId: tab.id },
css: 'body { border: 2px solid red !important; }'
});
// 移除注入的样式
await chrome.scripting.removeCSS({
target: { tabId: tab.id },
css: 'body { border: 2px solid red !important; }'
});动态注入
MV3 使用 chrome.scripting.executeScript 替代了旧的 tabs.executeScript 方法,支持更灵活的动态脚本注入:
// 注入函数
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (param) => {
console.log('收到参数:', param);
return document.title;
},
args: ['来自插件的消息']
});
// 注入文件
await chrome.scripting.executeScript({
target: { tabId: tab.id, allFrames: true },
files: ['inject.js']
});executeScript 支持注入函数(func)和文件(files)两种方式,并且可以指定是否在所有 iframe 中注入。
Popup
Popup 是用户点击浏览器工具栏上的插件图标时弹出的窗口,是插件与用户交互最直接的界面。
弹窗设计
Popup 本质上是一个 HTML 页面,可以包含 CSS 和 JavaScript。设计 Popup 时需要考虑以下因素:
尺寸限制:Popup 的默认宽度为 800px,高度为 600px,但具体尺寸会受浏览器窗口和屏幕大小的影响。建议设计响应式布局以适应不同尺寸。
简洁优先:Popup 的使用场景通常是快速操作,因此界面应当简洁明了,避免包含过多内容。
加载性能:Popup 在每次打开时都会重新加载,因此要注意减少资源加载时间,避免使用过大的图片或库文件。
生命周期
Popup 的生命周期与它的可见性直接相关:
- 打开时:Popup 的 HTML 文档被加载,执行初始化逻辑
- 失去焦点时:Popup 不会立即关闭,但用户点击其他区域时 Popup 会关闭
- 关闭时:Popup 文档被销毁,所有内存被释放
- 重新打开时:重新加载文档,因此状态不会保留(需要使用
chrome.storage持久化)
// Popup 中的初始化代码
document.addEventListener('DOMContentLoaded', async () => {
const { notes } = await chrome.storage.local.get('notes');
renderNotes(notes || []);
});UI 框架
Popup 可以使用任何前端 UI 框架开发,包括 React、Vue、Svelte 等。使用现代框架可以提升开发效率,但需要注意:
- Popup 的包体积要尽量小,加载更快
- 使用构建工具(Webpack、Vite 等)打包代码
- 避免使用过大的 UI 库,推荐使用轻量级组件
与 Background 通信
Popup 与 Background Service Worker 通过消息通信进行交互。典型的通信场景包括:
请求数据:Popup 向 Background 请求需要处理的数据。
触发操作:Popup 通知 Background 执行某些后台操作(如下载文件、管理标签页等)。
状态同步:Popup 打开时从 Background 获取最新状态,关闭时将变更持久化。
// Popup 向 Background 发送消息
chrome.runtime.sendMessage(
{ type: 'GET_TABS_INFO' },
(response) => {
console.log('收到响应:', response);
}
);
// Background 监听并响应
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_TABS_INFO') {
chrome.tabs.query({ currentWindow: true }, (tabs) => {
sendResponse(tabs);
});
return true; // 保持消息通道开放以支持异步响应
}
});消息传递
Popup 与页面或 Content Script 的消息传递遵循 Chrome 的消息通信模型,后文会有详细介绍。
与页面交互
Popup 本身无法直接访问页面的 DOM,如果需要与当前页面交互,必须通过以下方式:
- 向当前标签页的 Content Script 发送消息,由 Content Script 操作 DOM
- 使用
chrome.scripting.executeScript动态注入代码到页面中 - 通过
activeTab权限获取当前标签页的部分信息
// 在 Popup 中获取当前标签页的信息
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// 向当前标签页的 Content Script 发送消息
await chrome.tabs.sendMessage(tab.id, { action: 'highlightElement', selector: '#main' });消息通信架构
Chrome 插件的消息通信机制是其核心架构之一,不同的组件之间通过消息进行数据交换。
runtime.sendMessage
chrome.runtime.sendMessage 是最常用的消息发送方式,用于向插件的各个组件发送消息:
- Popup 发送给 Background Service Worker
- Content Script 发送给 Background Service Worker
- Options Page 发送给 Background Service Worker
// 发送消息
chrome.runtime.sendMessage({ greeting: '你好,Background!' }, (response) => {
console.log('收到回复:', response);
});
// 监听消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.greeting) {
sendResponse({ reply: '你好,Popup!' });
}
});tabs.sendMessage
chrome.tabs.sendMessage 用于向指定标签页中的 Content Script 发送消息:
// 向特定标签页的 Content Script 发送消息
chrome.tabs.sendMessage(tabId, { action: 'getPageInfo' }, (response) => {
console.log('页面信息:', response);
});connect / 端口通信
对于需要多次交互的场景,使用端口通信(long-lived connection)比重复调用 sendMessage 更加高效:
// Content Script 发起长连接
const port = chrome.runtime.connect({ name: 'content-background' });
port.postMessage({ action: 'startTracking' });
port.onMessage.addListener((msg) => {
console.log('收到消息:', msg);
});
// Background 接收长连接
chrome.runtime.onConnect.addListener((port) => {
if (port.name === 'content-background') {
port.onMessage.addListener((msg) => {
if (msg.action === 'startTracking') {
port.postMessage({ status: 'tracking started' });
}
});
}
});长连接
长连接(Long-lived Connection)适用于以下场景:
- 实时数据推送(如鼠标位置跟踪、页面状态变更)
- 需要维持状态的通信会话
- 批量消息传输(避免重复建立连接的开销)
长连接的端口在连接任何一方断开时会被自动关闭。在 MV3 中需要注意,Service Worker 可能在空闲时被终止,因此长连接需要被正确管理,避免因 Service Worker 休眠导致连接中断。
跨扩展通信
Chrome 支持不同插件之间的通信,通过 chrome.runtime.sendMessage 指定目标扩展的 ID:
// 向其他扩展发送消息
chrome.runtime.sendMessage(
'other-extension-id',
{ type: 'hello', data: {} },
(response) => {
console.log('其他扩展回复:', response);
}
);
// 接收跨扩展消息(需要在 manifest 中声明 external_connectable)
chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
if (sender.id === 'known-extension-id') {
sendResponse({ status: 'ok' });
}
});跨扩展通信需要在 manifest.json 中声明 external_connectable 字段,明确允许哪些扩展可以与当前插件通信。
Native Messaging
Native Messaging 允许 Chrome 插件与操作系统的原生应用程序进行通信。这对于需要调用系统级 API、访问本地硬件或与桌面应用集成的场景非常有用。
{
"permissions": ["nativeMessaging"]
}const port = chrome.runtime.connectNative('com.myapp.native');
port.postMessage({ text: '来自插件的数据' });
port.onMessage.addListener((msg) => {
console.log('原生应用返回:', msg);
});Native Messaging 需要在操作系统中注册 Native Messaging Host 的配置文件,指定可执行文件的路径和允许的扩展 ID。
Options / Side Panel
设置页面
插件的设置页面通过 options_page 或 options_ui 字段声明:
{
"options_page": "options.html",
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
}open_in_tab 设置为 true 时,设置页面在独立标签页中打开;设置为 false 时在嵌入式界面中打开。
设置页面的设计应当遵循以下原则:
- 分类清晰:将设置项按功能分组
- 默认合理:提供合理的默认值
- 实时生效:设置变更后即时生效,无需重启插件
- 提示充分:对每个设置项提供清晰的说明文字
侧边栏
Side Panel 是 Chrome 插件的新界面形式,位于浏览器窗口的侧边。通过声明 side_panel 字段启用:
{
"side_panel": {
"default_path": "sidepanel.html"
},
"permissions": ["sidePanel"]
}// 在当前窗口打开侧边栏
await chrome.sidePanel.open({ windowId: currentWindowId });
// 设置侧边栏在不同网站的显示行为
chrome.sidePanel.setOptions({
path: 'sidepanel-specific.html',
enabled: true
});Side Panel 适合需要长时间驻留的辅助功能,如笔记工具、AI 助手、调试工具等。
存储 API
Chrome 提供了多种存储方式用于插件数据的持久化:
chrome.storage.sync:数据会通过 Chrome 账户同步到用户的登录设备。适合存储用户设置、偏好等数据。同步存储的容量限制为 102,400 字节(约 100KB),单个项目最大 8,192 字节。
chrome.storage.local:数据仅存储在本地设备,不同步。容量限制为 10MB(大多数浏览器),适合存储较大的数据,如笔记内容、截图缓存、历史记录等。
chrome.storage.session:会话级别的存储,数据在浏览器关闭时清除,容量限制为 10MB(MV3)。适合存储临时数据,如当前会话的状态信息、缓存的计算结果等。
chrome.storage.managed:企业管理员可以通过策略配置文件为插件设置受管理的存储数据。存储的数据对插件来说是只读的,用户无法修改。
// 存储数据
await chrome.storage.local.set({ notes: ['笔记1', '笔记2'] });
await chrome.storage.sync.set({ theme: 'dark', fontSize: 14 });
// 读取数据
const { notes } = await chrome.storage.local.get('notes');
const result = await chrome.storage.sync.get(['theme', 'fontSize']);
// 监听存储变更
chrome.storage.onChanged.addListener((changes, areaName) => {
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(`存储区 ${areaName} 中的 ${key} 已变更:`, oldValue, '->', newValue);
}
});
// 存储空间管理
const { bytesInUse } = await chrome.storage.local.getBytesInUse(null);
console.log(`已使用 ${bytesInUse} 字节`);sync / local / session 存储对比
| 特性 | sync | local | session |
|---|---|---|---|
| 数据持久化 | 是(跨设备同步) | 是(仅本地) | 否(会话级) |
| 默认容量 | 102,400 字节 | 10MB | 10MB |
| 单个项上限 | 8,192 字节 | 无严格限制 | 无严格限制 |
| 写入频率限制 | 120次/分钟 | 无限制 | 无限制 |
| 适用场景 | 用户设置、偏好 | 大数据量、离线数据 | 临时状态、缓存 |
配置持久化
实现配置持久化的推荐实践:
- 默认配置:在代码中维护一份默认配置对象
- 初始化检查:在插件安装或更新时初始化默认配置
- 读取配置:使用
storageAPI 异步读取配置,合并默认值 - 保存配置:用户修改配置后立即保存到存储中
- 监听变更:通过
storage.onChanged事件响应配置变更
const DEFAULT_CONFIG = {
theme: 'light',
fontSize: 14,
autoSave: true,
maxNotes: 100
};
// 初始化配置
chrome.runtime.onInstalled.addListener(async () => {
await chrome.storage.sync.set(DEFAULT_CONFIG);
});
// 读取配置并与默认值合并
async function getConfig() {
const config = await chrome.storage.sync.get(null);
return { ...DEFAULT_CONFIG, ...config };
}Chrome API 实战
书签管理
chrome.bookmarks API 提供了对 Chrome 书签的增删改查功能:
// 获取所有书签
const tree = await chrome.bookmarks.getTree();
// 创建书签
await chrome.bookmarks.create({
parentId: '1',
title: '开发者手册',
url: 'https://developer.chrome.com/'
});
// 搜索书签
const results = await chrome.bookmarks.search('Chrome');历史记录
chrome.history API 允许插件访问和操作浏览历史:
// 搜索历史
const items = await chrome.history.search({
text: 'Chrome',
maxResults: 10,
startTime: Date.now() - 7 * 24 * 60 * 60 * 1000 // 最近一周
});
// 删除指定 URL 的历史记录
await chrome.history.deleteUrl({ url: 'https://example.com/' });
// 删除所有历史记录
await chrome.history.deleteAll();下载管理
chrome.downloads API 提供了文件下载的完整控制能力:
// 创建下载
const downloadId = await chrome.downloads.download({
url: 'https://example.com/file.zip',
filename: 'downloads/file.zip',
saveAs: true
});
// 监听下载事件
chrome.downloads.onCreated.addListener((downloadItem) => {
console.log('下载开始:', downloadItem.id);
});
chrome.downloads.onChanged.addListener((delta) => {
if (delta.state?.current === 'complete') {
console.log('下载完成');
}
});
// 搜索下载记录
const downloads = await chrome.downloads.search({
query: ['报告'],
limit: 10
});通知
chrome.notifications API 用于显示系统级通知:
// 创建通知
chrome.notifications.create('reminder', {
type: 'basic',
iconUrl: 'icons/icon128.png',
title: '提醒',
message: '该休息一下了!',
buttons: [{ title: '好的' }],
priority: 2
});
// 监听通知点击
chrome.notifications.onClicked.addListener((notificationId) => {
if (notificationId === 'reminder') {
chrome.tabs.create({ url: 'https://example.com' });
}
});
// 清除通知
chrome.notifications.clear('reminder');上下文菜单
chrome.contextMenus API 允许在右键菜单中添加自定义选项:
{
"permissions": ["contextMenus"]
}// 创建右键菜单
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'searchWithPlugin',
title: '使用插件搜索"%s"',
contexts: ['selection']
});
chrome.contextMenus.create({
id: 'imageTools',
title: '图片工具',
contexts: ['image'],
children: [
{ id: 'downloadImage', title: '下载此图片', parentId: 'imageTools' }
]
});
});
// 处理菜单点击
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'searchWithPlugin') {
console.log('选中的文本:', info.selectionText);
}
});标签页管理
chrome.tabs API 是使用最频繁的 API 之一,提供了对浏览器标签页的全面控制:
// 创建新标签页
const tab = await chrome.tabs.create({
url: 'https://example.com',
active: true,
index: 0
});
// 查询标签页
const [currentTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const tabs = await chrome.tabs.query({ url: 'https://developer.chrome.com/*' });
// 更新标签页
await chrome.tabs.update(tabId, { url: 'https://new-url.com', highlighted: true });
// 关闭标签页
await chrome.tabs.remove([tabId1, tabId2]);
// 移动标签页到新窗口
await chrome.tabs.move(tabIds, { windowId: targetWindowId, index: -1 });
// 监听标签页事件
chrome.tabs.onActivated.addListener((activeInfo) => {});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
// 页面加载完成
}
});
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {});Omnibox
chrome.omnibox API 允许插件在地址栏中注册自定义关键词,提供搜索建议:
{
"omnibox": {
"keyword": "myext"
}
}// 用户输入关键词后的处理
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
suggest([
{ content: `search:${text}`, description: `搜索 ${text}` },
{ content: `open:${text}`, description: `打开 ${text}` }
]);
});
// 用户确认选择
chrome.omnibox.onInputEntered.addListener((text) => {
if (text.startsWith('search:')) {
const query = text.replace('search:', '');
chrome.tabs.create({ url: `https://www.google.com/search?q=${query}` });
}
});Commands 快捷键
通过 commands 字段和 chrome.commands API 可以注册键盘快捷键:
{
"commands": {
"toggle-feature": {
"suggested_key": {
"default": "Alt+Shift+F"
},
"description": "切换功能"
},
"take-screenshot": {
"suggested_key": {
"default": "Ctrl+Shift+S",
"mac": "Command+Shift+S"
},
"description": "截图"
}
}
}chrome.commands.onCommand.addListener((command) => {
if (command === 'take-screenshot') {
// 执行截图操作
}
});插件安全
Chrome 插件的安全性直接关系到用户的隐私和数据安全,开发过程中必须将安全作为首要考量。
CSP
Content Security Policy(CSP)限制了插件可以执行和加载的资源来源:
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self';"
}
}MV3 的 CSP 默认禁止以下行为:
- 使用
eval()和相关函数执行任意字符串代码 - 加载远程 JavaScript 文件
- 使用内联脚本(所有脚本必须通过外部文件加载)
CSP 的这些限制有效防止了 XSS 攻击和代码注入。
数据隔离
不同的插件运行在独立的进程中,各自的数据和存储相互隔离:
- 每个插件拥有独立的
chrome.storage命名空间 - Content Script 运行在隔离世界中
- 不同插件的 Service Worker 运行在不同的进程中
- 插件的 IndexedDB、LocalStorage 与页面的存储完全隔离
权限最小化
遵循最小权限原则是插件安全开发的核心实践:
权限审查清单:
- 是否真的需要
<all_urls>权限?限制到具体域名更安全 - 是否真的需要
activeTab权限?可以只在需要时请求 - 是否可以使用可选权限替代安装时权限?
- 是否可以使用
declarativeNetRequest替代webRequest权限?
{
"host_permissions": [
"https://api.example.com/*"
],
"permissions": [
"storage",
"activeTab",
"scripting"
]
}输入校验
所有从外部来源接收的数据都必须进行严格的校验:
// 对通过消息传递的数据进行校验
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// 验证来源
if (!sender.id || sender.id !== chrome.runtime.id) {
return;
}
// 验证数据类型
if (typeof message?.action !== 'string') {
sendResponse({ error: '无效的操作类型' });
return;
}
// 验证数据内容
const allowedActions = ['create', 'update', 'delete'];
if (!allowedActions.includes(message.action)) {
sendResponse({ error: '不允许的操作' });
return;
}
// 验证数据格式
if (message.action === 'create' && typeof message.data !== 'object') {
sendResponse({ error: '无效的数据格式' });
return;
}
sendResponse({ success: true });
});敏感信息保护
插件在处理用户敏感信息时,需要采取额外的保护措施:
- 不要在代码中硬编码密钥或令牌:使用远程 API 或在插件打包时通过构建工具注入
- 避免将敏感数据暴露给 Content Script:Content Script 与页面共享 DOM,可能被页面脚本窃取
- 使用
chrome.storage而非localStorage:chrome.storage的数据不会被页面脚本访问到 - 谨慎使用 Native Messaging:验证原生应用的身份,确保通信安全
- HTTPS 传输:所有网络通信必须使用 HTTPS
审核要求
Chrome Web Store 对插件提交有严格的审核标准:
代码审核:Google 会对提交的插件进行自动和人工代码审核,检查是否存在恶意行为、隐私泄露风险等。
权限审核:插件声明的权限必须与功能相匹配,过度申请权限的插件会被拒绝。
隐私政策:收集用户数据的插件必须提供隐私政策链接,明确说明数据收集的范围、用途和保存期限。
功能完整性:插件必须具有完整的功能,不能仅仅是占位页面或测试版本。
更新审核:每次提交更新都会重新进入审核流程,重大变更可能触发更严格的审核。
合规性检查:插件必须遵守 Chrome Web Store 开发者协议和相关法律法规。
总结
Chrome 插件开发是一个涵盖面广泛的领域,从 Manifest 配置到多组件协作,从消息通信到安全防护,每个环节都需要深入理解。Manifest V3 带来的架构变化——Service Worker、声明式网络请求、更严格的权限模型——标志着 Chrome 插件平台向更高安全性、更优性能和更好用户体验的方向发展。无论是开发简单的工具类插件还是复杂的企业级插件,掌握本教程中涵盖的核心概念和最佳实践,都将为插件开发之路奠定坚实基础。