Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to intercept a WKWebView request to detect which local resource files (css, js, png, ...) load together with a HTML file?

I have a HTML file which contains local resource files such as css, js and png files inside its content. These local resource files are in zip format. My app use WKWebView to display this html file. I want to find a solution to intercept the web view request to detect which local resource files are load together with this html file -> then unzip them if they are still zip format.

My HTML data content contains thousands of these local resource file so I can't unzip all of them before display content. With UIWebView, we are using NSURLProtocol subclass to intercept the request, detect local resource files and un-zip it on demand based on the html page which user is viewing.

I am having this issue when convert UIWebView to WKWebView. Similar problem was post here: https://forums.developer.apple.com/thread/87474

======= Update =======>

I figured it out by using WKURLSchemeHandler.

Note: You need to change the file scheme to a custom scheme in order to use WKURLSchemeHandler because it will not work with standard schemes like file, http, https.

1. Register custom scheme with WKWebView

    let configuration = WKWebViewConfiguration()
    configuration.setURLSchemeHandler(self, forURLScheme: "x-file")
    webView = WKWebView(frame: view.bounds, configuration: configuration)

2. Convert file scheme to the custom scheme (x-file) then load it with WKWebView

    let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
    var htmlURL = URL(fileURLWithPath: htmlPath!, isDirectory: false)                    
    htmlURL = self.changeURLScheme(newScheme: "x-file", forURL: htmlURL)
    self.webView.load(URLRequest(url: htmlURL))


3. Implement 2 methods of WKURLSchemeHandler protocol and handle 3 delegate methods of WKURLSchemeTask.

    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        print("Function: \(#function), line: \(#line)")
        print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")

        // Your local resource files will be catch here. You can determine it by checking the urlSchemeTask.request.url.
        // From here I will unzip local resource files (js, css, png,...) if they are still in zip format
        ....

        // Handle WKURLSchemeTask delegate methods
        let url = changeURLScheme(newScheme: "file", forURL: urlSchemeTask.request.url!)

        do {
            let data = try Data(contentsOf: url)

            urlSchemeTask.didReceive(URLResponse(url: urlSchemeTask.request.url!, mimeType: "text/html", expectedContentLength: data.count, textEncodingName: nil))
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        } catch {
            print("Unexpected error when get data from URL: \(url)")
        }
    }

    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        print("Function: \(#function), line: \(#line)")
        print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
    }
like image 619
Tony Pham Avatar asked Oct 14 '19 23:10

Tony Pham


2 Answers

If WKWebView loads a local html file you could just give the WKWebView access to local app resources like so:

NSURL *documentDirectoryURL = [NSURL fileURLWithPath:DOCUMENTS_DIRECTORY];

// This code gives acces to a file that is outside of our webview html file root directory
[self.webView loadFileURL:documentDirectoryURL allowingReadAccessToURL:documentDirectoryURL];

// If you use one of these loads after, they will too have access if they are in app html files
[self.webView loadRequest:<inAppHTMLFile>];
[self.webView loadHTMLString: baseURL:];

This here gives access to all files in the document directory.

I found your solution helpful when I wanted to link local resources to online pages.

WKUserContentController *contentController = [[WKUserContentController alloc]init];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
CustomFileShemeHandler *schemeHandler = [[CustomFileShemeHandler alloc] init];
[config setURLSchemeHandler:schemeHandler forURLScheme:@"myApp-images"];
config.userContentController = contentController;
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];



//CustomFileShemeHandler.m

@implementation CustomFileShemeHandler

- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
    NSURL *customFileURL = urlSchemeTask.request.URL;
    NSString *securityFilter = [NSString stringWithFormat:@"Documents/LocalAppImages"];

    if (![customFileURL.absoluteString containsString:securityFilter]) {
        return;
    }

    NSURL *fileURL = [self changeURLScheme:customFileURL toScheme:@"file"];
    NSURLRequest* fileUrlRequest = [[NSURLRequest alloc] initWithURL:fileURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:.1];

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:fileUrlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSURLResponse *response2 = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL MIMEType:response.MIMEType expectedContentLength:data.length textEncodingName:nil];
        if(error){
            [urlSchemeTask didFailWithError:error];
        }
        [urlSchemeTask didReceiveResponse:response2];
        [urlSchemeTask didReceiveData:data];
        [urlSchemeTask didFinish];
    }];

    [dataTask resume];
}

- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
    NSLog(@"DUNNO WHAT TO DO HERE");
}

- (NSURL *)changeURLScheme:(NSURL *)url toScheme:(NSString *)newScheme {
    NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
    components.scheme = newScheme;
    return components.URL;
}

With this any page open in the webView can link to a local resource using its full path an the custom scheme:

<img src="myApp-images:///<YourDocumentsAppPahtHere>/LocalAppImages/funnyDog.jpg">
like image 79
h3dkandi Avatar answered Oct 30 '22 02:10

h3dkandi


Following the answer of h3dkandi this comment helped me solve my problem: (updated code in swift)

If you first use:

 // use this in order to access images, css files etc..
 webView.loadFileURL(baseURL, allowingReadAccessTo: baseURL) 

 // use this method to actually load the html with images, css etc..
 webView.loadHTMLString(htmlString, baseURL: baseURL)

Where baseURL is the path that my local .html file exists.

like image 34
kstefanou Avatar answered Oct 30 '22 02:10

kstefanou