4

I am accessing a website using WKWebView and on that website there is a download button. On Chrome using a laptop that downloads the file, when I click download on the iOS simulator it displays the image in the WKWebView. My goal is to save that image off and display it in a CollectionView later. If I had the URL as https://www.website.com/image.jpg then that would be easy to do, the problem is it is a blob url so the format blob:https://www.website.com/abcd-efgh-ijkl.

I found this link which is somewhat helpful: How to read a blob data URL in WKWebView?

Here is a slimmed down version of my ViewController:

import UIKit
import WebKit

class DownloadBlob: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {

    lazy var webView: WKWebView = {
        let config = WKWebViewConfiguration()
        config.userContentController.add(self, name: "readBlob")
        let webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
        webView.navigationDelegate = self
        return webView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let request = URLRequest(url: url!)
        webView.load(request)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("Finished")
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        print(navigationResponse)

        decisionHandler(.allow)
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        print(navigationAction)
        let navigationURL = navigationAction.request.url
        if (navigationURL?.absoluteString.contains("blob"))! {
            testDownloadBlob(navigationURL: navigationURL!)
        }
        decisionHandler(.allow)
    }

    func testDownloadBlob(navigationURL: URL) {

        var script = ""
        script = script + "var xhr = new XMLHttpRequest();"
        script = script + "xhr.open('GET', '\(navigationURL.absoluteString)', true);"
        script = script + "xhr.responseType = 'blob';"
        script = script + "window.webkit.messageHandlers.readBlob.postMessage('making sure script called');"
        script = script + "xhr.onload = function(e) { if (this.status == 200) { var blob = this.response; window.webkit.messageHandlers.readBlob.postMessage(blob); var reader = new window.FileReader(); reader.readAsBinaryString(blob); reader.onloadend = function() { window.webkit.messageHandlers.readBlob.postMessage(reader.result); }}};"
        script = script + "xhr.send();"

        self.webView.evaluateJavaScript(script) { (results, error) in
            print(results ?? "")
        }
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.name, message.body)
    }


}

I noticed that the xhr.onload isn't being called at all, but not sure how to fix this. I do, however, get a response with the blob in the decidePolicyFor navigationResponse.

Is there a way to do this to get the image data and save it?

Joe
  • 157
  • 1
  • 12

1 Answers1

0

This link has a complete workaround that also uses datauri. The important part is using fetch to get a blob and converting to a datauri. https://forums.developer.apple.com/thread/108394#333195
Although, I can't see any reason why the OP's postMessage of a binary string shouldn't work. If it does work, it will be more efficient than using this data uri which requires converting to base64.
Maybe the OP just didn't hook the Javascript communication right, or XHR doesn't work and fetch has to be used.

Below is just injecting Javascript into the page and making links work by intercepting the blobs and using datauris instead. Note that this is a really roundabout hack. Scan all href and replace any blob urls detected with datauri. EDIT: updated to show it running

function blobToDataURL(blob, callback) {
    var a = new FileReader();
    a.onload = function(e) {callback(e.target.result);}
    a.readAsDataURL(blob);
}
// not sure what elements you are going to intercept:
document.querySelectorAll('a').forEach(async (el)=>{
   const url = el.getAttribute('href');
   if( url.indexOf('blob:')===0 ) {
       let blob = await fetch(url).then(r => r.blob());
       blobToDataURL(blob, datauri => el.setAttribute('href',datauri));
   }
});

b=new Blob([new Int8Array([1,2,3,4,5,6,7,8,9,10]).buffer]);
test.href=URL.createObjectURL(b);
b=new Blob([new Int8Array([31,32,33,34,35]).buffer]);
test1.href=URL.createObjectURL(b);
b=new Blob([new Int8Array([51,52,53,54]).buffer]);
test2.href=URL.createObjectURL(b);



function blobToDataURL(blob, callback) {
    var a = new FileReader();
    a.onload = function(e) {callback(e.target.result);}
    a.readAsDataURL(blob);
}

document.addEventListener('click', function(event) {
  event.preventDefault();
  if ( event.target.matches('a[href^="blob:"]') )
     (async el=>{
       const url = el.href;
       const blob = await fetch(url).then(r => r.blob());
       blobToDataURL(blob, datauri => el.href=datauri);
     })(event.target);
});

// not sure what elements you are going to intercept:
/*document.querySelectorAll('a').forEach(async (el)=>{
   const url = el.href;
   if( url.indexOf('blob:')===0 ) {
       let blob = await fetch(url).then(r => r.blob());
       blobToDataURL(blob, datauri => el.href=datauri);
   }
});*/
<a id="test">test</a>
<a id="test1">test</a>
<a id="test2">test</a>

Example of data uri conversion on click:

b=new Blob([new Int8Array([1,2,3,4,5,6,7,8,9,10]).buffer]);
test.href=URL.createObjectURL(b);
b=new Blob([new Int8Array([31,32,33,34,35]).buffer]);
test1.href=URL.createObjectURL(b);
b=new Blob([new Int8Array([51,52,53,54]).buffer]);
test2.href=URL.createObjectURL(b);



function blobToDataURL(blob, callback) {
    var a = new FileReader();
    a.onload = function(e) {callback(e.target.result);}
    a.readAsDataURL(blob);
}

document.addEventListener('click', function(event) {
  if ( event.target.matches('a[href^="blob:"]') ) {
     event.preventDefault();
     (async el=>{
       const url = el.href;
       const blob = await fetch(url).then(r => r.blob());
       blobToDataURL(blob, datauri => window.open(el.href=datauri,el.target||'_self'));
     })(event.target);
   }
});

// not sure what elements you are going to intercept:
/*document.querySelectorAll('a').forEach(async (el)=>{
   const url = el.href;
   if( url.indexOf('blob:')===0 ) {
       let blob = await fetch(url).then(r => r.blob());
       blobToDataURL(blob, datauri => el.href=datauri);
   }
});*/
<a id="test">test</a>
<a id="test1">test</a>
<a id="test2">test</a>
user120242
  • 14,918
  • 3
  • 38
  • 52