Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deferred fetching #1647

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Deferred fetching #1647

wants to merge 5 commits into from

Conversation

noamr
Copy link
Contributor

@noamr noamr commented May 2, 2023

Add a JS-exposed function to request a deferred fetch, called fetchLater.

A deferred fetch would be invoked in one of two scenarios:

  • The document is destroyed (the fetch group is terminated)
  • A given period of time has passed.

A few constraints:

  • Request body streams are not allowed
  • This is only allowed in documents, and only reporting to potentially-trustworthy URLs
  • Deferred fetch requests are limited to 64KB per origin. Exceeding this would immediately throw.

The quota algorithm is a bit intricate, but its default should be somewhat reasonable for all but advanced cases.

  • A top level document has 640kb of quota for deferred fetching. This is important to avoid wasting high bandwidth after a tab has been closed. This quota is shared, by default, with the top-level's document same-origin same-agent descendants. The same-agent restriction is important for avoiding race conditions, as same-agent frames are guaranteed to call fetchLater in sequence.
  • By default, 128kb out of the 640kb quota is reserved for cross-origin or cross-agent iframes. Permissions policy (deferred-fetch-minimal) controls that, and the top-level document can disable that allocated quota by disabling that permissions policy.
  • Any document can delegate 64kb out of its reserved quota for cross-origin or cross-agent subframes, by explicitly enabling the deferred-fetch permissions policy.
  • Reserving some of the quota to a cross-origin or cross-agent subframe happens when the frame is being navigated by the container, e.g. setting src on an iframe. It is not guaranteed that the subframe would actually be able to use that
    quota, as it might end up navigating to a same-origin URL or disable the feature in its own permissions policy. However, the container's document only cares about the initial reserved value for subframes it doesn't have direct access to.

See WICG/pending-beacon#70

(See WHATWG Working Mode: Changes for more details.)


Preview | Diff

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. "inactive timeout" -> "deferred delay". At least that seems clearer to me.
  2. I think fetchLater was quite nice in that it sorts the same as fetch. fetchDeferred could also work, though is a bit harder to spell. I don't think we need request in the name.
  3. It's not clear how the name fetch group states get activated. That needs some kind of additional PR against HTML I suppose?
  4. I think we should describe the API in the same section as the fetch method. Could be called "Fetch methods" then.
  5. Deferred fetching itself could then precede the "Fetch API" section. Maybe it could even be a subsection of "Fetching" though I don't mind a new top-level section.

@noamr
Copy link
Contributor Author

noamr commented May 8, 2023

  1. "inactive timeout" -> "deferred delay". At least that seems clearer to me.

But this is deferred delay specifically in the case of inactivity. "inactivity deferred delay"?

  1. I think fetchLater was quite nice in that it sorts the same as fetch. fetchDeferred could also work, though is a bit harder to spell. I don't think we need request in the name.

I was thinking about "what are we doing right now?" which is requesting/scheduling a deferred fetch for later. But fetchLater is also fine with me, it's very easy to remember.

  1. It's not clear how the name fetch group states get activated. That needs some kind of additional PR against HTML I suppose?

Yes, HTML would activate/deactivate in the BFCache code path. Need to prepare a PR for that but wanted to see that I'm on the right track first.

  1. I think we should describe the API in the same section as the fetch method. Could be called "Fetch methods" then.

Will do

  1. Deferred fetching itself could then precede the "Fetch API" section. Maybe it could even be a subsection of "Fetching" though I don't mind a new top-level section.

OK

@mingyc
Copy link

mingyc commented May 9, 2023

cc @mingyc @fergald @yoavweiss @clelland latest API shape proposal for PendingBeacon

@mingyc
Copy link

mingyc commented May 16, 2023

Deferred fetch body sizes are limited to 64KB per origin. Exceeding this would immediately reject with a QuotaExceeded.

Another note about "origin" of a beacon request: there were some previous discussion about using 3P storage partitioning key (not origin, which is stricter) to decide whether pending beacon requests in a page are sendable or not in terms of privacy concern, see WICG/pending-beacon#30 (comment) and comments there below. I am not sure how this should be spec.

@noamr
Copy link
Contributor Author

noamr commented May 16, 2023

Deferred fetch body sizes are limited to 64KB per origin. Exceeding this would immediately reject with a QuotaExceeded.

