iosswiftwkwebviewfatal-errorevaluatejavascript

Fatal error while using evaluateJavaScript on WKWebView


WKWebView crashes while trying to evaluate JavaScript on Xcode 14.1, using Swift, tested on iOS but same behaviour should be on macOS.

I made a vastly simplified example to try to find a solution, and it keeps crashing:

let webView = WKWebView()
Task {
    try? await webView.evaluateJavaScript("console.log('hello world')")
}

:0: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value


Solution

  • Seems that part of the problem is method overload, as of Xcode 14.1 there are several methods named evaluateJavaScript as part of WKWebView.

    Due to optional parameters they seem to have the same signature, and the compiler is having a hard time understanding which one we mean.

    Methods

    open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
    
    open func evaluateJavaScript(_ javaScriptString: String) async throws -> Any
    
    @MainActor public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, in contentWorld: WKContentWorld, completionHandler: ((Result<Any, Error>) -> Void)? = nil)
    
    @MainActor public func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo? = nil, contentWorld: WKContentWorld) async throws -> Any?
    

    After testing different scenarios it seems that when using async/await version of these methods WKWebView expects JavaScript to return with a value (something other than Void), if there is no value returning from the JavaScript that you evaluate you will have a crash.

    Solution

    Option 1

    Always make sure JavaScript returns a value.

    Crashing:

    try? await webView.evaluateJavaScript("console.log('hello world')") // fatal error
    

    Not crashing:

    try? await webView.evaluateJavaScript("console.log('hello world'); 0")
    

    Option 2

    When not possible to return a value explicitly use the signature with a completion handler (even if you pass nil as the handler).

    webView.evaluateJavaScript("console.log('hello world')", completionHandler: nil)