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]]], or system events (e.g., shares from native apps).

This is a similar mechanism to {{NavigatorContentUtils/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 {{NavigatorContentUtils/registerProtocolHandler()}} registers the handler via a programmatic API, whereas a Web Share Target is declared in the [[[appmanifest]]], 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, a the user agent MUST support [[[appmanifest]]]. This spec also re-uses some definitions from the [[[Web-Share]]] spec. However, support for the [[[Web-Share]]] is OPTIONAL.

Usage Example

To register a site as a share target, a [=manifest/share_target=] entry is added to the [[[appmanifest]]], as shown:

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

The [=ShareTarget/params=] keys correspond to the key names in {{ShareData}} from [[[Web-Share]]], 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>
      <link rel="manifest" href="manifest.webmanifest">
      <script>
        window.addEventListener('load', () => {
          const parsedUrl = new URL(window.location);
          const { searchParams } = parsedUrl;
          console.log("Title shared:", searchParams.get('name'));
          console.log("Text shared:", searchParams.get('description'));
          console.log("URL shared:", searchParams.get('link'));
        });
      </script>
      

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;
          }

          const formDataPromise = event.request.formData();
          event.respondWith(
            formDataPromise.then((formData) => {
              const link = formData.get("link") || "";
              saveBookmark(link);
              return new Response(`Bookmark saved: ${link}`);
            })
          );
        });
      

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

As a [=manifest=] is JSON, this specification relies on the types defined in the [[JSON]] specification: namely object and string.

The following steps are added to the [=processing extension-point of web manifest=]:

  1. Let |json| and |manifest| be the corresponding variables from [=processing a manifest=].
  2. [=Process the `share_target` member=] with |json| and |manifest|.

`share_target` member

The share_target member of the manifest is an [=object=]. When present, it 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 [=manifest/share_target=] member.

A web share target is a type of share target (other types can be available, e.g., some system applications).

To process the `share_target` member given [=object=] |json:JSON| and [=ordered map=] |manifest:ordered map|:

  1. If |json|["share_target"] is not an [=object=], return.
  2. Let |target:object| be |json|["share_target"].
  3. If |target|["action"] or |target|["params"] is missing, return.
  4. Process [=ShareTarget/action=]:
    1. Let |action:URL| be the result of [=URL parser|parsing=] |share target|["action"] relative to the |manifest URL| and with no encoding override. If the result is failure, return.
    2. If |action| is not [=URL/within scope=] of the |manifest|["scope"], return.
    3. If the [=url/origin=] of |action| is not a [=potentially trustworthy origin=], return.
  5. Let |method:string| be "GET".
  6. If |target|["method"] is present, process [=ShareTarget/method=]:
    1. If |target|["method"] is neither an [=ASCII case-insensitive=] match for the strings `"GET"` nor `"POST"`, return.
    2. Set |method| to [=ASCII uppercase=] |target|["method"].
  7. Let |enctype:string| be "application/x-www-form-urlencoded".
  8. If |method| is `"POST"`:
    1. If |target|["enctype"] is neither an [=ASCII case-insensitive=] match for the strings `"application/x-www-form-urlencoded"` nor `"multipart/form-data"`, return.
    2. Set |enctype| to [=ASCII lowercase=] |target|["enctype"].
  9. Let |params:ordered map| be a new [=ordered map=].
  10. Process [=ShareTarget/params=]:
    1. [=List/For each=] |member:string| of « "title", "text", "url" »:
      1. If |target|["param"] doesn't have a property |member|, continue.
      2. If |target|["param"][member] is not a [=string=], return.
      3. Set |params|[member] to |target|["param"][member].
  11. Set |manifest|["share_target"] to [=ordered map=] «[
    "action" → [=URL serializer|serialize=] |action|,
    "enctype" → |enctype|,
    "method" → |method|,
    "params" → |params|,
    ]».

`ShareTarget` and its members

The ShareTarget [=object=] can have the following members:

action member
A [=string=] that specifies the [=URL=] for the [=web share target=].
method member
A [=string=] that specifies the HTTP [=request=] [=request/method=] for the [=web share target=].
enctype member
A [=string=] that specifies how the share data is encoded in the body of a `POST` request. It is ignored when [=method=] is `"GET"`.
params member
A ShareTargetParams [=object=].

`ShareTargetParams` and its members

The ShareTargetParams [=object=] can have the following members:

title member
A [=string=] that specifies the name of the query parameter used for the title of the document being shared.
text member
A [=string=] that specifies the name of the query parameter used for the arbitrary text that forms the body of the message being shared.
url member
A [=string=] that specifies the name of the query parameter used for the URL string referring to a resource being shared.

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 web application|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.

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}}'s {{Navigator/share()}} method 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}} object, if it is not already, by mapping to the members 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 {{ShareData/url}} member.

Launching the web share target

When web share target having [=ordered map=] |manifest| is invoked with {{ShareData}} |data|, run the following steps:

  1. Let |url:URL| be the result of [=URL parser|parsing=] |manifest|["share_target"]["action"].
  2. Let |entries:list| be a new empty [=list=].
  3. [=List/For each=] |member:string| of « "title", "text", "url" »:
    1. Let |name:string| be the value of |manifest|["share_target"]["params"][|member|].
    2. If |name| is `undefined` or the empty string, continue.
    3. If |data|[|member|] is `undefined`, continue.
    4. Let |value:string| be ToString(|data|[|member|]).
    5. [=List/Append=] [=tuple=] (|name|, |value|) to |entry list|.
  4. Let |header list| be a newly created [=Headers/header list=].
  5. Let |method:string| be |manifest|["share_target"]["method"].
  6. Let |enctype:string| be |manifest|["share_target"]["enctype"].
  7. If |method| is `"GET"`:
    1. Let |query| be the result of running the [=urlencoded serializer=] with |entries| and no encoding override.
    2. Set |url|'s [=URL/query=] component to |query|.
    3. Let |body| be null.
  8. Otherwise, if |method| is `"POST"` and |enctype| is `"application/x-www-form-urlencoded"`:
    1. Let |body:string| be the result of running the [=urlencoded serializer=] with |entries| and no encoding override.
    2. Set |body| to the result of [=UTF-8 encode=] |body|.
    3. [=header list/Append=] `"Content-Type"`/`"application/x-www-form-urlencoded"` to |header list|.
  9. 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 |entries| and the [=UTF-8=] encoding.
    2. Let |MIME type:string| 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. [=header list/Append=] `"Content-Type"`/|MIME type| to |header list|.
  10. Let |browsing context| be the result of creating a new [=top-level browsing context=].
  11. Let |request:Request| be a new [=Request=] whose method is |method|, url is |url|, header list is |header list|, and body is |body|.
  12. [=Navigate=] |browsing context| to |request =|

This algorithm assumes that |manifest| has had the [=process the `share_target` member=] algorithm run on it and still has a [=manifest/share_target=] afterwards.

Accessibility

This specification has no known accessibility considerations.

Security and privacy considerations

Acknowledgments

Thanks to the [[[WEBINTENTS]]] 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.