简体   繁体   English

我的 iPhone Objective-C 代码如何在 UIWebView 中收到 Javascript 错误的通知?

[英]How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView?

I need to have my iPhone Objective-C code catch Javascript errors in a UIWebView.我需要让我的 iPhone Objective-C 代码在 UIWebView 中捕获 Javascript 错误。 That includes uncaught exceptions, syntax errors when loading files, undefined variable references, etc.这包括未捕获的异常、加载文件时的语法错误、未定义的变量引用等。

This is for a development environment, so it doesn't need to be SDK-kosher.这是一个开发环境,所以它不需要是 SDK-kosher。 In fact, it only really needs to work on the simulator.事实上,它只需要在模拟器上工作。

I've already found used some of the hidden WebKit tricks to eg expose Obj-C objects to JS and to intercept alert popups, but this one is still eluding me.我已经发现使用了一些隐藏的 WebKit 技巧,例如将 Obj-C 对象暴露给 JS 并拦截警报弹出窗口,但我仍然无法理解这一点。

[NOTE: after posting this I did find one way using a debugging delegate. [注意:发布这篇文章后,我确实找到了一种使用调试委托的方法。 Is there a way with lower overhead, using the error console / web inspector?]有没有一种方法可以降低开销,使用错误控制台/网络检查器?]

I have now found one way using the script debugger hooks in WebView (note, NOT UIWebView).我现在找到了一种在 WebView 中使用脚本调试器挂钩的方法(注意,不是 UIWebView)。 I first had to subclass UIWebView and add a method like this:我首先必须继承 UIWebView 并添加一个这样的方法:

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject {
    // save these goodies
    windowScriptObject = newWindowScriptObject;
    privateWebView = webView;

    if (scriptDebuggingEnabled) {
        [webView setScriptDebugDelegate:[[YourScriptDebugDelegate alloc] init]];
    }
}

Next you should create a YourScriptDebugDelegate class that contains methods like these:接下来,您应该创建一个包含以下方法的 YourScriptDebugDelegate 类:

// in YourScriptDebugDelegate

- (void)webView:(WebView *)webView       didParseSource:(NSString *)source
 baseLineNumber:(unsigned)lineNumber
        fromURL:(NSURL *)url
       sourceId:(int)sid
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: called didParseSource: sid=%d, url=%@", sid, url);
}

// some source failed to parse
- (void)webView:(WebView *)webView  failedToParseSource:(NSString *)source
 baseLineNumber:(unsigned)lineNumber
        fromURL:(NSURL *)url
      withError:(NSError *)error
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: called failedToParseSource: url=%@ line=%d error=%@\nsource=%@", url, lineNumber, error, source);
}

- (void)webView:(WebView *)webView   exceptionWasRaised:(WebScriptCallFrame *)frame
       sourceId:(int)sid
           line:(int)lineno
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", 
          sid, lineno, [frame functionName], [frame caller], [frame exception]);
}

There is probably a large runtime impact for this, as the debug delegate can also supply methods to be called for entering and exiting a stack frame, and for executing each line of code.这可能会对运行时产生很大的影响,因为调试委托还可以提供用于进入和退出堆栈帧以及执行每一行代码的方法。

See http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx for the Objective-C++ definition of WebScriptDebugDelegate.有关 WebScriptDebugDelegate 的 Objective-C++ 定义,请参见http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx

Those other methods:那些其他方法:

// just entered a stack frame (i.e. called a function, or started global scope)
- (void)webView:(WebView *)webView    didEnterCallFrame:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;

// about to execute some code
- (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;

// about to leave a stack frame (i.e. return from a function)
- (void)webView:(WebView *)webView   willLeaveCallFrame:(WebScriptCallFrame *)frame
      sourceId:(int)sid
          line:(int)lineno
   forWebFrame:(WebFrame *)webFrame;

Note that this is all hidden away in a private framework, so don't try to put this in code you submit to the App Store, and be prepared for some hackery to get it to work.请注意,这一切都隐藏在一个私有框架中,因此不要尝试将其放入您提交给 App Store 的代码中,并准备好接受一些黑客来使其工作。

I created a nice little drop-in category that you can add to your project... It is based on Robert Sanders solution.我创建了一个不错的小插件类别,您可以将其添加到您的项目中...它基于 Robert Sanders 解决方案。 Kudos.荣誉。

You can dowload it here:你可以在这里下载:

UIWebView+Debug UIWebView+调试

This should make it a lot easier to debug you UIWebView :)这应该会让你更容易调试 UIWebView :)

