Skip to content

Browser.open with default fullscreen presentationStyle leaves WKContentView frame=(0,0,0,0) → blank page after dismiss #2533

@caret119

Description

@caret119

Bug Report

Capacitor Plugin(s): @capacitor/browser@8.0.3

Capacitor Version: @capacitor/core@8.3.1, @capacitor/ios@8.3.1

Platform / OS: iOS 18.6 (simulator iPhone 16 Plus, also reproduced on physical iPhone 13 mini iOS 18.6)

Xcode: 26.x (also occurs with Xcode 16.x)

Description

When Browser.open({ url }) is called with the default presentationStyle: 'fullscreen', the SFSafariViewController modal presentation detaches the underlying CAPBridgeViewController.view from its window during presentation, then reattaches it on dismiss. During this detach/reattach cycle, the underlying WKWebView's internal WKContentView becomes stuck at frame=(0, 0, 0, 0), while the outer WKWebView frame remains correct. The result is a fully blank/white screen after dismissing the in-app browser, with no possible recovery via webView.reload(), setNeedsLayout, or layoutIfNeeded.

This affects any Capacitor app that uses Browser.open with default options. The bug is most easily observed when the URL triggers a Universal Link hand-off (e.g. https://apps.apple.com/... → App Store hand-off) because the dismiss happens automatically, but it can also be reproduced with regular URLs by manual dismiss.

Reproduction

  1. Build a minimal Capacitor 8 app with @capacitor/browser installed
  2. Add a button that calls Browser.open({ url: 'https://apps.apple.com/kr/app/heri2go-clinic/id6767798415' }) (any Universal Link URL works)
  3. Tap the button, observe SFSafariViewController fullscreen modal
  4. Wait for iOS to hand off to App Store (or manually dismiss after a few seconds)
  5. Return to the app
  6. Underlying WKWebView is blank/white

Workaround

Pass presentationStyle: 'popover' to Browser.open. iPhone falls back to pageSheet, which does not detach the underlying view from its window, so WKContentView is not squeezed.

await Browser.open({ url, presentationStyle: 'popover' });

Detailed log evidence (xcrun simctl spawn ... log show ...)

Timeline (PID 53206 in our reproduction):

23:24:55.715  Browser.open called → SFSafariViewController being presented
23:24:57.486  WKContentView frame = (0 0; 430 873)   ← still normal at this moment
23:24:58.043  CAPBridgeViewController.viewDidAppear fires after dismiss
              WKContentView frame = (0 0; 0 0)        ← squeezed
23:24:58.043  setNeedsLayout / layoutIfNeeded called → no effect
23:24:58.148  webView.reload() + load(URLRequest) called → no effect
23:24:58.306  WebPageProxy::didCommitLoadForFrame
              WKContentView frame = (0 0; 0 0)        ← still squeezed after reload

The WKContentView instance pointer stays the same throughout, so it's not a fresh content view replacing an old one — the existing one is being resized to zero by some internal WebKit layout pass during the modal dismiss cycle.

Why we're reporting

We hit this in production after our app integrated linkified URLs in chat messages. The default behavior of Browser.open on iOS produces a non-recoverable blank screen for users who tap any external link. The popover workaround works but seems like an unexpected default; the documented and intuitive choice (fullscreen) silently breaks the host app's WKWebView.

A proper fix likely needs to be in the iOS implementation of @capacitor/browser — either:

  • Avoid the underlying view detach during present(_:animated:) (use a separate presentation context), or
  • Force a layout pass on the host view's WKWebView after the SFSafariViewController is dismissed.

Happy to provide a minimal reproduction repo if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions