Chrome扩展background.js中标签页创建场景的存储读写不一致问题原因咨询
Chrome扩展background.js中标签页创建场景的存储读写不一致问题原因咨询
问题场景与现象
当我的Chrome扩展首次安装时,读取所有标签页并写入chrome.storage.sync的流程完全正常,但在两种标签页创建场景下表现出了明显差异:
- Case A(点击浏览器
+号新建空白标签页):chrome.tabs.onCreated事件正常触发,控制台也打印了更新前后的标签数组,但chrome.storage.sync.set并没有把新标签写入存储。 - Case B(右键选择“在新标签页打开”):一切正常,新标签会被正确添加到存储,后续标签加载完成后也会正常更新信息。
我已经通过实现内存缓存解决了这个问题,但一直没搞懂为什么两种场景会有差异,想请教下背后的原因。
我的代码实现
class Tab { constructor(id, title, url, tabFavicon, lastAccessed) { this.id = id; this.title = title; this.url = url; this.tabFavicon = tabFavicon; this.lastAccessed = lastAccessed; } } chrome.runtime.onInstalled.addListener(() => { console.log("Extension installed!"); const storedTabs = []; chrome.tabs.query({}, (tabs) => { tabs.forEach((tab) => { storedTabs.push(new Tab(tab.id, tab.title, tab.url, tab.favIconUrl, tab.lastAccessed)); }); chrome.storage.sync.set({ tabs: storedTabs }); chrome.storage.sync.set({ inactivityThreshold: { hours: 0, minutes: 30 } }); }); }); // Handle tab creation chrome.tabs.onCreated.addListener((tab) => { chrome.storage.sync.get("tabs", (data) => { const storedTabs = data.tabs || []; storedTabs.push(new Tab(tab.id, tab.title || "New Tab", tab.url || "", tab.favIconUrl || "", tab.lastAccessed)); console.log("Tabs before ", storedTabs); chrome.storage.sync.set({ tabs: storedTabs }, () => { console.log("Tabs after ", storedTabs); }); }); }); // Handle tab updates chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (tab.status === "complete") { chrome.storage.sync.get("tabs", (data) => { const storedTabs = data.tabs || []; const updatedTabs = storedTabs.map((storedTab) => { if (storedTab.id === tabId) { return new Tab(tab.id, tab.title || storedTab.title, tab.url || storedTab.url, tab.favIconUrl || storedTab.tabFavicon, tab.lastAccessed); } return storedTab; }); chrome.storage.sync.set({ tabs: updatedTabs }, () => { console.log("Tabs updated: ", updatedTabs); console.log("Tab updated: ", tab); }); }); } }); chrome.tabs.onActivated.addListener((activeInfo) => { chrome.tabs.get(activeInfo.tabId, (tab) => { chrome.storage.sync.get("tabs", (data) => { const storedTabs = data.tabs || []; const updatedTabs = storedTabs.map((storedTab) => { if (storedTab.id === tab.id) { return { ...storedTab, lastAccessed: tab.lastAccessed }; } return storedTab; }); chrome.storage.sync.set({ tabs: updatedTabs }); }); }); }); // Handle tab removal chrome.tabs.onRemoved.addListener((tabId) => { chrome.storage.sync.get("tabs", (data) => { const storedTabs = data.tabs || []; const remainingTabs = storedTabs.filter((storedTab) => storedTab.id !== tabId); chrome.storage.sync.set({ tabs: remainingTabs }, () => { console.log("Tab removed: ", tabId); console.log(remainingTabs); }); }); }); // Get inactivity threshold function getInactivityThreshold() { return new Promise((resolve) => { chrome.storage.sync.get("inactivityThreshold", (data) => { const inactivityThreshold = data.inactivityThreshold || { hours: 0, minutes: 30 }; const { minutes, hours } = inactivityThreshold; resolve((minutes + hours * 60) * 60000); }); }); } // Setup interval (async function setupInterval() { const intervalTime = await getInactivityThreshold(); setInterval(() => { // Add your periodic code here }, intervalTime); })();
原因分析
出现这种差异的核心原因是两种场景下标签页创建后的事件触发顺序与异步存储操作的并发冲突,具体拆解如下:
1. Case A(点击+号新建空白标签页)的冲突逻辑
当你点击浏览器的+号新建空白标签页时,Chrome会触发两个几乎同时发生的异步事件:
- 第一步:触发
chrome.tabs.onCreated事件,你的代码会从chrome.storage.sync读取旧标签数组,添加新标签后调用set写入更新后的数组(但这个写入是异步的,需要时间完成)。 - 第二步:由于新建的空白标签页会自动被激活,紧接着触发
chrome.tabs.onActivated事件,此时onCreated的set操作还未完成,代码读取到的还是未包含新标签的旧数组。之后代码对旧数组做map操作(找不到新标签ID,数组完全不变),再调用set把旧数组写回存储。
由于chrome.storage.sync的异步特性,onActivated的set操作大概率比onCreated的set操作后完成,这就导致onCreated刚写入的新标签数组被旧数组覆盖,看起来就像onCreated的set没有生效。
2. Case B(“在新标签页打开”)的正常逻辑
当你右键选择“在新标签页打开”时,默认情况下新建的标签页是后台打开的,不会自动激活,因此不会触发chrome.tabs.onActivated事件。这就避免了两个set操作的并发冲突:
onCreated事件触发后,读取旧数组、添加新标签、调用set写入的操作可以正常完成,不会被后续的set覆盖。- 之后当标签页加载完成(status为complete)时,
chrome.tabs.onUpdated事件触发,此时存储中已经有了该标签的ID,所以可以正常更新标签信息。
3. 内存缓存解决问题的本质
你用内存缓存后,所有标签数组的操作都基于同步的内存变量,不再依赖chrome.storage.sync的异步get/set。不管事件触发顺序如何,所有操作都直接修改同一个内存数组,再同步到存储,从根本上避免了异步并发导致的覆盖问题。
备注:内容来源于stack exchange,提问作者Vivek
相关产品推荐
相关产品推荐

