@@ -6846,11 +6846,73 @@ i.e., when a <a>fetch group</a> is <a for="fetch group">terminated</a>, or after
6846
6846
6847
6847
<h4 id=deferred-fetch-quota>Deferred fetching quota</h4>
6848
6848
6849
- <p class=note> The quota asserts that this deferred fetch doesn't exceed two quotas: one for the
6850
- document and it's same-origin same-tree relatives, and one for the reporting origin (64 kibibytes).
6851
- The larger quota ensures that the top-level {{Document}} and its subresources don't continue using
6852
- an unlimited amount of bandwidth after being destroyed. The per-origin quota ensures that a single
6853
- reporting sink (e.g. RUM library) doesn't reserve the whole quota to itself.
6849
+ <!-- non-normative -->
6850
+ <p> The deferred-fetch quota is allocated to a <a for=/>top-level traversable</a> (a "tab"),
6851
+ amounting to 640 kibibytes. The top-level {{Document}} and its same-origin same-agent subframes can
6852
+ use this quota to queue deferred fetches, or delegate some of it to cross-origin or cross-agent
6853
+ subframes, using permissions policy.
6854
+
6855
+ <p> By default, 128 kibibytes out of these 640 kibibytes are allocated to delegating the quota to
6856
+ cross-origin or cross-agent subframes, each reserving 8 kibibytes.
6857
+
6858
+ <p> The top-level {{Document}} , and subsequently its subframes, can control how much of their quota
6859
+ is delegates to cross-origin/cross-agent subframes, by using {{PermissionsPolicy}} .
6860
+ By default, {{PermissionPolicy/"deferred-fetch-minimal"}} is enabled for any origin, while
6861
+ {{PermissionPolicy/"deferred-fetch"}} is enabled for the top-level document's origin only.
6862
+ By relaxing the {{PermissionPolicy/"deferred-fetch"}} policy for particular origins and subframes,
6863
+ the top-level document can allocate 64 kibibytes to those subframes. Similarly, by restricting the
6864
+ {{PermissionPolicy/"deferred-fetch-minimal"}} policy for a particular origin or subframe, the
6865
+ document can prevent the iframe from reserving the 8 kibibytes it would receive by default. By
6866
+ disabling {{PermissionPolicy/"deferred-fetch-minimal"}} for the top-level document itself, the
6867
+ entire 128 kibibytes delegated quota is collected back into the main pool of 640 kibibytes.
6868
+
6869
+ <p> Out of the allocated quota for a {{Document}} , only 64 kibibytes can be used concurrently for the
6870
+ same reporting origin (the <a for=/>request</a> 's <a for=request>URL</a>' s <a for=url>origin</a> ).
6871
+ This prevents a situation where particular 3rd party libraries would reserve quota
6872
+ opportunistically, before they have data to send.
6873
+
6874
+ <div class=example id=deferred-fetch-quota-examples>
6875
+ <p> Any of the following calls to <a method><code>fetchLater()</code></a> would throw due to
6876
+ the request itself exceeding the 64 kibibytes quota allocated to a reporting origin. Note that the
6877
+ size of the request includes the <a for=request>URL</a> itself, the <a for=request>body</a> , and the
6878
+ <a for=request>header list</a> .
6879
+ <pre><code class=lang-javascript>
6880
+ fetchLater(a_64_kb_url);
6881
+ fetchLater("https://origin.example.com", {headers: headers_exceeding_64kb});
6882
+ fetchLater(a_32_kb_url, {headers: headers_exceeding_32kb});
6883
+ fetchLater("https://origin.example.com", {method: "POST", body: body_exceeding_64_kb});
6884
+ </code></pre>
6885
+
6886
+ <p> In the following sequence, the first two requests would succeed, but the third one would throw.
6887
+ That's because al overall 640 kibibytes quota was not exceeded in the first to call, however the 3rd
6888
+ request exceeds the reporting-origin quota for <code> https://a.example.com</code> , and would throw.
6889
+ <pre><code class=lang-javascript>
6890
+ fetchLater("https://a.example.com", {method: "POST", body: a_64kb_body});
6891
+ fetchLater("https://b.example.com", {method: "POST", body: a_64kb_body});
6892
+ fetchLater("https://a.example.com");
6893
+ </code></pre>
6894
+
6895
+ <p> Same-origin same-agent subframes share the quota of their parent. However, cross-origin or
6896
+ cross-agent iframes only receive 8kb of quota by default. So in the following example, the first 3
6897
+ calls would succeed and the last one would throw.
6898
+ <pre><code class=lang-javascript>
6899
+ // In main page
6900
+ fetchLater("https://a.example.com", {method: "POST", body: a_64kb_body});
6901
+
6902
+ // In same-origin iframe
6903
+ fetchLater("https://b.example.com", {method: "POST", body: a_64kb_body});
6904
+
6905
+ // In cross-origin iframe at https://frame.example.com
6906
+ fetchLater("https://a.example.com", {body: a_5kb_body});
6907
+ fetchLater("https://a.example.com", {body: a_12kb_body});
6908
+ </code></pre>
6909
+
6910
+ <p> To make the previous example not throw, the top-level {{Document}} needs to delegate some of its
6911
+ quota to <code> https://frame.example.com</code> , for example by serving the following header:
6912
+ <pre><code> Permissions-Policy: deferred-fetch=(self "https://frame.example.com")</code></pre>
6913
+
6914
+ </div>
6915
+
6854
6916
6855
6917
<p> This specification defined a <a>policy-controlled feature</a> identified by the string
6856
6918
<dfn for=PermissionPolicy enum-value>"deferred-fetch"</dfn> . Its
@@ -7045,7 +7107,6 @@ shared.
7045
7107
</ol>
7046
7108
7047
7109
<li><p> Otherwise, set <var> container</var> 's <a>reserved deferred-fetch quota</a> to zero.
7048
-
7049
7110
</div>
7050
7111
7051
7112
@@ -8957,9 +9018,12 @@ method steps are:
8957
9018
<li><p> If <var> request</var> 's <a for=request>URL</a>' s <a for=url>scheme</a> is not an
8958
9019
<a>HTTP(S) scheme</a> , then throw a {{TypeError}} .
8959
9020
8960
- <li><p><li><p> If <var> request</var> 's
8961
- <a for=request>body</a> is not null, and <var> request</var> 's
8962
- <a for=request>body</a> <a for=body>source</a> is null, then throw a {{TypeError}} .
9021
+ <li>
9022
+ <p> If <var> request</var> 's
9023
+ <a for=request>body</a> is not null, and <var> request</var> 's <a for=request>body</a>
9024
+ <a for=body>length</a> is null, then throw a {{TypeError}} .
9025
+
9026
+ <p class=note> Requests whose <a for=request>body</a> is a {{ReadableStream}} cannot be deferred.
8963
9027
8964
9028
<li><p> If <var> request</var> 's <a for=request>client</a> is not a <a>fully active</a>
8965
9029
{{Document}} , then throw an "{{InvalidStateError}} " {{DOMException}} .
@@ -8992,6 +9056,73 @@ method steps are:
8992
9056
<var> deferredRecord</var> .
8993
9057
</ol>
8994
9058
9059
+ <div class=example id=fetch-later-examples>
9060
+ <p> The following call would queue a request to be fetched when the document is terminated:
9061
+ <pre><code class=lang-javascript> fetchLater("https://report.example.com", { method: "POST",
9062
+ body: JSON.stringify(myReport) })</code></pre>
9063
+
9064
+ <p> The following call would also queue this request after 5 seconds, and the returned value would
9065
+ allow callers to observe if it was indeed activated. Note that the request is guaranteed to be
9066
+ invoked, even in cases where the user agent throttles timers.
9067
+
9068
+ <pre><code class=lang-javascript>
9069
+ const result = fetchLater("https://report.example.com", {
9070
+ method: "POST",
9071
+ body: JSON.stringify(myReport),
9072
+ activateAfter: 5000
9073
+ });
9074
+
9075
+ function check_if_fetched() {
9076
+ return result.activated;
9077
+ }
9078
+ </code></pre>
9079
+
9080
+ <p> The {{FetchLaterResult}} object can be used together with an {{AbortSignal}} . For example:
9081
+ <pre><code class=lang-javascript>
9082
+ let accumulated_events = [];
9083
+ let previous_result = null;
9084
+ const abort_signal = new AbortSignal();
9085
+ function accumulate_event(event) {
9086
+ if (previous_result) {
9087
+ if (previous_result.activated) {
9088
+ // The request is already activated, we can start from scratch.
9089
+ accumulated_events = [];
9090
+ } else {
9091
+ // Abort this request, and start a new one with all the events.
9092
+ signal.abort();
9093
+ }
9094
+ }
9095
+
9096
+ accumulated_events.push(event);
9097
+ fetchLater("https://report.example.com", {
9098
+ method: "POST",
9099
+ body: JSON.stringify(accumulated_events),
9100
+ activateAfter: 5000,
9101
+ abort_signal
9102
+ });
9103
+
9104
+ return result.activated;
9105
+ }
9106
+ </code></pre>
9107
+
9108
+
9109
+ <p> Any of the following calls to <a method><code>fetchLater()</code></a> would throw:
9110
+ <pre><code class=lang-javascript>
9111
+ // Only <a>potentially trustworthy url</a> s are supported.
9112
+ fetchLater("http://untrusted.example.com");
9113
+
9114
+ // The length of the deferred request has to be known when.
9115
+ fetchLater("https://origin.example.com", {body: someDynamicStream});
9116
+
9117
+ // Deferred fetching only works on active windows.
9118
+ const detachedWindow = iframe.contentWindow;
9119
+ iframe.remove();
9120
+ detachedWindow.fetchLater("https://origin.example.com");
9121
+ </code></pre>
9122
+
9123
+ See <a href="#deferred-fetch-quota-examples">deferred fetch quota examples</a> for examples
9124
+ portraying how the deferred-fetch quota works.
9125
+ </div>
8995
9126
8996
9127
<h3 id=garbage-collection>Garbage collection</h3>
8997
9128
0 commit comments