Skip to content

iOS: synchronous window.prompt() CapacitorCookies/CapacitorHttp isEnabled checks can hang app launch indefinitely #8664

Open

Description

@bconway888

Capacitor version: @capacitor/ios@8.5.2 / @capacitor/core@8.5.2. The relevant code is identical in the current latest (8.5.3).

Platform: iOS (WKWebView). Reproduced on iOS 27.0 and iOS 27.1 simulators. We haven't confirmed it on a physical device yet.

Summary

At document-start, before <html> is parsed, native-bridge.js makes two unconditional, synchronous window.prompt() calls to ask the native side whether CapacitorCookies and CapacitorHttp are enabled:

const isCookiesEnabled = prompt(JSON.stringify({ type: 'CapacitorCookies.isEnabled' }));
const isHttpEnabled = prompt(JSON.stringify({ type: 'CapacitorHttp' }));

window.prompt() blocks the WebContent process in IPC::Connection::waitForSyncReply until WebViewDelegationHandler.webView(_:runJavaScriptTextInputPanelWithPrompt:...) answers it on the app's main run loop.

On some cold launches the app never gets past the native launch screen. It isn't slow; it waits indefinitely. Our app enables neither plugin, so both prompts should resolve to "false" almost instantly.

Observed rate

The hang is intermittent. On a full cold cycle (uninstall → install → launch) on an iPhone 17 simulator running iOS 27.0, about 1 in 8 launches hung on the native launch screen, confirmed by screenshot over 45 s plus sample. We also saw it on an iPhone Duo simulator running iOS 27.1. A plain terminate and relaunch did not reproduce it for us.

Stack while hung

sample of the hung com.apple.WebKit.WebContent process:

WebCore::HTMLDocumentParser::pumpTokenizer
 → LocalFrame::injectUserScripts → ...
 → WebCore::LocalDOMWindow::prompt
 → WebChromeClient::runJavaScriptPrompt
 → IPC::Connection::sendSyncMessage
 → IPC::Connection::waitForSyncReply
 → __psynch_cvwait

At the same moment, the app process's main run loop is idle in mach_msg/__CFRunLoopServiceMachPort. It is not inside runJavaScriptTextInputPanelWithPrompt. The delegate never runs, so completionHandler is never called and WebContent waits forever. We couldn't establish why this early-launch IPC message sometimes isn't serviced.

Why the round-trip isn't needed

Capacitor already injects a .atDocumentStart WKUserScript carrying static config (JSExport.exportCapacitorGlobalJS, called from CapacitorBridge.exportCoreJS before exportBridgeJS). Both booleans come from static config (getPluginConfig(...).getBoolean("enabled", false)), and native knows them before the page loads.

Suggested fix

Inject them in the existing document-start user script, for example window.CapacitorNativeConfig = { cookiesEnabled, httpEnabled }. Then have native-bridge.js read that global instead of calling prompt() for the two isEnabled checks. This doesn't cover the dynamic CapacitorCookies.get/set prompts.

We prototyped exactly this as a patch-package patch against 8.5.2. It touches three files: JSExport.swift, CapacitorBridge.swift and assets/native-bridge.js. With it applied we saw 0 hangs in 15 cold launches on the iPhone 17 simulator (iOS 27.0) and 0 in 15 on the iPhone Duo simulator (iOS 27.1). We're happy to share the diff.

Repro

  1. Use any Capacitor 8 iOS app that doesn't enable CapacitorCookies or CapacitorHttp.
  2. Repeat full cold cycles (uninstall, install, launch) on an iOS 27.0 or 27.1 simulator.
  3. Some launches stay on the launch screen. sample the WebContent process to see the stack above.

Activity

  1. OS-ruimoreiramendes commented on Oct 28, 2026

    @OS-ruimoreiramendes
    Contributor

    Hey @bconway888,
    thanks for the detailed report. Could you share the patch-package patch you mentioned?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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