3. 扩展核心API(上):chrome.runtime、chrome.tabs、chrome.storage API详解与实战

说实话,很多刚接触浏览器扩展开发的朋友,上来就被这三个API搞懵了。我当年也一样,总觉得它们长得差不多,用起来却总踩坑。今天咱们就把这三个核心API掰开揉碎了讲清楚。

先看一张总览图,帮你建立整体认知:

浏览器扩展核心API体系 chrome.runtime 生命周期管理 消息通信 扩展信息获取 chrome.tabs 标签页增删改查 页面注入脚本 标签通信 chrome.storage 本地持久化存储 同步/异步读写 数据监听 典型应用场景 🔹 后台脚本与popup通信 → runtime.sendMessage 🔹 获取当前活动标签页 → tabs.query 🔹 保存用户配置 → storage.sync

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 就轻松多了。

专注资料整理