1、谷歌插件概述
一、简单介绍
谷歌插件是运行在Chrome浏览器中的小型软件程序,用于扩展和定制浏览器的功能。
它基于HTML、CSS和JavaScript技术开发,通过manifest.json配置文件定义插件的属性和权限。
- 主要功能
- 内容修改:可以修改网页内容,如添加按钮、移除广告、高亮文本等
- 浏览器操作:管理标签页、书签、历史记录,拦截网络请求
- 数据存储:在本地存储用户数据,实现个性化设置
- 跨域通信:与不同域名的网站进行数据交互
- 用户界面:在工具栏添加图标、创建弹出页面、右键菜单选项
- 后台任务:定时执行任务、处理事件、发送通知
- 典型应用场景
- 广告拦截器(如AdBlock)
- 密码管理器(如LastPass)
- 网页翻译工具
- 开发者工具增强
- 社交媒体增强插件
- 网页截图工具
二、项目目录结构
- manifest.json(必须)
- 插件的“配置文件”,定义了名称、版本、权限、需要加载哪些脚本等核心信息。它使用JSON格式明确告知Chrome插件的能力,例如需要申请哪些权限(如访问特定网站数据、使用存储等),以及指定内容脚本(content_scripts)注入到哪些网页(通过matches字段匹配)
- background.js (或 service-worker.js)
- 插件的“后台进程”,负责管理生命周期和监听浏览器事件(如标签页更新),即使弹出窗口关闭也能运行。在Manifest V3版本中,它被实现为一个Service Worker。特点是无界面、常驻事件驱动(不持续占用资源)。它常用来设置右键菜单(contextMenus)或管理跨标签页的数据
- content.js (名称可自定义)
- “内容脚本”,被注入到特定网页中,用于读取或修改该网页的DOM,实现与页面内容的交互。这些脚本运行在被访问网页的上下文中,可以读取和修改页面的HTML和CSS。但它们与页面自身的JavaScript是隔离的,不能直接访问页面中定义的变量或函数,双方需要通过特殊API进行通信
- popup.html (名称可自定义)
- 点击浏览器工具栏插件图标后弹出的界面UI,通常伴随有对应的popup.js和popup.css
- options.html
- 插件的“设置页面”,用户可以通过右键点击插件图标选择“选项”来打开此页面进行详细配置
三、和普通网页开发的区别
| 特性维度 | 普通网页开发 | 谷歌插件开发 |
|---|---|---|
| 运行环境与作用域 | 在浏览器标签页内运行,受同源策略严格限制,通常只能操作自身页面内容 | 运行于浏览器的扩展上下文,可跨域访问多个网站,权限范围更广 |
| 核心配置文件 | 通常没有强制要求的根配置文件。 | 必须包含 manifest.json 文件,用于声明插件元数据、权限和核心组件 |
| 核心能力与权限 | 能力有限,主要依赖标准Web API,无法直接调用浏览器底层功能 | 通过Chrome API可操作标签页、书签、网络请求等,需在manifest.json中声明权限 |
| 安全策略 | 相对宽松,可以执行内联JavaScript(如onclick属性)或使用eval()函数 | 受严格的内容安全策略 (CSP) 约束,默认禁止内联脚本和eval()等不安全操作,资源加载受限 |
| 生命周期与资源加载 | 页面打开时加载,关闭时销毁;资源通常从远程服务器加载 | 组件有独立生命周期(如Service Worker事件驱动、Popup随点击打开);代码和资源通常从本地打包文件加载 |
- 调试工具:两者都使用Chrome开发者工具,但调试入口不同。
- 插件后台Service Worker:在扩展管理页面点击对应插件的“查看视图”下的“Service Worker”链接进行调试。
- 插件弹出页(Popup):右键点击浏览器工具栏上的插件图标,选择“检查”来调试Popup界面。
- 插件内容脚本(Content Scripts):在目标网页的开发者工具中,切换到“Sources”页签,在“Content scripts”栏下找到并调试
2、Manifest V3特性
V2在2023年的时候已经停用。
| 特性类别 | Manifest V2 | Manifest V3 | 核心变化与影响 |
|---|---|---|---|
| 架构核心 | 后台页面 (Background Page) | 服务工作者 (Service Worker) | 从持久运行的页面变为事件驱动、可被浏览器终止的工作线程。 |
| 网络请求处理 | webRequestAPI (命令式) | declarativeNetRequestAPI (声明式) | 扩展声明规则,浏览器负责执行,无需读取请求内容,更隐私安全 |
| 代码执行 | 允许远程托管的代码 | 禁止远程代码 | 所有逻辑必须打包在扩展内,提高安全性和可审查性。 |
| API 风格 | 普遍依赖回调 (Callbacks) | 广泛支持 Promise | 支持现代异步编程模式,如 async/await,代码更简洁。 |
| 权限管理 | 权限混合声明 | 权限细分 | 主机权限 (host_permissions) 与 API 权限 (permissions) 分离,声明更清晰。 |
| 界面操作 | 分离的 browser_action和 page_action | 统一的 actionAPI | 简化了扩展图标及其相关弹出页面 (popup) 的定义 |
⚙️ 深入核心特性
一、生命周期管理
Service Worker是事件驱动的,在不处理事件时会被浏览器终止(通常闲置约30秒后)以节省资源 。这意味着不能依赖全局变量来持久化状态。- 所有需要跨事件保存的数据都必须使用
chrome.storageAPI或IndexedDB
二、环境差异
Service Worker运行在一个独立的环境中,没有 DOM 访问权限,也不能使用XMLHttpRequest,必须使用现代的fetch()API进行网络请求
三、声明式网络请求
declarativeNetRequestAPI的工作方式是:扩展预定义一组规则(匹配条件和操作),浏览器直接应用这些规则,扩展本身不介入每个请求的实时处理 。这避免了扩展需要读取所有网络请求内容的需求,保护了用户隐私。
四、远程代码禁令与应对策略
- 此规则禁止从远程服务器加载 JavaScript 或 Wasm 文件,也禁止执行动态字符串代码(如 eval())这确保了提交到 Chrome 应用商店的扩展行为是固定的,便于安全审查。
- 如果业务逻辑需要更新,官方推荐的替代方案是远程配置。扩展可以定期从服务器获取 JSON 等格式的配置文件,然后由扩展包内预置的、安全的逻辑来解析和执行这些配置
五、处理长时间任务
- 由于
Service Worker会被终止,需要执行周期性任务时应使用chrome.alarmsAPI。对于极少数需要 DOM 访问的后台任务,可考虑使用chrome.offscreenAPI创建离屏文档
3、manifest.json字段详解
- 必填字段
- manifest_version:清单版本号,目前主流为v2和v3版本,v3是当前推荐标准
- name:插件名称,字符串类型,必填字段
- version:插件版本号,字符串类型,必填字段
- 基本信息字段
- description:插件功能描述,字符串类型
- icons:插件图标配置,包含不同尺寸的图标文件路径,建议提供16x16、32x32、48x48、128x128像素的图标
- author:插件作者信息,可包含email等联系方式
- 权限配置字段
- permissions:控制扩展能访问哪些Chrome API:如storage、tabs、activeTab、notifications等
"permissions": [ "storage", // 存储数据 "tabs", // 访问标签页 "bookmarks", // 书签操作 "notifications", // 桌面通知 "activeTab", // 当前标签页临时权限 "scripting", // 脚本注入 "alarms" // 定时任务 ]- host_permissions:声明插件可访问的主机域名权限,使用URL匹配模式
"host_permissions": [ "https://example.com/*", // 特定域名 "https://*.google.com/*", // 子域名通配符 "https://*/api/*", // 路径匹配 "http://localhost:*/*", // 本地开发服务器 "<all_urls>" // 所有网址(谨慎使用) ]- optional_permissions:运行时由用户授予的可选权限
- optional_host_permissions:运行时由用户授予的可选主机权限
- 核心功能字段
- action:定义插件在浏览器工具栏中的行为,包括图标、标题和弹出页面
- background:定义后台脚本或页面,用于处理全局事件、管理状态等
- content_scripts:定义注入到匹配页面的脚本和样式表,可指定注入时机和匹配规则
- web_accessible_resources:定义插件中可供网页或其他插件访问的资源文件
- 其他常用字段
- options_page:插件设置页面
- commands:定义快捷键命令
- content_security_policy:定义内容安全策略
- default_locale:默认本地化设置
- update_url:插件更新地址
4、Popup 和 Options 页面
| 特性 | Popup (弹出页面) | Options Page (选项页面) |
|---|---|---|
| 主要用途 | 临时性交互,快速操作 | 插件配置和设置,功能更复杂 |
| 触发方式 | 用户点击浏览器工具栏中的插件图标 | 用户右键点击插件图标选择“选项”,或在扩展管理页面点击“选项” |
| 生命周期 | 短暂,焦点离开页面即关闭 | 持久,作为独立标签页或弹窗存在 |
| 配置方式 | 在 manifest.json的 action字段中设置 default_popup | 在 manifest.json中使用 options_page或更现代的 options_ui |
| 页面能力 | 能力受限,适合简单交互 | 完整的网页能力,可构建复杂界面 |
| 打开方式 | 无法通过程序代码打开,只能用户点击 | 可通过 chrome.runtime.openOptionsPage()编程方式打开 |
5、Background Scripts(Service Worker)
Manifest V3 的 Background Scripts 是运行在 Service Worker 环境中的。
Service Worker 是 Manifest V3 中替代后台页面的技术,它是一个在浏览器后台独立运行的 JavaScript 文件,可以拦截和处理网络请求、管理缓存、接收推送消息等。
关键点:无持久状态、事件驱动、随时可能终止
| 特性 | Manifest V2 Background | Manifest V3 Service Worker |
|---|---|---|
| 运行环境 | 后台页面(有DOM) | Service Worker(无DOM) |
| 生命周期 | 可持久运行 | 事件驱动,可能被终止 |
| 文件配置 | 页面或脚本数组 | 单个Service Worker文件 |
| 调试方式 | chrome://extensions中打开 | chrome://serviceworker-internals |
- ✅ Service Worker 应该处理:
- 事件监听:tabs.onUpdated、downloads.onCreated
- 定时任务:chrome.alarms
- 消息中转:不同页面间的通信
- 数据同步:本地↔服务器同步
- 推送通知:chrome.notifications
生命周期

- Installing 状态
// 安装阶段 - 只发生一次
self.addEventListener('install', event => {
console.log('Service Worker 安装中...');
// 跳过等待,立即激活
event.waitUntil(
// 预缓存关键资源
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/styles.css',
'/app.js'
]);
}).then(() => {
// 强制进入激活状态
return self.skipWaiting();
})
);
});
- Installed/Waiting 状态
- 已安装但未激活
- 等待旧的
Service Worker控制的所有客户端关闭 - 可通过
skipWaiting()立即激活
- Activating 状态
// 激活阶段
self.addEventListener('activate', event => {
console.log('Service Worker 激活中...');
event.waitUntil(
// 清理旧缓存
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== 'v1') {
return caches.delete(cacheName);
}
})
);
}).then(() => {
// 立即控制所有客户端
return self.clients.claim();
})
);
});
- Active 状态
// 激活后,开始处理事件
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
- Terminated 状态
- 空闲时自动终止
- Chrome 会在 30 秒空闲后终止
- 下次事件触发时重新启动
- 所有变量状态都会丢失
无持久化状态
❌ 错误理解:认为 Service Worker 可以像普通页面一样保持变量值
✅ 正确理解:Service Worker 每次启动时都是全新的运行环境,所有内存状态都会重置
// ❌ 错误示例:状态会丢失
let requestCount = 0; // 每次Service Worker重启都会归零
// ✅ 正确示例:使用持久化存储
async function incrementCounter() {
const data = await chrome.storage.local.get(['requestCount']);
const count = (data.requestCount || 0) + 1;
await chrome.storage.local.set({ requestCount: count });
return count;
}
// 使用示例
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.type === 'TRACK_REQUEST') {
const newCount = await incrementCounter(); // ✅ 正确的状态管理
sendResponse({ count: newCount });
}
});
- 实际影响
- 不能依赖全局变量:每次启动都是从零开始
- 必须使用存储API:chrome.storage、IndexedDB、Cache API
- 初始化成本:每次启动都需要重新加载数据
事件驱动
❌ 错误理解:认为 Service Worker 可以主动轮询或持续运行
✅ 正确理解:Service Worker 只能被动响应事件,不能主动执行代码
// ❌ 错误示例:主动轮询(不会有效)
setInterval(() => {
checkForUpdates(); // Service Worker 终止后定时器就没了
}, 60000);
// ✅ 正确示例:使用事件监听
// 1. 监听消息事件
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'CHECK_UPDATES') {
checkForUpdates();
}
});
// 2. 使用chrome.alarms(chrome会唤醒Service Worker)
chrome.alarms.create('checkUpdates', {
periodInMinutes: 30
});
chrome.alarms.onAlarm.addListener(alarm => {
if (alarm.name === 'checkUpdates') {
checkForUpdates();
}
});
// 3. 监听浏览器事件
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url) {
handleUrlChange(tab.url);
}
});
- 事件类型
- 扩展事件:onMessage, onConnect
- 浏览器事件:tabs.onCreated, webRequest.onBeforeRequest
- 定时事件:alarms.onAlarm
- 网络事件:fetch(普通网页SW用)
随时可能终止
❌ 错误理解:认为 Service Worker 启动后会一直运行
✅ 正确理解:Chrome 会在 Service Worker 空闲约30秒后自动终止它
// ❌ 错误示例:假设Service Worker会持续运行
function startLongRunningProcess() {
// 这个处理如果超过30秒,可能被中断
processLargeDataset();
}
// ✅ 正确示例:分块处理 + 状态保存
async function processLargeDatasetSafely() {
const data = await loadData();
const chunkSize = 100;
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
// 处理当前块
await processChunk(chunk);
// 保存进度
await chrome.storage.local.set({
processingProgress: i + chunk.length,
lastProcessed: Date.now()
});
// 如果这是最后一块,清理进度
if (i + chunkSize >= data.length) {
await chrome.storage.local.remove(['processingProgress']);
}
}
}
// 恢复中断的任务
async function resumeInterruptedTask() {
const state = await chrome.storage.local.get(['processingProgress']);
if (state.processingProgress) {
console.log('恢复任务,从进度', state.processingProgress, '继续');
await processLargeDatasetSafely();
}
}
- 终止时机
- 30秒空闲:无事件处理时
- 内存压力:系统内存不足时
- 浏览器关闭:用户关闭浏览器时
- 扩展更新:扩展更新时
启动时机
Service Worker 的启动是事件驱动的,只有在特定事件发生时才会启动
1. 扩展相关事件
// Service Worker 会在以下事件发生时启动:
// 1️⃣ 扩展安装/更新
chrome.runtime.onInstalled.addListener(details => {
console.log('Service Worker 因扩展安装/更新而启动');
// details.reason 可能是:
// - 'install' (首次安装)
// - 'update' (扩展更新)
// - 'chrome_update' (浏览器更新)
// - 'shared_module_update'
});
2. API 事件监听
// 2️⃣ 监听的事件被触发时
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
console.log('Service Worker 因标签页更新而启动');
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('Service Worker 因收到消息而启动');
return true; // 保持通道开放
});
chrome.runtime.onConnect.addListener(port => {
console.log('Service Worker 因建立连接而启动');
});
3. 定时器事件
// 3️⃣ 注册的定时器触发
chrome.alarms.onAlarm.addListener(alarm => {
console.log('Service Worker 因定时器触发而启动');
});
// 创建定时器
chrome.alarms.create('dailyCheck', {
periodInMinutes: 24 * 60 // 每天触发
});
4. 浏览器事件
// 4️⃣ 浏览器事件触发
chrome.webRequest.onBeforeRequest.addListener(
details => {
console.log('Service Worker 因网络请求而启动');
},
{ urls: ["<all_urls>"] }
);
启动时间轴示例
// 用户一天的典型使用场景:
8:00 打开浏览器 → ❌ Service Worker 未启动
8:05 点击扩展图标 → ✅ 启动(chrome.runtime.onMessage)
8:10 访问新网页 → ✅ 启动(chrome.tabs.onUpdated)
8:30 定时任务 → ✅ 启动(chrome.alarms.onAlarm)
8:35 关闭浏览器 → ❌ Service Worker 终止
14:00 重新打开浏览器 → ❌ Service Worker 未启动
14:05 收到推送 → ✅ 启动(chrome.gcm.onMessage)
无法直接访问DOM
// ❌ 错误:无法访问
document.getElementById('element');
// ✅ 正确:通过消息传递
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'UPDATE_DOM') {
// 发送消息给content script处理
chrome.tabs.sendMessage(sender.tab.id, {
action: 'updateElement',
data: request.data
});
}
});
通过offscreen访问DOM
chrome.offscreenAPI 是 Manifest V3 中访问 DOM 的唯一合法途径,用于在 Service Worker 中执行需要 DOM 的操作。
一、什么情况下需要用?
- Service Worker 不能访问 DOM,但某些功能需要 DOM:
- ✅ 解析 HTML/XML
- ✅ 使用 Canvas
- ✅ 处理音频/视频
- ✅ 操作 document 对象
二、基本使用
- 配置权限
{
"manifest_version": 3,
"permissions": [
"offscreen" // 必需权限
]
}
- 创建离屏文档
// background.js
await chrome.offscreen.createDocument({
url: 'offscreen.html', // 离屏页面
reasons: ['DOM_PARSER'], // 用途说明
justification: '解析HTML' // 详细说明
});
- 通信处理
// 发送任务到离屏文档
chrome.runtime.sendMessage({
type: 'PARSE_HTML',
html: '<p>内容</p>'
});
// 离屏文档接收处理
chrome.runtime.onMessage.addListener((request) => {
if (request.type === 'PARSE_HTML') {
const doc = new DOMParser().parseFromString(request.html, 'text/html');
// 处理DOM...
}
});
三、重要限制
- 只能创建1个离屏文档
- 30秒不活动自动关闭
- 用户不可见,不显示窗口
- 需要明确声明使用原因
通信方式
1. 一次性消息传递(最常用)
// 发送方
chrome.runtime.sendMessage(
{
type: 'ACTION_TYPE',
data: { key: 'value' }
},
(response) => {
console.log('收到响应:', response);
}
);
// 接收方(任何地方)
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
if (request.type === 'ACTION_TYPE') {
// 处理请求
const result = processData(request.data);
// 发送响应
sendResponse({ success: true, data: result });
// 异步响应时返回 true
return true;
}
}
);
2. 长连接通信
// 建立连接
const port = chrome.runtime.connect({ name: 'my-port' });
// 发送消息
port.postMessage({ action: 'doSomething' });
// 接收消息
port.onMessage.addListener((msg) => {
console.log('收到消息:', msg);
});
// 断开连接
port.onDisconnect.addListener(() => {
console.log('连接已断开');
});
3. 通过Storage共享
// 所有页面监听存储变化
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync' || areaName === 'local') {
if (changes.userSettings) {
// 用户设置更新了
updateUI(changes.userSettings.newValue);
}
}
});
// Popup修改设置
chrome.storage.sync.set({ userSettings: newSettings });
// Background和Options会自动收到通知
定时任务管理
// 创建定时器
chrome.alarms.create('periodicTask', {
periodInMinutes: 60, // 每小时执行一次
delayInMinutes: 1 // 1分钟后首次执行
});
// 监听定时器触发
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'periodicTask') {
console.log('执行定时任务');
performPeriodicTask();
}
});
// 一次性定时器
chrome.alarms.create('oneTimeTask', {
when: Date.now() + 5000 // 5秒后执行
});
// 销毁特定的定时器
chrome.alarms.clear('callApi');
// 销毁所有定时器
chrome.alarms.clearAll();
6、content scripts
Content Scripts 是 Chrome 扩展的核心组件,允许将 JavaScript 和 CSS 代码注入到特定的网页中,与页面内容直接交互。
一、核心特性
- 运行环境
- 运行在网页的上下文中,能够访问和操作 DOM
- 但与网页的 JavaScript 环境隔离,有自己的独立作用域
// 在 content script 中 var pageVar = "这是 content script 的变量"; // 网页的 JavaScript 无法直接访问这个变量 // 网页中的变量 // var pageVar = "这是网页的变量"; // content script 也无法直接访问- 可以访问部分 Chrome API(通过扩展 API)
二、配置方式
1. 静态声明 (manifest.json)
{
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"css": ["styles.css"],
"js": ["content.js"],
"run_at": "document_idle", // 注入时机
"match_about_blank": false,
"all_frames": false
}
]
}
- 注入时机选项:
- document_start:DOM 加载前,CSS 加载前
- document_end:DOM 加载后,图片等资源可能还在加载
- document_idle(默认):DOM 完全加载后,window.onload前
2. 动态注入 (通过 background.js)
// 在 background.js 或 popup.js 中
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
});
三、与页面通信
1. DOM 事件通信
// content script 发送消息
window.postMessage({
type: "FROM_CONTENT_SCRIPT",
data: "Hello from content script"
}, "*");
// 网页监听消息
window.addEventListener("message", (event) => {
if (event.data.type === "FROM_CONTENT_SCRIPT") {
console.log(event.data.data);
}
});
2. 与扩展其他部分通信
// 与 background script 通信
chrome.runtime.sendMessage(
{ action: "logData", data: "some data" },
response => console.log("Response:", response)
);
// 监听来自 background 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "updateContent") {
// 更新页面内容
sendResponse({ status: "updated" });
}
});
四、访问 Chrome API 的权限
- Content scripts 可以访问有限的 Chrome API:
- chrome.runtime(发送消息、获取扩展信息)
- chrome.storage(存储数据)
- chrome.i18n(国际化)
- 不能访问:
- chrome.tabs(部分权限)
- chrome.windows -需要更高权限的 API
五、隔离突破
在 Content Scripts 中,world 定义了脚本执行的 JavaScript 隔离环境。Chrome 94+ 引入了两种 world:
- 1、"ISOLATED"(默认):隔离世界
- 2、"MAIN":主世界(网页的 JS 环境) 核心作用:共享网页的 JavaScript 环境
- 使用 world: "MAIN"让你能够:
- ✅ 直接访问网页的 JavaScript 对象和函数
- ✅ 修改网页的全局变量
- ✅ 与网页框架(React、Vue 等)直接交互
- 但要小心:
- ⚠️ 可能引起命名冲突
- ⚠️ 降低扩展的安全性
- ⚠️ 可能破坏网页功能
建议:只有在确实需要与网页 JavaScript 深度交互时才使用 "MAIN",并采取适当的防护措施。
六、与浏览器插件通信
Content Scripts 与插件其他部分(popup、background、options等)通过 消息传递 机制通信,主要有三种方式:
- chrome.runtime.sendMessage() - 一次性消息
- chrome.runtime.connect() - 长连接通信
- DOM 事件 - 与网页脚本通信
1. 一次性消息传递(最常用)
Content Script → Background/Popup
// content-script.js
// 发送消息
chrome.runtime.sendMessage(
{
type: "USER_ACTION",
data: { action: "click", element: "button" }
},
response => {
// 可选的回调,处理响应
console.log("收到响应:", response);
}
);
Background 接收消息
// background.js
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
console.log("收到来自 content script 的消息:", request);
console.log("发送者信息:", sender.tab?.url);
if (request.type === "USER_ACTION") {
// 处理消息
const result = processAction(request.data);
// 发送响应
sendResponse({ success: true, data: result });
}
// 返回 true 表示异步响应
return true;
}
);
2. 长连接通信(持续对话)
建立连接
// content-script.js
// 建立连接
const port = chrome.runtime.connect({
name: "content-script-connection"
});
// 监听来自 background 的消息
port.onMessage.addListener(msg => {
console.log("收到消息:", msg);
if (msg.command === "UPDATE_UI") {
updatePageUI(msg.data);
}
});
// 发送消息
port.postMessage({
action: "CONNECTION_READY",
tabId: chrome.devtools.inspectedWindow.tabId
});
// 断开连接时
port.onDisconnect.addListener(() => {
console.log("连接已断开");
cleanup();
});
Background 处理连接
// background.js
chrome.runtime.onConnect.addListener(port => {
console.log("新的连接:", port.name);
// 只处理来自 content script 的连接
if (port.name === "content-script-connection") {
// 监听消息
port.onMessage.addListener(msg => {
console.log("来自 content script:", msg);
// 响应消息
if (msg.action === "CONNECTION_READY") {
port.postMessage({
command: "SEND_DATA",
data: { config: "default" }
});
}
});
// 主动发送消息
setTimeout(() => {
port.postMessage({ command: "PING" });
}, 1000);
}
});
3. 与 Popup 页面通信
Popup → Content Script(需要标签页ID)
// popup.js
// 获取当前标签页
chrome.tabs.query(
{ active: true, currentWindow: true },
tabs => {
if (tabs[0]) {
// 发送消息到该标签页的 content script
chrome.tabs.sendMessage(
tabs[0].id,
{
type: "FROM_POPUP",
data: popupData
},
response => {
console.log("Content script 响应:", response);
}
);
}
}
);
4. 存储共享通信
通过 chrome.storage 共享数据
// content-script.js - 存储数据
chrome.storage.local.set(
{ pageData: { title: document.title, url: location.href } },
() => {
console.log("数据已保存");
}
);
// background.js - 读取数据
chrome.storage.local.get(["pageData"], result => {
console.log("获取的数据:", result.pageData);
});
通信限制与注意事项
- 生命周期:Content script 随页面卸载而销毁
- 跨域限制:Content script 只能与自己的扩展通信
- 性能考虑:避免频繁发送大量数据
- 安全考虑:验证消息来源
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// 验证发送者
if (sender.id !== chrome.runtime.id) {
return; // 忽略非本扩展的消息
}
});
7、Storage API(local、sync、managed)
| 特性 | chrome.storage.local | chrome.storage.sync | chrome.storage.managed |
|---|---|---|---|
| 数据位置 | 本地计算机 | Chrome 账户同步 | 由管理员推送 |
| 存储上限 | 10 MB | 100 KB 或 8 KB/项 | 无明确限制 |
| 同步范围 | 仅本机 | 跨设备(登录同一账户) | 企业域内所有设备 |
| 适用场景 | 本地临时数据、大文件 | 用户设置、书签、偏好 | 企业策略、统一配置 |
| 权限要求 | "storage" | "storage" | 无特殊权限 |
8、Tabs API
Tabs API 用于管理浏览器标签页,是扩展与网页交互的基础。
查询、创建、更新
查询标签页
// 获取当前标签页
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
const currentTab = tabs[0];
});
// 获取所有标签页
chrome.tabs.query({}, tabs => {});
创建标签页
chrome.tabs.create({url: 'https://example.com'});
更新标签页
chrome.tabs.update(tabId, {active: true});
创建、更新、激活、关闭事件监听
// 标签页创建
chrome.tabs.onCreated.addListener(tab => {});
// 标签页更新(URL/状态变化)
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url) {
console.log('URL变化:', changeInfo.url);
}
});
// 标签页激活
chrome.tabs.onActivated.addListener(info => {});
// 标签页关闭
chrome.tabs.onRemoved.addListener(tabId => {});
权限要求
{
"permissions": ["tabs"]
}
- 核心使用场景
- 获取当前页面信息 - 用于内容脚本注入
- 创建/管理标签页 - 构建浏览器工具
- 监听页面变化 - 实现自动化功能
- 跨标签页通信 - 协调多页面操作
9、Runtime API
- Runtime API 是扩展的"神经系统",负责:
- 扩展生命周期管理
- 组件间通信
- 运行时信息获取
四个关键用途
1. 消息通信
// 发送消息
chrome.runtime.sendMessage({type: 'action'}, response => {});
// 接收消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({result: 'ok'});
return true; // 异步响应时必需
});
2. 获取运行时信息
// 扩展信息
const manifest = chrome.runtime.getManifest();
const id = chrome.runtime.id;
const url = chrome.runtime.getURL('page.html');
3. 生命周期事件
// 扩展安装/更新
chrome.runtime.onInstalled.addListener(details => {
if (details.reason === 'install') {
// 首次安装
}
});
4. 打开页面
// 打开设置页
chrome.runtime.openOptionsPage();
// 获取所有打开的页面
const views = chrome.extension.getViews({type: 'popup'});
10、Permissions API
Permissions API 用于动态管理扩展权限,让用户在需要时才授予权限,而非安装时强制要求。
请求和检查权限
1. 请求权限
// 运行时请求权限
chrome.permissions.request({
permissions: ['storage'],
origins: ['https://api.example.com/*']
}, granted => {
if (granted) {
console.log('权限已授予');
}
});
2. 检查权限
// 检查是否拥有权限
chrome.permissions.contains({
permissions: ['tabs']
}, result => {
console.log('是否有tabs权限:', result);
});
11、Declarative Content API
Declarative Content API 允许扩展在特定页面自动激活,无需注入 content scripts。
主要控制扩展图标何时在工具栏中可点击,并可以添加页面操作按钮。
1. 设置规则
// 在background.js中
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: 'example.com' }
})
],
actions: [
new chrome.declarativeContent.ShowPageAction() // MV2
// 或
new chrome.declarativeContent.ShowAction() // MV3
]
}]);
2. 清除规则
chrome.declarativeContent.onPageChanged.removeRules();
- 使用场景
- 智能图标显示:只在相关网站显示扩展图标
- 减少注入:避免在所有页面注入content scripts
- 性能优化:非相关页面不激活扩展
12、通知(Notifications)
Notifications API 允许扩展显示系统级别的通知,即使用户不在浏览器中也能看到。
1. 创建通知
// 简单通知
chrome.notifications.create('notification1', {
type: 'basic',
iconUrl: 'icon.png',
title: '提醒',
message: '任务已完成',
buttons: [{ title: '查看' }]
});
// 带进度条
chrome.notifications.create('progress', {
type: 'progress',
title: '下载中',
message: '正在下载文件...',
progress: 50 // 0-100
});
2. 通知类型
- basic- 基本通知(图标+文字)
- image- 带图片的通知
- list- 列表通知(显示多个项目)
- progress- 进度条通知
3. 事件处理
// 用户点击通知
chrome.notifications.onClicked.addListener(notificationId => {
console.log('通知被点击:', notificationId);
});
// 用户点击按钮
chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => {
console.log('按钮被点击:', buttonIndex);
});
- 重要特性
- 系统级通知:即使浏览器最小化也能显示
- 多种样式:支持图标、图片、列表、进度条
- 交互支持:可点击通知或按钮
- 自动消失:可设置优先级和超时时间
13、右键菜单(Context Menus)
Context Menus API 允许扩展在浏览器的右键菜单中添加自定义项目。
1. 创建菜单
// 创建菜单项
chrome.contextMenus.create({
id: 'my-menu',
title: '我的操作',
contexts: ['selection'] // 选中文本时显示
});
// 子菜单
chrome.contextMenus.create({
id: 'parent',
title: '父菜单',
contexts: ['all']
});
chrome.contextMenus.create({
id: 'child',
parentId: 'parent',
title: '子菜单',
contexts: ['all']
});
2. 可用上下文
- all- 所有情况
- page- 页面空白处
- selection- 选中文本
- link- 链接
- image- 图片
- video/audio- 视频/音频
- editable- 可编辑区域
3. 点击事件
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'my-menu') {
console.log('选中的文本:', info.selectionText);
}
});
- 核心要点
- background中创建:菜单必须在background script中创建
- 多种上下文:可针对不同场景显示不同菜单
- 图标支持:可添加图标到菜单项
- 动态更新:可创建后更新、删除菜单
14、错误监控与日志收集
插件特定错误
// 监听扩展错误
chrome.runtime.onError.addListener(error => {
console.error('扩展错误:', error);
});
15、Plasmo框架详解
目前比较受欢迎的开发框架和模板:
| 框架/模板名称 | 核心特点 | 适用场景 |
|---|---|---|
| Plasmo Framework | 功能强大的浏览器扩展SDK,无需操心配置 | 希望获得开箱即用、现代化开发体验的项目 |
| Chrome Extension (MV3) Boilerplate with React 18 + Webpack5 | 基于React 18和Webpack 5的现代化样板 | 熟悉React生态,需要模块化、热重载的开发 |
| Create Chrome Extension (.crx) | 基于Vite 4,支持多种前端框架,敏捷热更 | 追求极速构建,并希望灵活选择Vue、React、Svelte等框架 |
一、核心特性
- 现代化开发体验
- Plasmo内置了React和TypeScript的顶级支持,提供实时重载功能,修改代码后立即生效,React热模块替换技术保证了流畅的开发体验。
- 声明式开发模式
- 采用声明式开发,无需手动处理繁琐的配置文件。框架自动处理从代码编译到插件打包的完整流程,大幅提升开发效率。
- 跨浏览器兼容
- 同一份代码可以轻松部署到Chrome、Firefox、Edge等多个浏览器平台,解决了传统插件开发中最大的痛点之一。
- 强大生态系统
- 从消息传递到存储管理,从环境变量到远程代码集成,Plasmo提供了完整的功能模块。
二、消息通信机制
Plasmo提供了简洁高效的消息通信机制,核心在于useMessage和usePort两个React钩子:
// 后台服务
import { relayMessage } from "@plasmohq/messaging"
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "GET_USER_DATA") {
sendResponse({ user: "Plasmo User", timestamp: new Date().toISOString() })
}
})
// 内容脚本
import { useMessage } from "@plasmohq/messaging/hook"
const ContentScript = () => {
const { data } = useMessage(async (req, res) => {
console.log("Received message:", req.body)
res.send({ status: "received" })
})
return <div>Message data: {JSON.stringify(data)}</div>
}
// 弹出页面
import { usePort } from "@plasmohq/messaging/hook"
const Popup = () => {
const { send, data } = usePort("popup-port")
const handleClick = () => send({ action: "FETCH_DATA" })
return (
<div>
<button onClick={handleClick}>获取数据</button>
<div>响应数据: {JSON.stringify(data)}</div>
</div>
)
}
三、状态管理
Plasmo的持久化存储API简化了跨上下文(弹出页、内容脚本、后台)的状态共享:
// store.ts - 状态定义
import { persistentStore } from "@plasmohq/persistent"
interface UserState {
theme: "light" | "dark"
notifications: boolean
}
export const userStore = persistentStore<UserState>({
theme: "light",
notifications: true
})
// popup.tsx - 修改状态
import { userStore } from "./store"
const ThemeToggle = () => {
const [theme, setTheme] = userStore.useState("theme")
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle {theme} theme
</button>
)
}
// content-script.tsx - 读取状态
import { userStore } from "./store"
userStore.watch((state) => {
document.documentElement.dataset.theme = state.theme
})
16、谷歌插件打包以及发布
打包CRX文件
方案1:
通过浏览器扩展管理页面打包(推荐):在Chrome浏览器地址栏输入 chrome://extensions/并回车,打开右上角的 “开发者模式” 开关,点击 “打包扩展程序” 按钮,选择你的插件根目录(包含manifest.json的文件夹),然后点击 “打包扩展程序” 即可。成功后会生成一个 .crx文件和一个 .pem私钥文件。请务必妥善保管 .pem私钥文件,它是该扩展程序的“身份证”,后续更新版本时必须使用同一私钥重新签名,否则无法正常更新。
方案2:
通过命令行工具打包(适合自动化):可以使用 Google 提供的脚本或 Node.js 工具(如 crx3)进行打包。基本命令格式如下
# 使用官方脚本示例
python crx_make.py --extension-dir=/path/to/your/extension --key-file=/path/to/your/key.pem --output-crx=output.crx
提高审核通过率的建议
- 权限申请遵循最小化原则:在 manifest.json中只申请插件运行所必需的权限。对于每个申请的权限,都要在提交页面的“权限”部分提供清晰、诚恳的理由,说明为何需要此权限以及如何保护用户数据。
- 避免使用过于宽泛的权限:尽量避免使用
<all_urls>这样的主机权限。如果插件并非需要在所有网站上运行,应将其替换为具体的域名列表,或使用 activeTab权限(仅在用户点击插件图标时临时获取当前标签页的权限)。 - 准备高质量的商品详情页:这是说服用户安装的重要环节。需要准备至少一张尺寸为 1280x800 或 640x400 像素的清晰宣传截图或宣传视频。插件的文字描述(包括名称、简短描述和详细说明)应准确、易懂,突出核心功能和价值。如果插件会处理用户数据,必须提供一份可公开访问的隐私政策
插件版本更新
1. 后台自动更新
Chrome浏览器会定期检查插件更新
// background.js - 监听更新事件
chrome.runtime.onUpdateAvailable.addListener(details => {
console.log('发现新版本:', details.version);
// 立即应用更新
chrome.runtime.reload();
});
// 监听更新安装完成
chrome.runtime.onInstalled.addListener(details => {
if (details.reason === 'update') {
console.log('插件已更新至版本:', details.previousVersion, '→', chrome.runtime.getManifest().version);
}
});
2. 手动检查更新
// 在popup或options页面中
function checkForUpdates() {
chrome.runtime.requestUpdateCheck((status, details) => {
if (status === 'update_available') {
console.log('发现更新:', details.version);
// 提示用户重启浏览器或重新加载插件
} else if (status === 'no_update') {
console.log('当前已是最新版本');
} else if (status === 'throttled') {
console.log('更新检查过于频繁,请稍后再试');
}
});
}
