前言
本文专注于 Chrome 插件 Manifest V3(MV3)版本的开发指南,原因如下:
-
行业标准:
- Chrome 浏览器占据全球浏览器市场 65% 以上的份额(2024年统计)
- 2024年1月起,Chrome 应用商店已全面下架所有 Manifest V2 插件,仅接受 MV3 版本上架
-
学习聚焦:
- 本教程针对区块链钱包开发场景,插件作为载体只需掌握核心功能:
- 通信相关
- 存储相关
- 基础组件(content-script,service worker,popup等)
- 本教程针对区块链钱包开发场景,插件作为载体只需掌握核心功能:
-
延伸学习:
- 官方最新文档: Chrome Extensions Developer Guide
- 中文补充资料(注意时效性):
Chrome插件开发全攻略
注:该教程基于 MV2 编写,部分 API(如 background page)在 MV3 中已废弃
开发调试
Chrome插件没有严格的项目结构要求,只要保证本目录有一个manifest.json即可,也不需要专门的IDE,普通的web开发工具即可。
从右上角菜单->更多工具->扩展程序可以进入 插件管理页面,也可以直接在地址栏输入 chrome://extensions 访问。
勾选开发者模式即可以文件夹的形式直接加载插件,否则只能安装.crx格式的文件。Chrome要求插件必须从它的Chrome应用商店安装,其它任何网站下载的都无法直接安装,所以,其实我们可以把crx文件解压,然后通过开发者模式直接加载。
核心组件
manifest.json
每个插件都必须包含一个 manifest.json 文件,位于扩展根目录。以下是一个基本的 MV3 清单文件结构:
{
"manifest_version": 3, // 必须为3
"name": "扩展名称", // 显示名称(≤45字符)
"version": "1.0.0", // 版本号
"action": { // 浏览器工具栏按钮配置
"default_popup": "popup.html"
},
"background": { // 后台脚本(Service Worker)
"service_worker": "background.js"
},
"content_scripts": [{ // 注入页面的脚本
"matches": ["*://*.example.com/*"],
"js": ["content.js"]
}],
"permissions": [ // 所需API权限
"storage", "tabs"
],
"host_permissions": [ // 需要访问的网站
"https://api.example.com/"
]
}
content-scripts
content_scripts 是 Chrome 扩展中用于向网页注入 JS/CSS 的脚本,我们每新建一个网页,都会被注入一个或多个content_scripts脚本
{
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://example.com/*"], // 匹配的网址
"js": ["content1.js, content2.js"], // 注入的JS
"css": ["content.css"], // 注入的CSS
"run_at": "document_idle" // 注入时机,可选值:"document_start","document_end",or "document_idle",最后一个表示页面空闲时,默认document_idle```(document_start|document_end|document_idle)
}
]
}
大多数教程里都会说content-scripts和原始页面共享DOM,但是不共享JS,想要访问JS需要通过DOM 事件或注入 script 标签,这是不严谨的,在manifest.json文件中配置content_scripts可以指定world参数,如果不指定默认为ISOLATED,这种情况确实无法访问JS,因为content-scripts运行在和页面隔离的环境中。但是如果我们把参数设置为MAIN,意味着脚本会被注入到页面的主执行环境(main world),与页面原有的 JavaScript 共享同一个全局作用域(即 window 对象)。这意味着注入的脚本可以:
- 直接访问和修改页面的全局变量(如
window.pageVariable)。 - 调用页面定义的函数。
- 覆盖或扩展页面原有的 JS 逻辑
需要特别注意的是我们直接在manifest.json文件中配置参数world: 'MAIN'无法生效,这是Chrome的一个bug,只能通过chrome.scripting.registerContentScripts动态注入:
// 注册一个在 MAIN 环境中运行的内容脚本
const scriptConfig = {
id: "main-world-script",
js: ["main-world.js"], // 脚本文件需打包在扩展中
matches: ["https://example.com/*"],
world: "MAIN" // 关键参数
};
await chrome.scripting.registerContentScripts([scriptConfig]);
background(service worker)
插件的后台服务,理论上可以随着浏览器的打开关闭一直运行在后台,但在MV3中如果长时间无操作会被关闭,我们可以通过定时触发后台事件的方式使其一直运行,这在钱包开发中很重要,因为我们要确保后台不会停止运行。
{
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
popup
popup是点击插件图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互
{
"manifest_version": 3,
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
消息通信
网页,content-script,popup,background之间相互通信的关系图:
flowchart LR
subgraph 网页环境
A[原始网页] -->|window.postMessage| B[Content Script]
end
subgraph 扩展环境
B -->|chrome.runtime.sendMessage/connect| C[Background\nService Worker]
C -->|chrome.runtime.sendMessage| D[Popup]
D -->|chrome.runtime.sendMessage| C
C -->|chrome.tabs.sendMessage/connect| B
end
B -->|window.postMessage| A
style C fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style D fill:#bfb,stroke:#333
style A fill:#ffb,stroke:#333
实际的钱包开发中我们会对这些API做进一步的封装,详见后续系列教程。
存储
在 Chrome 扩展开发(Manifest V3)中,我们应当避免过度依赖全局变量,因为 Service Worker 的生命周期特性决定了它会在不活跃时被浏览器自动终止,此时所有全局变量都会丢失。为了确保数据的持久性和一致性,我们需要将关键变量进行持久化存储。Chrome 扩展提供了多种存储方案,以下是详细说明和实际应用建议:
为什么避免全局变量?
- Service Worker 特性:MV3 的后台脚本运行在 Service Worker 中,浏览器会在空闲时终止它(通常 30 秒无活动后)
- 数据丢失风险:全局变量在 Service Worker 重启后会被重置
- 状态不可靠:依赖内存状态的逻辑会失效(如计数器、临时缓存)
持久化存储方案
chrome.storage.local
用途:长期持久化存储(扩展卸载前一直存在)
特点:
- 异步操作(避免阻塞主线程)
- 默认配额 5MB(可通过
unlimitedStorage权限申请更多) - 数据以键值对存储,支持 所有 JSON 兼容类型
示例代码:
// 存储数据
await chrome.storage.local.set({
userSettings: { theme: "dark", locale: "zh-CN" }
});
// 读取数据
const { userSettings } = await chrome.storage.local.get("userSettings");
console.log(userSettings.theme); // "dark"
// 监听变化
chrome.storage.onChanged.addListener((changes) => {
if (changes.userSettings) {
console.log("设置已更新:", changes.userSettings.newValue);
}
});
适用场景:
- 用户配置项
- 需要跨扩展组件共享的数据
- 低频更新的缓存数据
chrome.storage.session
用途:内存级临时存储(仅在浏览器会话期间有效)
特点:
- 数据保存在内存中,浏览器关闭后清除
- 默认配额 10MB
- 读写速度比
local更快
示例代码:
// 存储会话数据
await chrome.storage.session.set({
tempToken: "abc123",
lastActiveTime: Date.now()
});
// 读取数据
const { tempToken } = await chrome.storage.session.get("tempToken");
适用场景:
- 敏感数据(如 OAuth token,避免长期存储)
- 高频更新的临时状态
- 需要快速读写的中间结果
IndexedDB
用途:结构化大数据存储(类似小型数据库)
特点:
- 支持事务、索引、复杂查询
- 存储量更大(通常 50% 磁盘空间)
示例代码:
// 打开/创建数据库
const db = await new Promise((resolve) => {
const request = indexedDB.open("WalletDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains("wallets")) {
db.createObjectStore("wallets", { keyPath: "id" });
}
};
request.onsuccess = (e) => resolve(e.target.result);
});
// 存储钱包数据
const tx = db.transaction("wallets", "readwrite");
tx.objectStore("wallets").put({
id: "1",
address: "0x123...",
balance: "100 ETH"
});
await new Promise((resolve) => tx.oncomplete = resolve);
在实际钱包开发中我们既需要chrome.storage也需要IndexedDB做备用,详见后续系列教程。
学习交流请添加vx: gh313061
下期预告:创建manifest文件