From 1636db878fadca47fc6e1a9db508e74a00562ab4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:23:57 +0000
Subject: [PATCH 01/10] Initial plan
From c02484b78af4f2ba4a75395a2cc65bcc58e61f39 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:29:26 +0000
Subject: [PATCH 02/10] Modernize unclose for current Chrome extensions
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/93b36160-7632-48fe-9bd2-80cc78fb3ad3
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/background.js | 1 +
unclose/bg.js | 56 +++++++++++---------
unclose/init.js | 61 ++++++++++++++-------
unclose/manifest.json | 12 +++--
unclose/popup.html | 39 +++++++-------
unclose/popup.js | 120 +++++++++++++++++++++++++++++-------------
unclose/storage.js | 31 +++++++++++
7 files changed, 217 insertions(+), 103 deletions(-)
create mode 100644 unclose/background.js
create mode 100644 unclose/storage.js
diff --git a/unclose/background.js b/unclose/background.js
new file mode 100644
index 0000000..37f0b05
--- /dev/null
+++ b/unclose/background.js
@@ -0,0 +1 @@
+importScripts("storage.js", "init.js", "bg.js");
diff --git a/unclose/bg.js b/unclose/bg.js
index f2a8d3e..9a3b566 100644
--- a/unclose/bg.js
+++ b/unclose/bg.js
@@ -1,33 +1,41 @@
-// Replace HTML tags < >
-function quote(s) {
- var s1 = s.replace("<", "<");
- var s2 = s1.replace(">", ">");
- return s2;
-}
-
-chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
- localStorage["TabList-"+tabId] = tab.url;
- localStorage["TabIndex-"+tabId] = tab.index;
+chrome.tabs.onUpdated.addListener(async function(tabId, changeInfo, tab) {
+ await ensureInitialized();
+ var updates = {};
+ if (tab.url)
+ updates["TabList-" + tabId] = tab.url;
+ updates["TabIndex-" + tabId] = tab.index;
if (tab.favIconUrl)
- localStorage["TabFavicon-"+tabId] = tab.favIconUrl;
+ updates["TabFavicon-" + tabId] = tab.favIconUrl;
if(tab.title != null)
- localStorage["TabTitle-"+tabId] = quote(tab.title);
- else
- localStorage["TabTitle-"+tabId] = tab.url;
+ updates["TabTitle-" + tabId] = tab.title;
+ else if (tab.url)
+ updates["TabTitle-" + tabId] = tab.url;
+ await storageSet(updates);
});
-chrome.tabs.onRemoved.addListener(function(tabId, info) {
+chrome.tabs.onRemoved.addListener(async function(tabId, info) {
+ await ensureInitialized();
// Should we record this tab?
- var url = localStorage["TabList-"+tabId];
+ 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 digital = new Date();
-
- localStorage["ClosedTab-"+localStorage["closeCount"]] = tabId;
- localStorage["ClosedTabTime-"+localStorage["closeCount"]] = digital.getTime();
- localStorage["closeCount"] ++;
- localStorage["actualCount"] ++;
- setBadgeText();
+ 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] = Date.now();
+ await storageSet(updates);
+ await setBadgeText();
}
- else clear(tabId);
+ else
+ await clear(tabId);
});
diff --git a/unclose/init.js b/unclose/init.js
index 0deb60b..794c308 100644
--- a/unclose/init.js
+++ b/unclose/init.js
@@ -1,28 +1,53 @@
-function clear(tabId) {
- delete localStorage["TabList-"+tabId];
- delete localStorage["TabIndex-"+tabId];
- delete localStorage["TabTitle-"+tabId];
- delete localStorage["TabFavicon-"+tabId];
+async function clear(tabId) {
+ return storageRemove(getTabKeys(tabId));
}
-function setBadgeText() {
- var n = localStorage["actualCount"];
- if (parseInt(n) > 0)
- chrome.browserAction.setBadgeText({text:n});
+async function setBadgeText() {
+ var n = parseInt(await storageGet("actualCount"), 10) || 0;
+ if (n > 0)
+ await chrome.action.setBadgeText({text: String(n)});
else
- chrome.browserAction.setBadgeText({text:""});
+ await chrome.action.setBadgeText({text:""});
}
-function initialize() {
- localStorage["closeCount"] = 0;
- localStorage["actualCount"] = 0;
- setBadgeText();
+async function initialize() {
+ await storageSet({
+ closeCount: 0,
+ actualCount: 0
+ });
+ await setBadgeText();
}
-function init()
+async function ensureInitialized()
{
- // Deep clean: clear localStorage otherwise we get old history from previous sessions
- localStorage.clear();
+ var state = await storageGet({
+ closeCount: null,
+ actualCount: null
+ });
+ var updates = {};
- initialize();
+ if (state.closeCount === null)
+ updates.closeCount = 0;
+ if (state.actualCount === null)
+ updates.actualCount = 0;
+
+ if (Object.keys(updates).length > 0)
+ await storageSet(updates);
+
+ await setBadgeText();
}
+
+async function init()
+{
+ // Deep clean: clear storage otherwise we get old history from previous sessions
+ await storageClear();
+ await initialize();
+}
+
+chrome.runtime.onInstalled.addListener(function() {
+ init();
+});
+
+chrome.runtime.onStartup.addListener(function() {
+ init();
+});
diff --git a/unclose/manifest.json b/unclose/manifest.json
index 1e4a4a5..ed8119c 100644
--- a/unclose/manifest.json
+++ b/unclose/manifest.json
@@ -3,18 +3,22 @@
"version": "4.0",
"author": "MK & Wei Hu & Xerios",
"description": "Undo your closed tabs",
- "background_page": "background.html",
+ "manifest_version": 3,
+ "background": {
+ "service_worker": "background.js"
+ },
"icons": {
"48": "icon.png",
- "128": "icon-128.png"
- },
+ "128": "icon-128.png"
+ },
"options_page": "popup.html",
- "browser_action": {
+ "action": {
"default_icon": "icon.png",
"popup": "popup.html",
"default_title": "Undo closed tabs"
},
"permissions": [
+ "storage",
"tabs"
]
}
diff --git a/unclose/popup.html b/unclose/popup.html
index 108e37f..048359a 100644
--- a/unclose/popup.html
+++ b/unclose/popup.html
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
diff --git a/unclose/popup.js b/unclose/popup.js
index 7e2c6d0..eb74108 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -8,33 +8,51 @@ function createLink(id, url) {
return link;
}
-function loadText()
+function appendTime(parent, label)
{
- // Don't popup if there's nothing to show
- var n = localStorage["actualCount"];
- if (parseInt(n) == 0)
+ if (!label)
+ return;
+
+ var textdiv = document.createElement('span');
+ var bold = document.createElement('b');
+ bold.textContent = label;
+ textdiv.appendChild(bold);
+ parent.appendChild(textdiv);
+}
+
+async function loadText()
+{
+ await ensureInitialized();
+
+ var state = await storageGetAll();
+ var n = parseInt(state.actualCount, 10) || 0;
+ if (n == 0) {
window.close();
+ return;
+ }
+
+ while (pageNo > 0 && n <= pageNo * nItems)
+ pageNo--;
var tabId, tabUrl, tabTime;
-
- content = document.getElementById("contentDiv");
+ var content = document.getElementById("contentDiv");
// Clear
while (content.hasChildNodes())
content.removeChild(content.firstChild);
// Drop the first (pageNo*nItems) valid items
- for (j = 0, i = localStorage["closeCount"] - 1; i>=0 && j=0 && j=0 && j';
else if (hoursDifference < 4) timeTextz= '' + hoursDifference + 'hr ' + minutesDifference + 'm';
else if (hoursDifference < 24) timeTextz='' + hoursDifference + ' hr';
- textdiv2.innerHTML=timeTextz;
- text_link.appendChild(textdiv2);
+ if (timeTextz)
+ appendTime(text_link, timeTextz.replace(/<\/?b>/g, ""));
content.appendChild(text_link);
j++;
@@ -82,7 +99,7 @@ function loadText()
if (pageNo > 0)
document.getElementById("prev").style.visibility="visible";
else document.getElementById("prev").style.visibility="hidden";
- if (localStorage["actualCount"] > (pageNo+1) * nItems)
+ if (n > (pageNo+1) * nItems)
document.getElementById("next").style.visibility="visible";
else document.getElementById("next").style.visibility="hidden";
}
@@ -93,15 +110,14 @@ function loadFavicon() {
imgs[i].src = imgs[i].alt;
}
-function loadContent() {
- loadText();
+async function loadContent() {
+ await loadText();
// Delay this function a little bit in order not to halt the popup
setTimeout(loadFavicon, 500);
}
function next() {
- if (localStorage["actualCount"] > (pageNo+1) * nItems)
- pageNo++;
+ pageNo++;
loadContent();
}
@@ -112,29 +128,57 @@ function prev() {
}
// Show |url| in a new tab.
-function showUrl(tabId) {
- var url = localStorage["TabList-"+tabId];
- var index = parseInt(localStorage["TabIndex-"+tabId]);
- chrome.tabs.create({"url": url, "index": index});
- clear(tabId);
- localStorage["actualCount"] --;
- setBadgeText();
- loadContent();
+async function showUrl(tabId) {
+ var state = await storageGet({
+ actualCount: 0,
+ ["TabList-" + tabId]: null,
+ ["TabIndex-" + tabId]: null
+ });
+ var url = state["TabList-" + tabId];
+ var index = parseInt(state["TabIndex-" + tabId], 10);
+ if (!url)
+ return;
+
+ var createProperties = {"url": url};
+ if (!isNaN(index))
+ createProperties.index = index;
+
+ await chrome.tabs.create(createProperties);
+ await clear(tabId);
+ await storageSet({
+ actualCount: Math.max((parseInt(state.actualCount, 10) || 0) - 1, 0)
+ });
+ await setBadgeText();
+ await loadContent();
}
-function reset()
+async function reset()
{
// Shallow clean: only forgets history about closed tabs
- for(i = localStorage["closeCount"]-1; i >= 0; i--)
+ var state = await storageGetAll();
+ var keys = [];
+
+ for(var i = (parseInt(state.closeCount, 10) || 0) - 1; i >= 0; i--)
{
- tabId = localStorage["ClosedTab-"+i];
- delete localStorage["ClosedTab-"+i];
- delete localStorage["ClosedTabTime-"+i];
- clear(tabId);
+ var tabId = state["ClosedTab-"+i];
+ keys.push("ClosedTab-"+i, "ClosedTabTime-"+i);
+ if (tabId != null)
+ keys = keys.concat(getTabKeys(tabId));
}
- initialize();
+ await storageRemove(keys);
+ await initialize();
pageNo = 0;
window.close();
}
+
+document.addEventListener("DOMContentLoaded", function() {
+ document.body.addEventListener("selectstart", function(event) {
+ event.preventDefault();
+ });
+ document.getElementById("prev").addEventListener("click", prev);
+ document.getElementById("next").addEventListener("click", next);
+ document.getElementById("clear").addEventListener("click", reset);
+ loadContent();
+});
diff --git a/unclose/storage.js b/unclose/storage.js
new file mode 100644
index 0000000..b3ad93c
--- /dev/null
+++ b/unclose/storage.js
@@ -0,0 +1,31 @@
+function getTabKeys(tabId) {
+ return [
+ "TabList-" + tabId,
+ "TabIndex-" + tabId,
+ "TabTitle-" + tabId,
+ "TabFavicon-" + tabId
+ ];
+}
+
+async function storageGet(keyOrKeys) {
+ var data = await chrome.storage.session.get(keyOrKeys);
+ if (typeof keyOrKeys == "string")
+ return data[keyOrKeys];
+ return data;
+}
+
+async function storageGetAll() {
+ return chrome.storage.session.get(null);
+}
+
+async function storageSet(items) {
+ return chrome.storage.session.set(items);
+}
+
+async function storageRemove(keys) {
+ return chrome.storage.session.remove(keys);
+}
+
+async function storageClear() {
+ return chrome.storage.session.clear();
+}
From e1b6a07b38761ac1b253685efce205506ce6d695 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:30:38 +0000
Subject: [PATCH 03/10] Address validation feedback for unclose modernization
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/93b36160-7632-48fe-9bd2-80cc78fb3ad3
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/init.js | 8 ++++----
unclose/popup.js | 19 ++++++++++---------
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/unclose/init.js b/unclose/init.js
index 794c308..7251ea4 100644
--- a/unclose/init.js
+++ b/unclose/init.js
@@ -44,10 +44,10 @@ async function init()
await initialize();
}
-chrome.runtime.onInstalled.addListener(function() {
- init();
+chrome.runtime.onInstalled.addListener(async function() {
+ await init();
});
-chrome.runtime.onStartup.addListener(function() {
- init();
+chrome.runtime.onStartup.addListener(async function() {
+ await init();
});
diff --git a/unclose/popup.js b/unclose/popup.js
index eb74108..73c90a8 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -69,7 +69,7 @@ async function loadText()
text_link.appendChild(img);
var textdiv = document.createElement('a');
- textdiv.textContent = " " + (state["TabTitle-"+tabId] || tabUrl);
+ textdiv.textContent = state["TabTitle-"+tabId] || tabUrl;
text_link.appendChild(textdiv);
var timeTextz='';
@@ -83,13 +83,13 @@ async function loadText()
var secondsDifference = Math.floor(difference/1000);
// This next line below looks for entries over a day old
- if ( hoursDifference < 1 && minutesDifference < 1 &&secondsDifference < 60) timeTextz = ''+ secondsDifference + ' sec';
- else if (hoursDifference < 1 && minutesDifference < 10) timeTextz = ''+ minutesDifference + ' min';
- else if (hoursDifference < 1) timeTextz = ''+ minutesDifference + ' min';
- else if (hoursDifference < 4) timeTextz= '' + hoursDifference + 'hr ' + minutesDifference + 'm';
- else if (hoursDifference < 24) timeTextz='' + hoursDifference + ' hr';
+ if ( hoursDifference < 1 && minutesDifference < 1 &&secondsDifference < 60) timeTextz = secondsDifference + ' sec';
+ else if (hoursDifference < 1 && minutesDifference < 10) timeTextz = minutesDifference + ' min';
+ else if (hoursDifference < 1) timeTextz = minutesDifference + ' min';
+ else if (hoursDifference < 4) timeTextz = hoursDifference + 'hr ' + minutesDifference + 'm';
+ else if (hoursDifference < 24) timeTextz = hoursDifference + ' hr';
if (timeTextz)
- appendTime(text_link, timeTextz.replace(/<\/?b>/g, ""));
+ appendTime(text_link, timeTextz);
content.appendChild(text_link);
j++;
@@ -116,8 +116,9 @@ async function loadContent() {
setTimeout(loadFavicon, 500);
}
-function next() {
- pageNo++;
+async function next() {
+ if ((parseInt(await storageGet("actualCount"), 10) || 0) > (pageNo+1) * nItems)
+ pageNo++;
loadContent();
}
From af73dac319bfa29d8040d562bce7b2ceaacdfc54 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:31:50 +0000
Subject: [PATCH 04/10] Polish popup follow-up review nits
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/93b36160-7632-48fe-9bd2-80cc78fb3ad3
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/popup.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/unclose/popup.js b/unclose/popup.js
index 73c90a8..ce1d38e 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -69,7 +69,7 @@ async function loadText()
text_link.appendChild(img);
var textdiv = document.createElement('a');
- textdiv.textContent = state["TabTitle-"+tabId] || tabUrl;
+ textdiv.textContent = " " + (state["TabTitle-"+tabId] || tabUrl);
text_link.appendChild(textdiv);
var timeTextz='';
@@ -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
});
From 103bfe7988bf8d534165b7643151a6060d9dba95 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:32:56 +0000
Subject: [PATCH 05/10] Fix popup restore count regression
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/93b36160-7632-48fe-9bd2-80cc78fb3ad3
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/bg.js | 2 +-
unclose/popup.js | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/unclose/bg.js b/unclose/bg.js
index 9a3b566..efb3203 100644
--- a/unclose/bg.js
+++ b/unclose/bg.js
@@ -32,7 +32,7 @@ chrome.tabs.onRemoved.addListener(async function(tabId, info) {
actualCount: actualCount
};
updates["ClosedTab-" + closeCount] = tabId;
- updates["ClosedTabTime-" + closeCount] = Date.now();
+ updates["ClosedTabTime-" + closeCount] = new Date().getTime();
await storageSet(updates);
await setBadgeText();
}
diff --git a/unclose/popup.js b/unclose/popup.js
index ce1d38e..6bdc8a9 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -83,7 +83,7 @@ async function loadText()
var secondsDifference = Math.floor(difference/1000);
// This next line below looks for entries over a day old
- if ( hoursDifference < 1 && minutesDifference < 1 &&secondsDifference < 60) timeTextz = secondsDifference + ' sec';
+ if ( hoursDifference < 1 && minutesDifference < 1 && secondsDifference < 60) timeTextz = secondsDifference + ' sec';
else if (hoursDifference < 1 && minutesDifference < 10) timeTextz = minutesDifference + ' min';
else if (hoursDifference < 1) timeTextz = minutesDifference + ' min';
else if (hoursDifference < 4) timeTextz = hoursDifference + 'hr ' + minutesDifference + 'm';
@@ -131,6 +131,7 @@ function prev() {
// Show |url| in a new tab.
async function showUrl(tabId) {
var state = await storageGet({
+ actualCount: 0,
["TabList-" + tabId]: null,
["TabIndex-" + tabId]: null
});
From 47b3f0cb7912d27987ddf22b1c98eaa4ac01d308 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 08:34:31 +0000
Subject: [PATCH 06/10] Tighten tab update and popup spacing logic
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/93b36160-7632-48fe-9bd2-80cc78fb3ad3
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/bg.js | 10 ++++++----
unclose/popup.html | 13 +++++++------
unclose/popup.js | 4 ++--
3 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/unclose/bg.js b/unclose/bg.js
index efb3203..58405a9 100644
--- a/unclose/bg.js
+++ b/unclose/bg.js
@@ -1,15 +1,17 @@
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["TabList-" + tabId] = tab.url;
+ updates[tabKey] = tab.url;
updates["TabIndex-" + tabId] = tab.index;
if (tab.favIconUrl)
updates["TabFavicon-" + tabId] = tab.favIconUrl;
- if(tab.title != null)
+ if(tab.title != null && currentUrl)
updates["TabTitle-" + tabId] = tab.title;
- else if (tab.url)
- updates["TabTitle-" + tabId] = tab.url;
+ else if (currentUrl)
+ updates["TabTitle-" + tabId] = currentUrl;
await storageSet(updates);
});
diff --git a/unclose/popup.html b/unclose/popup.html
index 048359a..8687335 100644
--- a/unclose/popup.html
+++ b/unclose/popup.html
@@ -25,12 +25,13 @@
#contentDiv div:hover {
background:#E4E7FF;
}
- #contentDiv div a{
- display:inline-block;
- width:330px;
- margin: 3px 0 3px 0;
- overflow:hidden;
- }
+ #contentDiv div a{
+ display:inline-block;
+ width:330px;
+ margin: 3px 0 3px 0;
+ padding-left: 1px;
+ overflow:hidden;
+ }
#contentDiv div span,b{
padding: 3px 0 0 0;
vertical-align:top;
diff --git a/unclose/popup.js b/unclose/popup.js
index 6bdc8a9..57f830d 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -69,7 +69,7 @@ async function loadText()
text_link.appendChild(img);
var textdiv = document.createElement('a');
- textdiv.textContent = " " + (state["TabTitle-"+tabId] || tabUrl);
+ textdiv.textContent = state["TabTitle-"+tabId] || tabUrl;
text_link.appendChild(textdiv);
var timeTextz='';
@@ -83,7 +83,7 @@ async function loadText()
var secondsDifference = Math.floor(difference/1000);
// This next line below looks for entries over a day old
- if ( hoursDifference < 1 && minutesDifference < 1 && secondsDifference < 60) timeTextz = secondsDifference + ' sec';
+ if ( hoursDifference < 1 && minutesDifference < 1) timeTextz = secondsDifference + ' sec';
else if (hoursDifference < 1 && minutesDifference < 10) timeTextz = minutesDifference + ' min';
else if (hoursDifference < 1) timeTextz = minutesDifference + ' min';
else if (hoursDifference < 4) timeTextz = hoursDifference + 'hr ' + minutesDifference + 'm';
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 07/10] 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 08/10] 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 09/10] 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;
From 24269301ffac2068c6a276adcb5eb36a28143b6f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 12 May 2026 21:42:00 +0000
Subject: [PATCH 10/10] Fix remaining unclose MV3 review races
Agent-Logs-Url: https://github.com/wh5a/chrome/sessions/9965d665-d2af-432a-abf7-e8d3f0e536ea
Co-authored-by: wh5a <81014+wh5a@users.noreply.github.com>
---
unclose/bg.js | 42 ++++++++++++++++++++++++++++++++++++++++++
unclose/init.js | 36 ++++++++++++++++++++++++------------
unclose/popup.js | 28 ++++++----------------------
3 files changed, 72 insertions(+), 34 deletions(-)
diff --git a/unclose/bg.js b/unclose/bg.js
index 6362674..f93d31f 100644
--- a/unclose/bg.js
+++ b/unclose/bg.js
@@ -7,6 +7,32 @@ function enqueueWrite(asyncFn) {
return _writeQueue;
}
+async function restoreTab(tabId) {
+ await ensureInitialized();
+
+ var state = await storageGet({
+ actualCount: 0,
+ ["TabList-" + tabId]: null,
+ ["TabIndex-" + tabId]: null
+ });
+ var url = state["TabList-" + tabId];
+ var index = parseInt(state["TabIndex-" + tabId], 10);
+ if (!url)
+ return false;
+
+ var createProperties = {"url": url};
+ if (!isNaN(index))
+ createProperties.index = index;
+
+ await chrome.tabs.create(createProperties);
+ await clear(tabId);
+ await storageSet({
+ actualCount: Math.max((parseInt(state.actualCount, 10) || 0) - 1, 0)
+ });
+ await setBadgeText();
+ return true;
+}
+
chrome.tabs.onUpdated.addListener(async function(tabId, changeInfo, tab) {
await enqueueWrite(async () => {
await ensureInitialized();
@@ -54,3 +80,19 @@ chrome.tabs.onRemoved.addListener(async function(tabId, info) {
await clear(tabId);
});
});
+
+chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
+ if (!message || message.method !== "restoreTab")
+ return false;
+
+ enqueueWrite(async function() {
+ return restoreTab(message.tabId);
+ }).then(function(restored) {
+ sendResponse({ok: restored});
+ }).catch(function(error) {
+ console.error('[unclose] restore failed:', error);
+ sendResponse({ok: false});
+ });
+
+ return true;
+});
diff --git a/unclose/init.js b/unclose/init.js
index 6ddf2d0..8016c03 100644
--- a/unclose/init.js
+++ b/unclose/init.js
@@ -1,3 +1,5 @@
+var _initializationPromise = null;
+
async function clear(tabId) {
return storageRemove(getTabKeys(tabId));
}
@@ -15,32 +17,42 @@ async function initialize() {
closeCount: 0,
actualCount: 0
});
+ _initializationPromise = Promise.resolve();
await setBadgeText();
}
async function ensureInitialized()
{
- var state = await storageGet({
- closeCount: null,
- actualCount: null
- });
- var updates = {};
+ if (_initializationPromise)
+ return _initializationPromise;
- if (state.closeCount === null)
- updates.closeCount = 0;
- if (state.actualCount === null)
- updates.actualCount = 0;
+ _initializationPromise = (async function() {
+ var state = await storageGet({
+ closeCount: null,
+ actualCount: null
+ });
+ var updates = {};
- if (Object.keys(updates).length > 0)
- await storageSet(updates);
+ if (state.closeCount === null)
+ updates.closeCount = 0;
+ if (state.actualCount === null)
+ updates.actualCount = 0;
- await setBadgeText();
+ if (Object.keys(updates).length > 0)
+ await storageSet(updates);
+ })().catch(function(error) {
+ _initializationPromise = null;
+ throw error;
+ });
+
+ return _initializationPromise;
}
async function init()
{
// Deep clean: clear storage otherwise we get old history from previous sessions
await storageClear();
+ _initializationPromise = null;
await initialize();
}
diff --git a/unclose/popup.js b/unclose/popup.js
index 7531b27..515ae77 100644
--- a/unclose/popup.js
+++ b/unclose/popup.js
@@ -106,7 +106,7 @@ async function loadText()
function loadFavicon() {
var imgs = document.images;
- for (i=0; i