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

Document render blocking with a <link rel=expect> element #9970

Merged
merged 31 commits into from
Feb 27, 2024
Merged
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 149 additions & 5 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,10 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li><p><span>Reset the form owner</span> of the <span>form-associated element</span>.</p></li>
</ol>
</li>

<li><p>If <var>insertedNode</var> is an <code>Element</code> whose not on the
noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
<span>stack of open elements</span> of an <span>HTML parser</span>, then
<span>unblock on expected element</span> given <var>insertedNode</var>.</p></li>
</ol>

<p>The <span data-x="concept-node-remove-ext">removing steps</span> for the HTML Standard, given
Expand Down Expand Up @@ -2449,6 +2453,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li>The <dfn data-x-href="https://url.spec.whatwg.org/#concept-urlencoded"><code>application/x-www-form-urlencoded</code></dfn> format</li>
<li>The <dfn data-x-href="https://url.spec.whatwg.org/#concept-urlencoded-serializer"><code>application/x-www-form-urlencoded</code> serializer</dfn></li>
<li><dfn data-x-href="https://url.spec.whatwg.org/#is-special">is special</dfn></li>
<li><dfn data-x-href="https://url.spec.whatwg.org/#url-fragment-string">URL-fragment string</dfn></li>
domenic marked this conversation as resolved.
Show resolved Hide resolved
</ul>

