aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTianren Dong <tianrendong.dtr@gmail.com>2026-09-28 19:45:18 -0400
committerTianren Dong <tianrendong.dtr@gmail.com>2026-09-28 19:45:18 -0400
commitb33d18552599664901356ae27db0270df3cc3129 (patch)
tree362a08c4721e65f084390cf6976292ccda375253
parent9e4514f020ecab9af0a8b8d36f01fc802ff5123e (diff)
Create session tab groups in the tab's own window
chrome.tabs.group without createProperties.windowId creates the group in the current (user's) window and moves the tab there, so a session's automation tab left its dedicated window and became a hidden inactive tab. Make the unit mock match Chrome's behavior.
-rw-r--r--CHANGELOG.md1
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/service_worker.js8
-rw-r--r--test-suite/unit/automation-target.test.mjs11
3 files changed, 14 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94bc359..accad1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable user-facing changes to `pi-chrome`.
## 0.15.56 — 2026-09-28
+- **Pi's automation tab stays in its own window.** Joining the session tab group called `chrome.tabs.group` without a window. Chrome then creates the group in the current window (yours) and moves the tab there, where it sits as an inactive, hidden tab and trusted input is refused. This happened on a session's first page action and is the main source of the hidden-tab errors. New groups are now created in the tab's own window.
- **`chrome_fill` replaces all of a multi-line textarea.** Fill selected the old value with a triple-click, which in a textarea selects only the clicked line. The rest stayed, and the tool still reported success. Inputs and textareas now select the whole value with Chrome's `selectAll` editing command before deleting. Contenteditables are unchanged.
- **Early capture covers a new tab's first page.** `chrome_tab new` could commit its first page before the tab was recorded as Pi's, so early console/network capture skipped that load. Early capture now waits for in-flight Pi tab creations before deciding.
diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
index bcc795b..68fe8c9 100644
--- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js
+++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
@@ -1711,10 +1711,14 @@ async function groupTab(tab, title, color) {
const groupTitle = cleanGroupTitle(title);
let groupId = tab.groupId;
if (typeof groupId !== "number" || groupId < 0) {
- const existing = await findGroupByTitle(tab.windowId, groupTitle);
+ // Some browsers omit windowId on freshly created tabs; read it back instead of guessing.
+ const windowId = typeof tab.windowId === "number" ? tab.windowId : (await chrome.tabs.get(tab.id).catch(() => null))?.windowId;
+ const existing = await findGroupByTitle(windowId, groupTitle);
+ // Without createProperties.windowId Chrome creates the group in the *current* (user's) window
+ // and moves the tab there, pulling Pi's automation tab out of its own window as a hidden tab.
groupId = existing !== null
? await chrome.tabs.group({ groupId: existing, tabIds: [tab.id] })
- : await chrome.tabs.group({ tabIds: [tab.id] });
+ : await chrome.tabs.group(typeof windowId === "number" ? { tabIds: [tab.id], createProperties: { windowId } } : { tabIds: [tab.id] });
}
await chrome.tabGroups.update(groupId, { title: groupTitle, color: cleanGroupColor(color), collapsed: false });
const grouped = await chrome.tabs.get(tab.id);
diff --git a/test-suite/unit/automation-target.test.mjs b/test-suite/unit/automation-target.test.mjs
index 7f46597..73fbc77 100644
--- a/test-suite/unit/automation-target.test.mjs
+++ b/test-suite/unit/automation-target.test.mjs
@@ -87,14 +87,17 @@ function makeChrome(state, { withWindows = true, withStorage = true, withTabGrou
// Chrome closes a window automatically when its final tab is removed.
if (tab && ![...tabs.values()].some((other) => other.windowId === tab.windowId)) windows.delete(tab.windowId);
},
- group: async ({ groupId, tabIds = [] } = {}) => {
+ // Like Chrome: a new group goes into createProperties.windowId, else the *current* window
+ // (the user's), and grouped tabs move into the group's window.
+ group: async ({ groupId, tabIds = [], createProperties } = {}) => {
let gid = groupId;
if (typeof gid !== "number") {
gid = alloc.group();
- const firstTab = tabs.get(tabIds[0]);
- groups.set(gid, { id: gid, title: "", color: "grey", collapsed: false, windowId: firstTab ? firstTab.windowId : userWindowId });
+ const windowId = typeof createProperties?.windowId === "number" ? createProperties.windowId : userWindowId;
+ groups.set(gid, { id: gid, title: "", color: "grey", collapsed: false, windowId });
}
- for (const tid of tabIds) { const t = tabs.get(tid); if (t) t.groupId = gid; }
+ const groupWindow = groups.get(gid).windowId;
+ for (const tid of tabIds) { const t = tabs.get(tid); if (t) { t.groupId = gid; t.windowId = groupWindow; } }
return gid;
},
ungroup: async (id) => { const ids = Array.isArray(id) ? id : [id]; for (const tid of ids) { const t = tabs.get(tid); if (t) t.groupId = -1; } },