Skip to content

Commit

Permalink
Deferred fetching
Browse files Browse the repository at this point in the history
Add a JS-exposed function to request a deferred fetch.

A deferred fetch would be invoked in one of two scenarios:
- The document is destroyed (the fetch group is terminated)
- The document is backgrounded (the fetch group is deactivated)
  and not restored after a certain time.

A few constraints:
- Deferred fetch body sizes are limited to 64KB per origin.
  Exceeding this would immediately reject with a QuotaExceeded.
- Request body streams are not allowed. A request body, if exists,
  has to be a byte sequence.

The JS function is called `requestDeferredFetch` but that's
bikesheddable.

See WICG/pending-beacon#70
  • Loading branch information
noamr committed Sep 5, 2024
1 parent 3153e5e commit b8699dc
Showing 1 changed file with 208 additions and 7 deletions.
215 changes: 208 additions & 7 deletions fetch.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2719,6 +2719,9 @@ functionality.
<p>A <a for=fetch>fetch group</a> holds an ordered list of
<dfn lt="fetch record" export for="fetch group" id=concept-fetch-record>fetch records</dfn>.

<p>A <a for=fetch>fetch group</a> holds an ordered list of
<dfn export for="fetch group" id=concept-defer=fetch-record>deferred fetch records</dfn>.

<p>A <a for="fetch group">fetch record</a> has an associated
<dfn export for="fetch record" id=concept-fetch-record-request>request</dfn> (a
<a for=/>request</a>).
Expand All @@ -2727,16 +2730,103 @@ functionality.
<dfn export for="fetch record" id=concept-fetch-record-fetch>controller</dfn> (a
<a for=/>fetch controller</a> or null).

<p>A <dfn export>deferred fetch record</dfn> is a <a for=/>struct</a> used to maintain state needed
to invoke a fetch at a later time, e.g. when a <code>Document</code> is unloaded or backgrounded. It
has the following <a for=struct>items</a>:

<dl>
<dt><dfn export for="deferred fetch record">request</dfn>
<dd>A <a for=/>request</a>

<dt><dfn export for="deferred fetch record">background timeout</dfn> (default null)
<dd>Null or a <a>duration</a>

<dt><dfn export for="deferred fetch record">pending steps</dfn> (default null)
<dt><dfn export for="deferred fetch record">invoked callback</dfn> (default null)
<dd>Null or an algortihm accepting nothing

<dt><dfn export for="deferred fetch record">invoked</dfn> (default false)
<dd>A boolean
</dl>


<hr>

<p>When a <a for=fetch>fetch group</a> is
<dfn export for="fetch group" id=concept-fetch-group-terminate>terminated</dfn>, for each associated
<a for="fetch group">fetch record</a> whose <a for="fetch group">fetch record</a>'s
<a for="fetch record">controller</a> is non-null, and whose <a for="fetch record">request</a>'s
<a>done flag</a> is unset or <a for=request>keepalive</a> is false,
<a for="fetch controller">terminate</a> the <a for="fetch group">fetch record</a>'s
<a for="fetch record">controller</a>.
<p>When a <a for=fetch>fetch group</a> <var>fetchGroup</var> is
<dfn export for="fetch group" id=concept-fetch-group-terminate>terminated</dfn>:

<ol>
<li>
<p><a for=list>For each</a> <a for="fetch group">deferred fetch record</a>
<var>deferredRecord</var> in <var>fetchGroup</var>'s
<a for="fetch group">deferred fetch records</a> whose <a for="deferred fetch record">invoked</a> is
false:

<ol>
<li><p>If <var>deferredRecord</var>'s <a for="deferred fetch record">pending steps</a> is not
null then <a>abort</a> <var>deferredRecord</var>'s
<a for="deferred fetch record">pending steps</a>.

<li><p><a for=/>fetch</a> <var>deferredRecord</var>'s <a for="deferred fetch record">request</a>.
</ol>

<li><p>For each associated <a for="fetch group">fetch record</a> <var>record</var>,
if <var>record</var>'s <a for="fetch record">controller</a> is non-null and
<var>record</var>'s <a for="fetch record">request</a>'s <a>done flag</a> is unset or
<a for=request>keepalive</a> is false, <a for="fetch controller">terminate</a> <var>record</var>'s
<a for="fetch record">controller</a>.
</ol>

