This specification defines an API that allows websites to declare themselves as web share targets, which can receive shared content from either the Web Share API, or system events (e.g., shares from native apps).

This is a similar mechanism to navigator.registerProtocolHandler, in that it works by registering the website with the user agent, to later be invoked from another site or native application via the user agent (possibly at the discretion of the user). The difference is that registerProtocolHandler registers the handler via a programmatic API, whereas a Web Share Target is declared in the Web App Manifest, to be registered at a time of the user agent or user's choosing.

This is an early draft of the Web Share Target spec.

Prerequisites

In order to implement this API, it is REQUIRED that the user agent implements Web App Manifest. This spec also re-uses some definitions from the Web Share API spec. Implementation of that spec is NOT REQUIRED to implement this one (but it is RECOMMENDED).

Usage Example

To register a site as a share target, a share_target entry is added to the Web App Manifest, as shown:

{
  "name": "Includinator",
  "share_target": {
    "action": "share.html",
    "params": {
      "title": "name",
      "text": "description",
      "url": "link"
    }
  }
}

The params keys correspond to the key names in ShareData from [[WebShare]], while the values are arbitrary names that will be used as query parameters when the target is launched.

When a share takes place, if the user selects this share target, the user agent opens a new browsing context at the action URL, with query parameter values containing the shared data, just like an HTML form submission.

For the purpose of this example, we assume the manifest is located at https://example.org/includinator/manifest.webmanifest.

<html>
  <head>
    <link rel="manifest" href="manifest.webmanifest">
  </head>
  <body>
    <script>
      window.addEventListener('load', () => {
        var parsedUrl = new URL(window.location);
        console.log('Title shared: ' + parsedUrl.searchParams.get('name'));
        console.log('Text shared: ' + parsedUrl.searchParams.get('description'));
        console.log('URL shared: ' + parsedUrl.searchParams.get('link'));
      });
    </script>
  </body>
</html>

If an incoming share contains the title "My News" and the URL http://example.com/news, the user agent will open a new window or tab and navigate to:

https://example.org/includinator/share.html?name=My+News&link=http%3A%2F%2Fexample.com%2Fnews

U+0020 (SPACE) characters are encoded as "+", due to the use of application/x-www-form-urlencoded encoding, not "%20" as might be expected. Processors must take care to decode U+002B (+) characters as U+0020 (SPACE), which some URL decoding libraries, including ECMAScript's decodeURIComponent function, may not do automatically.

The query parameters are populated with information from the ShareData being shared. If the ShareData contains no information for a given member, the query parameter is omitted.

A share target might only be interested in a subset of the ShareData members. This example also shows a share target that receives data as a POST request, which should be the case if the request causes an immediate side effect.

{
  "name": "Bookmark",
  "share_target": {
    "action": "/bookmark",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "url": "link"
    }
  }
}

The shared information might be read by a service worker, rather than being sent over the network to the server.

self.addEventListener('fetch', event => {
  if (event.request.method !== 'POST') {
    event.respondWith(fetch(event.request));
    return;
  }

  event.respondWith((async () => {
    const formData = await event.request.formData();
    const link = formData.get('link') || '';
    saveBookmark(link);
    return new Response('Bookmark saved: ' + link);
  })());
});

A files entry in params is used to declare that files are accepted by the share target:

{
  "name": "Aggregator",
  "share_target": {
    "action": "/cgi-bin/aggregate",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "title": "name",
      "text": "description",
      "url": "link",
      "files": [
        {
          "name": "records",
          "accept": ["text/csv", ".csv"]
        },
        {
          "name": "graphs",
          "accept": "image/svg+xml"
        }
      ]
    }
  }
}

The target will be invoked with a multipart/form-data POST request, with field names as specified in params. Each shared file is assigned to the first files entry that accepts its MIME type. (The "POST" method and "multipart/form-data" enctype are necessary when file sharing, as with HTML forms.)

How the handler deals with the shared data is at the handler's discretion, and will generally depend on the type of app. Here are some suggestions:

Extension to the Web App Manifest

The following IDL extends the WebAppManifest dictionary.

dictionary ShareTargetFiles {
  required USVString name;
  required (USVString or sequence<USVString>) accept;
};

dictionary ShareTargetParams {
  USVString title;
  USVString text;
  USVString url;
  (ShareTargetFiles or sequence<ShareTargetFiles>) files;
};

dictionary ShareTarget {
  required USVString action;
  DOMString method = "GET";
  DOMString enctype = "application/x-www-form-urlencoded";
  required ShareTargetParams params;
};

partial dictionary WebAppManifest {
  ShareTarget share_target;
};

The following steps are added to the extension point in the steps for processing a manifest:

  1. Set manifest["share_target"] to the result of running post-processing the share_target member given manifest["share_target"], manifest["scope"], and manifest URL.

