diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-09-28 20:19:57 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-28 20:19:57 -0400 |
| commit | 72233686a3fb5fabc496dc301c85beaf8aec9f0b (patch) | |
| tree | 813066f1694e887f10eb0effb3cb4f1b31e99e7a | |
| parent | 0192da77e06b6ee3d0ce04e9d43ea387372281ca (diff) | |
Drop the full-screen tab rule for automation targets (#27)
0.15.54 opened Pi's automation target as a tab in the user's window while it
was full screen, assuming macOS would merge a new window into it as a hidden
window tab. Chrome disables automatic window tabbing (app_controller_mac.mm),
so that never happens; the hidden tabs came from the tab-group bug fixed in
0.15.56. Always prefer Pi's own window.
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | extensions/chrome-profile-bridge/browser-extension/service_worker.js | 15 | ||||
| -rw-r--r-- | test-suite/unit/hidden-input.test.mjs | 18 |
3 files changed, 12 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c7bf6df..c3a447d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable user-facing changes to `pi-chrome`. ## Unreleased +- **Pi gets its own window again while your Chrome is full screen.** 0.15.54 put Pi's tab in your window during full screen, assuming macOS would merge a new window into yours as a hidden window tab. Chrome disables macOS automatic window tabbing (`chrome/browser/app_controller_mac.mm`), so that never happens. The hidden tabs seen at the time came from the tab-group bug fixed in 0.15.56. A new window opened during full screen lands on another Space, and a tab in your window is just as hidden, so the special case only cost isolation. - **`/chrome doctor` flags a hidden Pi tab.** Chrome ignores clicks and typing in hidden pages. Doctor now checks this session's own tab and says whether it is visible. If it's hidden, doctor gives the reason (inactive tab, minimized window, covered by another window) and the fix, instead of the first click or type failing. It never creates or activates a tab to check. - **Pi keeps track of its tabs across extension reloads.** Ownership of Pi's automation window and session tabs lived only in `chrome.storage.session`, which Chrome wipes when the extension reloads. The extension reloads itself on every pi-chrome update, so afterwards Pi could not close or reuse the windows and tabs it had opened, and they were left behind. Ownership is now also kept in `chrome.storage.local` and restored after a reload. A restored tab is trusted only if it still exists in the window it was recorded in (adopted tabs must also still be in Pi's group). The copy is dropped on browser startup, since tab ids do not carry over between browser runs. diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js index 64ed8a9..1f178e4 100644 --- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js +++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js @@ -226,17 +226,6 @@ function isPiChromeOwnedTarget(tabId, sessionKey) { return false; } -// A new window created while the user's Chrome window is full screen does not become a visible -// standalone window: macOS either merges it into the full-screen window as a hidden native window -// tab ("Prefer tabs: in full screen") or places it on another Space. Its page is hidden, so trusted -// input cannot reach it. Use a grouped background tab in the user's window instead, which is at least -// visible in the tab strip and does not add a macOS tab bar to the user's full-screen window. -async function userWindowIsFullscreen() { - if (typeof chrome.windows?.getLastFocused !== "function") return false; - const win = await chrome.windows.getLastFocused({ windowTypes: ["normal"] }).catch(() => null); - return win?.state === "fullscreen"; -} - // Create a fresh automation target for `sessionKey`. If this session already has a tab group, // create the tab inside that group's window so one Pi session keeps one Chrome tab group (Chrome // groups cannot span windows). If no group exists yet, prefer an isolated window; fall back to a @@ -250,7 +239,7 @@ async function createAutomationTarget(sessionKey, groupTitle) { await persistAutomationTargets(); return tab; } - if (chrome.windows && typeof chrome.windows.create === "function" && !(await userWindowIsFullscreen())) { + if (chrome.windows && typeof chrome.windows.create === "function") { try { const win = await chrome.windows.create({ url: "about:blank", focused: false }); const created = win && Array.isArray(win.tabs) ? win.tabs[0] : undefined; @@ -504,7 +493,7 @@ async function pageVisibilityState(tabId) { function hiddenTabReason(tab, win) { if (win?.state === "minimized") return "its window is minimized"; if (tab && tab.active === false) return "it is an inactive tab in its window"; - if (win?.state === "fullscreen") return "its window is behind a full-screen window (on macOS, new windows can become hidden window tabs)"; + if (win?.state === "fullscreen") return "its window is behind a full-screen window or on another Space"; return "its window is covered by another window or is on another Space (Chrome treats fully covered windows as hidden)"; } diff --git a/test-suite/unit/hidden-input.test.mjs b/test-suite/unit/hidden-input.test.mjs index 4b45a49..874b5e5 100644 --- a/test-suite/unit/hidden-input.test.mjs +++ b/test-suite/unit/hidden-input.test.mjs @@ -111,15 +111,15 @@ test("visible pages, failed probes, and foreground tabs that become visible proc }); // ---- automation target placement ---- -test("automation targets use a background tab, not a new window, while the user's window is full screen", async () => { - const full = worker({ win: { id: 1, state: "fullscreen" } }); - await full.w.createAutomationTarget("s1", undefined); - assert.equal(full.calls.some((c) => c.method === "windows.create"), false); - assert.deepEqual(clone(full.calls.find((c) => c.method === "tabs.create").props), { url: "about:blank", active: false }); - - const normal = worker({ win: { id: 1, state: "maximized" } }); - await normal.w.createAutomationTarget("s2", undefined); - assert.deepEqual(clone(normal.calls.find((c) => c.method === "windows.create").props), { url: "about:blank", focused: false }); +test("automation targets get their own window even while the user's window is full screen", async () => { + // Chrome disables macOS automatic window tabbing, so a new window is never merged into the user's + // full-screen window; putting Pi's tab in the user's window would only lose isolation. + for (const state of ["fullscreen", "maximized"]) { + const h = worker({ win: { id: 1, state } }); + await h.w.createAutomationTarget(`s-${state}`, undefined); + assert.deepEqual(clone(h.calls.find((c) => c.method === "windows.create").props), { url: "about:blank", focused: false }, state); + assert.equal(h.calls.some((c) => c.method === "tabs.create"), false, state); + } }); // ---- input.debug ---- |