<p>When a <a for=fetch>fetch group</a> <var>fetchGroup</var> is
<dfn export for="fetch group" id=concept-fetch-group-activate>activated</dfn>:
<a for=list>for each</a> <a for=/>deferred fetch record</a> <var>deferredRecord</var> in
<var>fetchGroup</var>'s <a for="fetch group">deferred fetch records</a>:

<ol>
<li>
<p>If <var>deferredRecord</var>'s <a for="deferred fetch record">invoked</a> is true then:
<ol>
<li><p>If <var>deferredRecord</var>'s <a for="deferred fetch record">invoked callback</a> is not
null then call <var>deferredRecord</var>'s <a for="deferred fetch record">invoked callback</a>.

<li><p><a for=list>Remove</a> <var>deferredRecord</var> from <var>fetchGroup</var>'s
<a for="fetch group">deferred fetch records</a>.
</ol>

<li><p>Otherwise, if <var>deferredRecord</var>'s
<a for="deferred fetch record">pending steps</a> is not null, then <a>abort</a>
<var>deferredRecord</var>'s <a for="deferred fetch record">pending steps</a> and set
<var>deferredRecord</var>'s <a for="deferred fetch record">pending steps</a> to null.
</ol>

<p>When a <a for=fetch>fetch group</a> <var>fetchGroup</var> is
<dfn export for="fetch group" id=concept-fetch-group-deactivate>deactivated</dfn>:

<ol>
<li>
<p><a for=list>For each</a> <a>deferred fetch record</a> <var>deferredRecord</var> in
<var>fetchGroup</var>'s <a for="fetch group">deferred fetch records</a> whose
<a for="deferred fetch record">background timeout</a> is not null: set <var>deferredRecord</var>'s
<a for="deferred fetch record">pending steps</a> to running the following steps <a>in parallel</a>:

<ol>
<li><p>Wait until <var>deferredRecord</var>'s
<a for="deferred fetch record">background timeout</a> have passed.

<li>
<p><a>Queue a fetch task</a> to run the following steps with
<a for="fetch record">request</a>'s <a for=request>client</a>'s
<a for="environment settings object">global object</a>:

<ol>
<li><p><a for=/>Fetch</a> <var>record</var>'s <a for="fetch record">request</a>.

<li><p>Set <var>deferredRecord</var> <a for="deferred fetch record">invoked</a> to true.
</ol>
</li>
</ol>
</li>
</ol>

<h3 id=resolving-domains>Resolving domains</h3>

Expand Down Expand Up @@ -8584,6 +8674,117 @@ fetch("https://www.example.com/")



<h2 id=deferred-fetching>Deferred fetching</h2>

<p>Deferred fetches allow callers to request that a fetch is invoked at the latest possible moment,
when a <a>fetch group</a> is <a for="fetch group">terminated</a>, or after a timeout after it is
<a for="fetch group">deactivated</a>.

<h3 id="requesting-a-deferred-fetch">Requesting a deferred fetch</h3>

<div algorithm="request-a-deferred-fetch">
<p>To <dfn>request a deferred fetch</dfn> given a
<a for=/>request</a> <var>request</var> and a null-or-{{DOMHighResTimeStamp}}
<var>backgroundTimeout</var> (default null):

<ol>
<li><p>Assert: <var>request</var>'s <a for=request>client</a> is an
<a>environment settings object</a>.

<li>Let <var>totalScheduledDeferredBytesForOrigin</var> be 0.

<li>
<p>If <var>request</var>'s <a for=request>body</a> is not null then:

<ol>
<li><p>If <var>request</var>'s
<a for=request>body</a>'s <a for=body>length</a> is null, then throw a {{TypeError}}.

<li><p>Set <var>totalScheduledDeferredBytesForOrigin</var> to <var>request</var>'s
<a for=request>body</a>'s <a for=body>length</a>.
</ol>
</li>