share_target member

The share_target member of the manifest is a ShareTarget dictionary that declares this application to be a web share target, and describes how the application receives share data.

A web share target is a web site with a valid manifest containing a share_target member. A web share target is a type of share target.

The steps for post-processing the share_target member is given by the following algorithm. The algorithm takes a ShareTarget share target, a URL scope URL, and a URL manifest URL. This algorithm returns a ShareTarget or undefined.

  1. If share target is undefined, then return undefined.
  2. If share target["method"] is neither an ASCII case-insensitive match for the strings "GET" nor "POST", issue a developer warning that the method is not supported, and return undefined.
  3. If share target["method"] is an ASCII case-insensitive match for the string "GET" and share target["enctype"] is not an ASCII case-insensitive match for the string "application/x-www-form-urlencoded", issue a developer warning that the enctype is not supported with method GET, and return undefined.
  4. If share target["method"] is an ASCII case-insensitive match for the string "POST" and share target["enctype"] is neither an ASCII case-insensitive match for the strings "application/x-www-form-urlencoded" nor "multipart/form-data", issue a developer warning that the enctype is not supported, and return undefined.
  5. If share target["params"]["files"] is a ShareTargetFiles dictionary, bucket, set share target["params"]["files"] to «bucket».
  6. If share target["params"]["files"] contains one or more ShareTargetFiles dictionaries, and share target["method"] is not an ASCII case-insensitive match for the string "POST", or share target["enctype"] is not an ASCII case-insensitive match for the string "multipart/form-data", issue a developer warning that files are only supported with multipart/form-data POST, and return undefined.
  7. For each bucket in share target["params"]["files"]:
    1. If bucket["name"] is an empty string, issue a developer warning, remove bucket from share target["params"]["files"] and continue.
    2. If bucket["accept"] is a USVString, accept, set bucket["accept"] to «accept».
    3. For each string in bucket["accept"] that does not match any of the following, issue a developer warning and remove the string from bucket["accept"].
      • a string whose first character is a U+002E FULL STOP character (.)
      • type/subtype (where type and subtype are RFC7230 tokens)
      • type/* (where type is a RFC7230 token)
      • */*
    4. If bucket["accept"] is empty, issue a developer warning and remove bucket from share target["params"]["files"].
  8. Let action be the result of parsing the URL share target["action"], relative to the manifest URL and with no encoding override. If the result is failure, issue a developer warning and return undefined.
  9. If action is not within scope of scope URL, issue a developer warning that action is outside of the navigation scope , and return undefined.
  10. If the origin of action is not potentially trustworthy, issue a developer warning and return undefined.
  11. Set share target["action"] to action.
  12. Return share target.

ShareTarget and its members

The ShareTarget dictionary contains the following members.

The action member specifies the URL for the web share target.

The method member specifies the HTTP request method for the web share target.

A use case for GET requests is when the share target drafts a message for subsequent user approval. If the share target performs a side-effect without any user interaction, POST requests should be used.

The enctype member specifies how the share data is encoded in the request.

The params member contains a ShareTargetParams dictionary.

ShareTargetParams and its members

The ShareTargetParams dictionary contains the following members.

The title member specifies the name of the query parameter used for the title of the document being shared.

The text member specifies the name of the query parameter used for the arbitrary text that forms the body of the message being shared.

The url member specifies the name of the query parameter used for the URL string referring to a resource being shared.

The files member contains zero or more ShareTargetFiles dictionaries.

ShareTargetFiles and its members

The ShareTargetFiles dictionary contains the following members.

The name member specifies the name of the form field used to share the files.

The accept member specifies a sequence of accepted MIME type(s) or file extension(s), the latter expressed as strings starting with U+002E FULL STOP (.).

Registration of web share targets

How and when web share targets are "registered" is at the discretion of the user agent and/or the end user. In fact, "registration" is a user-agent-specific concept that is not formally defined here; user agents are NOT REQUIRED to "register" web share targets at all; they are only REQUIRED to provide some mechanism to convey shared data to a web share target of the end user's choosing. User agents MAY consider a web share target "registered" even if it is not installed.

The user agent MAY automatically register all web share targets as the user visits the site, but it is RECOMMENDED that more discretion is applied, to avoid overwhelming the user with the choice of a large number of targets.

Examples of registration strategies that user agents can employ are:

When presenting the end user with a list of web share targets, the user agent MAY use an online service which has pre-indexed manifests, and therefore show the user targets that they have never visited or explicitly registered.

An implementation supports filtering on MIME types if it takes into account the MIME types of the files being shared when presenting the user with a choice of share targets.

An implementation supports filtering on file extensions if it takes into account the extensions in the names of the files being shared when presenting the user with a choice of share targets.

An implementation MUST support filtering on MIME types or filtering on file extensions, or both.

