In the iOS WKWebView, is there a way to detect when the website makes a resource request (e.g. an HttpRequest via Ajax)?
In Android, there is a very convenient method "shouldInterceptRequest", but I cannot find an equivalent in iOS.
In the iOS WKWebView, is there a way to detect when the website makes a resource request (e.g. an HttpRequest via Ajax)?
In Android, there is a very convenient method "shouldInterceptRequest", but I cannot find an equivalent in iOS.
As we know we can NOT WKWebViewConfiguration.setURLSchemeHandler
with http/https schemes because of the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: ''https' is a URL scheme that WKWebView handles natively'
This happens because WKWebView.handlesURLScheme
is called inside and generates this error in case of returned true
. Thus we can use an approach by swizzling this method and it allows to handle http/https schemes as well as custom ones.
func swizzleClassMethod(cls: AnyClass, origSelector: Selector, newSelector: Selector) {
guard let origMethod = class_getClassMethod(cls, origSelector),
let newMethod = class_getClassMethod(cls, newSelector),
let cls = object_getClass(cls)
else {
return
}
if class_addMethod(cls, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)) {
class_replaceMethod(cls, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod))
}
else {
method_exchangeImplementations(origMethod, newMethod)
}
}
extension WKWebView {
@objc
static func _handlesURLScheme(_ urlScheme: String) -> Bool {
false // Allow all schemes
}
}
...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
swizzleClassMethod(cls: WKWebView.self,
origSelector: #selector(WKWebView.handlesURLScheme),
newSelector: #selector(WKWebView._handlesURLScheme))
return true
}
Now we can set http/https schemes and intercept all fetch
and XMLHttpRequest
inside WKWebView
, for instance:
override func viewDidLoad() {
super.viewDidLoad()
let js = """
fetch('https://api.github.com/users')
.then(response => {
return response.text()
})
.then(text => {
document.getElementById("body").innerHTML += "<br> fetch:" + text;
})
var ajax = new XMLHttpRequest();
ajax.onreadystatechange = function() {
if (this.readyState == 3) {
document.getElementById("body").innerHTML += "<br> ajax:" + this.responseText;
}
};
ajax.open("GET", "https://api.github.com/users", true);
ajax.send();
"""
let script = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let config = WKWebViewConfiguration()
config.userContentController.addUserScript(script)
config.setURLSchemeHandler(self, forURLScheme: "https")
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
webView.loadHTMLString("<html><body id='body'></body></html>", baseURL: nil)
}
extension ViewController: WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
let json = #"[{"login": "mojombo","id": 1}, {"login": "defunkt", "id": 2}]"#
let response = HTTPURLResponse(
url: urlSchemeTask.request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: [ "access-control-allow-origin": "*"]
)!
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(json.data(using: .utf8)!)
urlSchemeTask.didFinish()
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
}
}
Outputs:
fetch:[{"login": "mojombo","id": 1}, {"login": "defunkt", "id": 2}]
ajax:[{"login": "mojombo","id": 1}, {"login": "defunkt", "id": 2}]
You can inspect the requests from a WKWebView
using the WKNavigationDelegate
.
You may override webView:decidePolicyFor:...
to inspect the request, check for its type (e.g. link clicked, form submitted, etc.) and inspect the associated URL
to take some action:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: view.bounds)
webView.navigationDelegate = self
view.addSubview(webView)
let url = URL(string: "https://www.google.com")!
let request = URLRequest(url: url)
webView.load(request)
}
// WKNavigationDelegate method to intercept resource requests
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// `navigationType` indicates how this request got created
// `.other` may indicate an Ajax request
// More info: https://developer.apple.com/documentation/webkit/wknavigationtype
// Update: remove the if clause for now and see if you get any result for your Ajax requests.
// if navigationAction.navigationType == .other {
if let request = navigationAction.request.url {
print("Resource Request: \(request)")
}
// }
// Allow the navigation to continue or not, depending on your business logic
decisionHandler(.allow)
}
}
navigationAction.navigationType == .other
doesn't do it. Can you remove this if statement and try again? –
Shame © 2022 - 2024 — McMap. All rights reserved.
WKWebView
has a couple of delegates with lots of methods. Have you tested to see if any of them are called for your use case? Have you done any searching? Updating your question with the results of these efforts would prove beneficial. – CavallarosetURLSchemeHandler
method on WKWebview and implementingWKURLSchemeHandler
. When you try to use http, https as custom scheme app will crash because Apple does not allow doing it. – DuntaddUserScript
that will override XMLHTTPRequest (https://mcmap.net/q/261096/-intercept-xmlhttprequest-and-modify-responsetext)? And inside of override send window.webkit.messageHandlers.{your_handler}.postMessage() (you need to register your_handler for webview). – Dunt