How to Read Console Logs of Wkwebview Programmatically

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

Handling JavaScript events in WKWebView

you can use a WKUserScript and add it to the userContentController of the WKWebView's configuration.

    let config = WKWebViewConfiguration()
let source = "document.addEventListener('click', function(){ window.webkit.messageHandlers.iosListener.postMessage('click clack!'); })"
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)

this will make the script and inject it into the page when the document is finished loading. Now, you need to implement the WKScriptMessageHandler protocol to receive the message:

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("message: \(message.body)")
// and whatever other actions you want to take
}

How can i monitor requests on WKWebview?

Finally I solved it

Since I don't have control over the web view content, I injected to the WKWebview a java script that include a jQuery AJAX request listener.

When the listener catches a request it sends the native app the request body in the method:

webkit.messageHandlers.callbackHandler.postMessage(data);

The native app catches the message in a delegate called:

(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

and perform the corresponding actions

here is the relevant code:

ajaxHandler.js -

//Every time an Ajax call is being invoked the listener will recognize it and  will call the native app with the request details

$( document ).ajaxSend(function( event, request, settings ) {
callNativeApp (settings.data);
});

function callNativeApp (data) {
try {
webkit.messageHandlers.callbackHandler.postMessage(data);
}
catch(err) {
console.log('The native context does not exist yet');
}
}

My ViewController delegate are:

@interface BrowserViewController : UIViewController <UIWebViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIWebViewDelegate>

And in my viewDidLoad(), I'm creating a WKWebView:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
[self addUserScriptToUserContentController:configuration.userContentController];
appWebView = [[WKWebView alloc]initWithFrame:self.view.frame configuration:configuration];
appWebView.UIDelegate = self;
appWebView.navigationDelegate = self;
[appWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: @"http://#############"]]];

Here is the addUserScriptToUserContentController:

- (void) addUserScriptToUserContentController:(WKUserContentController *) userContentController{
NSString *jsHandler = [NSString stringWithContentsOfURL:[[NSBundle mainBundle]URLForResource:@"ajaxHandler" withExtension:@"js"] encoding:NSUTF8StringEncoding error:NULL];
WKUserScript *ajaxHandler = [[WKUserScript alloc]initWithSource:jsHandler injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[userContentController addScriptMessageHandler:self name:@"callbackHandler"];
[userContentController addUserScript:ajaxHandler];
}

How to show the inspector within your WKWebView based desktop app?

This was patched here: https://lists.webkit.org/pipermail/webkit-dev/2014-August/026790.html

Just expose the private property like this and you can use it.

@interface WKPreferences (WKPrivate)
@property (nonatomic, setter=_setDeveloperExtrasEnabled:) BOOL _developerExtrasEnabled;
@end

Now you get the "Inspect Element" menu on right click.

The only thing I still need to find out is how to show the inspector directly from code.

Programmatically select text in UIWebView

This seems to be a bug in Mobile Safari. When I toggle contentEditable to true in touchstart and set it to false in touchend it works. If I remove those lines and refresh it still works. If I close Mobile Safari, clear the cache, and then re-open the document with the lines removed it stops working again.

I've updated the code below with a working version (although I removed the long-press for simplicity).

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">

(function() {
var node,
range,
offset,
clientX,
clientY;

document.addEventListener("DOMContentLoaded", function() {
document.body.addEventListener("touchstart", function(event) {
var selection = window.getSelection();
selection.removeAllRanges();

clientX = event.touches[0].clientX;
clientY = event.touches[0].clientY;

range = document.caretRangeFromPoint(clientX, clientY);
node = range.startContainer;
offset = range.startOffset;

document.body.contentEditable = "true";
event.preventDefault();
});
document.body.addEventListener("touchmove", function(event) {
var selection = window.getSelection(),
range = document.caretRangeFromPoint(event.touches[0].clientX, event.touches[0].clientY),
newRange = document.createRange();

if(clientY < event.touches[0].clientY) {
newRange.setStart(node, offset);
newRange.setEnd(range.startContainer, range.startOffset);
}
else {
newRange.setStart(range.startContainer, range.startOffset);
newRange.setEnd(node, offset);
}

selection.removeAllRanges();
selection.addRange(newRange);

event.preventDefault();
});
document.body.addEventListener("touchend", function(event) {
document.body.contentEditable = "false";
event.preventDefault();
});
});
})();
</script>
<body>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam tempus volutpat mauris sed porta. Phasellus euismod malesuada eleifend. Donec mattis, orci quis scelerisque mattis, turpis sem pulvinar nisi, et sagittis nunc nisi sed nulla. Pellentesque pharetra consequat neque, ultrices mattis mauris auctor id. Aenean tincidunt turpis non odio gravida semper. Praesent feugiat, lorem at lacinia tristique, orci eros tincidunt leo, at adipiscing sapien felis at tellus. Phasellus ac est nec nibh posuere euismod vel vitae neque. Vestibulum mollis adipiscing urna ut tristique. Vivamus purus tortor, venenatis id aliquam nec, elementum et lacus. Praesent elementum purus eget sapien ornare laoreet. Vestibulum ac odio enim.
</body>
</head>
</html>


Related Topics



Leave a reply



Submit