<li><p><a for=list>For each</a> <a>deferred fetch record</a> <var>deferredRecord</var> in
<var>request</var>'s <a for=request>client</a>'s <a for=fetch>fetch group</a>'s
<a for="fetch group">deferred fetch records</a>: if <var>deferredRecord</var>'s
<a for="deferred fetch record">request</a>'s <a for=request>body</a> is not null and
<var>deferredRecord</var>'s <a for="deferred fetch record">request</a>'s <a for=request>URL</a>'s
<a for=url>origin</a> is <a>same origin</a> with <var>request</var>'s <a for=request>URL</a>'s
<a for=url>origin</a>, then increment <var>totalScheduledDeferredBytesForOrigin</var> by
<var>deferredRecord</var>'s <a for="deferred fetch record">request</a>'s <a for=request>body</a>'s
<a for=body>length</a>.

<li><p>If <var>totalScheduledDeferredBytesForOrigin</var> is greater than 64 kilobytes, then
throw a {{QuotaExceededError}}.

<li><p>Let <var>deferredRecord</var> be a new <a>deferred fetch record</a> whose
<a for="deferred fetch record">request</a> is <var>request</var>.

<li><p>Set <var>deferredRecord</var>'s <a for="deferred fetch record">background timeout</a> to
<var>backgroundTimeout</var>.

<li><p><a for=list>Append</a> <var>deferredRecord</var> to <var>request</var>'s
<a for=request>client</a>'s <a for=fetch>fetch group</a>'s
<a for="fetch group">deferred fetch records</a>.

<li><p>Return <var>deferredRecord</var>.
</ol>
</div>

<h3 id=request-deferred-fetch-method>RequestDeferredFetch method</h3>

<pre class=idl>

dictionary DeferredRequestInit : RequestInit {
DOMHighResTimeStamp? backgroundTimeout;
};

partial interface mixin WindowOrWorkerGlobalScope {
[NewObject] Promise&lt;Response> requestDeferredFetch(RequestInfo input, optional DeferredRequestInit init = {});
};
</pre>

<div algorithm="dom-requestdeferredfetch">
<p>The
<dfn id=dom-global-requestdeferredfetch method for=WindowOrWorkerGlobalScope><code>requestDeferredFetch(<var>input</var>, <var>init</var>)</code></dfn>
method steps are:

<ol>
<li><p>Let <var>promise</var> be a new promise.

<li><p>Let <var>requestObject</var> be the result of invoking the initial value of {{Request}} as
constructor with <var>input</var> and <var>init</var> as arguments. If that threw an exception,
<a for=/>reject</a> <var>promise</var> with that exception and return <var>promise</var>.

<li><p>If <var>requestObject</var>'s <a for=Request>signal</a> is <a for=AbortSignal>aborted</a>,
then <a for=/>reject</a> <var>promise</var> with <var>requestObject</var>'s
<a for=Request>signal</a>'s <a for=AbortSignal>abort reason</a> and return <var>promise</var>.

<li><p>Let <var>request</var> be <var>requestObject</var>'s <a for=Request>request</a>.

<li><p>Let <var>backgroundTimeout</var> be null.

<li><p>If <var>init</var> is given and <var>init</var>["<code>backgroundTimeout</code>"]
<a for=map>exists</a> then set <var>backgroundTimeout</var> to
<var>init</var>["<code>backgroundTimeout</code>"].

<li><p>If <var>backgroundTimeout</var> is not a {{DOMHighResTimeStamp}} then throw a {{TypeError}}.

<li><p>Let <var>deferredRecord</var> be the result of calling
<a>request a deferred fetch</a> given <var>request</var> and <var>backgroundTimeout</var>. If that
threw an exception, <a for=/>reject</a> <var>promise</var> with that exception and return
<var>promise</var>.

<li><p>Set <var>deferredRecord</var>'s <a for="deferred fetch record">invoke callback</a> to
<a for=/>resolve</a> <var>promise</var>.

<li><p><a for=AbortSignal lt=add>Add the following abort steps</a> to <var>requestObject</var>'s
<a for=Request>signal</a>: <a for=list>remove</a> <var>deferredRecord</var> from
<var>request</var>'s <a for=request>client</a>'s <a for=fetch>fetch group</a>'s
<a for="fetch group">deferred fetch records</a>.
</ol>

<h2 id=data-urls><code>data:</code> URLs</h2>

<p>For an informative description of <code>data:</code> URLs, see RFC 2397. This section replaces
Expand Down

0 comments on commit b8699dc

Please sign in to comment.