3. 扩展核心API(上):chrome.runtime、chrome.tabs、chrome.storage API详解与实战
说实话,很多刚接触浏览器扩展开发的朋友,上来就被这三个API搞懵了。我当年也一样,总觉得它们长得差不多,用起来却总踩坑。今天咱们就把这三个核心API掰开揉碎了讲清楚。
先看一张总览图,帮你建立整体认知:
3.1 chrome.runtime:扩展的"心脏"
chrome.runtime 是扩展的生命线。它管三件事:生命周期、消息通信、扩展信息。我个人觉得,理解 runtime 的关键在于「它连接了扩展的所有部分」。
3.1.1 生命周期管理
扩展有四个关键事件:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
| onInstalled | 安装或更新时 | 初始化数据、设置默认配置 |
| onStartup | 浏览器启动时 | 恢复上次会话状态 |
| onSuspend | 后台页面即将卸载 | 保存临时数据 |
| onSuspendCanceled | 取消卸载时 | 恢复被中断的操作 |
💡 核心要点:onInstalled 是最常用的。我习惯在这里做版本升级的迁移逻辑。比如用户从 v1 升到 v2,旧的数据结构变了,就在这里做兼容处理。
看个实际例子:
// background.js
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
// 首次安装,设置默认配置
chrome.storage.sync.set({
theme: 'light',
fontSize: 14,
autoSave: true
});
console.log('扩展已安装,默认配置已写入');
} else if (details.reason === 'update') {
// 版本更新,做数据迁移
const oldVersion = details.previousVersion;
console.log(`从 ${oldVersion} 升级到 ${chrome.runtime.getManifest().version}`);
// 这里可以写迁移逻辑
}
});
3.1.2 消息通信机制
扩展里不同部分怎么说话?靠 runtime 的消息系统。说白了就是「发消息」和「收消息」。
两种模式:
- 一次性消息:sendMessage + onMessage,一问一答
- 长连接:connect + onConnect,持续对话
🔧 实战技巧:我在项目中遇到过一个问题——popup 关闭后消息就丢了。后来发现用长连接可以解决。popup 打开时建立连接,关闭时自动断开,后台能感知到。
一次性消息示例:
// popup.js 发送消息
chrome.runtime.sendMessage(
{ action: 'getUserData', userId: '123' },
(response) => {
if (chrome.runtime.lastError) {
console.error('消息发送失败:', chrome.runtime.lastError.message);
return;
}
console.log('收到回复:', response);
}
);
// background.js 监听消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'getUserData') {
// 模拟异步操作
fetchUserData(message.userId).then(data => {
sendResponse({ success: true, data });
});
return true; // 重要!表示异步回复
}
});
⚠️ 注意:sendResponse 是异步的,必须在监听函数里 return true。我曾经忘了这个,结果回调一直没触发,排查了半天才发现。
3.2 chrome.tabs:操作浏览器标签页
tabs API 是扩展和用户浏览行为交互的桥梁。你想获取当前页面、注入脚本、或者打开新标签,都得靠它。
3.2.1 标签页查询与操作
最常用的几个方法:
| 方法 | 作用 | 常用场景 |
|---|---|---|
| tabs.query | 查询符合条件的标签 | 获取当前活动标签 |
| tabs.create | 创建新标签 | 打开设置页或帮助页 |
| tabs.update | 更新标签属性 | 修改URL或高亮标签 |
| tabs.remove | 关闭标签 | 批量关闭无用标签 |
获取当前活动标签页,这是最常用的操作:
// 获取当前窗口的活动标签
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
console.log('当前标签页:', currentTab.url, currentTab.title);
// 向当前页面注入脚本
chrome.scripting.executeScript({
target: { tabId: currentTab.id },
func: () => document.title
});
});
💡 经验之谈:query 的筛选条件很灵活。比如你想找所有在某个域名下的标签,可以传 { url: '*://example.com/*' }。我做过一个标签管理工具,就是用 query 配合 url 模式来分组。
3.2.2 标签页通信
除了 runtime 的消息,tabs 也有自己的通信方式——tabs.sendMessage。它专门用来给指定标签页的内容脚本发消息。
// background.js 向指定标签发送消息
chrome.tabs.sendMessage(tabId, { action: 'highlightElement', selector: '#main' }, (response) => {
if (chrome.runtime.lastError) {
// 内容脚本可能还没加载
console.warn('内容脚本未响应');
return;
}
console.log('内容脚本已执行:', response);
});
// content_script.js 监听消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'highlightElement') {
const el = document.querySelector(message.selector);
if (el) {
el.style.border = '3px solid red';
sendResponse({ success: true });
} else {
sendResponse({ success: false, error: '元素未找到' });
}
}
});
3.3 chrome.storage:数据持久化
扩展需要存数据怎么办?localStorage 不行,因为不同页面不共享。chrome.storage 就是干这个的。
3.3.1 存储区域对比
| 存储区域 | 容量 | 特点 | 适用场景 |
|---|---|---|---|
| storage.sync | 100KB | 自动同步到用户账号 | 用户配置、偏好设置 |
| storage.local | 约5MB | 本地存储,不自动同步 | 缓存数据、大文件 |
| storage.session | 约10MB | 内存存储,关闭即清 | 临时状态、会话数据 |
🔧 建议:我一般把用户配置放 sync,这样用户换电脑也能同步。缓存数据放 local。临时状态比如「当前选中的标签」放 session,关闭扩展就自动清掉,省得手动清理。
3.3.2 读写操作
API 设计得很简洁,就三个方法:get、set、remove。但要注意,它们都是异步的。
// 写入数据
chrome.storage.sync.set({
theme: 'dark',
bookmarks: ['url1', 'url2'],
lastVisit: Date.now()
}, () => {
console.log('数据保存成功');
});
// 读取数据
chrome.storage.sync.get(['theme', 'bookmarks'], (result) => {
console.log('读取结果:', result);
// 如果键不存在,返回 undefined
if (result.theme) {
applyTheme(result.theme);
}
});
// 删除数据
chrome.storage.sync.remove('bookmarks', () => {
console.log('bookmarks 已删除');
});
// 清空所有数据
chrome.storage.sync.clear(() => {
console.log('所有 sync 数据已清空');
});
3.3.3 数据变化监听
这个功能很实用——当数据变化时,所有页面都能收到通知。
// 监听 storage 变化
chrome.storage.onChanged.addListener((changes, areaName) => {
for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(`存储区域 ${areaName} 中的 ${key} 发生变化`);
console.log(`旧值:`, oldValue);
console.log(`新值:`, newValue);
// 根据变化做相应处理
if (key === 'theme') {
updateUITheme(newValue);
}
}
});
⚠️ 避坑指南:我曾经在 onChanged 里又调用了 storage.set,结果造成了死循环。记住,onChanged 里不要修改触发变化的那个 key,或者加个标志位判断。
3.4 实战:构建一个标签页管理器
把三个 API 串起来,做个实用的小工具——标签页管理器。功能很简单:保存当前所有标签页的 URL,下次一键恢复。
// background.js
// 保存当前所有标签
function saveSession() {
chrome.tabs.query({ currentWindow: true }, (tabs) => {
const urls = tabs.map(tab => tab.url);
chrome.storage.local.set({ savedSession: urls }, () => {
console.log(`已保存 ${urls.length} 个标签`);
});
});
}
// 恢复保存的标签
function restoreSession() {
chrome.storage.local.get('savedSession', (result) => {
if (result.savedSession && result.savedSession.length > 0) {
result.savedSession.forEach((url, index) => {
if (index === 0) {
// 第一个标签在当前标签打开
chrome.tabs.update({ url });
} else {
// 其余在新标签打开
chrome.tabs.create({ url });
}
});
}
});
}
// 监听来自 popup 的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'saveSession') {
saveSession();
sendResponse({ success: true });
} else if (message.action === 'restoreSession') {
restoreSession();
sendResponse({ success: true });
}
});
💡 扩展思路:这个例子虽然简单,但你可以继续加功能。比如用 storage.sync 跨设备同步会话,用 runtime.onInstalled 做首次安装引导,用 tabs.onRemoved 监听标签关闭自动保存。三个 API 配合起来,能做的事情很多。
好了,这一章的内容就到这。chrome.runtime、chrome.tabs、chrome.storage 这三个 API 是扩展开发的基石。你想想看,一个扩展无非就是「管理自身生命周期」、「操作浏览器标签」、「持久化数据」这三件事。把这三个搞明白,后面学其他 API 就轻松多了。