Repository navigation
iOS: synchronous window.prompt() CapacitorCookies/CapacitorHttp isEnabled checks can hang app launch indefinitely #8664
Description
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
- Use any Capacitor 8 iOS app that doesn't enable
CapacitorCookiesorCapacitorHttp. - Repeat full cold cycles (uninstall, install, launch) on an iOS 27.0 or 27.1 simulator.
- Some launches stay on the launch screen.
samplethe WebContent process to see the stack above.
Activity
OS-ruimoreiramendes commentedon Oct 28, 2026
Hey @bconway888,
thanks for the detailed report. Could you share the
patch-packagepatch you mentioned?