If a file being shared is not accepted by any of a share target's files entries, the user MUST NOT be presented with that web share target as an option.

Handling incoming shares

A web share target is invoked when the end user is sharing some data intended for a generic application, and indicates that specific web share target as the receiver of the data.

It is not specified where the data comes from, or how the end user indicates the web share target as the receiver. However, one possible source is a call to navigator.share in the same user agent.

Examples of other possible sources of a web share target invocation are:

Obtaining a ShareData

When a web share target is invoked, the data MAY be in an unspecified format. The user agent MUST first convert the data into a ShareData dictionary, if it is not already, by mapping to the fields of ShareData from equivalent concepts in the host system. If the source was a call to navigator.share, the user agent SHOULD use the ShareData argument unmodified (but this is not always possible, as it might have to round-trip through some other format in a lossy manner). The user agent MAY employ heuristics to map the data onto the ShareData fields as well as possible.

For example, the host share system may not have a dedicated URL field, but a convention that both plain text and URLs are sometimes transmitted in a "text" field. This is the case on Android. The user agent can check whether all or part of the "text" field is a valid URL string, and if so, move that part of the "text" field to the ShareData's url field.

Launching the web share target

When web share target having WebAppManifest manifest is invoked with ShareData data, run the following steps:

  1. Let url be a copy of manifest["share_target"]["action"].
  2. Let entry list be a new empty list of name-value tuples. Each value can be either a USVString or a File list.
  3. For each member in the sequence « "title", "text", "url" »,
    1. Let name be the value of manifest["share_target"]["params"][member].
    2. If name is undefined or an empty string, then continue.
    3. Let value be the value of data[member].
    4. If value is undefined, then continue.
    5. Append to entry list a tuple with name and value.
  4. Let accepted be an empty map of name/File list pairs.
  5. For each bucket in the sequence manifest["share_target"]["params"]["files"],
    1. Set accepted[bucket["name"]] to a new empty File list.
  6. For each shared file in the sequence data["files"],
    1. Find the first bucket in the sequence manifest["share_target"]["params"]["files"] that accepts shared file.
    2. Append shared file to accepted[bucket["name"]].
  7. For each bucket in the sequence manifest["share_target"]["params"]["files"],
    1. Let name be the value of bucket["name"].
    2. Let value be the value of accepted[name], a (possibly empty) File list.
    3. Append to entry list a tuple with name and value.
  8. Let header list be a new empty header list.
  9. Let method be manifest["share_target"]["method"].
  10. Let enctype be manifest["share_target"]["enctype"].
  11. If method is "GET":
    1. Let query be the result of running the application/x-www-form-urlencoded serializer with entry list and no encoding override.
    2. Set url's query component to query.
    3. Let body be null.
  12. Otherwise, if method is "POST" and enctype is "application/x-www-form-urlencoded":
    1. Let body be the result of running the application/x-www-form-urlencoded serializer with entry list and no encoding override.
    2. Set body to the result of encoding body.
    3. Append "Content-Type"/"application/x-www-form-urlencoded" to header list.
  13. Otherwise, if method is "POST" and enctype is "multipart/form-data":
    1. Let body be the result of running the multipart/form-data encoding algorithm with entry list and UTF-8 encoding.
    2. Let MIME type be the concatenation of the string "multipart/form-data;", a U+0020 SPACE character, the string "boundary=", and the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm.
    3. Append "Content-Type"/MIME type to header list.
  14. Let browsing context be the result of creating a new top-level browsing context.
  15. Navigate browsing context to a new request whose method is method, url is url, header list is header list, and body is body.

This algorithm assumes that manifest has had the post-processing the share_target member algorithm run on it and still has a share_target afterwards.

Determining if a file is accepted

The algorithm for determining if a ShareTargetFiles bucket accepts a File shared file is as follows:-

  1. For each criterion in bucket["accept"]:
    1. If the first character of criterion is a U+002E FULL STOP character (.) and shared file["name"] ends with criterion, and the implementation supports filtering on file extensions, return true.
    2. If criterion is type/subtype (where type and subtype are RFC7230 tokens) and matches the MIME type of shared file, and the implementation supports filtering on MIME types, return true.
    3. If criterion is type/* (where type is a RFC7230 token) and the MIME type of shared file is a subtype of type, and the implementation supports filtering on MIME types, return true.
    4. If criterion is */*, return true.
  2. Return false.

Security and privacy considerations

Acknowledgments

Thanks to the Web Intents team, who laid the groundwork for the web app interoperability use cases. In particular, Paul Kinlan, who did a lot of early advocacy for Web Share and Web Share Target.

Thanks to Connie Pyromallis, who wrote an early draft of this spec, and helped design and prototype the API.

Thanks to Alex Russell and David Baron, for their feedback on early drafts of this spec.