WICG/file-handling

Consider multiple vs singular handlers

Closed this issue ยท 8 comments

This API was initially written to be similar to WebShareTarget. However, that API works by triggering a POST request to a certain URL of a web app so there is a lot of extra information (e.g. the open url, groups of files, query params).

A POST request isn't suitable for this API, as we need to pass the application a FileSystemFileHandle, so we will be implementing a
Launch Event. This means that we could potentially remove everything but the list of accept urls from the file handler manifest entry.

The following JSON

{
  "name": "Grafr",
  "file_handler": [
    {
      "name": "raw",
      "accept": [".csv", "text/csv"]
    },
    {
      "name": "graph",
      "accept": [".svg", "image/svg+xml"]
    }
  ]
}

could become

{
  "name": "Grafr",
  "file_handler": [
    ".csv",
    "text/csv"
    ".svg"
    "image/svg+xml"
  ]
}

This does mean that an application would have to inspect the files it was passed itself, to determine how to handle them (e.g. this is an svg file --> do svg stuff) instead of relying on the name being passed in with the event. However, it seems likely that an application would have to do some form of this anyway (an image editor that accepts image/* would have to decide what codec to use).

Any thoughts?

Hmm this is a good thought. I'm interested to chat with the SW folks about performance issues with fetch and to see how they might apply (or not apply) to launch event. If there are performance issues and it does mean that we need to open windows directly and send events there, then we would need a syntax like this. I think we would prefer not to have to do that (for the sake of simplicity of the API) but I think we should rule it out before we decide on the syntax change.

I guess it would be an option to allow both ways (or maybe something similar) - both because it is more consistent and because there will surely be very simple cases.

Also why is

{
  "name": "Grafr",
  "file_handler": [
    ".csv",
    "text/csv"
    ".svg"
    "image/svg+xml"
  ]
}

Much better than

{
  "name": "Grafr",
  "file_handler": [
    {
      "name": "any",
      "accept": [".csv", "text/csv", ".svg", "image/svg+xml"]
    }
  ]
}

or just ignoring the "name" in the service worker. I don't think saving a few lines of code here is really worth it @mgiuca

It's not so much that it's 'better' but that we would be making the API more complicated than it needs to be. If we can make an API that's as powerful and ergonomic while also being simpler I think we should do that.

If this kind of grouping is desirable, it could be replicated with JavaScript, and potentially added into the spec after it's popularity has been proven.

That said, I think @raymeskhoury is right, we should confirm with the SW people that a launch event on the ServiceWorker is the right approach before we go too far down this rabbit hole :)

As mentioned in #24, I think we do want multiple separate file handlers, not really as a mechanism for allowing different file extensions (for that, we already have the list of "accept" values), but rather as a way of declaring that the app handles different user-facing "types" of file.

By "user-facing type", I mean something that may have a different file type name and icon to another file type. For example, you may have a file handler for "JPEG image" with accept ["image/jpeg", ".jpg", ".jpeg"] --- three different low-level types for the same high-level (user-facing) type. Then a second file handler for "GIF image", which may have a different icon. Or you could combine these all into one user-facing type "Image file" that accepts all of those extensions. It would be up to the app.

As discussed in #24, an app might handle wildly different types of file ("Word document", "Excel spreadsheet") which should have different icons.

Shall I update the explainer to reflect this?

Reopening since this discussion isn't settled, due to implementation difficulty with the above.

Since we keep going around in circles on this, let me enumerate the different options on the table for future discussions.

Basically, file handlers have three pieces of information:

  • action URL: the URL to navigate to when handling a file.
  • type identification: the MIME type(s) and/or file extension(s), to identify what the type of file is.
  • user type info: the file type name and file type icon, to show to the user what the file type is. (This last category isn't part of the current explainer or implementation, but we're considering it for the future; see my comment above.)

This issue questions whether each of the above should be singular or multiple.

Option A: Multiple file handlers. Each handler has its own action URL, type identification (multiple MIME types and/or extensions per handler) and user type info.

Example:

"file_handlers": [
  {
    "action": "/open_png",
    "accept": {"image/png": [".png"]},
    "type_name": "PNG image",
    "type_icons": [{"src": "/pngfile.png"}]
  },
  {
    "action": "/open_jpeg",
    "accept": {"image/jpeg": [".jpg", ".jpeg"]},
    "type_name": "JPEG image",
    "type_icons": [{"src": "/jpegfile.png"}]
  }
]

This is the current proposal appearing in the explainer (except with the "type_name" and "type_icons" members added).

Option B: Singular action URL for the entire app. Multiple file handlers (or "file types"). Each handler has its own type identification (multiple MIME types and/or extensions per handler) and user type info.

Example:

"file_handler": {
  "action": "/open",
  "types": [
    {
      "accept": {"image/png": [".png"]},
      "type_name": "PNG image",
      "type_icons": [{"src": "/pngfile.png"}]
    },
    {
      "accept": {"image/jpeg": [".jpg", ".jpeg"]},
      "type_name": "JPEG image",
      "type_icons": [{"src": "/jpegfile.png"}]
    }
  ]
}

Compared to Option A, this option does not allow different file types to be opened in different URLs, but still allows each file type to be presented with a distinct name and icon to the user.

If the application needs to handle different types with different code, it must make that distinction itself. If the application needs to handle different types at different URLs (for example, a word processing document and spreadsheet are handled by the same app, but at completely different URLs), it must redirect to the correct URL and somehow pass the file handle across (this may actually be impossible, so by choosing Option B or C we may be blocking this use case).

Option C: Single file handler for the entire app. The file handle has an action URL, type identification (multiple MIME types and/or extensions for the one handler) and user type info.

Example:

"file_handler": {
  "action": "/open",
  "accept": {
    "image/png": [".png"],
    "image/jpeg": [".jpg", ".jpeg"]
  },
  "type_name": "Image file",
  "type_icons": [{"src": "/imagefile.png"}]
}

Compared to Option B, this option does not allow any distinction to be presented to the user based on the file type (note that we dropped "PNG image" and "JPEG image", instead just showing "Image file").

Brief comparison between the three options:

  • Option A is the most flexible for the developer. However, on some host OSes, it is difficult to implement. We ask the OS to invoke the browser whenever any of the supported file types is opened, but the OS may not distinguish between the different handlers. Therefore, to choose the correct URL, we need to look at the filename and/or MIME type and select the correct handler. This may mean performing MIME type mapping redundantly to what the OS has already done.
  • Options B and C mean the UA doesn't need to do that mapping. If we are invoked, we just open the one handler URL. This is the easiest to implement.
  • Options B and C are functionally identical if we don't implement user type info, but they have different syntax. The B syntax looks pretty funny without user type info (there is no difference between [{"accept": {"one": []}}, {"accept": {"two": []}}] and [{"accept": {"one": [], "two": []}}]; really no need to allow multiple accepts). So C seems better if we're not ever going to have user type info, but if we do want user type info then B is a lot nicer for users.

Current plan is to proceed with A, and report back on implementation difficulty encountered on Windows, macOS and Linux. If the implementation is prohibitive, we may consider switching to B. I'm not really a fan of C because I do like the idea of user type info.

Note: This came up in #33, which was part of our motivation for reopening.

Thank you for the detailed explanation of the different approaches and their pros/cons. I agree that Option A sounds best if it's realistic to implement.

As an update here, we did end up implementing Option A, and Chromium Origin Trials will begin in M92, so I find it unlikely that we'll go back to use option B or C, unless there's a not-yet-considered drawback of option A. (Going with option B or C may also block the redirect use case that a developer has shown interest for).

I'll close this comment as I believe the issue is resolved now, but please feel free to re-open if this doesn't seem right.