Another note about "origin" of a beacon request: there were some previous discussion about using 3P storage partitioning key (not origin, which is stricter) to decide whether pending beacon requests in a page are sendable or not in terms of privacy concern, see WICG/pending-beacon#30 (comment) and comments there below. I am not sure how this should be spec.

OK, perhaps the 64kb constraint can be per network partition key rather than origin.

Copy link

@mingyc mingyc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL, added some more comments.

Copy link

@mingyc mingyc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noamr@ PTAL. I've added some more questions and comments. Really thanks for your help!

Copy link

@mingyc mingyc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you noamr@! I've added some last comments. And also added the missing features from the original proposal belows. Please let us know if they are suitable here.

  1. It is suggested to allow only secure requests for new API in Do we want to enforce HTTPS request? WICG/pending-beacon#27. Should we enforce HTTPS-only requests on fetchLater()?
  2. Should this spec mention [Permission Policy]https://www.w3.org/TR/permissions-policy/? In [Fetch-Based API] Permissions Policy WICG/pending-beacon#77, the suggestion is to allow the API by default. But we might want to provide a way to manage 3rd party iframe's usage.
  3. Consider to support retry mechanism WICG/pending-beacon#40 Should this spec mention retry when fetchLater() fails to send/commit?
  4. The original PendingBeacon proposal also includes Crash recovery WICG/pending-beacon#34, not sure how it can be incorporated into fetch spec.

mingyc added a commit to WICG/pending-beacon that referenced this pull request Jul 4, 2023
This PR adds overview and example codes for the draft `fetchLater()` API spec from whatwg/fetch#1647

The API will address the discussions in #70 #72 #73 #74 #75 #76.
@noamr
Copy link
Contributor Author

noamr commented Jul 4, 2023

Thank you noamr@! I've added some last comments. And also added the missing features from the original proposal belows. Please let us know if they are suitable here.

  1. It is suggested to allow only secure requests for new API in Do we want to enforce HTTPS request? WICG/pending-beacon#27. Should we enforce HTTPS-only requests on fetchLater()?

Probably a good idea, from the point of view of enabling new features only for secure requests.

  1. Should this spec mention [Permission Policy]https://www.w3.org/TR/permissions-policy/? In [Fetch-Based API] Permissions Policy WICG/pending-beacon#77, the suggestion is to allow the API by default. But we might want to provide a way to manage 3rd party iframe's usage.

I don't think we should integrate with permission policy. But we should allow the user agent to deny a fetchLater and throw an error immediately. I'll add that to the PR.

  1. Consider to support retry mechanism WICG/pending-beacon#40 Should this spec mention retry when fetchLater() fails to send/commit?

Perhaps consider adding this later?

  1. The original PendingBeacon proposal also includes Crash recovery WICG/pending-beacon#34, not sure how it can be incorporated into fetch spec.

I don't think that changes anything in the spec.

@mingyc
Copy link

mingyc commented Jul 18, 2023

Deferred fetch body sizes are limited to 64KB per origin. Exceeding this would immediately reject with a QuotaExceeded.

Another note about "origin" of a beacon request: there were some previous discussion about using 3P storage partitioning key (not origin, which is stricter) to decide whether pending beacon requests in a page are sendable or not in terms of privacy concern, see WICG/pending-beacon#30 (comment) and comments there below. I am not sure how this should be spec.

OK, perhaps the 64kb constraint can be per network partition key rather than origin.

@noamr Following up on the sendable beacon discussion:

As mentioned in WICG/pending-beacon#30 (comment), there were discussions around whether a beacon (or deferred request) should be sent when network changes. I tried to summarize them in [this PR](WICG/pending-beacon@feb3cf9, but basically to process a beacon request when BackgroundSync is off, we need to see if another open document (tab/frame/etc) with the same storage partitioning key as the current document's one, to avoid unexpected sending the request after network changes.

Do you think the above makes sense to be integrated into Fetch spec?

mingyc added a commit to mingyc/pending-beacon that referenced this pull request Jul 26, 2023
mingyc added a commit to mingyc/pending-beacon that referenced this pull request Jul 26, 2023
mingyc added a commit to mingyc/pending-beacon that referenced this pull request Jul 26, 2023
mingyc added a commit to WICG/pending-beacon that referenced this pull request Jul 26, 2023
noamr added a commit to whatwg/html that referenced this pull request Jan 8, 2025
The logic for deferred fetching (the `fetchLater` function), as
defined in the fetch spec, specifies a "quota" which is shared
with between a document and its direct same-origin descendants.

