我正在尝试以编程方式读取 WKWebView 中加载的 Web 应用程序的控制台日志。
到目前为止,根据我的研究,这是不可能的。
我怎样才能实现这个目标?
这对我有用(Swift 4.2/5):
// inject JS to capture console.log output and send to iOS
let source = "function captureLog(msg) { window.webkit.messageHandlers.logHandler.postMessage(msg); } window.console.log = captureLog;"
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
webView.configuration.userContentController.addUserScript(script)
// register the bridge script that listens for the output
webView.configuration.userContentController.add(self, name: "logHandler")
然后,遵循 WKScriptMessageHandler 协议,使用以下内容获取重定向的控制台消息:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "logHandler" {
print("LOG: \(message.body)")
}
}
我需要一种在 Xcode 控制台中查看 JavaScript 日志的方法。根据 noxo 的回答,我得出以下结论:
let overrideConsole = """
function log(emoji, type, args) {
window.webkit.messageHandlers.logging.postMessage(
`${emoji} JS ${type}: ${Object.values(args)
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
.join(", ")}`
)
}
let originalLog = console.log
let originalWarn = console.warn
let originalError = console.error
let originalDebug = console.debug
console.log = function() { log("📗", "log", arguments); originalLog.apply(null, arguments) }
console.warn = function() { log("📙", "warning", arguments); originalWarn.apply(null, arguments) }
console.error = function() { log("📕", "error", arguments); originalError.apply(null, arguments) }
console.debug = function() { log("📘", "debug", arguments); originalDebug.apply(null, arguments) }
window.addEventListener("error", function(e) {
log("💥", "Uncaught", [`${e.message} at ${e.filename}:${e.lineno}:${e.colno}`])
})
"""
class LoggingMessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
let userContentController = WKUserContentController()
userContentController.add(LoggingMessageHandler(), name: "logging")
userContentController.addUserScript(WKUserScript(source: overrideConsole, injectionTime: .atDocumentStart, forMainFrameOnly: true))
let webViewConfig = WKWebViewConfiguration()
webViewConfig.userContentController = userContentController
let webView = WKWebView(frame: .zero, configuration: webViewConfig)
它有一些改进:
log
、warn
、error
和 debug
console.log
的所有参数,而不仅仅是第一个您可以重新评估(覆盖)Javascript console.log() 默认实现,以使用 window.webkit.messageHandlers.postMessage(msg) 来转发消息。然后使用 WKScriptMessageHandler ::didReceiveScriptMessage 在本机代码中拦截 javascript postMessage(msg) 调用以获取记录的消息。
步骤 1)重新评估 console.log 默认实现以使用 postMessage()
// javascript to override console.log to use messageHandlers.postmessage
NSString * js = @"var console = { log: function(msg){window.webkit.messageHandlers.logging.postMessage(msg) } };";
// evaluate js to wkwebview
[self.webView evaluateJavaScript:js completionHandler:^(id _Nullable ignored, NSError * _Nullable error) {
if (error != nil)
NSLog(@"installation of console.log() failed: %@", error);
}];
步骤 2)在 WKScriptMessageHandler::didReceiveScriptMessage 处拦截本机代码中的 javascript postMessage
- (void)viewDidLoad
{
// create message handler named "logging"
WKUserContentController *ucc = [[WKUserContentController alloc] init];
[ucc addScriptMessageHandler:self name:@"logging"];
// assign usercontentcontroller to configuration
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
[configuration setUserContentController:ucc];
// assign configuration to wkwebview
self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) configuration:configuration];
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// what ever were logged with console.log() in wkwebview arrives here in message.body property
NSLog(@"log: %@", message.body);
}
此页面上的一些原本很棒的解决方案可能会神秘地使您的页面崩溃。我发现这是因为某些对象(例如事件)导致 JSON.stringify 抛出异常(例如,请参阅如何对事件对象进行字符串化?)
为了简单起见,我捕获了异常并继续。当我这样做时,我将逻辑封装到一个类中,以便 userContentController 对象的使用变得简单:
WebLogHandler().register(with: userContentController)
实现的来源如下。我注入的脚本比这里的一些脚本更简单,出于我的目的,我实际上不想听到警告/错误等,所以我可以专注于日志,并且我不想看到任何表情符号,但除此之外,这与 Soeholm 的类似回答。这是一个练习,可以使用这些选项使类更加可配置,或者巧妙地处理有问题的对象,以便至少部分地对它们进行字符串化。
class WebLogHandler: NSObject, WKScriptMessageHandler {
let messageName = "logHandler"
lazy var scriptSource:String = {
return """
function stringifySafe(obj) {
try {
return JSON.stringify(obj)
}
catch (err) {
return "Stringify error"
}
}
function log(type, args) {
window.webkit.messageHandlers.\(messageName).postMessage(
`JS ${type}: ${Object.values(args)
.map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? stringifySafe(v) : v.toString())
.map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
.join(", ")}`
)
}
let originalLog = console.log
console.log = function() { log("log", arguments); originalLog.apply(null, arguments) }
"""
}()
func register(with userContentController: WKUserContentController) {
userContentController.add(self, name: messageName)
// inject JS to capture console.log output and send to iOS
let script = WKUserScript(source: scriptSource,
injectionTime: .atDocumentStart,
forMainFrameOnly: false)
userContentController.addUserScript(script)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
这是理查德答案的一个调整(嗯,几个答案使用相同的方法)来处理
console.log
的字符串替换。
我需要这个,因为 React 错误是用像
Hey you had an error:%s
这样的字符串记录的,当然我需要看看 %s
是什么。我还使用了 .documentStart
,因为我想立即捕获错误。
将
WKUserScript
添加到 WKUserContentController
(这是您初始化 WKWebViewConfiguration
的 WKWebView
的一部分):
let source = """
function sprintf(str, ...args) { return args.reduce((_str, val) => _str.replace(/%s|%v|%d|%f|%d/, val), str); }
function captureLog(str, ...args) { var msg = sprintf(str, ...args); window.webkit.messageHandlers.logHandler.postMessage({ msg, level: 'log' }); }
function captureWarn(str, ...args) { var msg = sprintf(str, ...args); window.webkit.messageHandlers.logHandler.postMessage({ msg, level: 'warn' }); }
function captureError(str, ...args) { var msg = sprintf(str, ...args); window.webkit.messageHandlers.logHandler.postMessage({ msg, level: 'error' }); }
window.console.error = captureError; window.console.warn = captureWarn;
window.console.log = captureLog; window.console.debug = captureLog; window.console.info = captureLog;
"""
let script = WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: false)
let config = WKWebViewConfiguration.init()
let userContentController = WKUserContentController()
config.userContentController = userContentController
userContentController.addUserScript(script)
// ... potentially somewhere else ...
let webView = WKWebView(frame: CGRect.zero, configuration: config)
...并在您实现
NSObject
的任何类中处理它(提示:必须是 WKScriptMessageHandler
):
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard
let body = message.body as? [String:Any],
let msg = body["msg"] as? String,
let level = body["level"] as? String
else {
assert(false)
return
}
NSLog("WEB \(level): \(msg)")
}
将
NSLog
替换为您实际使用的任何内容(即您的自定义 Swift 日志处理函数)。
通常控制台日志记录在 js 中定义为
"window.addEventListener("message",function(e){console.log(e.data)});"
我的答案改编自handling-javascript-events-in-wkwebview!
通过配置初始化WKWebView
let config = WKWebViewConfiguration()
let source = "document.addEventListener('message', function(e){
window.webkit.messageHandlers.iosListener.postMessage(e.data); })"
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
config.userContentController.addUserScript(script)
config.userContentController.add(self, name: "iosListener")
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
或者使用 KVO 观察属性“estimatedProgress”并通过评估 JavaScript 来注入 js
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"estimatedProgress"])
{
CGFloat progress = [change[NSKeyValueChangeNewKey] floatValue];
if (progress>= 0.9)
{
NSString *jsCmd = @"window.addEventListener(\"message\",function(e){window.webkit.messageHandlers.iosListener.postMessage(e.data)});";
//@"document.addEventListener('click', function(e){ window.webkit.messageHandlers.iosListener.postMessage('Customize click'); })";
[_webView evaluateJavaScript:jsCmd completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
NSLog(@"error:%@",error);
}];
}
}
}
实现WKScriptMessageHandler协议来接收消息:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
{
print("message: \(message.body)")
// and whatever other actions you want to take
}
斯威夫特 4.2 和 5
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("your javascript string") { (value, error) in
if let errorMessage = (error! as NSError).userInfo["WKJavaScriptExceptionMessage"] as? String {
print(errorMessage)
}
}
}
请使用这个漂亮的应用内“Bridge”
编辑:
self.webView = [[WBWKWebView alloc] initWithFrame:self.view.bounds];
self.webView.JSBridge.interfaceName = @"WKWebViewBridge";
WBWebDebugConsoleViewController * controller = [[WBWebDebugConsoleViewController alloc] initWithConsole:_webView.console];
然后就可以使用委托方法了:
- (void)webDebugInspectCurrentSelectedElement:(id)sender
{
// To use the referenced log values
}