What are some methods to debug Javascript inside of a UIWebView?
If you're using iOS >= 6 and you have mountain lion (10.8) or Safari >= 6, you can just:
- Open the application in the simulator (or your device in XCode >= 4.5.x).
- Open Safari (go to
Preferences -> Advanced
and make sure "Show Develop Menu in Menubar" is on. - From the Menu-bar (of Safari) select
Develop -> iPhone Simulator -> [your webview page]
.
That's it !
Debugging on-launch Javascript in UIWebView
Important edit: in Safari 7.0, you can reload the page by selecting the "Resources" view, and clicking the refresh arrow next to the top-level page. [It seems you can also do it in at least some versions of Safari 6 by selecting the document tab, clicking the top-level page to select it, and pressing Command+R (the same shortcut used to refresh the page in normal Safari).] Breakpoints you set will still exist if you refresh the page from the Safari Web Inspector, because doing so does not cause SWI to detach the way reloading the page from within your app or the Xcode debugger does. This means that as long as the page doesn't do a Javascript redirect or trigger side-effects in the app itself, you can step through the onload Javascript by loading the page once, setting your breakpoint there, and then reloading the page from within SWI.
Original post: The only solution I was able to find was putting in an "extra" call to shouldStartLoadWithRequest:
as follows:
Add a script (not onload, synchronous) as the first element in the page head:
<script type="text/javascript">
window.location = "myapp://catchme";
</script>Set an XCode breakpoint in
shouldStartLoadWithRequest:
Edit the breakpoint to set a condition of:
(bool)[[[request URL] absoluteString] isEqualToString:@"myapp://catchme"]
(Without this condition it will stop on the initial
shouldStartLoadWithRequest:
call, which isn't what you want because the page won't yet be available to attach the Mobile Web Inspector to at this stage.)Start the page load, and when it hits the (Xcode) breakpoint, switch to Safari, and launch Mobile Web Inspector with Develop > iPhone Simulator > (my page), then switch back to Xcode and resume execution within a short window before all the resource requests on the page time out.
Debugging JS in UIWebView
For those who are desperate, apparently the only way to debug JS in a UIWebView is with caveman methods. Basically alert() everything that looks suspicious.
UIWebView - Debug javascript
I have been running quite a lot of Javascript an invisible webview.
Debugging Javascript on iOS is somewhat tricky. The following I have found helpful, although I have found no silver bullets:
- There is no
console.log
. I implemented it myself, using the same- (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
everyone else uses. - There are facilities for remote debugging in iOS 5.x. You can set break points, inspect variables etc, but no step-by-step debugger. iOS imposes a hard limit of 10s where if a javascript call is taking its time, then it will be terminated without warning. This makes this debug bridge considerably less useful.
- iOS 5.x has terrible Errors. No stack traces, no line numbers, no filenames, nothing. Quite frequently I'm stuck with a
'undefined' is not a function
with no extra help. - iOS 4.3 has better errors. Still no stack traces, but line numbers and filenames exist.
- There is a method
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
which as a long shot may report Javascript errors. It doesn't.
Things I haven't tried, but are on my list:
- Weinre looks interesting, if a little heavy for my purposes.
- JSConsole looks like a winner, though may not be very helpful for debugging errors.
Javascript console.log() in an iOS UIWebView
I have a solution to log, using javascript, to the apps debug console.
It's a bit crude, but it works.
First, we define the console.log() function in javascript, which opens and immediately removes an iframe with a ios-log: url.
// Debug
console = new Object();
console.log = function(log) {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "ios-log:#iOS#" + log);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
};
console.debug = console.log;
console.info = console.log;
console.warn = console.log;
console.error = console.log;
Now we have to catch this URL in the UIWebViewDelegate in the iOS app using the shouldStartLoadWithRequest function.
- (BOOL)webView:(UIWebView *)webView2
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
//NSLog(requestString);
if ([requestString hasPrefix:@"ios-log:"]) {
NSString* logString = [[requestString componentsSeparatedByString:@":#iOS#"] objectAtIndex:1];
NSLog(@"UIWebView console: %@", logString);
return NO;
}
return YES;
}
Capturing console.log within a UIWebView
After some more googling, I came about this answer: Javascript console.log() in an iOS UIWebView
Converting it to MonoTouch yields this solution:
using System;
using System.Web;
using System.Json;
using MonoTouch.UIKit;
namespace View
{
public class JsBridgeWebView : UIWebView
{
public object BridgeDelegate {get;set;}
private const string BRIDGE_JS = @"
function invokeNative(functionName, args) {
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'jsbridge://' + functionName + '#' + JSON.stringify(args));
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
var console = {
log: function(msg) {
invokeNative('Log', [msg]);
}
};
";
public JsBridgeWebView ()
{
ShouldStartLoad += LoadHandler;
LoadFinished += (sender, e) => {
EvaluateJavascript(BRIDGE_JS);
};
}
public bool LoadHandler (UIWebView webView, MonoTouch.Foundation.NSUrlRequest request, UIWebViewNavigationType navigationType)
{
var url = request.Url;
if(url.Scheme.Equals("jsbridge")) {
var func = url.Host;
if(func.Equals("Log")) {
// console.log
var args = JsonObject.Parse(HttpUtility.UrlDecode(url.Fragment));
var msg = (string)args[0];
Console.WriteLine(msg);
return false;
}
return true;
}
}
}
}
Now all console.log
statements in javascript in a UIWebView
will be sent to Console.WriteLine
. This could of course be extended to any kind of output one would want.
Related Topics
How to Get Last Characters of a String
Fastest Way to Copy a File in Node.Js
Detect When an Image Fails to Load in JavaScript
Should I Use Encodeuri or Encodeuricomponent for Encoding Urls
Creating an Iframe with Given HTML Dynamically
What Are Passive Event Listeners
How to Get the Index of an Object by Its Property in JavaScript
Calling Dynamic Function with Dynamic Number of Parameters
Parse Large JSON File in Nodejs
Uncaught Syntaxerror: Unexpected Token with JSON.Parse
Jquery to Load JavaScript File Dynamically
Jquery Document.Ready VS Self Calling Anonymous Function
Getting Blob Data from Xhr Request
JavaScript Parser in JavaScript
Javascript: Using a Condition in Switch Case
How to Reorder Divs Using Flex Box