For this logic to work in a secure way, the quota needs to be:
- reserved when a frame-initiated navigation starts. This way,
  the container document can only reserve quota based on URLs
  it knows it navigates to.
- freed if the document ends up being same origin with its
  container, upon document creation.
  This ensures quota is handled correctly in the case of
  redirects.

This PR adds those two calls:
- Call "reserve" on navigation, based on `sourceDocument`.
- Call "potentially free" on document creation.

Depends on whatwg/fetch#1647, where
the quota logic itself is defined.
@noamr
Copy link
Contributor Author

noamr commented Apr 8, 2025

@annevk any chance you could look at this soon?

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before addressing these comments, maybe squash and merge the existing commits and rebase on main. 103 is a bit much. :-)

fetch.bs Outdated
Comment on lines 6945 to 6976
<pre><code>
+ https://me.example.com with Permissions-policy: deferred-fetch=(self "https://ok.example.com")
| (See below for quota)
|
+ ---- + https://me.example.com
| | Shares quota with the <a for=/>top-level traversable</a>, as they're same origin.
| |
| + ---- + https://x.example.com
| 8 kibibytes.
|
|
+ ---- + https://x.example.com
| 8 kibibytes.
| |
| + https://me.example.com
| 0. Even though it's same origin with the <a for=/>top-level traversable</a>, it does not
| automatically share its quota as they are separated by a cross-origin intermediary.
|
+ ---- + https://ok.example.com/good
| | 64 kibibytes, granted via the "{{PermissionsPolicy/deferred-fetch}}" policy.
| |
| + ---- + https://x.example.com
| 0. Only documents with the same origin as the <a for=/>top-level traversable</a> can
| grant the 8 kibibytes based on the "{{PermissionsPolicy/deferred-fetch-minimal}}" policy.
|
+ ---- + https://ok.example.com/redirect, navigated to https://x.example.com
| 0. The reserved 64 kibibytes for https://ok.example.com are not available for https://x.example.com.
|
+ ---- + https://ok.example.com/back, navigated to https://me.example.com
Shares quota with the <a for=/>top-level traversable</a>, as they're same origin.
</code></pre>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is accessible so I don't think we can include this as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed to use nested ul and li.

fetch.bs Outdated
</div>

<div algorithm>
<p>To get the <dfn>deferred-fetch control document</dfn> of a {{Document}} <var>document</var>:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it should be {{Document}} object for instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of them should be <a for=/>document</a> actually, why should this one be a {{Document}} object?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine too.

@noamr
Copy link
Contributor Author

noamr commented Apr 9, 2025

Before addressing these comments, maybe squash and merge the existing commits and rebase on main. 103 is a bit much. :-)

Squashed and addressed all the comments.

Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very close. If anyone has any final comments please make them this week. I'll work with Noam to get this merged early next week.


<li><p><a data-lt="process a deferred fetch">Process</a> <var>deferredRecord</var>.
</ol>
</li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
</li>

<a for=request>URL</a>, <a lt="URL serializer">serialized</a> with
[=URL serializer/exclude fragment=] set to true.

<li><p>Increment <var>totalRequestLength</var> by the <a for=string>length</a> of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<li><p>Increment <var>totalRequestLength</var> by the <a for=string>length</a> of
<li><p>Increment <var>totalRequestLength</var> by the <a for=string>length</a> of

<ol>
<li><p>Let <var>totalRequestLength</var> be the <a for=string>length</a> of <var>request</var>'s
<a for=request>URL</a>, <a lt="URL serializer">serialized</a> with
[=URL serializer/exclude fragment=] set to true.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this will get the correct formatting. It needs to use <i><a> iirc.

pool of 640 kibibytes.

