I need to have my iPhone Objective-C code catch Javascript errors in a UIWebView. That includes uncaught exceptions, syntax errors when loading files, undefined variable re
I have now found one way using the script debugger hooks in WebView (note, NOT UIWebView). I first had to subclass UIWebView and add a method like this:
- (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:
// 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.
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.
I created a nice little drop-in category that you can add to your project... It is based on Robert Sanders solution. Kudos.
You can dowload it here:
UIWebView+Debug
This should make it a lot easier to debug you UIWebView :)
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.
I used the great solution proposed from Robert Sanders: How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView?
That hook for webkit works fine also on iPhone. Instead of standard UIWebView I allocated derived MyUIWebView. I needed also to define hidden classes inside MyWebScriptObjectDelegate.h:
@class WebView;
@class WebFrame;
@class WebScriptCallFrame;
Within the ios sdk 4.1 the function:
- (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. I ignore them and it works nice.
I have created an SDK kosher error reporter that includes:
It is part of the QuickConnectiPhone framework available from the sourceForge project
There is even an example application that shows how to send an error message to the Xcode terminal.
All you need to do is to surround your JavaScript code, including function definitions, etc. with try catch. 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. Maybe this would help you out.
I have done this in firmware 1.x but not 2.x. Here is the code I used in 1.x, it should at least help you on your way.
// 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];
}