Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download API #4148

Open
annevk opened this issue Oct 31, 2018 · 26 comments
Open

Download API #4148

annevk opened this issue Oct 31, 2018 · 26 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: navigation

Comments

@annevk
Copy link
Member

annevk commented Oct 31, 2018

In order to avoid having to create URLs from File objects, potentially leak the object forever, and navigate some browsing context with such a URL, I was thinking it might be good to have something akin navigator.download(file).

@annevk annevk added the addition/proposal New features or enhancements label Oct 31, 2018
@domenic
Copy link
Member

domenic commented Oct 31, 2018

+1. I think you would make your case more compelling by showing the problematic example code that you have to use today.

(Alternate Bikeshed: file.download()? Maybe that's bad though, putting too much responsibility into the File class.)

/cc @jsbell @mkruisselbrink. Not really related to writable files but kind of in the same ballpark.

@jimmywarting
Copy link

We got native filsystem system also. So do we need this?

@woody-li
Copy link

woody-li commented Oct 19, 2022

A related case:
Download a dynamic file from server. Server needs some time to generate the file.

So needs a callback (or event) to known the download has started. And shows a loading before it starts.

@annevk annevk added topic: navigation needs implementer interest Moving the issue forward requires implementers to express interest labels Oct 19, 2022
@Zipdox2
Copy link

Zipdox2 commented Jan 19, 2023

Such an API would absolutely need to have a way to download streams or download files in pieces. Currently doing this requires a hack using service workers, but these are not available in private mode. Otherwise this wouldn't provide any new functionality.

We got native filsystem system also. So do we need this?

This is not implemented in Firefox for security reasons. It's also only available in secure contexts.

With regards to implementation, I'm not sure whether it should be a method of navigator or of window.

@jimmywarting
Copy link

Oh, btw file.download() would not make so much since in server side. think it would be better with a download(file) instead. even better would be to save a stream download(readableStream, { suggestedName: 'readme.md' })
but i don't see any reason for FF to just only support some parts of the file-system-access like the showSaveFilePicker

@Zipdox2
Copy link

Zipdox2 commented Jan 21, 2023

This issue is kind of obsoleted by the existence of the File System Access API, but Mozilla refuses to implement it because it's "harmful".

So unless they change their mind, or implement part of it, this issue still stands.

@saschanaz
Copy link
Member

I don't think File System Access can provide the same protection browsers do for file downloads, nor it can provide good UI for download progress.

@Zipdox2
Copy link

Zipdox2 commented Jan 22, 2023

I propose an API like this:

{{APIRef("HTML DOM")}}

The navigator.download() method of the {{domxref('Navigator')}} interface tries to download a file and returns a {{jsxref('Promise')}} that resolves to a Download object if the download starts, and rejects otherwise. It may reject if the user rejects the download or there is a problem creating the file.

Syntax

navigator.download(data, name);

Parameters

  • data

    • : A {{domxref("File")}}, {{domxref("Blob")}}, {{domxref("ArrayBuffer")}}, {{domxref("TypedArray")}}, or {{domxref("ReadableStream")}} to download. If supplied a ReadableStream, it will be locked until the download is aborted, rejected, or the stream closes.
  • options {{optional_inline}}

    • : An options object containing optional attributes for the download. Available options are
      as follows:

      • name
        • : A string representing the file name. If not specified, and data is a {{domxref("File")}}, its name will be used, otherwise a file name will be generated by the browser.
      • prompt
        • : A boolean, which if set to true, prompts the user where to download the file, and allows the user to choose a different file name.

Return value

A {{jsxref("Promise")}} that resolves to a Download object if the download starts, and rejects otherwise.

Exceptions

The {{jsxref("Promise")}} may be rejected with one of the following DOMException values:

  • AbortError {{domxref("DOMException")}}
    • : The user or browser rejected the download.
  • DataError {{domxref("DOMException")}}
    • : There was a problem creating the file or starting the download.

Example

This example downloads a text file containing "Hello, world!"

const encoder = new TextEncoder();
const encoded = encoder.encode('Hello, world!');
navigator.download(encoded, {name: 'hello.txt'});

Specifications

{{Specifications}}

Browser compatibility

{{Compat}}

@Zipdox2
Copy link

Zipdox2 commented Jan 22, 2023

And the download object:

{{APIRef("HTML DOM")}}

The Download object represents an ongoing or finished download.

Downloads can tell you about how far the file has been downloaded, how big it is (if not a stream), its file name, and when it finishes or aborts.

Instance properties

  • {{DOMxRef("Download.prototype.size")}} {{ReadOnlyInline}}
    • : The size, in bytes, of the data being downloaded. If it is a stream, this will be undefined.
  • {{DOMxRef("Download.prototype.progress")}} {{ReadOnlyInline}}
    • : The size, in bytes, of the data that has been downloaded so far.
  • {{DOMxRef("Download.prototype.name")}} {{ReadOnlyInline}}
    • : A string indicating the name of the file being downloaded.

Instance methods

  • {{DOMxRef("Download.prototype.abort()")}}
    • : Aborts the download and deletes the downloaded file. If the download was created from a stream, it will be unlocked.
  • {{DOMxRef("Download.prototype.finish()")}}
    • : Stops downloading the file and closes it up. If the download was created from a stream, it will be unlocked.

Events

  • {{DOMxRef("Download.progress_event", "progress")}}
    • : Fired when the downloaded progresses.
  • {{DOMxRef("Download.finish_event", "finish")}}
    • : Fired when the download has finished. If the download was created from a stream, the stream has closed.
  • {{DOMxRef("Download.error_event", "error")}}
    • : Fired when there is an error with downloading the file. This includes the user canceling the download.

Specifications

{{Specifications}}

Browser compatibility

{{Compat}}

@Zipdox2
Copy link

Zipdox2 commented Jan 22, 2023

I don't think File System Access can provide the same protection browsers do for file downloads

True. But by not implementing FileSystemDirectoryHandle it wouldn't introduce any unforeseen risks.

nor it can provide good UI for download progress.

Wouldn't FileSystemWritableFileStream enable download progress? The write method is a promise.

@Zipdox2
Copy link

Zipdox2 commented Jan 27, 2023

Can I get some feedback on what I proposed?

@saschanaz
Copy link
Member

saschanaz commented Jan 27, 2023

I think what needed here is an implementer interest rather than a proposal (although it's also needed too). But anyway:

I don't think there's any proven use case to go that far. What's needed here is to be able to download a file and stream without hacking with object URL and service worker and whatnot, and not really about to track and control the download in JS. The user should already be able to do that via the browser UI.

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

What's needed here is to be able to download a file and stream without hacking with object URL and service worker and whatnot, and not really about to track and control the download in JS.

The downloading of streams is absolutely the most important feature in my opinion. Here's a few use cases.

  • Client side decryption for cloud storage downloads (For example, mega.nz currently needs to store and decrypt entire files in memory before they can be downloaded.)
  • Downloading real-time streams media from things like conference calls and web games (multiplayerpiano.com for example).
  • Streamable large file transfers through WebRTC or other means.
  • Reduce the memory footprint of such applications by writing the data to storage instead of collecting it in memory to download when completed.

Being able to abort/finish a download isn't a feature you should omit, especially not when downloading streams. Tracking the progress of a download is also important, at least getting callbacks on errors and completion, to be able to free resources after they have been downloaded.

@saschanaz
Copy link
Member

Being able to abort/finish a download isn't a feature you should omit, especially not when downloading streams.

Why? Browsers already provide UI for that, why do you need to implement your own?

Tracking the progress of a download is also important, at least getting callbacks on errors and completion, to be able to free resources after they have been downloaded.

That's a feature request for streams. I think there was an open issue for that but can't find it...

@saschanaz
Copy link
Member

getting callbacks on errors and completion, to be able to free resources after they have been downloaded.

Still you have ReadableStream's cancel callback to track the download cancel. You don't need to track completion as you already provided everything in that case, you can immediately free the source in that case. The error, that's a missing thing, but not sure the consumer (a browser here) can cause any error.

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

Still you have ReadableStream's cancel callback to track the download cancel.

Oh yeah that's true.

You don't need to track completion as you already provided everything in that case, you can immediately free the source in that case.

This only applies to streams. If downloading a fixed size object it's a different story.

The error, that's a missing thing, but not sure the consumer (a browser here) can cause any error.

Sure there is. The device can run out of free space for example. The user can abort the download, though that might not be considered an error, but should definitely be an event. I'm sure there's other IO erros that can occur.

@saschanaz
Copy link
Member

If downloading a fixed size object it's a different story.

Can you give at example?

The device can run out of free space for example. The user can abort the download, though that might not be considered an error, but should definitely be an event. I'm sure there's other IO erros that can occur.

I think both should cancel the stream? 🤔

@saschanaz
Copy link
Member

To clarify, AFAIK stream readers can't cause an error. And in this case the download is reading the stream, hence a reader. Errors happen from the source side, which devs should be able to catch somehow via try-catch.

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

Can you give at example?

ArrayBuffer, TypedArray, Blob

I think both should cancel the stream? 🤔

Possibly, but once again, that's only for streams. I was under the impression that this API would also be for fixed size objects.

@saschanaz
Copy link
Member

Can you give at example?

ArrayBuffer, TypedArray, Blob

I mean, why do you need a signal for them, you can pass them to the function and forget about them as long as you don't have another reference.

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

I mean, why do you need a signal for them, you can pass them to the function and forget about them as long as you don't have another reference.

True, but you might want to do other things when the download has finished. I don't understand why you're insisting on not having a completion event. It only seems reasonable to include it.

@saschanaz
Copy link
Member

Well, because there should be a very good reason to convince implementers to add something to the web platform 🙂

And since we currently don't have a download API at all, I think it's easier to start small, e.g. a function that receives (Response or BodyInit), triggers a browser download, and return nothing (or perhaps a Promise?). Anything else can be added later.

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

Anything else can be added later.

This is the biggest trap in programming. Most of the time you end up with a badly designed system that is hard to extend. How would you add monitoring of progress to the API if the download function call returns a promise that doesn't resolve until download completion? What could also happen is that implementers implement the initial spec, but then delay implementing later additions (cough cough). It's better to do it right from the start.

Prove me wrong, but I think implementing a download API is relatively simple in comparison to other APIs (e.g. WebMIDI, Web Audio). No new core functionality has to be implemented really, downloading files is already part of browsers, as are download managers. Downloads already report progress and completion.

So I suggest we start by listing all the features that web developers want before defining an initial spec. Then, which the required features in mind, design a mechanism and specification that makes sense to implementers.

So far these are my requirements:

  • Downloading of both fixed-length data and ReadableStreams
  • Settable file name
  • Option to prompt the user for a download location
  • Error reporting
  • Progress reporting (including completion)
  • Cancellation (more relevant to fixed-length downloads)

I also suspect possible security concerns, which is why I propose the following optional implementation limitations:

  • Downloads must be initiated by user interaction (like with playing media)
  • Repeated downloads can be blocked by the user agent (like with window.prompt and window.alert)

@saschanaz
Copy link
Member

saschanaz commented Jan 28, 2023

How would you add monitoring of progress to the API if the download function call returns a promise that doesn't resolve until download completion?

Sorry, I wasn't clear. I mean maybe resolve it if the browser shows the dialog. (But maybe that's not needed, it's not like there's a long delay.)

What could also happen is that implementers implement the initial spec, but then delay implementing later additions (cough cough). It's better to do it right from the start.

Exactly why I want to start small. Implementers can have different opinions and doing everything at once can delay every process. No one wants such situation.

  • Downloading of both fixed-length data and ReadableStreams
  • Settable file name

Fair.

  • Option to prompt the user for a download location

Why? Not following the browser behavior is surprising.

  • Error reporting

What errors? Should it really care whether it's canceled by user or by browser (which is "error" but that should also cancel the stream)

  • Progress reporting (including completion)
  • Cancellation (more relevant to fixed-length downloads)

If you really need these, getting a proxy ReadableStream should work for non-stream data too.

  • Downloads must be initiated by user interaction (like with playing media)

While I agree, downloads can already be initiated without interaction AFAIK. Has the situation changed?

  • Repeated downloads can be blocked by the user agent (like with window.prompt and window.alert)

I wonder this also applies to existing auto-initiated download mechanism. (A bit out of scope in that case)

@Zipdox2
Copy link

Zipdox2 commented Jan 28, 2023

  • Option to prompt the user for a download location

Why? Not following the browser behavior is surprising.

A web developer might want to implement a "save as" button. But IMO it would be acceptable for a browser to prompt the user regardless of whether this parameter is specified.

What errors? Should it really care whether it's canceled by user or by browser (which is "error" but that should also cancel the stream)

We talked about this before. Multiple things could cause a download to fail. Lack of storage space, anti virus deleting a downloading file, the user deleting a downloading file, hardware I/O errors.

While I agree, downloads can already be initiated without interaction AFAIK. Has the situation changed?

I know, this limitation might cause problems for web developers and I'd rather not have it, but Mozilla tends to be finicky about standards so I want to stay on the safe side. Ideally we could discuss this with Mozilla devs and get approval though. Perhaps it's best to have it be a setting just like the autoplay setting.

I wonder this also applies to existing auto-initiated download mechanism. (A bit out of scope in that case)

I actually don't know. I might test this.

@Zipdox2
Copy link

Zipdox2 commented Feb 1, 2023

What about adding HTMLAnchorElement.srcObject? And allow setting it to a Blob, File or ReadableStream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: navigation
Development

No branches or pull requests

6 participants