fix(desktop): a browser pane's popup is a real tab for extensions

Merged
KI
15m ago
+1075-235

1Password's icon showed up in a "Sign in with Google" popup opened from a browser pane, but clicking it filled nothing — the credentials went into the pane behind the popup.

Why

Two defects, stacked:

  1. A popup window was not a tab. configurePopupWindow set up the scheme guard and window-open policy but never told the extension host, which called extensions.addTab for panes and extension windows only. Measured live from a background service worker: chrome.tabs.query({}) listed the pane and nothing else while the popup was open.
  2. tabs.query ignores currentWindow. electron-chrome-extensions reads the flag and never applies it, so the query returns every window's active tab and the caller takes [0]. 1Password's activeNativeTab is exactly tabs.query({active: true, currentWindow: true}), so even once the popup was a tab it still resolved to the pane.

While fixing those, four more places where a popup is second-class:

  • every window reported type: "normal", and windows.getAll ignored windowTypes — 1Password creates its own detached popout with type: "popup" and reads the type back;
  • a tab's index was always -1, and query renumbered index by the position in the result it was returning, so the field never meant position in the window;
  • chrome.runtime.getContexts (Electron's own) answers with the service worker and nothing else, so an extension cannot see its own open popup and opens a second one. chrome.extension.getViews is unfixable here — it must return Window objects from another process — which is why MV3 code reaches for getContexts first;
  • a popup window had no context menu at all: no copy or paste in a sign-in field, no back, and none of the items an extension contributes.

What changed

  • browser-manager emits popup-created; the extension host adds the window as its own single-tab window, which also covers the consent window Google opens mid-flow and windows an extension page opens.
  • The app tells the package a window's type, so pane popups and extension windows read as popup.
  • patches/electron-chrome-extensions@4.10.0.patch: currentWindow/lastFocusedWindow in tabs.query; a window focused when it is added becomes the current one (Chromium focuses a popup before did-create-window hands it over); windowType from the app plus the windowTypes filter; real tab index with no renumbering, and index/windowType filters.
  • chrome.runtime.getContexts is served from the extension host, over the existing service-worker IPC, for the extension's own pages.
  • Panes and popups share one context-menu builder; the popup leaves out the split it cannot open.

Verification

Driven in the dev app over CDP, pane on localhost with a popup window opened from it.

With a probe extension that mirrors 1Password's call chain (content script → runtime.sendMessage → tabs.query({active, currentWindow}) → tabs.sendMessage):

beforeafter
tabs.query({})pane onlypane + popup
tabs.query({active, currentWindow})the panethe popup
popup's password fieldemptyfilled
pane's field behind itfilled — wrong pageuntouched

With the real 1Password extension installed and signed in, a throwaway login saved for the test origin: clicking the in-field icon in the popup filled it, and 1Password's own background now answers tabs.query({active: true, currentWindow: true}) with the popup.

Also confirmed live in the app: windows.getAll() reports the popup as popup and {windowTypes: ["popup"]} filters to it; tabs carry index: 0 in their own window; tabs.query({windowType: "popup"}) returns the popup tab; runtime.getContexts({}) lists the service worker and an open extension page, which Electron's own implementation misses; and a right-click in a popup builds the same menu as the pane, 1Password's own item included.

Not verified visually: the native menu that Menu.popup() draws appears at the OS cursor, which a synthetic click does not move, so the check was on the menu the app builds for that window.

bun test for the touched packages is green, typecheck and lint clean, check:i18n reports no new messages (every label already existed).


Summary by cubic

Fixes the sign-in popup opened from a browser pane so extensions can act on it — 1Password's in-field icon appeared in a "Sign in with Google" popup, but clicking it filled the pane behind the popup instead.

Bug Fixes

  • A popup from a pane or an extension's own window is now a real tab for extensions, so chrome.tabs.query returns it and extension requests target the popup. It is announced only after its scheme guard and window-open policy are in place, inside a try/catch.
  • tabs.query now honors currentWindow and lastFocusedWindow, resolving the focused window the same way windows.getCurrent does, and a popup already focused when registered becomes the current window.
  • Windows report a real type (popup vs normal), windows.getAll filters on windowTypes, and tabs carry a real index that query also filters on.
  • chrome.runtime.getContexts now lists the extension's own open pages, so an extension can see its open popup instead of opening a second one.
  • Popup windows now get the same right-click menu as panes (minus the split action), which they previously lacked entirely.

Summary by CodeRabbit

  • New Features
    • Browser extensions can now access information about their background, tab, popup, and offscreen contexts.
    • Extension windows and popups are more consistently reflected in tab and window listings, including window types and tab positions.
  • Improvements
    • Web content and popup windows now share consistent context menus, with unavailable page actions disabled.
    • Extension API results better respect permissions, and tab and window queries support additional filters.
  • Compatibility
    • Native messaging APIs are no longer available.