Skip to content

Commit

Permalink
Merge pull request WICG#14 from clelland/add-spec
Browse files Browse the repository at this point in the history
Add spec to this repository
  • Loading branch information
mingyc authored Jul 11, 2022
2 parents 2dd0628 + 3ee1129 commit 321cdd5
Show file tree
Hide file tree
Showing 2 changed files with 2,120 additions and 0 deletions.
362 changes: 362 additions & 0 deletions index.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
<pre class="metadata">
Title: Page Unload Beacon
Status: UD
ED: http://wicg.github.io/unload-beacon/
Shortname: unload-beacon
Level: 1
Editor: Ian Clelland, Google
Abstract: This document introduces an API for registering data to be sent to a predetermined server
at the point that a page is unloaded.
Group: WebPerf
Repository: WICG/unload-beacon
</pre>

Introduction {#introduction}
============================

This is an introduction.

Issue: This introduction needs to be more of an introduction.

Pending Beacon Framework {#pending-beacon-framework}
====================================================

Concepts {#concepts}
--------------------

A <dfn>pending beacon</dfn> represents a piece of data which has been
registered with the user agent for later sending to an origin server.

A [=pending beacon=] has a <dfn for="pending beacon">url</dfn>, which is a
[=/URL=].

A [=pending beacon=] has a <dfn for="pending beacon">method</dfn>, which is a
string, which is initally <code>"POST"</code>.

A [=pending beacon=] has a <dfn for="pending beacon">timeout</dfn>, which is
either null or an integer, and which is initially null.

A [=pending beacon=] has an <dfn for="pending beacon">is_pending</dfn> flag,
which is a [=boolean=], which is initially true.

A [=pending beacon=] has a <dfn for="pending beacon">payload</dfn>, which is a
[=byte sequence=]. It is initially empty.

A [=Document=] has a <dfn for="Document">pending beacon set</dfn>, which is an
[=ordered set=] of [=pending beacons=].

Issue: Add worker beacons as well?

Note: In this spec, the [=pending beacon set=] is associated with a [=Document=].
In an actual implementation, this set will likely need to be stored in the user
agent, separate from the document itself, in order to be able to send beacons
when the document is destroyed (either by being unloaded, or because of a crash).

Issue: Define these to be part of the user agent formally.

Updating beacons {#updating-beacons}
------------------------------------

<div algorithm>
To <dfn for="pending beacon">set the url</dfn> of a pending beacon |beacon| to a [=/URL=] |url|:
1. If |beacon|'s [=pending beacon/is_pending=] is false, return false.
1. If |url| is not a valid [=/URL=], return false.
1. If |url| is not a [=potentially trustworthy URL=], return false.
1. Set |beacon|'s [=pending beacon/url=] to |url|.
1. Return true.

</div>

<div algorithm>
To <dfn for="pending beacon">set the method</dfn> of a pending beacon |beacon| to a string |method|:
1. If |beacon|'s [=pending beacon/is_pending=] is false, return false.
1. If |method| is not a case-insensitive match for <code>"GET"</code> or <code>"POST"</code>, return false.
1. Set |beacon|'s [=pending beacon/method=] to |method|.
1. Return true.

</div>

<div algorithm>
To <dfn for="pending beacon">set the timeout</dfn> of a pending beacon |beacon| to an integer |timeout|:
1. If |beacon|'s [=pending beacon/is_pending=] is false, return false.
1. If |timeout| is negative, return false.
1. Set |beacon|'s [=pending beacon/timeout=] to |timeout|.
1. Return true.

</div>

<div algorithm>
To <dfn for="pending beacon">set the payload</dfn> of a pending beacon |beacon| to a [=byte sequence=] |payload|,
1. If |beacon|'s [=pending beacon/is_pending=] is false, return false.
1. Set |beacon|'s [=pending beacon/payload=] to |payload|.
1. Return true.

</div>

<div algorithm>
To <dfn for="pending beacon">cancel</dfn> a [=pending beacon=] |beacon|, set |beacon|'s [=pending beacon/is_pending=] to false.

Note: Once canceled, a [=pending beacon=]'s payload will no longer be used,
and it is safe for a user agent to discard that, and to cancel any associated
timers. However, other attributes may still be read, and so this algorithm
does not destroy the beacon itself.
</div>

Sending beacons {#sending-beacons}
----------------------------------

Note: This is written as though Fetch were used as the underlying mechanism.
However, since these are sent out-of-band, an implementation might not use the
actual web-exposed Fetch API, and may instead use the underlying HTTP primitives
directly.

<div algorithm>
To <dfn>send a document's beacons</dfn>, given a Document |document|, run these steps:

1. For each [=pending beacon=] |beacon| in |document|'s [=pending beacon set=],
1. Call [=send a queued pending beacon=] with |beacon|.

</div>

<div algorithm>
To <dfn>send a queued pending beacon</dfn> |beacon|, run these steps:

1. If |beacon|'s [=pending beacon/is_pending=] flag is false, then return.
1. Set |beacon|'s [=pending beacon/is_pending=] flag to false.
1. Check permission.
1. If |beacon|'s [=pending beacon/method=] is "GET", then call [=send a pending beacon over GET=] with |beacon|.
1. Else call [=send a pending beacon over POST=] with |beacon|.

Issue: "Check permission" is not defined. A specific permission should be used
here, and this should integrate with the permissions API.
</div>

<div algorithm>
To <dfn>send a pending beacon over GET</dfn>, given a pending beacon |beacon|:

1. Let |pairs| be the [=/list=] « ("data", |beacon|'s [=pending beacon/payload=]) ».
1. Let |query| be the result of running the [=urlencoded serializer=] with |pairs|.
1. Let |url| be a clone of |beacon|'s [=pending beacon/url=].
1. Set |url|'s query component to |query|.
1. Let |req| be a new [=/request=] initialized as follows:

: method
:: <code>GET</code>
: client
:: The <a spec="html">entry settings object</a>
: url
:: |url|
: credentials mode
:: same-origin

1. Fetch |req|.

</div>

<div algorithm>
To <dfn>send a pending beacon over POST</dfn>, given a pending beacon |beacon|:

1. Let |transmittedData| be the result of serializing |beacon|'s [=pending beacon/payload=].
1. Let |req| be a new [=/request=] initialized as follows:

: method
:: <code>POST</code>
: client
:: The <a spec="html">entry settings object</a>
: url
:: |beacon|'s [=pending beacon/url=]
: header list
:: headerList
: origin
:: The <a spec="html">entry settings object</a>'s [=/origin=]
: keep-alive flag
:: true
: body
:: |transmittedData|
: mode
:: cors
: credentials mode
:: same-origin

1. Fetch |req|.

Issue: headerList is not defined.
</div>



Integration with HTML {#integration}
====================================

Note: The following sections modify the [[HTML]] standard to enable sending of
beacons automatically by the user agent. These should be removed from this spec
as appropriate changes are made to [[HTML]].

When a document with a non-empty [=pending beacon set=] is to be discarded, <a
lt="send a document's beacons">send the document's pending beacons</a>.

Issue: "discarded" is not well defined.

When a process hosting a document with a non-empty [=pending beacon set=] crashes,
<a lt="send a document's beacons">send the document's pending beacons</a>.

Issue: The concepts of "process" and "crashes" are not well defined.

<div algorithm="on visibility state change">
When a [=Document=] |document| is to become hidden (visibility state change), run these steps:

1. For each [=pending beacon=] |beacon| in |document|'s [=pending beacon set=],
1. Let |timeout| be |beacon|'s [=pending beacon/timeout=].
1. If |timeout| is not null, start a timer to run a task in |timeout| ms.

Note: The user agent may choose to coalesce multiple timers in order to send
multiple beacons at the same time.

1. When the timer expires, call [=send a queued pending beacon=] with |beacon|.

Note: The pending beacons may have been sent before this time, in cases
where the document is unloaded, or its hosting process crashes before the
timer fires. In that case, if the user agent still reaches this step, then
the beacons will not be sent again, as their [=pending beacon/is_pending=]
flag will be false.

Issue: "visibility state change" should be more specific here, and should refer
to specific steps in either [[PAGE-VISIBILITY]] or [[HTML]]
</div>

The PendingBeacon interface {#pendingbeacon-interface}
======================================================

<pre class=idl>
enum BeaconMethod {
"POST",
"GET"
};

dictionary PendingBeaconOptions {
BeaconMethod method = "POST";
unsigned long pageHideTimeout;
};

[Exposed=(Window, Worker)]
interface PendingBeacon {
constructor(USVString url, optional PendingBeaconOptions options = {});

attribute USVString url;
attribute BeaconMethod method;
attribute unsigned long pageHideTimeout;
readonly attribute boolean isPending;

undefined deactivate();
undefined setData(object data);
undefined sendNow();
};
</pre>

A {{PendingBeacon}} object has an associated <dfn for=PendingBeacon>beacon</dfn>, which is a [=pending beacon=].

<div algorithm>
The <dfn constructor for="PendingBeacon" lt="PendingBeacon(url, options)"><code>new PendingBeacon(|url|, |options|)</code></dfn> steps are:

1. Let |beacon| be a new [=pending beacon=].
1. Set [=this=]'s [=PendingBeacon/beacon=] to |beacon|.
1. Set [=this=]'s {{PendingBeacon/url}} to |url|.
1. If |options| has a {{PendingBeaconOptions/method}} member, then set
[=this=]'s {{PendingBeacon/method}} to |options|'s
{{PendingBeaconOptions/method}}.
1. If |options| has a {{PendingBeaconOptions/pageHideTimeout}} member, then set [=this=]'s
{{PendingBeacon/pageHideTimeout}} to |options|'s {{PendingBeaconOptions/pageHideTimeout}}.
1. Insert |beacon| into the user agent's pending beacon set.

</div>

<div algorithm>
The <dfn attribute for="PendingBeacon"><code>url</code></dfn> getter steps are to return [=this=]'s [=PendingBeacon/beacon=]'s [=pending beacon/url=].

</div>

<div algorithm="set url" data-algorithm-for="PendingBeacon">
The {{PendingBeacon/url}} setter steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw a {{"NoModificationAllowedError"}} {{DOMException}}.
1. Let |urlString| be the argument to the setter.
1. If |urlString| is not a [=valid URL string=], throw a {{TypeError}}.
1. Let |base| be the <a spec="html">entry settings object</a>'s [=API base URL=].
1. Let |url| be the result of running the [=URL parser=] on |urlString| and |base|.
1. If |url| is failure, throw a {{TypeError}}.
1. If the result of <a lt="set the url">setting</a> |beacon|'s [=pending beacon/url=] to |url| is false, throw a {{TypeError}}.

</div>

<div algorithm>
The <dfn attribute for="PendingBeacon">method</code></dfn> getter steps are to return [=this=]'s [=PendingBeacon/beacon=]'s [=pending beacon/method=].

</div>

<div algorithm="set method" data-algorithm-for="PendingBeacon">
The {{PendingBeacon/method}} setter steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw a {{"NoModificationAllowedError"}} {{DOMException}}.
1. Let |method| be the argument to the setter.
1. If the result of <a lt="set the method">setting</a> |beacon|'s [=pending beacon/method=] to |method| is false, throw a {{TypeError}}.

</div>

<div algorithm>
The <dfn attribute for="PendingBeacon"><code>pageHideTimeout</code></dfn> getter steps are to return [=this=]'s [=PendingBeacon/beacon=]'s [=pending beacon/timeout=].

</div>

<div algorithm="set pageHideTimeout" data-algorithm-for="PendingBeacon">
The {{PendingBeacon/pageHideTimeout}} setter steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw a {{"NoModificationAllowedError"}} {{DOMException}}.
1. Let |timeout| be the argument to the setter.
1. If |timeout| is not a non-negative integer, throw a {{TypeError}}.
1. If the result of <a lt="set the timeout">setting</a> |beacon|'s [=pending beacon/timeout=] to |timeout| is false, throw a {{TypeError}}.

</div>

<div algorithm>
The <dfn attribute for="PendingBeacon"><code>isPending</code></dfn> getter steps are to return [=this=]'s [=PendingBeacon/beacon=]'s [=pending beacon/is_pending=] flag.

</div>

<div algorithm>
The <dfn method for="PendingBeacon"><code>deactivate()</code></dfn> steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw an {{"InvalidStateError"}} {{DOMException}}.
1. [=pending beacon/cancel=] |beacon|.

</div>

<div algorithm>
The <dfn method for="PendingBeacon"><code>setData(|data|)</code></dfn> steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw a {{"NoModificationAllowedError"}} {{DOMException}}.
1. Let (|body|, <var ignore>contentType</var>) be the result of <a for="BodyInit" lt="extract">extracting</a> a [=body with type=] from |data| with keepalive set to true.
1. Let |bytes| be the [=byte sequence=] obtained by reading |body|'s stream.
1. If the result of <a lt="set the payload">setting</a> |beacon|'s [=pending beacon/payload=] to |bytes| is false, throw a {{TypeError}}.

</div>

<div algorithm>
The <dfn method for="PendingBeacon"><code>sendNow()</code></dfn> steps are:
1. Let |beacon| be [=this=]'s [=PendingBeacon/beacon=].
1. If |beacon|'s [=pending beacon/is_pending=] is not true, throw an {{"InvalidStateError"}} {{DOMException}}.
1. Call [=send a queued pending beacon=] with |beacon|.

</div>

Privacy {#privacy}
==================

Issue: This section is woefully incomplete. These all need to be fleshed out in
enough detail to accurately describe the privacy issues and suggested or
prescribed mitigations.

* When the network changes, drop all queued beacons

* Clear-site-data?

* Incognito?
Loading

0 comments on commit 321cdd5

Please sign in to comment.