JavaScript Console.Log() in an iOS Uiwebview

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

Javascript console log in UIWebView in Swift

Here's a basic implementation for the UIWebView:

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// Create the web view.
let webView = UIWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
webView.delegate = self
webView.loadRequest(URLRequest(url: URL(string: "https://www.google.com.py")!))
}
}

extension ViewController: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
let js = "console.log = function() {window.location = 'logger://webview?' + JSON.stringify(Array.prototype.slice.call(arguments))}"
webView.stringByEvaluatingJavaScript(from: js)
}

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.url?.scheme == "logger" {
guard let data = request.url?.query?.removingPercentEncoding?.data(using: .utf8) else { return true }
guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) else { return true }
guard let jsonData = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) else { return true }
guard let json = String(data: jsonData, encoding: .utf8) else { return true }
print(json)
}
return true
}
}

Example

console.log(4, 3.53, 'Hello', {d: {f: 4}}, function() {}, undefined, null, true)

Prints the following in Xcode log:

[
4,
3.5299999999999998,
"Hello",
{
"d" : {
"f" : 4
}
},
null,
null,
null,
true
]

Caveats

  • This only prints logs executed after the page has loaded.
  • Since we're using JSON.stringify, we can't print the following types: function, undefined
  • Since we're using JSON.stringify, true and false are
    printed as 1 and 0, respectively

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.

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.

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:

  1. Open the application in the simulator (or your device in XCode >= 4.5.x).
  2. Open Safari (go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.
  3. From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> [your webview page].

That's it !



Related Topics



Leave a reply



Submit