Javascript 如何以编程方式读取wkwebview的控制台日志
我正在尝试以编程方式读取WkWebview中加载的webapp的控制台日志 到目前为止,在我的研究中,这是不可能的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
我如何才能做到这一点?请使用这个漂亮的应用程序“” 编辑: 然后,您可以使用委托方法:
- (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)
}