<p>Out of the allocated quota for a <a for=/>document</a>, only 64 kibibytes can be used
concurrently for the same reporting origin (the <a for=/>request</a>'s <a for=request>URL</a>'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
concurrently for the same reporting origin (the <a for=/>request</a>'s <a for=request>URL</a>'
concurrently for the same reporting origin (the <a for=/>request</a>'s <a for=request>URL</a>'s


<p>Out of the allocated quota for a <a for=/>document</a>, only 64 kibibytes can be used
concurrently for the same reporting origin (the <a for=/>request</a>'s <a for=request>URL</a>'
<a for=url>origin</a>). This prevents a situation where particular 3rd party libraries would reserve
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<a for=url>origin</a>). This prevents a situation where particular 3rd party libraries would reserve
<a for=url>origin</a>). This prevents a situation where particular third-party libraries would reserve

Comment on lines +7147 to +7152
<li><p>The <a for=list>size</a> of <var>controlDocument</var>'s <a>node navigable</a>'s
<a>descendant navigables</a>, <a for=list data-lt=remove>removing</a> any <a for=/>navigable</a>
whose <a>navigable container</a>'s <a>reserved deferred-fetch quota</a> is not
<a for="reserved deferred-fetch quota">minimal quota</a>, is less than
<a>quota reserved for <code>deferred-fetch-minimal</code></a> /
<a for="reserved deferred-fetch quota">minimal quota</a>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<li><p>The <a for=list>size</a> of <var>controlDocument</var>'s <a>node navigable</a>'s
<a>descendant navigables</a>, <a for=list data-lt=remove>removing</a> any <a for=/>navigable</a>
whose <a>navigable container</a>'s <a>reserved deferred-fetch quota</a> is not
<a for="reserved deferred-fetch quota">minimal quota</a>, is less than
<a>quota reserved for <code>deferred-fetch-minimal</code></a> /
<a for="reserved deferred-fetch quota">minimal quota</a>.
<li><p>the <a for=list>size</a> of <var>controlDocument</var>'s <a>node navigable</a>'s
<a>descendant navigables</a>, <a for=list data-lt=remove>removing</a> any <a for=/>navigable</a>
whose <a>navigable container</a>'s <a>reserved deferred-fetch quota</a> is not
<a for="reserved deferred-fetch quota">minimal quota</a>, is less than
<a>quota reserved for <code>deferred-fetch-minimal</code></a> /
<a for="reserved deferred-fetch quota">minimal quota</a>,

Comment on lines +7178 to +7182
<li><p>If <var>document</var>' <a>node navigable</a>'s <a>container document</a> is null or a
<a for=/>document</a> whose <a for=Document>origin</a> is not <a>same origin</a> with
<var>document</var>, return <var>document</var>; Otherwise return the
<a>deferred-fetch control document</a> given <var>document</var>' <a>node navigable</a>'s
<a>container document</a>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<li><p>If <var>document</var>' <a>node navigable</a>'s <a>container document</a> is null or a
<a for=/>document</a> whose <a for=Document>origin</a> is not <a>same origin</a> with
<var>document</var>, return <var>document</var>; Otherwise return the
<a>deferred-fetch control document</a> given <var>document</var>' <a>node navigable</a>'s
<a>container document</a>.
<li><p>If <var>document</var>' <a>node navigable</a>'s <a>container document</a> is null or a
<a for=/>document</a> whose <a for=Document>origin</a> is not <a>same origin</a> with
<var>document</var>, then return <var>document</var>; otherwise, return the
<a>deferred-fetch control document</a> given <var>document</var>'s <a>node navigable</a>'s
<a>container document</a>.


<div algorithm="dom-fetch-later">
<p>The
<dfn id=dom-global-fetch-later method for=Window><code>fetchLater(<var>input</var>, <var>init</var>)</code></dfn>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<dfn id=dom-global-fetch-later method for=Window><code>fetchLater(<var>input</var>, <var>init</var>)</code></dfn>
<dfn method for=Window><code>fetchLater(<var>input</var>, <var>init</var>)</code></dfn>

readonly attribute boolean activated;
};

partial interface mixin Window {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
partial interface mixin Window {
partial interface Window {

I don't understand why this would be a mixin.

Comment on lines +9091 to +9092
<li><p>If <var>request</var>'s <a for=request>window</a>'s <a>associated document</a> is not a
<a>fully active</a> <a for=/>document</a>, then throw a {{TypeError}}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<li><p>If <var>request</var>'s <a for=request>window</a>'s <a>associated document</a> is not a
<a>fully active</a> <a for=/>document</a>, then throw a {{TypeError}}.
<li><p>If <var>request</var>'s <a for=request>window</a>'s <a>associated document</a> is not
<a>fully active</a>, then throw a {{TypeError}}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

7 participants