如何从WKWebView中捕获JavaScript错误及相关警告?
当然有办法搞定这个需求!先说说你当前代码的小问题:你写的evaluateJavaScriptWithReturn用了阻塞RunLoop的方式实现同步调用,这种做法不仅会阻塞主线程影响性能,而且只能捕获调用evaluateJavaScript时的执行错误,没法监听网页中其他JS代码运行时的错误、警告,甚至Promise未处理的拒绝这类情况。
下面给你两种常用的方案,覆盖各类JS错误和警告的捕获:
方案一:通过WKUserScript注入监听脚本,原生接收错误信息
这是最通用的方式,能捕获网页中所有JS代码的错误、警告,包括第三方脚本的问题。
步骤1:配置WKWebView并注入监听脚本
首先创建WKWebViewConfiguration,注入自定义的JS脚本,监听window.onerror、console.error、console.warn以及unhandledrejection(处理Promise未捕获的拒绝)事件,把这些信息通过window.webkit.messageHandlers发送给原生代码。
import WebKit class JSErrorHandler: NSObject, WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let errorInfo = message.body as? [String: Any] else { return } // 这里处理收到的JS错误/警告信息 switch errorInfo["type"] as? String { case "error": print("JS错误: \(errorInfo["message"] ?? "未知错误"),文件: \(errorInfo["file"] ?? "未知文件"),行号: \(errorInfo["line"] ?? 0)") case "warn": print("JS警告: \(errorInfo["message"] ?? "未知警告"),文件: \(errorInfo["file"] ?? "未知文件"),行号: \(errorInfo["line"] ?? 0)") case "unhandledrejection": print("Promise未处理拒绝: \(errorInfo["reason"] ?? "未知原因")") default: break } } } // 初始化WKWebView的配置 let errorHandler = JSErrorHandler() let userContentController = WKUserContentController() // 注入监听JS错误的脚本 let jsScript = """ // 监听全局错误 window.onerror = function(message, source, line, column, error) { window.webkit.messageHandlers.jsErrorHandler.postMessage({ type: 'error', message: message, file: source, line: line, column: column, stack: error ? error.stack : null }); return true; // 阻止默认错误上报行为,可选 }; // 监听console.error const originalConsoleError = console.error; console.error = function(...args) { window.webkit.messageHandlers.jsErrorHandler.postMessage({ type: 'error', message: args.join(' '), file: new Error().stack?.split('\\n')[1] || 'unknown', line: 0 }); originalConsoleError.apply(console, args); // 保留原console.error行为 }; // 监听console.warn const originalConsoleWarn = console.warn; console.warn = function(...args) { window.webkit.messageHandlers.jsErrorHandler.postMessage({ type: 'warn', message: args.join(' '), file: new Error().stack?.split('\\n')[1] || 'unknown', line: 0 }); originalConsoleWarn.apply(console, args); // 保留原console.warn行为 }; // 监听未处理的Promise拒绝 window.addEventListener('unhandledrejection', function(event) { window.webkit.messageHandlers.jsErrorHandler.postMessage({ type: 'unhandledrejection', reason: event.reason?.toString() || 'Unknown reason', promise: event.promise.toString() }); }); """ let userScript = WKUserScript(source: jsScript, injectionTime: .atDocumentStart, forMainFrameOnly: false) userContentController.addUserScript(userScript) userContentController.add(errorHandler, name: "jsErrorHandler") let configuration = WKWebViewConfiguration() configuration.userContentController = userContentController // 创建WKWebView let webView = WKWebView(frame: .zero, configuration: configuration)
步骤2:处理原生收到的错误信息
上面的JSErrorHandler类实现了WKScriptMessageHandler协议,在didReceive message方法里就能拿到JS传过来的错误详情,你可以根据需求把这些信息上报、存储或者展示。
方案二:捕获evaluateJavaScript调用的错误
如果你只是需要捕获自己通过evaluateJavaScript执行的代码的错误,那可以优化你原来的代码,用异步回调或者Combine/Promise的方式,避免阻塞RunLoop:
extension WKWebView { func evaluateJavaScriptAsync(_ javaScriptString: String) async throws -> Any? { return try await withCheckedThrowingContinuation { continuation in evaluateJavaScript(javaScriptString) { result, error in if let error = error { continuation.resume(throwing: error) } else { continuation.resume(returning: result) } } } } } // 使用示例 Task { do { let result = try await webView.evaluateJavaScriptAsync("your JS code here") print("JS执行结果: \(result ?? "无返回值")") } catch { print("JS执行错误: \(error)") } }
这种方式不会阻塞主线程,而且能准确捕获当前执行的JS代码的错误,但没法捕获网页中其他脚本的错误。
内容的提问来源于stack exchange,提问作者Konstantinos Natsios

