Javascript 如何以编程方式读取wkwebview的控制台日志

Javascript 如何以编程方式读取wkwebview的控制台日志,javascript,ios,swift,uiwebview,wkwebview,Javascript,Ios,Swift,Uiwebview,Wkwebview,我正在尝试以编程方式读取WkWebview中加载的webapp的控制台日志 到目前为止,在我的研究中,这是不可能的 我如何才能做到这一点?请使用这个漂亮的应用程序“” 编辑: 然后,您可以使用委托方法: - (void)webDebugInspectCurrentSelectedElement:(id)sender { // To use the referenced log values } 通常,控制台日志在js中定义为 "window.addEventListener("mess

我正在尝试以编程方式读取WkWebview中加载的webapp的控制台日志

到目前为止,在我的研究中,这是不可能的


我如何才能做到这一点?

请使用这个漂亮的应用程序“”

编辑:

然后,您可以使用委托方法:

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

通常,控制台日志在js中定义为

    "window.addEventListener("message",function(e){console.log(e.data)});"
我的答案改编自

使用配置初始化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”并通过evaluate 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);
                }];
            }
        }
    }
您可以重新评估(重写)Javascript console.log()默认实现,改为使用window.webkit.messageHandlers.postMessage(msg)传递消息。然后使用WKScriptMessageHandler::didReceiveScriptMessage在本机代码处截获javascript postMessage(msg)调用,以获取记录的消息

步骤1)重新评估console.log默认实现以使用postMessage()

步骤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);
}

可以将Mac上的Safari浏览器连接到WKWebView并访问控制台

在Safari中,打开“开发”选项卡,当iOS模拟器在WKWebView打开的情况下运行时,只需单击它即可打开控制台。见:


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

这对我很有效(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 overrideconosole=“”
函数日志(表情符号、类型、参数){
window.webkit.messageHandlers.logging.postMessage(
`${emoji}JS${type}:${Object.values(args)
.map(v=>typeof(v)==“未定义”?“未定义”:typeof(v)==“对象”?JSON.stringify(v):v.toString()
.map(v=>v.substring(0,3000))//将消息限制为3000个字符
.join(“,”}`
)
}
让originallo=console.log
让originalWarn=console.warn
let originalError=console.error
让originalDebug=console.debug

console.log=function(){log(“这里是Richard答案的一个调整(好吧,几个答案使用相同的方法),它处理
console.log
的字符串替换

我之所以需要这个,是因为React错误是用字符串记录的,比如
嘿,你有一个错误:%s
,当然我需要看看
%s
是什么。我还使用了
.documentStart
,因为我想立即捕获错误

WKUserScript
添加到
WKUserContentController
(这是
WKWebView配置的一部分
初始化
WKWebView
):

…并在实现
WKScriptMessageHandler
的任何类中处理它(提示:必须是
NSObject
):

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

此页面上的一些其他优秀解决方案可能会神秘地使您的页面崩溃。我发现这是因为某些对象(例如事件)导致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)
}

}

当这些消息在js文件中可用时,为什么您需要阅读这些日志?您可以在任何地方简单地存储。将加载到Webview中的这些Web应用程序已经开发,我们不能期望Web开发人员对此进行更改。当我读到这篇
let context=self.Webview.valueForKeyPath时,Yitz崩溃了(“documentView.webView.mainFrame.javaScriptContext”)作为!JSContext
。我正在使用WkwebviewYes Buddy…但我只想通过编程方式阅读我应用程序中的JS控制台日志…Buddy该桥是为UIWebView设计的。还有其他人使用过该解决方案吗(或下面的另一个?)。我尝试了这两种方法,但…从未调用过didReceiveScriptMessage:。@Locksleyu:我尝试将其作为用户脚本添加到页面末尾。它没有被调用。如果我使用此代码
函数日志记录(msg){window.webkit.messageHandlers.logging.postMessage(msg)};
并使用
日志记录('testing')调用它;
调用了
didReceiveScriptMessage
方法。不知道与此解决方案中建议的
evaluateJavaScript
是否有区别。对我来说,这意味着您不能使用
console.log()
。相反,您必须使用自定义JS函数,该函数在
消息中有一些标识符,以便您可以区分
log
和其他方法。或者使用多个处理程序。这是可行的,但帖子中有一些小问题阻止它工作;我将立即进行编辑。最重要的更改是添加一个single}在第一步计算的JavaScript末尾。注意,如果您有catch(e){console.log(e)},它甚至不会发送消息。不适用于日志对象,但您可以执行e.toString(),我在wkwebview中看到空控制台,但如果我在safari中打开该链接,我会看到日志。:(我没有“模拟器”)“我的Safari的“开发”菜单中的选项,即使模拟器正在运行。我有最新的Xcode和最新的Safari。这种情况已经持续了几个月。点击顶部菜单中的Safari,从那里开始:Safar
        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)
    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)")
}
        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)
}