From 69d7159eef27d541cf01ea3ea1471ef0ce3a7677 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Sun, 8 Nov 2026 16:10:10 +0000 Subject: overlay.clear: run in the page's own world with the click the fallback uses The isolated-world body events did not free a 1Password-blocked tab; the DOM click fallback (MAIN world, full pointer event shape, el.click()) did. Use that shape, aimed at the page root so no control is clicked. --- .../browser-extension/service_worker.js | 18 +++++++++++------- test-suite/unit/overlay.test.mjs | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js index b1710ef..e3f5a05 100644 --- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js +++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js @@ -709,7 +709,7 @@ async function overlayBlockedError(tabId) { } // Injected into the page by overlay.clear. Removes other extensions' iframes (including inside -// open shadow roots), blurs the focused field, sends Escape and clicks the page body. +// open shadow roots), blurs the focused field, sends Escape and clicks the page root. function clearForeignOverlays(ownId) { const removed = []; const visit = (root) => { @@ -727,11 +727,15 @@ function clearForeignOverlays(ownId) { if (active && active !== document.body && typeof active.blur === "function") active.blur(); document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", code: "Escape", bubbles: true })); // A click outside is what closes a password manager's inline menu (measured on 1Password: this, - // not Escape or blur, is what freed the tab). Sent to the body, so no page control is clicked. - for (const type of ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]) { - const Ctor = type.startsWith("pointer") && typeof PointerEvent === "function" ? PointerEvent : MouseEvent; - document.body.dispatchEvent(new Ctor(type, { bubbles: true, cancelable: true, view: window, button: 0, clientX: 900, clientY: 500 })); - } + // not Escape or blur, is what freed the tab). Same event shape as domClickFallback, which worked, + // sent to the page root so no page control is clicked. + const root = document.documentElement; + const init = { bubbles: true, cancelable: true, view: window, clientX: 900, clientY: 500, button: 0, buttons: 1 }; + root.dispatchEvent(new PointerEvent("pointerdown", { ...init, pointerId: 1, pointerType: "mouse", isPrimary: true })); + root.dispatchEvent(new MouseEvent("mousedown", init)); + root.dispatchEvent(new PointerEvent("pointerup", { ...init, pointerId: 1, pointerType: "mouse", isPrimary: true, buttons: 0 })); + root.dispatchEvent(new MouseEvent("mouseup", { ...init, buttons: 0 })); + root.click(); return { removed }; } @@ -739,7 +743,7 @@ async function clearOverlay(params) { const tab = await getTabByParams(params, { createOwnedTarget: false }); let injected = { removed: [] }; try { - const [first] = await executeScriptTimed({ target: { tabId: tab.id }, func: clearForeignOverlays, args: [chrome.runtime.id] }, "chrome_clear_overlay"); + const [first] = await executeScriptTimed({ target: { tabId: tab.id, frameIds: [0] }, world: "MAIN", func: clearForeignOverlays, args: [chrome.runtime.id] }, "chrome_clear_overlay"); injected = first?.result ?? injected; } catch (error) { throw new Error(`Could not run the clean-up on tab ${tab.id}: ${error?.message || error}`); diff --git a/test-suite/unit/overlay.test.mjs b/test-suite/unit/overlay.test.mjs index a4247b3..87c8fb9 100644 --- a/test-suite/unit/overlay.test.mjs +++ b/test-suite/unit/overlay.test.mjs @@ -52,6 +52,7 @@ test("overlay.clear removes the frame and reports the tab usable", async () => { assert.deepEqual(result.removedFrames, ["chrome-extension://aeblf/inline/universal-sign-on/x.html"]); assert.equal(result.debuggerAttachable, true); assert.equal(calls[0].options.args[0], "ours"); + assert.equal(calls[0].options.world, "MAIN"); }); test("overlay.clear says so when the tab is still blocked (toolbar popup, desktop app)", async () => { @@ -73,7 +74,7 @@ test("the injected clean-up removes foreign extension iframes, not ours, and blu let blurred = false, escaped = false; const bodyEvents = []; const sandbox = { - document: { querySelectorAll: () => [foreign, ours, page, host], activeElement: { blur: () => { blurred = true; } }, body: { dispatchEvent: (e) => { bodyEvents.push(e.type); } }, dispatchEvent: (e) => { escaped = e.key === "Escape"; } }, + document: { querySelectorAll: () => [foreign, ours, page, host], activeElement: { blur: () => { blurred = true; } }, body: {}, documentElement: { dispatchEvent: (e) => { bodyEvents.push(e.type); }, click: () => { bodyEvents.push("click"); } }, dispatchEvent: (e) => { escaped = e.key === "Escape"; } }, KeyboardEvent: class { constructor(type, init) { this.type = type; Object.assign(this, init); } }, MouseEvent: class { constructor(type) { this.type = type; } }, PointerEvent: class { constructor(type) { this.type = type; } }, -- cgit v1.2.3