chrome.tabs 实战:标签页查询、创建与分组
标签页是扩展最容易感知的浏览器对象:查询、创建、移动、静音、分组,全在 chrome.tabs 一个命名空间里。而且它有个反直觉的好消息——多数方法不需要任何权限。这一篇把高频操作和权限边界一次讲全。
权限的真相
先纠正一个广泛流传的误解:使用 chrome.tabs API 大部分功能不需要权限。tabs.create、tabs.update、tabs.move、tabs.reload 这些随便调。需要权限的只有两类事:
tabs权限:解锁读取 Tab 对象的四个敏感属性 url、pendingUrl、title、favIconUrl;- host_permissions 或 activeTab:对特定站点做同样的事,并允许注入脚本、截屏(captureVisibleTab)。
所以先想清楚:只是开关标签页,零权限;要按 URL 过滤标签页,就需要上面的授权之一。
查询:query 的条件语法
// 当前窗口的活动标签页
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// 所有窗口的活动标签页(多窗口场景)
const actives = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
// 按 URL 模式找(需要 tabs 或 host 权限才能读到 url)
const baike = await chrome.tabs.query({ url: "*://*.baidu.com/*" });
query 条件是 AND 组合:active、currentWindow、url、pinned、audible、muted 等字段同时生效。返回数组,解构取第一个时注意空数组的可能。注意 currentWindow 与 lastFocusedWindow 的差别:前者是「调用者所在窗口」,后者是「最近聚焦的窗口」,popup 里通常用 currentWindow,后台里通常用 lastFocusedWindow。
创建与更新
// 打开扩展自己的引导页(onInstalled 里做,无需任何权限)
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === "install") {
chrome.tabs.create({ url: "onboarding.html" });
}
});
// 更新当前页到新地址
await chrome.tabs.update(tab.id, { url: "https://example.com/new" });
// 静音某个标签页
const t = await chrome.tabs.get(tabId);
await chrome.tabs.update(tabId, { muted: !t.mutedInfo.muted });
update 能改 url、muted、pinned、active 等。move 可把标签页挪位置,挪动时若用户正在拖拽标签,Chrome 会抛错「Tabs cannot be edited right now」,官方示例的处理是捕获后延迟 50 毫秒重试。
分组:tabs.group 与 TabGroups API
标签页分组(Chrome 88+)的入口是 tabs.group / tabs.ungroup,视觉控制靠 chrome.tabGroups:
// 把两个标签页组成一个组
const groupId = await chrome.tabs.group({
tabIds: [tabA.id, tabB.id],
createProperties: { windowId: tabA.windowId }
});
// 设置组的标题和颜色(需要 "tabGroups" 权限)
await chrome.tabGroups.update(groupId, { title: "工作区", color: "blue" });
// 监听组变化
chrome.tabGroups.onUpdated.addListener((group) => {
console.log("组更新", group.id, group.title);
});
颜色是固定枚举:grey、blue、red、yellow、green、pink、purple、cyan、orange。做「一键整理」类扩展时,常用 query 找出同类标签再 group,这正是第五章实战项目「标签页管理器」的核心套路。
事件:让扩展对标签页活动做出反应
| 事件 | 触发时机 |
|---|---|
| tabs.onCreated | 新标签页创建 |
| tabs.onUpdated | 标签页 URL、状态、标题等变化 |
| tabs.onRemoved | 标签页关闭 |
| tabs.onActivated | 切换活动标签页 |
onUpdated 事件会在一个页面加载过程中触发多次(loading、title 变化、favicon 就绪……),处理时按 changeInfo 字段过滤,别把每个回调都当「页面加载完成」。
实战提醒
按 URL 过滤时模式串写错会静默返回空数组,排查方法是把条件放宽成 {} 逐步加严。URL 模式与正则的关系见 正则实战。标签页数据要持久化的话,结合 《chrome.storage 实战》 篇的方案落库。官方文档:https://developer.chrome.com/docs/extensions/reference/api/tabs
一个标签页整理器的雏形
把本篇 API 组合成一个实用函数:找出当前窗口所有已静音或长期不活跃的标签页并折叠成组——「一键收纳」类扩展的核心逻辑:
async function tidyUp() {
const tabs = await chrome.tabs.query({ currentWindow: true });
const byHost = new Map();
for (const t of tabs) {
if (!t.url) continue; // 无 tabs 权限时 url 不可读,需授权
const host = new URL(t.url).hostname;
if (!byHost.has(host)) byHost.set(host, []);
byHost.get(host).push(t.id);
}
const colors = ["blue", "green", "yellow", "pink", "purple", "orange"];
let i = 0;
for (const [host, ids] of byHost) {
if (ids.length < 2) continue; // 单页不成组
const gid = await chrome.tabs.group({
tabIds: ids,
createProperties: { windowId: tabs[0].windowId }
});
await chrome.tabGroups.update(gid, { title: host, color: colors[i++ % colors.length] });
}
}
注意其中 if (!t.url) continue:没有 tabs 权限或 host 权限时,Tab 对象的 url 是 undefined,按域名分组的功能直接失效。功能设计与权限申请是绑定的,这是「权限模型」篇反复强调的主题在实操中的样子。
事件处理的两个防坑
事件驱动的标签页功能有两个经典坑。其一,onUpdated 高频触发:一个页面从加载到完成可能回调五六次,处理函数里先检查 changeInfo.status === "complete" 或按需过滤字段,否则同一段逻辑会被执行多次。其二,onRemoved 的时机:tabId 已失效,回调里再用它去 get 会报错;需要「关闭前保存内容」的逻辑要在关闭前完成(监听 onUpdated 缓存状态),而不是指望在 onRemoved 里抢救。
多窗口场景别忘了窗口维度:query 加 windowId 或 currentWindow,onActivated 回调里区分 windowId,跨窗口的「活动标签」是每个窗口各一个。做「全局唯一活动标签」假设的代码在双显示器用户那里必然出错。
结合存储做会话恢复
标签页管理器的高级形态是「保存会话、随时恢复」:把 query 结果的 url 列表写进 storage.sync(数据小,跨设备恢复),恢复时循环 tabs.create。完整代码在第五章实战项目 《标签页管理器》 一篇。URL 模式匹配与 host 解析的正则技巧见 正则实战。官方文档:https://developer.chrome.com/docs/extensions/reference/api/tabs
本章由 WorkBuddy 原创整理,API 行为依据 Chrome 扩展官方文档(Manifest V3)。
