javascript 如何编程读取WKWebView的控制台日志

bwleehnv  于 2023-02-02  发布在  Java
关注(0)|答案(9)|浏览(341)

我正在尝试读取以编程方式加载到我的WKWebView中的Web应用程序的控制台日志。
据我的研究,这是不可能的。
我怎样才能做到这一点?

drnojrws

drnojrws1#

可以将Mac上的Safari浏览器连接到WKWebView并访问控制台。
在Safari中,打开“开发”选项卡,当iOS模拟器在WKWebView打开的情况下运行时,只需单击它即可打开控制台。

vulvrdjw

vulvrdjw2#

这对我很有效**(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)")  
    }
}
7eumitmz

7eumitmz3#

我需要一种在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)

它有一些改进:

  • 如果您决定在Web检查器中查看,它仍会调用原始日志函数
  • 它同时从logwarnerrordebug进行报告
  • 它添加了一个很好的表情符号,这样你就可以很容易地区分不同种类的日志和JS日志在Xcode控制台中脱颖而出
  • 它记录给console.log的所有参数,而不仅仅是第一个参数
  • 它记录未捕获的错误,以备需要
1l5u6lss

1l5u6lss4#

您可以重新评估(覆盖)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);
}
ergxz8rk

ergxz8rk5#

下面是Richard的答案的一个调整(好吧,有几个答案使用相同的方法),它处理console.log的字符串替换。
我之所以需要这个,是因为React错误是用像Hey you had an error:%s这样的字符串记录的,当然我需要看看%s是什么,我还使用了.documentStart,因为我想立即捕获错误。
WKUserScript添加到WKUserContentController(它是用于初始化WKWebViewWKWebViewConfiguration的一部分):

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日志处理程序函数)。

h9a6wy2h

h9a6wy2h6#

通常,控制台日志记录在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”并通过evalueJavaScript注入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
    }
qf9go6mv

qf9go6mv7#

这个页面上的一些很好的解决方案可能会神秘地崩溃你的页面。我发现这是因为一些对象(例如事件)导致JSON.stringify抛出一个exeception(例如参见How to stringify event object?
为了简单起见,我捕获了异常并继续下一步,在此过程中,我将逻辑封装到一个类中,这样userContentController对象的使用就变成了一行代码:

WebLogHandler().register(with: userContentController)

实现的源代码如下。我的注入脚本比这里的一些简单,对于我的目的,我不想听到警告/错误等,所以我可以专注于日志,我不想看到任何表情符号,但除此之外,这和苏霍姆的答案很相似这是一个练习,可以使用这些选项使类更易于配置,或者巧妙地处理有问题的对象,以便至少在部分。

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

}

izkcnapc

izkcnapc8#

Swift 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)
          }
      }
 }
i7uq4tfw

i7uq4tfw9#

请使用这个漂亮的应用程序内"Bridge"
编辑:

self.webView = [[WBWKWebView alloc] initWithFrame:self.view.bounds];
self.webView.JSBridge.interfaceName = @"WKWebViewBridge";
WBWebDebugConsoleViewController * controller = [[WBWebDebugConsoleViewController alloc] initWithConsole:_webView.console];

然后,您可以使用delegate方法:

- (void)webDebugInspectCurrentSelectedElement:(id)sender
{
// To use the referenced log values
}

相关问题