How can i monitor requests on WKWebview?

How can i monitor requests on WKWebview?

I'v tried using NSURLprotocol (canInitWithRequest) but it won't monitor ajax requests (XHR), only navigation requests(document requests)


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

@Benzi Heler answer is great, but it uses jQuery which seems like is not working in WKWebView anymore, so I have found solution without using jQuery.

Here is ViewController implementation that lets you be notified every AJAX request is completed in WKWebView:

import UIKit
import WebKit

class WebViewController: UIViewController {

    private var wkWebView: WKWebView!
    private let handler = "handler"

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = WKWebViewConfiguration()
        let userScript = WKUserScript(source: getScript(), injectionTime: .atDocumentStart, forMainFrameOnly: false)
        config.userContentController.addUserScript(userScript)
        config.userContentController.add(self, name: handler)

        wkWebView = WKWebView(frame:  view.bounds, configuration: config)
        view.addSubview(wkWebView)

        if let url = URL(string: "YOUR AJAX WEBSITE") {
            wkWebView.load(URLRequest(url: url))
        } else {
            print("Wrong URL!")
        }
    }

    private func getScript() -> String {
        if let filepath = Bundle.main.path(forResource: "script", ofType: "js") {
            do {
                return try String(contentsOfFile: filepath)
            } catch {
                print(error)
            }
        } else {
            print("script.js not found!")
        }
        return ""
    }
}

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let dict = message.body as? Dictionary<String, AnyObject>, let status = dict["status"] as? Int, let responseUrl = dict["responseURL"] as? String {
            print(status)
            print(responseUrl)
        }
    }
}

Pretty standard implementation. There is a WKWebView created programmatically. There is injected script that is loaded from script.js file.

And the most important part is script.js file:

var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
    this.addEventListener("load", function() {
        var message = {"status" : this.status, "responseURL" : this.responseURL}
        webkit.messageHandlers.handler.postMessage(message);
    });
    open.apply(this, arguments);
};

userContentController delegate method will be called every time there is AJAX request loaded. I'm passing there status and responseURL, because this was what I needed in my case, but you can also get more informations about request. Here is the list of all properties and methods available: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

My solution is inspired by this answer written by @John Culviner: https://stackoverflow.com/a/27363569/3448282


If you have control of the content inside the WkWebView you can send messages to your native app using window.webkit.messageHandlers whenever you make an ajax request, which will be received as a WKScriptMessage that can be processed by whatever you've designated as your WKScriptMessageHandler. The messages can contain whatever information you wish, and will be automatically converted into native objects/values in your Objective-C or Swift code.

If you don't have control over the content you can still do this by injecting your own JavaScript via a WKUserScript to track ajax requests and send back messages using the method stated above.


You can use this to respond to requests from the WKWebView. It works similar to UIWebView.

- (void)webView:(WKWebView *)webView2 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
     
        NSString *url = [navigationAction.request.URL absoluteString];
        
       // Handle URL request internally

    }

    decisionHandler(WKNavigationActionPolicyAllow); // Will continue processing request

    decisionHandler(WKNavigationActionPolicyCancel); // Cancels request
}