8

Is there any way to get the Javascript errors from the Webview? I mean the following errors and possible warnings?

JS error

I run JS code with the following extension

```

extension WKWebView {
    func evaluateJavaScriptWithReturn(_ javaScriptString: String) -> String? {
        var finished = false
        var jsValue: String?

        evaluateJavaScript(javaScriptString) { (result, error) in
            if error == nil {
                if result != nil {
                    jsValue = result as? String
                }
            } else {
                jsValue = nil
            }
            finished = true
        }

        while !finished {
            RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
        }

        return jsValue
    }
}

```

But error is not what i'm looking for! Is there any way to get in my app, the above errors? Thanks a lot!

Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
Konstantinos Natsios
  • 2,874
  • 9
  • 39
  • 74

4 Answers4

12

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.

Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
  • 1
    Going to accept this certain answer as a correct one. I've already been handling error messages in this certain way! And mosdef I should also update my question with the way I did it. Thaks Sam! – Konstantinos Natsios Apr 05 '20 at 17:34
  • 1
    Worked for me on iOS (12, 13) to get JS errors in the Xcode log. I had to use "http://localhost/" as stated in the solution. Also, use `WKWebView` `initWithFrame:configuration:` Plus 1. – Jeff May 21 '20 at 03:14
  • Can you please elaborate on the significance of localhost? I see how base url is important for relative url resolution in the page, but how is it related to errors turning "Script error"? – Андрей Вахрушев Aug 04 '22 at 15:50
  • If you don't use `localhost` as the host in `baseURL` all errors will just be `"Script error."` instead of the real error message. – Sam Soffes Aug 11 '22 at 04:26
7

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")

Yun CHEN
  • 6,450
  • 3
  • 30
  • 33
  • 1
    Now I think using `webkit.messageHandlers` is a better solution.Details are in @3spot 's answer and this post: https://stackoverflow.com/questions/34574864/catch-javascript-event-in-ios-wkwebview-with-swift – Yun CHEN Feb 19 '19 at 01:28
7

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")
3spot
  • 1,839
  • 1
  • 11
  • 5
2

The best solution I've found is to use Safari's Web Inspector. There are instructions found here.

I kept running into Script error. messages with Yun CHEN's solution (which was great) with no actionable content.

Mr Rogers
  • 6,091
  • 2
  • 32
  • 34
  • You need to make sure your base URL is `localhost` to fix `"Script error."` see my answer for more details. – Sam Soffes Apr 04 '20 at 16:53