WKWebView get Javascript Errors

To handle errors, we are going to add some JavaScript to the HTML we load into our page that will notify our native code about the error.

First, you need to setup your web view with a script message handler:

let controller = WKUserContentController()
controller.add(self, name: "error")

let configuration = WKWebViewConfiguration()
configuration.userContentController = controller

let webView = WKWebView(frame: .zero, configuration: configuration)

I locally create the full HTML document and inject the following JavaScript:

window.onerror = (msg, url, line, column, error) => {
  const message = {
    message: msg,
    url: url,
    line: line,
    column: column,
    error: JSON.stringify(error)
  }

  if (window.webkit) {
    window.webkit.messageHandlers.error.postMessage(message);
  } else {
    console.log("Error:", message);
  }
};

(I have the check and log since I sometime run the generated JavaScript in a browser.) You could also use the WKUserContentController to add a WKUserScript if you want to inject it into HTML you don’t fully control.

Next when you load a string into your web view, be sure to use localhost as the base URL. If you don’t do this, all of your errors will be "Script error.".

webView.loadHTMLString(html, baseURL: URL(string: "http://localhost/")!)

Now define your WKScriptMessageHandler:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    switch message.name {
    case "error":
        // You should actually handle the error :)
        let error = (message.body as? [String: Any])?["message"] as? String ?? "unknown"
        assertionFailure("JavaScript error: \(error)")
    default:
        assertionFailure("Received invalid message: \(message.name)")
    }
}

You can add other cases to the switch statement for other messages you add to the WKUserContentController.


Try the following way.

First, inject JS code to catch the error in html content:

window.onerror = function(error) {
  alert(error); // Fire when errors occur. Just a test, not always do this.
};

Second, display the error in WKWebView delegate method:

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    let alertController = UIAlertController(title: "alert", message: message, preferredStyle: UIAlertControllerStyle.alert)
    alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil))
    self.present(alertController, animated: true, completion: nil)

    print("An error from web view: \(message)")
}

Remember to set delegate:

webView.uiDelegate = self

If you want to avoid the alerts, the alternative way is using decidePolicyForNavigationAction method to receive the JS's callback which is like window.open("error://message","_self")


In the JavaScript, hook the window onerror event to call back your script message handler:

window.onerror = function (msg, url, line) {
    window.webkit.messageHandlers.mylog.postMessage("JS: ERROR:" + msg + " @" + url + ":" + line);
};

You may just get "Script error." messages, which is a permissions issue. (documentation) Depending on your setup this can be fixed by adding a permission to your preferences. Swift:

webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")