Bug 167341 - [iOS] Add support for the download attribute
Summary: [iOS] Add support for the download attribute
Status: ASSIGNED
Alias: None
Product: WebKit
Classification: Unclassified
Component: HTML DOM (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Chris Dumez
URL:
Keywords: InRadar
Depends on: 167337
Blocks: 156056
  Show dependency treegraph
 
Reported: 2017-01-23 16:14 PST by Chris Dumez
Modified: 2018-12-17 10:13 PST (History)
25 users (show)

See Also:


Attachments
Testcase (2.23 KB, text/html)
2018-06-18 23:01 PDT, Frédéric Wang (:fredw)
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Chris Dumez 2017-01-23 16:14:59 PST
Add support for the download attribute on iOS (for things like ebooks).
Comment 1 Chris Dumez 2017-01-23 16:18:35 PST
Test case at:
https://bugs.webkit.org/show_bug.cgi?id=162788#c15
Comment 2 Chris Dumez 2017-01-24 12:55:57 PST
When you click such link, you get a dialog saying it failed to install the profile (instead of opening the file in iBooks).
Comment 3 Radar WebKit Bug Importer 2017-01-31 15:01:57 PST
<rdar://problem/30296281>
Comment 4 Adam Lippai 2017-06-30 06:54:17 PDT
iOS 11 beta has major file handling changes. Do the new APIs help fixing this bug? Is there any chance this can be shipped with Mobile Safari 11?
Comment 5 Ben Frain 2017-12-05 12:12:34 PST
Can’t believe we are still waiting for this and it is nearly 2018. 

It’s difficult to make a simple cross-browser mobile app that allows a user to backup their config on iOS. 

Is there any timeline for this or is it another feature reserved for apps that live in the app store?

I should qualify; I’m talking about a prompt to save off to icloud/dropbox etc. Not just serve the user a dirty raw text file with no option to save.
Comment 6 Ben Frain 2018-01-23 11:42:07 PST
Just to be clear, even in ios11, it is not possible to save a file (json/text/md etc) to ‘Files’ app. 

For example visit the text file on iOS: https://wordpress.org/plugins/readme.txt

You cannot save that txt file to ‘Files’ from Safari share menu on iOS.
Comment 7 Marcela Errazquin 2018-03-16 04:20:58 PDT
Is there an update as to when this bug will be addressed? I find it hard to believe that this is not available functionality in iOS11.
Comment 8 Jonathan Lahue 2018-03-18 09:46:24 PDT
This is also something I need. I'm making an educational web app and it's a must to be able to save json files.
https://www.npmjs.com/package/file-saver works perfectly on most browsers, but not iOS 😢
Comment 9 David Morehead 2018-06-17 11:16:11 PDT
built a web app which allows users to save layered .psd files from web-gl ..on IOS I have to open the .psd in an iframe, then expect the user to figure out that they have to; select "More" (NOT "open in documents")  then "save to files" (NOT "Save Image") >> "On my ipad" >> "Procreate" ..then exit Safari, open Procreate, and open the psd.


..all of this instead of the browser standard click to download (not even long press) ..then "Always Open Files of This Type" (the first time) from then on, one click to Download & automatically Open.
Comment 10 Frédéric Wang (:fredw) 2018-06-18 23:01:01 PDT
Created attachment 343024 [details]
Testcase

Link in comment 1 seems dead, so here is a simple testcase.
Comment 11 Frédéric Wang (:fredw) 2018-06-19 01:52:43 PDT
Preliminary analysis: The code involved is the same as in bug 165627 comment 6. When the anchor is clicked, the Web Process dispatches a "decide policy for navigation action":

# WebKit::WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction
# WebCore::PolicyChecker::checkNavigationPolicy
# WebCore::FrameLoader::loadWithDocumentLoader
# WebCore::FrameLoader::loadWithNavigationAction
# WebCore::FrameLoader::loadURL
# WebCore::FrameLoader::loadFrameRequest
# WebCore::FrameLoader::urlSelected
# WebCore::FrameLoader::urlSelected
# WebCore::HTMLAnchorElement::handleClick

On the UI Process, we arrive on

# WebKit::NavigationState::NavigationClient::decidePolicyForNavigationAction
# WebKit::WebPageProxy::decidePolicyForNavigationAction

Then some proprietary code is executed and we arrive on the decideHandlerWithPolicies callback:

(1) For attachment 343024 [details] (data: URL) it returns WKNavigationActionPolicyAllow and tryAppLink fails with
    Error Domain=NSURLErrorDomain Code=-1002 "(null)" UserInfo={NSErrorFailingURLKey=data:image/png%3Bbase64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA3BAMAAAC1N6RLAAAAMFBMVEWCARyBB0a/AQ0NSo4odqvXf4D/hgZmqMnLp5ykwjP/ ... rkJggg==}
   Finally the alert message of comment 2 is displayed.

(2) For other links (e.g. https://www.w3schools.com/TAGS/tryit.asp?filename=tryhtml5_a_download) it returns _WKNavigationActionPolicyAllowWithoutTryingAppLink
   and again the alert message of comment 2 is displayed.
   However, if one forces the tryAppLink code to be executed before, we get:
   Error Domain=NSOSStatusErrorDomain Code=-10814 "(null)"

In both case, forcing _WKNavigationActionPolicyDownload still results in the same error message mentioned in comment 2.
Comment 12 Arcangelo Vicedomini 2018-07-10 03:20:08 PDT
Hello, This issue was opened 1year and 6months ago, and I really cannot understand why this is not yet resolved, after 4 years since the publication of the specs (HTML5 W3C publication was on October 2014).

Me, and a lot of other companies that I know, downloads a blob via RESTful API and uses the HTML5 `<a href="{objectUrl}" download="resume.pdf">Download resume</a>` technique, which works in all other major browsers (Chrome, FF, Edge and Opera), or the `window.saveAs` function.

I've tried both solution on my Web App and we're unable to download a single PDF to the users' iPhone or iPad.

Is there any ETA for this issue to be resolved? 

Best regards,

Arcangelo
Comment 13 Frédéric Wang (:fredw) 2018-07-10 03:31:45 PDT
(In reply to Arcangelo Vicedomini from comment #12)
> Is there any ETA for this issue to be resolved? 

As mentioned in comment 11, fixing this will certainly involve proprietary code so it is up to Apple to fix it. And in general, there is unfortunately no ETA about when a bug will be fixed in releases: https://trac.webkit.org/wiki/FAQ#WhenwillbugXXXbefixed ; sorry about that.
Comment 14 Adam Lippai 2018-09-26 07:09:50 PDT
Using Service Workers and streams we face other problems: https://github.com/jimmywarting/StreamSaver.js/issues/69#issuecomment-424716393

If it has a different cause, I'd be happy to file a new issue.
Comment 15 Henrik Joreteg 2018-12-12 23:52:58 PST
My company has built a web app that doctors use during surgery to produce anesthesia charts. The final product of the web app is a PDF that the doctors download.

The user flow is great in every major browser except Safari on iOS.

This has been a giant thorn in our side and frankly makes Apple look bad. Because we have to explain to these doctors that if they're using an iPad they have to do extra work to of saving the final report. The UX flow for this is not ideal, by the way.

Adding support for the `download` attribute, especially now that iOS 11+ has a Files app... would be a huge relief for us and our product team.

**Please** consider adding this.

Thank you!
Comment 16 jon.ronnenberg 2018-12-13 00:10:38 PST
@Henrik, you're in luck!

I'm pretty sure I've found the holy grail in cross-browser PDF download.

Here is a live example: https://dotnetcarpenter.github.io/FileReader_Chrome/

And here is the repo: https://github.com/dotnetCarpenter/FileReader_Chrome

```js
fetch('example.pdf', {
    method: 'get',
})
.then(response => response.blob())
.then(blob => {
    const fileName = 'example.pdf'
    if (navigator.msSaveBlob) { // IE11 and Edge 17-
        navigator.msSaveBlob(blob, fileName)
    } else { // every other browser
        const reader = new FileReader()
        reader.onloadend = () => {
            const a = document.createElement("a")
            a.href = reader.result
            a.style.display = 'none'
            a.download = fileName
            document.body.appendChild(a)
            a.click()
            a.parentNode.removeChild(a)
        }
        reader.readAsDataURL(blob)
    }
})
```

For IE11 support, if you do not use a transpiler then, you can substitute fetch with https://github.com/axios/axios and change fat arrows` to ES5 anonymous functions. But everything else should "Just work".
Comment 17 Nicola Zanon 2018-12-13 00:47:42 PST
Hi,

we rely on the download attribute for our application as well. Works like a charm for all the major browser except Safari on IOS.

Is there any update, timeline by any chance?

Anything we can do to help?

Thank you!
Comment 18 Thomas Steiner 2018-12-13 10:57:23 PST
+1, this would make a couple of our use cases easier as well.
Comment 19 Henrik Joreteg 2018-12-13 13:59:59 PST
(In reply to jon.ronnenberg from comment #16)
> @Henrik, you're in luck!
> 
> I'm pretty sure I've found the holy grail in cross-browser PDF download.
> 
> Here is a live example: https://dotnetcarpenter.github.io/FileReader_Chrome/
> 
> And here is the repo: https://github.com/dotnetCarpenter/FileReader_Chrome
> 
> ```js
> fetch('example.pdf', {
>     method: 'get',
> })
> .then(response => response.blob())
> .then(blob => {
>     const fileName = 'example.pdf'
>     if (navigator.msSaveBlob) { // IE11 and Edge 17-
>         navigator.msSaveBlob(blob, fileName)
>     } else { // every other browser
>         const reader = new FileReader()
>         reader.onloadend = () => {
>             const a = document.createElement("a")
>             a.href = reader.result
>             a.style.display = 'none'
>             a.download = fileName
>             document.body.appendChild(a)
>             a.click()
>             a.parentNode.removeChild(a)
>         }
>         reader.readAsDataURL(blob)
>     }
> })
> ```
> 
> For IE11 support, if you do not use a transpiler then, you can substitute
> fetch with https://github.com/axios/axios and change fat arrows` to ES5
> anonymous functions. But everything else should "Just work".

Have you tried this in iOS Safari. I tried doing it with a FileReader to no avail. Your last step still depends on the `download` attribute, no? The other issue I'm having is I have no way to give the blob file a proper name. It's always "unknown" :-/
Comment 20 jon.ronnenberg 2018-12-14 07:18:52 PST
(In reply to Henrik Joreteg from comment #19)
> (In reply to jon.ronnenberg from comment #16)
> > @Henrik, you're in luck!
> > 
> > I'm pretty sure I've found the holy grail in cross-browser PDF download.
> > 
> > Here is a live example: https://dotnetcarpenter.github.io/FileReader_Chrome/
> > 
> > And here is the repo: https://github.com/dotnetCarpenter/FileReader_Chrome
> > 
> > ```js
> > fetch('example.pdf', {
> >     method: 'get',
> > })
> > .then(response => response.blob())
> > .then(blob => {
> >     const fileName = 'example.pdf'
> >     if (navigator.msSaveBlob) { // IE11 and Edge 17-
> >         navigator.msSaveBlob(blob, fileName)
> >     } else { // every other browser
> >         const reader = new FileReader()
> >         reader.onloadend = () => {
> >             const a = document.createElement("a")
> >             a.href = reader.result
> >             a.style.display = 'none'
> >             a.download = fileName
> >             document.body.appendChild(a)
> >             a.click()
> >             a.parentNode.removeChild(a)
> >         }
> >         reader.readAsDataURL(blob)
> >     
> > ```
> > 
> > For IE11 support, if you do not use a transpiler then, you can substitute
> > fetch with https://github.com/axios/axios and change fat arrows` to ES5
> > anonymous functions. But everything else should "Just work".
> 
> Have you tried this in iOS Safari. I tried doing it with a FileReader to no
> avail. Your last step still depends on the `download` attribute, no? The
> other issue I'm having is I have no way to give the blob file a proper name.
> It's always "unknown" :-/
 

Yes, I have tried on an iPhone, with iOS12 and it worked fine. https://dotnetcarpenter.github.io/FileReader_Chrome/ Which iOS version did you test with?

The trick is to base64 encode the `a.href` content, which is what the `FileReader` does. `a.download` doesn't work on iOS Safari, as you know, but sets the file name in Chrome and Firefox.

In IE11 (and Edge) you do not need to base64 encode the binary PDF data but can use `navigator.msSaveBlob` (and `navigator.msSaveOrOpenBlob`), so we can just use that.

One other solution (but won't force save) is to have a normal link that points to your server and your server response with the binary PDF data and the following 3 headers:

```
Content-Length: <blob.size>
Content-Type: application/pdf
Content-Disposition: attachment;filename=<defaultName>
```
_Source: https://msdn.microsoft.com/en-us/library/hh772332(v=vs.85).aspx_


Hope it helps.

PS. It would still be far easier if Apple got support for the `download` attribute on iOS.
Comment 21 jon.ronnenberg 2018-12-14 07:22:14 PST
The MS documentation link should have been https://msdn.microsoft.com/en-us/library/hh772332(v=vs.85).aspx
Comment 22 jon.ronnenberg 2018-12-14 07:28:54 PST
Hmmm... According to https://msdn.microsoft.com/en-us/library/hh772331(v=vs.85).aspx you could try to force save a PDF document with the following headers (I haven't tested this though):

```
Content-Length: <blob.size>
Content-Type: application/pdf
Content-Disposition: attachment;filename=<defaultName>
X-Download-Options: noopen
```
Comment 23 jon.ronnenberg 2018-12-17 10:13:31 PST
(In reply to jon.ronnenberg from comment #20)

I wrote "binary PDF data" a couple of times. That's not accurate. My IDE (vscode) reports my test file as binary data but it turns out that both Github and AmazoneS3 servers response with the data as base64 (according to Firefox Network tool).

The "example.pdf" is (like all other PDFs) a mixed of ASCII and different encodings, neither are binary.
.
> (In reply to Henrik Joreteg from comment #19)
> > The other issue I'm having is I have no way to give the blob file a proper name.
> > It's always "unknown" :-/

I have no solution for setting the name on iOS Safari.
Other than fixing this bug (167341).