I used the great solution proposed from Robert Sanders: How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView?我使用了 Robert Sanders 提出的很棒的解决方案: 我的 iPhone Objective-C 代码如何在 UIWebView 中收到 Javascript 错误的通知?

That hook for webkit works fine also on iPhone . webkit 的那个钩子在iPhone 上也能正常工作。 Instead of standard UIWebView I allocated derived MyUIWebView.我分配了派生的 MyUIWebView,而不是标准的 UIWebView。 I needed also to define hidden classes inside MyWebScriptObjectDelegate.h:我还需要在 MyWebScriptObjectDelegate.h 中定义隐藏类:

@class WebView;
@class WebFrame;
@class WebScriptCallFrame;

Within the ios sdk 4.1 the function:在 ios sdk 4.1 中的功能:

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject 

is deprecated and instead of it I used the function:弃用,而不是我使用的功能:

- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame

Also, I get some annoying warnings like "NSObject may not respond -windowScriptObject" because the class interface is hidden.另外,我收到了一些烦人的警告,比如“NSObject 可能不会响应 -windowScriptObject”,因为类接口是隐藏的。 I ignore them and it works nice.我忽略它们,效果很好。

One way that works during development if you have Safari v 6+ (I'm uncertain what iOS version you need) is to use the Safari development tools and hook into the UIWebView through it.如果您拥有 Safari v 6+(我不确定您需要什么 iOS 版本),那么在开发过程中的一种有效方法是使用 Safari 开发工具并通过它连接到 UIWebView。

  1. In Safari: Enable the Develop Menu (Preferences > Advanced > Show Develop menu in menu bar)在 Safari 中:启用开发菜单(首选项 > 高级 > 在菜单栏中显示开发菜单)
  2. Plug your phone into the computer via the cable.通过电缆将手机插入计算机。
  3. List item列表项
  4. Load up the app (either through xcode or just launch it) and go to the screen you want to debug.加载应用程序(通过 xcode 或直接启动它)并转到要调试的屏幕。
  5. Back in Safari, open the Develop menu, look for the name of your device in that menu (mine is called iPhone 5), should be right under User Agent.返回 Safari,打开“开发”菜单,在该菜单中查找您设备的名称(我的名为 iPhone 5),应该就在“用户代理”下。
  6. Select it and you should see a drop down of the web views currently visible in your app.选择它,您应该会看到当前在您的应用程序中可见的 Web 视图的下拉列表。
  7. If you have more than one webview on the screen you can try to tell them apart by rolling over the name of the app in the develop menu.如果屏幕上有多个 webview,您可以尝试通过在开发菜单中滚动应用程序名称来区分它们。 The corresponding UIWebView will turn blue.对应的 UIWebView 会变成蓝色。
  8. Select the name of the app, the develop window opens and you can inspect the console.选择应用程序的名称,开发窗口打开,您可以检查控制台。 You can even issue JS commands through it.你甚至可以通过它发出 JS 命令。

Straight Forward Way: Put this code on top of your controller/view that is using the UIWebView直截了当的方式:将此代码放在使用 UIWebView 的控制器/视图之上

#ifdef DEBUG
@interface DebugWebDelegate : NSObject
@end
@implementation DebugWebDelegate
@class WebView;
@class WebScriptCallFrame;
@class WebFrame;
- (void)webView:(WebView *)webView   exceptionWasRaised:(WebScriptCallFrame *)frame
       sourceId:(int)sid
           line:(int)lineno
    forWebFrame:(WebFrame *)webFrame
{
    NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", 
          sid, lineno, [frame functionName], [frame caller], [frame exception]);
}
@end
@interface DebugWebView : UIWebView
id windowScriptObject;
id privateWebView;
@end
@implementation DebugWebView
- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame
{
    [sender setScriptDebugDelegate:[[DebugWebDelegate alloc] init]];
}
@end
#endif

And then instantiate it like this:然后像这样实例化它:

#ifdef DEBUG
        myWebview = [[DebugWebView alloc] initWithFrame:frame];
#else
        myWebview = [[UIWebView alloc] initWithFrame:frame];
#endif