<p>A number of schemes and protocols are referenced by this specification also:</p>
Expand Down Expand Up @@ -15133,6 +15138,7 @@ interface <dfn interface>HTMLLinkElement</dfn> : <span>HTMLElement</span> {
data-x="concept-supported-tokens">supported tokens</span> are
<code data-x="rel-alternate">alternate</code>,
<code data-x="rel-dns-prefetch">dns-prefetch</code>,
<code data-x="rel-expect">expect</code>,
<code data-x="rel-icon">icon</code>,
<code data-x="rel-manifest">manifest</code>,
<code data-x="rel-modulepreload">modulepreload</code>,
Expand Down Expand Up @@ -24576,6 +24582,11 @@ document.body.appendChild(wbr);</code></pre>
<dd><p>These are links to other resources that are generally exposed to the user by the user
domenic marked this conversation as resolved.
Show resolved Hide resolved
agent so that the user can cause the user agent to <span>navigate</span> to those resources, e.g.
to visit them in a browser or download them.</p></dd>

<dt><dfn data-x="Internal resource link" export>Internal resource link</dfn></dt>

noamr marked this conversation as resolved.
Show resolved Hide resolved
<dd><p>These are links to resources within the current document, used to give those resources
special meaning or behavior.</p></dd>
</dl>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<p>For <code>link</code> elements with an <code data-x="attr-link-href">href</code> attribute and a
Expand Down Expand Up @@ -25856,6 +25867,15 @@ document.body.appendChild(wbr);</code></pre>
<td>Specifies that the user agent should preemptively perform DNS resolution for the target resource's <span>origin</span>.</td>
domenic marked this conversation as resolved.
Show resolved Hide resolved
</tr>

<tr>
<td><code data-x="rel-expect">expect</code></td>
<td><span>Internal resource link</span></td>
<td colspan="2"><em>not allowed</em></td>
noamr marked this conversation as resolved.
Show resolved Hide resolved
<td class="no"> &middot; </td>
<td class="no"> &middot; </td>
<td>Expect an element with the target ID to appear in the current document.</td>
</tr>

<tr>
<td><code data-x="rel-external">external</code></td>
<td><em>not allowed</em></td>
Expand Down Expand Up @@ -26315,6 +26335,117 @@ document.body.appendChild(wbr);</code></pre>
</ol>


<h5>Link type "<dfn attr-value for="link/rel"><code data-x="rel-expect">expect</code></dfn>"</h5>

<p>The <code data-x="rel-expect">expect</code> keyword may be used with <code>link</code>
elements.</p>
domenic marked this conversation as resolved.
Show resolved Hide resolved

domenic marked this conversation as resolved.
Show resolved Hide resolved
<p>The <code data-x="rel-expect">expect</code> keyword indicates that an element with a certain
domenic marked this conversation as resolved.
Show resolved Hide resolved
<span data-x="concept-id">ID</span> is expected in this document, and the link might
<span>block rendering</span> until that element's closing tag is parsed.</p>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<p>There is no default type for resources given by the <code data-x="rel-expect">expect</code>
keyword.</p>

<p>Whenever the following conditions occur for an <code data-x="rel-expect">expect</code>
<code>link</code> element <var>el</var>:</p>

domenic marked this conversation as resolved.
Show resolved Hide resolved
<ul>
<li><p><var>el</var>'s <code data-x="attr-link-href">href</code> attribute is
changed;</p></li>
domenic marked this conversation as resolved.
Show resolved Hide resolved

noamr marked this conversation as resolved.
Show resolved Hide resolved
<li><p>The environment changed and <var>el</var> has a <code
noamr marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

From implementation perspective, this may be hard to realize because we need to know which environment can change to satisfy or not satisfy a media value. We also need to be future proof about this.

Is there a precedence in the spec for re-checking media attribute if the environment changes? IMHO the ideal think would be to define specific points at which the media attribute is checked (e.g. for rel=expect, when the link element is parsed/encountered) and make the decision whether it applies at that time.

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 how all existing media attributes work? E.g. viewport width media queries, dark mode...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not different from MediaQueryList change event (https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event). Preload links are a precedence you can probably copy from.

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 how all existing media attributes work?

I'm trying to find a place where that's spelled out in the spec, do you have a link?

The best I can find in rel=stylesheet fetch setup steps is

If el's media attribute's value matches the environment and el is potentially render-blocking, then block rendering on el.

The only other place I can see is in https://html.spec.whatwg.org/#processing-the-media-attribute (which btw, needs to updated in this PR):

However, if the link is an external resource link, then the media attribute is prescriptive. The user agent must apply the external resource when the media attribute's value matches the environment and the other relevant conditions apply, and must not apply it otherwise.

This is a bit ambiguous. One can read that as "check if media matches the environment when you're about to apply the resource". I'm not sure the interpretation of "anytime environment changes, recheck all media attributes" is clear here. What is the correct reading of this?

Copy link
Member

Choose a reason for hiding this comment

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

Preload links are a precedence you can probably copy from.

Likewise here, the only relevant text I can find is in https://html.spec.whatwg.org/#link-type-preload

When the media attribute of the link element of an external resource link that is already browsing-context connected, but was previously not obtained due to the media attribute not matching the environment, is changed or removed.

This says

When the media attribute [...] is changed or removed

not when the environment itself changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it as a requirement and added a note instead

Copy link
Member

Choose a reason for hiding this comment

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

I guess this is just a victim of the general under-specification of link processing, indeed..

I don't understand the new note though. The normative text says that when media changes, if el's media doesn't match the environment, then unblock rendering on el. Which is the opposite of what the note says.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The note is about the environment changing without media changing.

Copy link
Member

Choose a reason for hiding this comment

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

For stylesheets, the handling of environment changes for the media attribute should be defined in CSSOM. I'm not sure it actually is, but HTML forwards it to CSSOM.
https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet%3Aconcept-css-style-sheet-media

CSSOM View defines when the change event should fire for MediaQueryList (which is separate) here: https://drafts.csswg.org/cssom-view-1/#evaluate-media-queries-and-report-changes

Copy link
Member

Choose a reason for hiding this comment

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

I'm unsure where this discussion leaves us but I personally am content with what's currently written because this area feels like an existing problem. @zcorpan, if you have any concerns or concrete suggestions, please let us know.

data-x="attr-link-media">media</code> attribute; or</p></li>

<li><p><var>el</var>'s <code data-x="attr-link-media">media</code> attribute changed.</p></li>
</ul>

<p>perform the following steps:</p>
noamr marked this conversation as resolved.
Show resolved Hide resolved
<ol>
<li>
<p>If all of the following are true:</p>

<ul>
domenic marked this conversation as resolved.
Show resolved Hide resolved
<li><p><var>el</var> is <span>browsing-context connected</span>;</p></li>

<li><p><var>el</var>'s <code data-x="attr-link-rel">rel</code> attribute
contains <code data-x="rel-expect">expect</code>;</p></li>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<li><p><var>el</var>'s <code data-x="attr-link-media">media</code> attribute
<span>matches the environment</span>; and</p></li>

<li><p>the <span>expected ID fragment</span> for <var>el</var> is not null.</p>
</ul>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<p>then <span>block rendering</span> on <var>el</var>.</p>
</li>

noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Otherwise, <span>unblock rendering</span> on <var>el</var>.</p></li>
noamr marked this conversation as resolved.
Show resolved Hide resolved
</ol>
domenic marked this conversation as resolved.
Show resolved Hide resolved

<p>To retrieve the <dfn>expected ID fragment</dfn> of an <span
data-x="rel-expect">expect</span> <code>link</code> element <var>el</var>, run these steps:
noamr marked this conversation as resolved.
Show resolved Hide resolved

domenic marked this conversation as resolved.
Show resolved Hide resolved
<ol>
<li><p>Let <var>url</var> be the result of <span>encoding-parsing a URL</span> given
noamr marked this conversation as resolved.
Show resolved Hide resolved
<var>el</var>'s <code data-x="attr-link-href">href</code> attribute's value, relative to
domenic marked this conversation as resolved.
Show resolved Hide resolved
<var>el</var>'s <span>node document</span>.</p></li>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<li><p>If <span>node document</span>'s <span data-x="concept-document-url">URL</span> does
not <span data-x="concept-url-equals">equal</span> <var>url</var> with <i
data-x="url equals exclude fragments">exclude fragments</i> set to true, then return
null.</p></li>

<li><p>If <var>url</var>'s <span
data-x="concept-url-fragment">fragment</span> is the empty string, return null.</p></li>

noamr marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Return <var>fragment</var>.</p></li>
</ol>

<p>To <dfn>unblock on expected element</dfn> <var>el</var>:</p>
noamr marked this conversation as resolved.
Show resolved Hide resolved

<ol>
<li>
<p><span data-x="list iterate">For each</span> <code
data-x="rel-expect">expect</code> <code>link</code> element <var>link</var> in
<var>doc</var>'s <span>render-blocking element set</span>:</p>

<ol>
<li><p>Let <var>fragment</var> be the <span>expected ID fragment</span> for
<var>link</var>.</p></li>

noamr marked this conversation as resolved.
Show resolved Hide resolved
<li><p><span>Assert</span>: <var>fragment</var> is not null.</p></li>

<li>
<p>If any of the following are true:</p>

domenic marked this conversation as resolved.
Show resolved Hide resolved
<ul>
<li><p><var>el</var> is the <span>document element</span></p></li>

<li><p><var>fragment</var> is <var>el</var>'s <span
domenic marked this conversation as resolved.
Show resolved Hide resolved
data-x="concept-id">ID</span>;</p></li>

<li><p>The result of <span data-x="decode indicator from fragment">decoding</span>
noamr marked this conversation as resolved.
Show resolved Hide resolved
<var>fragment</var>is <var>el</var>'s <span data-x="concept-id">ID</span>; or</p></li>
</ul>

noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
<p>then <span>unblock rendering</span> on <var>link</var>.</p>
</ol>
noamr marked this conversation as resolved.
Show resolved Hide resolved
</li>
noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
</ol>

noamr marked this conversation as resolved.
Show resolved Hide resolved
<p>The following<span
noamr marked this conversation as resolved.
Show resolved Hide resolved
data-x="concept-element-attributes-change-ext">attribute change steps</span>, given
<var>element</var> and <var>localName</var> are used to ensure <span
data-x="rel-expect">expect</span> <code>link</code> elements respond to dynamic <span
data-x="attr-id">id</span> changes:</p>

noamr marked this conversation as resolved.
Show resolved Hide resolved
<ol>
<li><p>If <var>localName</var> is <code data-x="attr-id">id</code>, and <var>element</var> is not
noamr marked this conversation as resolved.
Show resolved Hide resolved
in a <span>stack of open elements</span> of an <span>HTML parser</span>, then
noamr marked this conversation as resolved.
Show resolved Hide resolved
<span>unblock on expected element</span> <var>element</var>.</p></li>
</ol>

<h5>Link type "<dfn attr-value for="a/rel,area/rel,form/rel"><code
domenic marked this conversation as resolved.
Show resolved Hide resolved
data-x="rel-external">external</code></dfn>"</h5>
<!-- fifth and sixth most used <a rel> value (sixth is "external nofollow") -->
Expand Down Expand Up @@ -101891,11 +102022,8 @@ location.href = '#foo';</code></pre>
<li><p>If <var>potentialIndicatedElement</var> is not null, then return
<var>potentialIndicatedElement</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

Slightly better phrasing:

For an HTML document document, its indicated part is the result of [selecting the indicated part] given document and document's [URL].


<li><p>Let <var>fragmentBytes</var> be the result of <span
data-x="percent-decode">percent-decoding</span> <var>fragment</var>.</p></li>

<li><p>Let <var>decodedFragment</var> be the result of running <span>UTF-8 decode without
BOM</span> on <var>fragmentBytes</var>.</p></li>
<li><p>Let <var>decodedFragment</var> be the result of <span
data-x="decode indicator from fragment">decoding</span> <var>fragment</var>.
noamr marked this conversation as resolved.
Show resolved Hide resolved

<li><p>Set <var>potentialIndicatedElement</var> to the result of <span data-x="find a potential
indicated element">finding a potential indicated element</span> given <var>document</var> and
Expand All @@ -101910,6 +102038,17 @@ location.href = '#foo';</code></pre>
<li><p>Return null.</p></li>
</ol>

<p>To <dfn>decode indicator from fragment</dfn> given an <span>ASCII string</span>
<var>fragment</var>, run these steps:</p>

<ol>
noamr marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Let <var>fragmentBytes</var> be the result of <span
data-x="percent-decode">percent-decoding</span> <var>fragment</var>.</p></li>

<li><p>Return result of running <span>UTF-8 decode without
BOM</span> on <var>fragmentBytes</var>.</p></li>
</ol>

<p>To <dfn>find a potential indicated element</dfn> given a <code>Document</code>
<var>document</var> and a string <var>fragment</var>, run these steps:</p>

Expand Down Expand Up @@ -122316,6 +122455,9 @@ dictionary <dfn dictionary>StorageEventInit</dfn> : <span>EventInit</span> {
the <span>stack of open elements</span> has only one element in it (<span>fragment case</span>);
otherwise, the <span>adjusted current node</span> is the <span>current node</span>.</p>

<p>When the <span>current node</span> <var>el</var> is removed from the
<span>stack of open elements</span>, <span>unblock on expected element</span> <var>el</var>.</p>

<p>Elements in the <span>stack of open elements</span> fall into the following categories:</p>

<dl>
Expand Down Expand Up @@ -129161,6 +129303,8 @@ document.body.appendChild(text);

<li><p>Pop <em>all</em> the nodes off the <span>stack of open elements</span>.</p></li>

<li><p><span>Unblock on expected element</span> given the <span>document element</span>.</p></li>
domenic marked this conversation as resolved.
Show resolved Hide resolved

<li><p>While the <span>list of scripts that will execute when the document has finished
parsing</span> is not empty:</p>

Expand Down
Loading