Skip to content

fix: support OAuth/Sign-in-with-Google popups on both engines#17

Merged
smellouk merged 6 commits into
mainfrom
fix/oauth-signin-popups
Jun 13, 2026
Merged

fix: support OAuth/Sign-in-with-Google popups on both engines#17
smellouk merged 6 commits into
mainfrom
fix/oauth-signin-popups

Conversation

@smellouk

Copy link
Copy Markdown
Owner

Fixes #14

Problem

"Sign in with Google" (and other OAuth/SSO flows) open their consent screen in a popup via window.open() / target="_blank". Neither browser engine surfaced that popup, so sign-in could never complete — the site then falls back to a generic "cookies required" message (the symptom reported in #14 on System WebView).

  • System WebView (the engine in the report): never overrode WebChromeClient.onCreateWindow, so the popup was silently dropped.
  • GeckoView: onNewSession loaded the popup URL into the main session, navigating the page away and destroying the window.opener relationship the flow needs.

Fix

Both engines now open a real popup window and surface it to the host:

  • WebViewManager enables setSupportMultipleWindows(true) + javaScriptCanOpenWindowsAutomatically.
  • SystemWebViewEngine overrides onCreateWindow (spawns a popup WebView) and onCloseWindow (dismisses it).
  • GeckoViewEngine.onNewSession returns a new unopened child GeckoSession (per the GeckoView contract) built from the parent's settings, so it inherits the contextId and shares cookies/storage with the opener; ContentDelegate.onCloseRequest dismisses it.
  • New BrowserEngineCallback.onShowPopup/onClosePopup and BrowserEngine.closeTopPopup().
  • WebViewActivity hosts popups as full-screen overlays. For System WebView popups it re-applies the per-app isolation profile so sign-in cookies land in the app's own store (Android 13+). Back-press dismisses the topmost popup first.

Tests

Verified on a real arm64 emulator (incl. a real GeckoView runtime):

Test Engine Result
systemWebView_windowOpen_showsPopupOverlay_andBackDismisses Real System WebView
geckoView_windowOpen_showsPopupOverlay Real GeckoView (skips on CI via assumeTrue(isInstalled()))
popupHostingContract_showsAndRemovesOverlay Fake engine (shared host wiring)

Plus ExternalSchemeTest unit tests. detekt, lintDebug, testDebugUnitTest (+ Konsist) and verifyRoborazziDebug all pass.

Note

Google additionally blocks OAuth inside embedded WebViews by user-agent (disallowed_useragent) for some sites; that is independent of this popup fix and would need a Custom Tab path if it surfaces.

smellouk added 6 commits June 13, 2026 06:40
Google sign-in and other OAuth/SSO flows open their consent screen in a
popup via window.open()/target=_blank. Neither engine surfaced it:

- System WebView never overrode onCreateWindow, so the popup was silently
  dropped and sign-in could not complete.
- GeckoView loaded the popup URL into the main session, destroying the
  window.opener relationship the flow needs.

Both engines now create a real popup window and hand it to the host via
BrowserEngineCallback.onShowPopup/onClosePopup; BrowserEngine.closeTopPopup
lets the host dismiss the topmost one. WebViewManager enables multiple
windows. GeckoView returns a new unopened child session (per contract) that
inherits the parent contextId so cookies are shared with the opener.
Display engine popups (onShowPopup) as full-bleed overlays in the WebView
container and remove them on onClosePopup. For System WebView popups the
per-app isolation profile is re-applied so sign-in cookies land in the
app's own store; GeckoView popups inherit the parent contextId. Back-press
dismisses the topmost popup before navigating the page.
Real System WebView test drives window.open() and asserts the popup overlay
appears and back-press dismisses it. Real GeckoView test taps a sign-in
button (Gecko's popup blocker requires a user gesture) and asserts the
overlay appears; it is gated behind assumeTrue(isInstalled()) so it skips on
CI where the Gecko runtime is absent. A fake-engine test covers the shared
host wiring used by both engines.
…engine

isExternalScheme treated every non-http(s) URL as an external link, handing
data:/blob:/about:/javascript: to startActivity() instead of loading them in
the WebView. These are web-content schemes used by OAuth popups (window.open
+ about:blank/document.write) and JS-generated documents — externalizing them
left the popup blank so its script never ran. Only genuinely external schemes
(tel:, mailto:, intent:, custom app schemes) are dispatched to the host now.
Adds two on-device System WebView E2E tests: a popup that loads (about:blank +
document.write — Chromium blocks window.open to data: URLs) and calls
window.close() must dismiss its overlay; and a popup that postMessages its
window.opener must round-trip (opener opens a second window on receipt).
Loads https://shellify.app/tools.html and invokes its own openSelfClosing()
instead of an injected fixture, asserting the popup appears and self-dismisses.
Skips (assumeTrue) when the page is unreachable so it never reds an offline
runner. Requires the deployed page to use the about:blank popup pattern.
@smellouk smellouk merged commit c6ba923 into main Jun 13, 2026
10 checks passed
@smellouk smellouk deleted the fix/oauth-signin-popups branch June 13, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cookies

1 participant