From 8c16c5a56a9f88deb3d92f0ba72c9479bb39e3cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 09:09:31 +0000 Subject: [PATCH 1/3] Initial plan From 8ac02105d78b9ee92601f6e9995804b87312f929 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 09:13:52 +0000 Subject: [PATCH 2/3] Fix concurrency bugs: write queue, onInstalled gate, title bug, onStartup cleanup Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/89b171b4-e2a4-4bc6-9b6e-b4407919ab0f Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com> --- unclose/bg.js | 91 +++++++++++++++++++++++++++--------------------- unclose/init.js | 9 +++-- unclose/popup.js | 6 ++-- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/unclose/bg.js b/unclose/bg.js index 58405a9..e82e159 100644 --- a/unclose/bg.js +++ b/unclose/bg.js @@ -1,43 +1,56 @@ -chrome.tabs.onUpdated.addListener(async function(tabId, changeInfo, tab) { - await ensureInitialized(); - var tabKey = "TabList-" + tabId; - var currentUrl = tab.url || await storageGet(tabKey); - var updates = {}; - if (tab.url) - updates[tabKey] = tab.url; - updates["TabIndex-" + tabId] = tab.index; - if (tab.favIconUrl) - updates["TabFavicon-" + tabId] = tab.favIconUrl; - if(tab.title != null && currentUrl) - updates["TabTitle-" + tabId] = tab.title; - else if (currentUrl) - updates["TabTitle-" + tabId] = currentUrl; - await storageSet(updates); +// Serialize all storage writes to prevent counter races on rapid tab events. +var _writeQueue = Promise.resolve(); +function enqueueWrite(asyncFn) { + _writeQueue = _writeQueue.then(asyncFn).catch((err) => { + console.error('[unclose] storage write failed:', err); + }); + return _writeQueue; +} + +chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { + enqueueWrite(async () => { + await ensureInitialized(); + var tabKey = "TabList-" + tabId; + var currentUrl = tab.url || await storageGet(tabKey); + var updates = {}; + if (tab.url) + updates[tabKey] = tab.url; + updates["TabIndex-" + tabId] = tab.index; + if (tab.favIconUrl) + updates["TabFavicon-" + tabId] = tab.favIconUrl; + if (tab.title != null) + updates["TabTitle-" + tabId] = tab.title; + else if (currentUrl) + updates["TabTitle-" + tabId] = currentUrl; + await storageSet(updates); + }); }); -chrome.tabs.onRemoved.addListener(async function(tabId, info) { - await ensureInitialized(); - // Should we record this tab? - var tabKey = "TabList-" + tabId; - var state = await storageGet({ - closeCount: 0, - actualCount: 0, - [tabKey]: null +chrome.tabs.onRemoved.addListener(function(tabId, info) { + enqueueWrite(async () => { + await ensureInitialized(); + // Should we record this tab? + var tabKey = "TabList-" + tabId; + var state = await storageGet({ + closeCount: 0, + actualCount: 0, + [tabKey]: null + }); + var url = state[tabKey]; + var re = /^(http:|https:|ftp:|file:)/; + if (url && re.test(url)) { + var closeCount = parseInt(state.closeCount, 10) || 0; + var actualCount = (parseInt(state.actualCount, 10) || 0) + 1; + var updates = { + closeCount: closeCount + 1, + actualCount: actualCount + }; + updates["ClosedTab-" + closeCount] = tabId; + updates["ClosedTabTime-" + closeCount] = new Date().getTime(); + await storageSet(updates); + await setBadgeText(); + } + else + await clear(tabId); }); - var url = state[tabKey]; - var re = /^(http:|https:|ftp:|file:)/; - if (url && re.test(url)) { - var closeCount = parseInt(state.closeCount, 10) || 0; - var actualCount = (parseInt(state.actualCount, 10) || 0) + 1; - var updates = { - closeCount: closeCount + 1, - actualCount: actualCount - }; - updates["ClosedTab-" + closeCount] = tabId; - updates["ClosedTabTime-" + closeCount] = new Date().getTime(); - await storageSet(updates); - await setBadgeText(); - } - else - await clear(tabId); }); diff --git a/unclose/init.js b/unclose/init.js index 7251ea4..6ddf2d0 100644 --- a/unclose/init.js +++ b/unclose/init.js @@ -44,10 +44,13 @@ async function init() await initialize(); } -chrome.runtime.onInstalled.addListener(async function() { - await init(); +chrome.runtime.onInstalled.addListener(async function(details) { + if (details.reason === 'install') await init(); + else await ensureInitialized(); }); chrome.runtime.onStartup.addListener(async function() { - await init(); + // chrome.storage.session is cleared automatically on browser restart; + // just reset the counters and badge. + await initialize(); }); diff --git a/unclose/popup.js b/unclose/popup.js index 57f830d..7531b27 100644 --- a/unclose/popup.js +++ b/unclose/popup.js @@ -131,7 +131,6 @@ function prev() { // Show |url| in a new tab. async function showUrl(tabId) { var state = await storageGet({ - actualCount: 0, ["TabList-" + tabId]: null, ["TabIndex-" + tabId]: null }); @@ -146,8 +145,11 @@ async function showUrl(tabId) { await chrome.tabs.create(createProperties); await clear(tabId); + // Re-read actualCount right before decrementing to minimise the race window + // with concurrent onRemoved increments. + var currentCount = parseInt(await storageGet("actualCount"), 10) || 0; await storageSet({ - actualCount: Math.max((parseInt(state.actualCount, 10) || 0) - 1, 0) + actualCount: Math.max(currentCount - 1, 0) }); await setBadgeText(); await loadContent(); From 360c75ce15a057d9f4d4e45cef67f128929eabfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 16:31:23 +0000 Subject: [PATCH 3/3] Make tab event listeners async and await enqueueWrite to keep MV3 worker alive Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/88e12e2b-d875-4235-bc17-b22a6f740199 Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com> --- unclose/bg.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unclose/bg.js b/unclose/bg.js index e82e159..6362674 100644 --- a/unclose/bg.js +++ b/unclose/bg.js @@ -7,8 +7,8 @@ function enqueueWrite(asyncFn) { return _writeQueue; } -chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { - enqueueWrite(async () => { +chrome.tabs.onUpdated.addListener(async function(tabId, changeInfo, tab) { + await enqueueWrite(async () => { await ensureInitialized(); var tabKey = "TabList-" + tabId; var currentUrl = tab.url || await storageGet(tabKey); @@ -26,8 +26,8 @@ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { }); }); -chrome.tabs.onRemoved.addListener(function(tabId, info) { - enqueueWrite(async () => { +chrome.tabs.onRemoved.addListener(async function(tabId, info) { + await enqueueWrite(async () => { await ensureInitialized(); // Should we record this tab? var tabKey = "TabList-" + tabId;