Search code examples
swiftmacoswkwebview

WKWebView: How to handle BLOB URL


I have my WKWebView when I click on a file to download it. I have a popup writing:

No application configured to open the URL blob:https// ...

I tried to register a custom URL scheme blob to WKWebView, the application crash saying that this scheme is already supported by default.

However, when I click on the file, the delegate is not called:

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

So I can't even know when a blob url is clicked to try to download the file by injecting JavaScript.

My application is for macOS.

enter image description here


Solution

  • 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>