Fix: Unhandled completion handlers cause crashes when delegate is deallocated#233
Fix: Unhandled completion handlers cause crashes when delegate is deallocated#233noah44846 wants to merge 2 commits intohotwired:mainfrom
Conversation
Guard against nil delegate in ColdBootVisit's authentication challenge handler and WKUIController's alert/confirm panel handlers. When the delegate is deallocated before WebKit fires the callback, the completion handler was silently dropped, causing an NSInternalInconsistencyException crash on the main thread.
|
Follow-up: We observed another crash where a user tapped a push notification while a JavaScript confirm dialog was visible. In our app, tapping a notification can replace the active navigator, which causes the previous one and its presented alert to disappear without any action being triggered — so the completion handler is never called. One option we considered would be to store a reference to the pending completion handler on A more defensive fix would be a small private |
Hey! We're running a production app with quite a large user base and have been seeing a few rare but hard crashes traced back to these methods. After digging into the code I think I found the cause and put together a fix — happy to be corrected if I'm reading this wrong.
The issue
Several
WKNavigationDelegate/WKUIDelegatemethods pass their mandatory completion handlers through an optional delegate chain. If the delegate has been deallocated by the time WebKit fires the callback, the optional chain silently does nothing, the handler is never called, and WebKit raisesNSInternalInconsistencyException— crashing the entire app.Affected methods
ColdBootVisit—didReceiveAuthenticationChallengeColdBootVisit.delegateis a weak reference toSession. WhileSessionstrongly owns theWKWebView, but from what I gathered WebKit's network process can keep the web view alive independently during an in-flight request. IfSessionis torn down mid-cold-boot (e.g. during a navigator replacement),ColdBootVisitcan outlive it just long enough for the challenge callback to arrive with a nil delegate.Worth noting: this challenge apparently fires on the first HTTPS connection as part of standard TLS server trust evaluation, not just pages with HTTP auth (as I originally thought) — so the race can happen on any cold boot.
WKUIController—runJavaScriptConfirmPanelWithMessageandrunJavaScriptAlertPanelWithMessageWKUIControlleris strongly owned byNavigatorviawebkitUIDelegate, butWKWebView.uiDelegateholds it weakly. IfNavigatoris torn down while WebKit is simultaneously invoking one of these methods (WebKit temporarily retains the delegate during the call),WKUIController.delegateis nil, the alert is never presented, its actions never fire, and the handler is never called.Crash reports
ColdBootVisitWKUIControllerThe fix
The pattern is the same in all cases: guard on
delegateand call the handler with a safe default if it's nil. For the auth challenge that's.performDefaultHandling, for the confirm panelfalse(mirrors the user cancelling), and for the alert panel just calling through. I also proactively fixedrunJavaScriptAlertPanelWithMessagesince it has the same pattern, even though we haven't seen it crash yet.