whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.18k stars 2.69k forks source link

Download API #4148

Open annevk opened 6 years ago

annevk commented 6 years ago

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).

domenic commented 6 years ago

+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 commented 4 years ago

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

woody-li commented 2 years ago

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.

Zipdox2 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

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:

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 commented 1 year ago

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

Instance methods

Events

Specifications

{{Specifications}}

Browser compatibility

{{Compat}}

Zipdox2 commented 1 year ago

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 commented 1 year ago

Can I get some feedback on what I proposed?

saschanaz commented 1 year ago

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 commented 1 year ago

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.

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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:

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

saschanaz commented 1 year ago

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 commented 1 year ago
  • 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 commented 1 year ago

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