Using #ifdef DEBUG ensures that it doesn't go in the release build, but I would also recommend commenting it out when you're not using it since it has a performance impact.使用 #ifdef DEBUG 可确保它不会出现在发布版本中,但我也建议您在不使用它时将其注释掉,因为它会影响性能。 Credit goes to Robert Sanders and Prcela for the original code归功于 Robert Sanders 和 Prcela 的原始代码

Also if using ARC you may need to add "-fno-objc-arc" to prevent some build errors.此外,如果使用 ARC,您可能需要添加“-fno-objc-arc”以防止一些构建错误。

I have created an SDK kosher error reporter that includes:我创建了一个 SDK kosher 错误报告器,其中包括:

  1. The error message错误信息
  2. The name of the file the error happens in发生错误的文件名
  3. The line number the error happens on发生错误的行号
  4. The JavaScript callstack including parameters passed JavaScript 调用堆栈,包括传递的参数

It is part of the QuickConnectiPhone framework available from the sourceForge project它是sourceForge 项目提供的 QuickConnectiPhone 框架的一部分

There is even an example application that shows how to send an error message to the Xcode terminal.甚至还有一个示例应用程序展示了如何向 Xcode 终端发送错误消息。

All you need to do is to surround your JavaScript code, including function definitions, etc. with try catch.你需要做的就是用 try catch 包围你的 JavaScript 代码,包括函数定义等。 It should look like this.它应该是这样的。

try{
//put your code here
}
catch(err){
    logError(err);
}

It doesn't work really well with compilation errors but works with all others.对于编译错误,它不能很好地工作,但可以与所有其他错误一起工作。 Even anonymous functions.甚至匿名函数。

The development blog is here is here and includes links to the wiki, sourceForge, the google group, and twitter.开发博客在这里,包括指向 wiki、sourceForge、google group 和 twitter 的链接。 Maybe this would help you out.也许这会帮助你。

I have done this in firmware 1.x but not 2.x.我在固件 1.x 但不是 2.x 中做到了这一点。 Here is the code I used in 1.x, it should at least help you on your way.这是我在 1.x 中使用的代码,它至少应该对您有所帮助。

// Dismiss Javascript alerts and telephone confirms
/*- (void)alertSheet:(UIAlertSheet*)sheet buttonClicked:(int)button
{
    if (button == 1)
    {
        [sheet setContext: nil];
    }

    [sheet dismiss];
}*/

// Javascript errors and logs
- (void) webView: (WebView*)webView addMessageToConsole: (NSDictionary*)dictionary
{
    NSLog(@"Javascript log: %@", dictionary);
}

// Javascript alerts
- (void) webView: (WebView*)webView runJavaScriptAlertPanelWithMessage: (NSString*) message initiatedByFrame: (WebFrame*) frame
{
    NSLog(@"Javascript Alert: %@", message);

    UIAlertSheet *alertSheet = [[UIAlertSheet alloc] init];
    [alertSheet setTitle: @"Javascript Alert"];
    [alertSheet addButtonWithTitle: @"OK"];
    [alertSheet setBodyText:message];
    [alertSheet setDelegate: self];
    [alertSheet setContext: self];
    [alertSheet popupAlertAnimated:YES];
}

See exception handling in iOS7: http://www.bignerdranch.com/blog/javascriptcore-example/请参阅 iOS7 中的异常处理: http : //www.bignerdranch.com/blog/javascriptcore-example/

[context setExceptionHandler:^(JSContext *context, JSValue *value) {
    NSLog(@"%@", value);
}];

First setup WebViewJavascriptBridge , then override console.error function.首先设置WebViewJavascriptBridge ,然后覆盖 console.error 函数。

In javascript在 JavaScript 中

    window.originConsoleError = console.error;
    console.error = (msg) => {
        window.originConsoleError(msg);
        bridge.callHandler("sendConsoleLogToNative", {
            action:action,
            message:message
        }, null)
    };

In Objective-C在 Objective-C 中

[self.bridge registerHandler:@"sendConsoleLogToNative" handler:^(id data, WVJBResponseCallback responseCallback) {
    NSString *action = data[@"action"];
    NSString *msg = data[@"message"];
    if (isStringValid(action)){
        if ([@"console.error" isEqualToString:action]){
            NSLog(@"JS error :%@",msg);
        }
    }
}];

对于某些情况,一个更简单的解决方案可能是将Firebug Lite添加到网页中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM