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

[View Transitions] Extend render-blocking to support Document #9332

Closed
khushalsagar opened this issue May 25, 2023 · 83 comments · Fixed by #9970
Closed

[View Transitions] Extend render-blocking to support Document #9332

khushalsagar opened this issue May 25, 2023 · 83 comments · Fixed by #9970
Labels
addition/proposal New features or enhancements topic: rendering

Comments

@khushalsagar
Copy link
Contributor

render-blocking allows the author to specify which resources are important for a good first paint. This allows the browser to defer a Document's first paint (new load only) up till a timeout.

It currently supports a limited set of resources that allow the blocking attribute. Two common use-cases which are missing are:

  • Images. Documents frequently have a hero image. While a network delay here could be too slow, fetching them from the disk cache could be worth the delay.
  • Document itself. Authors have no way of saying whether the "above the fold" content has been parsed and fetched.

It would be good to extend this concept to cover the above use-cases. Likely with a generic promise based API like:

document.renderBlockUntil(loadBlockingResources);

The downside of a promise based API is that the browser has no visibility into which resources are render-blocking, to prioritize fetching them. But it provides a lot of flexibility, and would work well if other APIs can already be used to prioritize network work.

This API would also be an important building block for View Transitions.

@noamr @xiaochengh @yoavweiss @nickcoury

@nickcoury
Copy link

I was just prototyping a version of this yesterday with a pair of style tags:

<style>
.render-blocked {
  display: none;
}
</style>
<!-- Above the fold content -->
<style>
.render-blocked {
  display: block;
}

There are several uses I see for this:

  1. Avoid flashes of partially rendered content.
  2. Lower the difficulty of getting frame-perfect transitions especially with more complicated DOM manipulations and async code paths. (VT API can also fulfill this if appropriate)
  3. Possible performance gains from reducing layout thrashing.

JS API

I don't have a strong sense of an ergonomic JS API yet, but the promise one seems reasonable and would be flexible. The other method would be to have an explicit start() and stop() method, though the advantage of this is multiple pieces of code could independently render-block, and one wouldn't accidentally unblock the other.

A timeout could be a convenient parameter. It could be manually built into the promise API, though that's subject to a setTimeout not being blocked on the main thread.

On point 2 above, we frequently have cases where we remove old DOM, run some cleanup code, render new DOM, hydrate controllers that may further change DOM on instantiation, then finally have the final state ready. Given a lot of code is built to be async and non-blocking by releasing control back to the main thread, or uses requestAnimationFrame for certain use cases, we sometimes see 1 frame flashes of intermediate state before the final render. This is jarring to users and takes tedious coordination of client and framework code to prevent. This would bypass all of that and allow better structured code to focus on behaviors rather than rendering quirks.

One example we hit is fetching content from an async cache. Our transition service is separate from our fetching and rendering services. The transition code is responsible for cleaning up the previous content and removing old DOM, but the fetching + rendering holds the implementation details for getting and rendering new content. If the fetch service gets the content from a local async cache, the timing is such that we can show a blank page briefly before the renderer gets the content. We had to add complexity to coordinate this, rather than the transition service blocking rendering, removing old code, sending the request to fetch and render, then unblocking once the renderer is finished.

One consideration that comes to mind from some VT API use case is if animations or videos are allowed to continue to run during render blocking. In the previous example, the transition service might play a partial fade out animation on the old content to show the user the page is changing, not knowing if the fetch service will make a network request or hit local cache. Though I'm not sure if the complexity required for this is worth it.

HTML API

I can see the role of an HTML API, especially if this is used on an initial page render where JS will have less knowledge of the streaming page as it is parsed than the browser will. It might look something like this.

<meta rb-id="unblock" rb-timeout="500">
<!-- Above the fold content -->
<div id="unblock"></div>

I'm less certain this would be useful after the initial streaming load, as usually HTML inserted later on can be batched instead of streamed.

@khushalsagar
Copy link
Contributor Author

It sounds like the use-case for a JS API is primarily for same-document navigations where removing the old DOM and populating the new DOM can't be an atomic operation. Does the HTML API suffice when loading a new Document? We're erring towards a declarative solution since it works better with browser optimizations like preload scanning.

One consideration that comes to mind from some VT API use case is if animations or videos are allowed to continue to run during render blocking.

This is fairly hard. Render blocking works by pausing frame production completely (as if the tab was backgrounded) so no animations or video playback can run. As an internal detail, we can keep accelerated animations running while you're mutating the DOM. But it becomes very difficult to reason about if the DOM mutation removes some element and its associated animation keeps running.

If you're ok with the page being static then you can kinda get what you want by hacking the VT API just for the render-blocking part. Something like:

document.documentElement.style.viewTransitionName = "none";
document.startViewTransition(asyncDOMUpdate);

We had to add complexity to coordinate this, rather than the transition service blocking rendering, removing old code, sending the request to fetch and render, then unblocking once the renderer is finished.

The transition service can use the code above to block rendering in the sequence you mentioned. This seems ok if the async part is hitting a local cache. If the fetch needs to hit the network, you probably still want to do that before suppressing rendering to keep the current page animating and interactive while waiting on the network.

@nickcoury
Copy link

Yes, that's the primary use case for the JS API for us, and the VT API would likely work well enough as long as it's still performant on a window of a frame or two.

@khushalsagar
Copy link
Contributor Author

As spec'd, startViewTransition will do the following:

  1. Render one frame and snapshot it. If no element has a view-transition-name, snapshotting has no cost. Chrome's current implementation might have a one frame delay here (we wait for this frame to progress until a point before realizing no element needs a snapshot) which we can optimize.
  2. Dispatch the callback passed to that function. Now its up to the author how long this takes.
  3. When the callback is done (or timeout happens), start rendering again.

There shouldn't be any performance issues even if the callback takes one or 2 frame's worth of latency. Let us know if you're not seeing that in practice.

@khushalsagar khushalsagar changed the title Extend render-blocking to support other resources and Document Jun 6, 2023
@khushalsagar khushalsagar changed the title [View Transitions] Extend render-blocking to support other resources and Document Jun 6, 2023
@noamr
Copy link
Contributor

noamr commented Jun 14, 2023

I wonder if for the view transitions case this can be a CSS property rather than an HTML property, e.g. putting
view-transition-capture: stall or so on elements that are not yet "settled", giving a hint to the UA that the new state is not ready to be captured. This means though that we keep have to be calculate style even if we end up not performing layout/paint.

@zcorpan zcorpan added addition/proposal New features or enhancements topic: rendering labels Jun 15, 2023
@khushalsagar
Copy link
Contributor Author

An explainer clarifying the use-case and a concrete proposal is now available here.

@nickcoury
Copy link

One question on the proposal is if requiring JavaScript to unblock rendering will be a performance bottleneck relative to a more declarative solution? My understanding is that the context switch to JS land can be expensive especially on initial page load when a lot of things are going on. Or is that just as performant as an HTML or CSS directive to unblock?

@eeeps
Copy link
Contributor

eeeps commented Aug 10, 2023

For the partial rendering case, this proposal expects authors to:

  1. know the layout (true most of the time)
  2. know the "settled" DOM content (not true in many dynamic/interactive contexts)
  3. make a judgement call about a "worst-case scenario" - the most content that will generally be rendered above the fold, given the known layout and current device/software landscape, and common user preference settings. (this is really hard, see also, the frequency of above-the-fold <img loading=lazy>)

That's a lot to ask, and I worry that poor usage of this API (because of the difficulty and in many cases impossibility of using it well) will lead to worse experiences for users (very slow LCPs).

@khushalsagar
Copy link
Contributor Author

One question on the proposal is if requiring JavaScript to unblock rendering will be a performance bottleneck relative to a more declarative solution? My understanding is that the context switch to JS land can be expensive especially on initial page load when a lot of things are going on. Or is that just as performant as an HTML or CSS directive to unblock?

I don't think the script switch should be a problem. If an author is explicitly unblocking rendering before full Document parsing, that's a strong hint that this is a good spot for the parser to yield for rendering. So a content switch away from the parser will likely be done anyway.

That said, a declarative solution like a new tag: <unblock render> is also doable. The script way is preferred just for consistency, since that's how authors are supposed to preemptively unblock rendering on other resources (script, stylesheets). @mfreed7 @chrishtr in case they have an opinion on whether a declarative tag is worth any perf implications.

That's a lot to ask, and I worry that poor usage of this API

The targeted use-case for partial rendering is View Transitions. The author knows exactly which DOM nodes need to animate (will have a view-transition-name) for each transition. So they can unblock rendering when the last required node for a transition has been parsed.

@noamr
Copy link
Contributor

noamr commented Aug 10, 2023

For the partial rendering case, this proposal expects authors to:

  1. know the layout (true most of the time)

  2. know the "settled" DOM content (not true in many dynamic/interactive contexts)

  3. make a judgement call about a "worst-case scenario" - the most content that will generally be rendered above the fold, given the known layout and current device/software landscape, and common user preference settings. (this is really hard, see also, the frequency of above-the-fold <img loading=lazy>)

That's a lot to ask, and I worry that poor usage of this API (because of the difficulty and in many cases impossibility of using it well) will lead to worse experiences for users (very slow LCPs).

People can do this today eg by starting with body at display:none and changing to display:block once enough content had been parsed.

In addition, LCP is not the only relevant metric here - there is also CLS.

Blocking render until enough is parsed to avoid jumpiness is a legit trade off that developers should be able to make use of. Sometimes the way to avoid the footguns is with education rather than by limiting the choices.

@khushalsagar
Copy link
Contributor Author

People can do this today eg by starting with body at display:none and changing to display:block once enough content had been parsed.

Adding some anecdotal data for this, Chrome has a feature called paint holding which will defer displaying a web page until it has FCP content. We've heard from authors using display: none or opacity: 0 on html/body element on top of that to effectively get the behaviour proposed here.

The author knows exactly which DOM nodes need to animate (will have a view-transition-name) for each transition.

Adding a concrete use-case from a real world site. Say you have a header or footer which is position: fixed so its guaranteed to be displayed in viewport irrespective of the device. And you want it to slide in from viewport edge (or stay at the same spot if it was in the old Document as well). The only way to get the transition right is for the browser to parse those elements before first render. Otherwise incorrect animations will be set up as described here.

@nickcoury
Copy link

I agree that the potential for misuse shouldn't be a blocker on this proposal especially since it's opt-in to solve a specific problem. While web users are used to stream rendering, there's an argument that it's a large barrier to more modern and "native, app-like" experiences on the web because we don't have sufficient control of rendering when needed. The web community already recognizes the problematic nature of flashes of partial content (flashes of unstyled content, CLS, etc) and initial page loading need not be an exception for visually seamless transitions.

At the same time I also agree the magnitude of impact (multiple second delay for first paint) is also larger than average.

Are there safeguards we can put in place to help balance the concerns here?

  • Default timeout after which the page is shown, which can be overridden by the developer e.g. `
  • Console warning when render blocking is used and a problematic timeout is hit. May be covered by LCP already, but an explicit warning could be more explicit and impactful.
@khushalsagar
Copy link
Contributor Author

Default timeout after which the page is shown

+1. That's already how the render-blocking concept is spec'd.

Console warning when render blocking is used

That sounds reasonable, we can emit a warning if the timeout is hit and which resources were timed out. @xiaochengh FYI.

@noamr
Copy link
Contributor

noamr commented Aug 15, 2023

Default timeout after which the page is shown

+1. That's already how the render-blocking concept is spec'd.

Console warning when render blocking is used

That sounds reasonable, we can emit a warning if the timeout is hit and which resources were timed out. @xiaochengh FYI.

Side note - the console is already cluttered with such warnings, but we can find a different solution in devtools UI to help with this.

@eeeps
Copy link
Contributor

eeeps commented Aug 15, 2023

Couple of Qs as I continue to wrap my head around this:

  1. What is Chrome's existing blocking=render timeout, and would this use the same timeout?
  2. If the timeout is reached, would it be better to do a possibly-janky View Transition, or (somehow?) cancel the Transition?
@noamr
Copy link
Contributor

noamr commented Aug 15, 2023

Couple of Qs as I continue to wrap my head around this:

  1. Chrome's existing blocking=render timeout seems to be 30 seconds. Would this use the same timeout?

For regular (non-transition) blocking, I don't see why not.

  1. If the timeout is reached, would it be better to do a possibly-janky View Transition, or (somehow?) cancel the Transition?

I think this would require experimentation, and might not need to be the same across browsers. In the same-document case the transition times out a lot faster than 30s.

@eeeps
Copy link
Contributor

eeeps commented Aug 15, 2023

@noamr Note that I think I got the test for the existing implementation wrong, a second (more accurate?) test indicated 500ms, which is... very different. Updated my comment accordingly.

@noamr
Copy link
Contributor

noamr commented Aug 15, 2023

@noamr Note that I think I got the test for the existing implementation wrong, a second (more accurate?) test indicated 500ms, which is... very different. Updated my comment accordingly.

My answer is the same... A lot of this is heuristic and differs between browsers. This proposal (together with the existing render blocking styles & scripts) allows developers to set an intention that guides the UA to a different trade-off between first-paint time and layout stability.

@khushalsagar
Copy link
Contributor Author

What is Chrome's existing blocking=render timeout, and would this use the same timeout?

@xiaochengh @mfreed7 on that. I'm not sure if there is any reason to use a different timeout for different types of resources. The spec leaves this up to the UA so we can if needed.

If the timeout is reached, would it be better to do a possibly-janky View Transition, or (somehow?) cancel the Transition?

FWIW, this came up on the TAG review as well. Here's the tracking issue: w3c/csswg-drafts#9155. We'll likely add spec text to recommend that UAs should consider aborting the transition if the navigation has crossed some time threshold (up to the UA what exactly that is).

@xiaochengh
Copy link
Contributor

What is Chrome's existing blocking=render timeout, and would this use the same timeout?

We don't have any explicit timeout currently. We just use the default network timeout -- when the resource loading fails, we unblock rendering.

But anyway, it's free to change.

@khushalsagar
Copy link
Contributor Author

Another update to the explainer based on some offline feedback. Latest version is here.

The interesting open questions are:

  1. There's 2 syntax options now: Blocking attribute vs Set of Element IDs declared in the head.

    I'm more inclined towards the blocking attribute. Its much simpler to explain, implement and flexible to use. The only motivation to take the Set of Element IDs would be if it encourages developers to limit blocking to what's strictly needed. But I'm not convinced that it will and the feature will become unnecessary complex. It also seems more error prone to fallback to full blocking from minor bugs.

  2. Limiting document render-blocking to when there's a transition, described here. That'll be a good addition for optimal use of render-blocking even for other resource types (scripts and stylesheets). And if we want to be conservative, document render-blocking can start off by being limited to when there is a transition.

@khushalsagar
Copy link
Contributor Author

Leaving a note for bikeshedding, the decision for whether the id specified by link is blocking should be based on the blocking attribute. So a name which requires omitting the use of blocking will be confusing.

I could go with rel=maybeblock. Makes it clear that the link element is to specify an element ID which is potentially blocking. And the blocking attribute specifies which stage is blocked by it (rendering).

@domenic
Copy link
Member

domenic commented Jan 25, 2024

I don't think omitting blocking on the link is confusing. I think it's the only thing that makes sense. Because the link itself isn't blocking, right? It's just telling you about other blocking things. So it shouldn't have the attribute.

@khushalsagar
Copy link
Contributor Author

The reason why blocking has been spec'd to allow more tokens is so we can extend it to specify other lifecycle stages of the Document that the resource referenced in link needs to block. One of the use-cases we heard was the browser's loading indicator for example. Ignoring blocking here means we're designing ourselves into a corner, parsing can only be about blocking rendering (no other stage).

@domenic
Copy link
Member

domenic commented Jan 25, 2024

I don't see why that's true? If you add a future blocking=loadingindicator, then that's a separate feature, and if people want to add that to the link, they can in that future. They're not prohibited from doing so, because at that time we'd change the spec to pay attention to it. (Although only the loadingindicator part; the render part would be ignored, because again, the link element does not block rendering.)

@khushalsagar
Copy link
Contributor Author

The proposed syntax if we don't use blocking is as follows, next to how other link elements which support blocking behave:

<link href="main.css" rel="stylesheet" blocking="render"/>
<link rel="renderblocking" href=”#foo"/>

Later on if we add "loadingindicator", it will be the following instead:

<link href="main.css" rel="stylesheet" blocking="loadingindicator"/>
<link rel="loadingindicatorblocking" href=”#foo"/>

I'm just not seeing the advantage of parallel keywords in rel that blocking has. Unless you think we might not want to support some blocking stages for the element parsing case. But that can be spelled out similar to how the spec right now only applies blocking to rel=stylesheet.

The other thing is that blocking allows passing a list of tokens. I don't know if rel is supposed to work that way, so the author would have to make 2 link elements:

<link rel=loadingindicatorblocking href=”#foo"/>
<link rel=renderblocking href=”#foo"/>

Overall, as an author I'd find it weird to have 1 syntax for specifying blocking stylesheets and another for elements. If I'm the only one who finds this weird, I'm willing to concede.

@zcorpan
Copy link
Member

zcorpan commented Jan 25, 2024

It's not clear to me that loading indicator blocking makes sense for document blocking (would it make the loading indicator complete sooner than normal?).

Anyway, a possible alternative is to omit the rel attribute and only have blocking+href:

<link blocking=render href="#foo">
@khushalsagar
Copy link
Contributor Author

It's not clear to me that loading indicator blocking makes sense for document blocking (would it make the loading indicator complete sooner than normal?).

Yes. For example after the above-the-fold content (with some margin) has been parsed. I agree that its unclear whether the use-case is legit, but nicer to avoid a syntax which assumes that it won't be.

a possible alternative is to omit the rel attribute and only have blocking+href

Interesting. Unless a link with no rel is already meaningful, we could consider this.

@domenic
Copy link
Member

domenic commented Jan 26, 2024

I see what you mean about having a parallel keyword space being weird. We should probably avoid that.

I think what's happening here is that I'm uncomfortable with the proposal at a more general level, because of how it changes the meaning of <link> and href="". Let me explain.

Currently, <link rel="something" href="x"> means "fetch x and use it for something". And thus, <link rel="something" href="x" blocking="a"> means "fetch x and use it for something. Until x is fetched, block a."

This proposal changes the behavior to say: "go look in the DOM for the element referenced by x, call it y. When/if that shows up, then that element will take care of fetching y. Until y is fetched, block a."

This is strange on multiple levels:

  • The <link> is no longer performing a fetch. It's instead doing nothing, by itself.

  • It's not clear what all the other functionality of <link> means for these no-fetch <link>s. What does disabled="" do? Does fetchpriority="", integrity="", or referrerpolicy="" affect the fetch that is eventually spawned by the other element? (If blocking="" affects the blocking of that other fetch, why not those??)

  • We change the behavior of blocking=x from "block x on this element's fetch" (what it means for <link>, <style>, and <script> today) to "block x on some other element's fetch".


I'm sorry raise this late in the game, when I know the path toward getting multi-implementer interest on this approach has taken a while. So I'm not insisting that we abandon the current approach. But, if you'd indulge me by trying to think of alternate paths, I'd appreciate it.

I (and probably others, like the TAG and Blink API owners) would appreciate an explainer with the alternatives considered. https://github.com/WICG/view-transitions/blob/link-proposal/document-render-blocking.md lists <html blocking="render">, seemingly as a live proposal, and doesn't summarize why it wasn't chosen. Up-thread I also see discussion of a JavaScript API; documenting why that wasn't chosen would also be helpful.

But I think I can assume the general form of the solution that has multi-implementer interest is a list of blocking element IDs. So let me discuss how else we could accomplish that, without so dramatically changing the semantics of <link> and href="".

Given that this needs to exist in the head tag, we only have a few reasonable choices: link, meta, script, and style. Some ideas:

<script type="blockingelements" blocking="render">["id1", "id2"]</script>

<script type="blocking">
{
  render: ["id1", "id2"]
}
</script>

<link rel="blocking" elements="id1 id2" blocking="render">
<!-- or choose a different rel -->

<link blockers="id1 id2" blocking="render">

<style>
:root {
  blocking-elements: id1, id2;
  blocking: render;
}
</style>

<meta http-equiv="blockers" content="id1 id2" blocking="render">
<meta blockers="id1 id2" blocking="render">
<!-- See https://github.com/whatwg/html/issues/2335 for why
     http-equiv="" / new blockers="" attribute, and not name="" -->

To my mind, all of these would be better than changing <link>. What do you all think?

@noamr
Copy link
Contributor

noamr commented Jan 26, 2024

I see what you mean about having a parallel keyword space being weird. We should probably avoid that.

I think what's happening here is that I'm uncomfortable with the proposal at a more general level, because of how it changes the meaning of <link> and href="". Let me explain.

Currently, <link rel="something" href="x"> means "fetch x and use it for something". And thus, <link rel="something" href="x" blocking="a"> means "fetch x and use it for something. Until x is fetched, block a."

This proposal changes the behavior to say: "go look in the DOM for the element referenced by x, call it y. When/if that shows up, then that element will take care of fetching y. Until y is fetched, block a."

This is strange on multiple levels:

  • The <link> is no longer performing a fetch. It's instead doing nothing, by itself.

Same as many other rels, like alternate/help.

  • It's not clear what all the other functionality of <link> means for these no-fetch <link>s. What does disabled="" do? Does fetchpriority="", integrity="", or referrerpolicy="" affect the fetch that is eventually spawned by the other element? (If blocking="" affects the blocking of that other fetch, why not those??)

Yes, this is consistent with other links. Many of the <link> attributes apply to some rels and not to others, e.g. as only applies to preload.

  • We change the behavior of blocking=x from "block x on this element's fetch" (what it means for <link>, <style>, and <script> today) to "block x on some other element's fetch".

We change the behavior from "block until sheet is applied" for stylesheet or "block until fetch" for preloads to "block until this expected element is seen by the parser".

Given that this needs to exist in the head tag, we only have a few reasonable choices: link, meta, script, and style. Some ideas:

<script type="blockingelements" blocking="render">["id1", "id2"]</script>

I don't think this needs blocking=render, but this is a viable alternative. I don't mind it but I don't agree with the reason you've raised against the link proposal.

<style> :root { blocking-elements: id1, id2; blocking: render; } </style>

We don't tend to refer from CSS attributes back to selectors.

I don't think meta should affect anything to do with rendering. Render-blocking is not a meta-concern.

@domenic
Copy link
Member

domenic commented Jan 26, 2024

Same as many other rels, like alternate/help.

Those are saying to non-browser user agents, you should fetch the URL indicated by href="" if you want to look up the alternate representation / help content / etc. That's an established pattern.

Yes, this is consistent with other links. Many of the <link> attributes apply to some rels and not to others, e.g. as only applies to preload.

This is a bit different, as it's taking all existing attributes and saying that they don't apply. Except for one specific attribute, blocking="", which does. Why blocking="" and not fetchpriority=""? That seems hard to explain.

We change the behavior from "block until sheet is applied" for stylesheet or "block until fetch" for preloads to "block until this expected element is seen by the parser".

Exactly. It no longer has anything to do with the resource fetched from the href="" URL; it now has to do with the resource fetched by the element referenced by the href="" URL. That's a big semantic change. If you used normal semantics, href="#foo" would "block" until the network fetch to #foo (which is a no-op) completes.

I don't think meta should affect anything to do with rendering. Render-blocking is not a meta-concern.

<meta> is not a "meta" element, unless you use name="". If you use http-equiv="" or charset="", it's a pragma for affecting document processing. Compare to <meta http-equiv="content-security-policy">.

@noamr
Copy link
Contributor

noamr commented Jan 26, 2024

Same as many other rels, like alternate/help.

Those are saying to non-browser user agents, you should fetch the URL indicated by href="" if you want to look up the alternate representation / help content / etc. That's an established pattern.

Yes, this is consistent with other links. Many of the <link> attributes apply to some rels and not to others, e.g. as only applies to preload.

This is a bit different, as it's taking all existing attributes and saying that they don't apply. Except for one specific attribute, blocking="", which does. Why blocking="" and not fetchpriority=""? That seems hard to explain.

We change the behavior from "block until sheet is applied" for stylesheet or "block until fetch" for preloads to "block until this expected element is seen by the parser".

Exactly. It no longer has anything to do with the resource fetched from the href="" URL; it now has to do with the resource fetched by the element referenced by the href="" URL. That's a big semantic change. If you used normal semantics, href="#foo" would "block" until the network fetch to #foo (which is a no-op) completes.

I don't think meta should affect anything to do with rendering. Render-blocking is not a meta-concern.

<meta> is not a "meta" element, unless you use name="". If you use http-equiv="" or charset="", it's a pragma for affecting document processing. Compare to <meta http-equiv="content-security-policy">.

That's fair. I am ok with either these alternatives (meta/new script type).
One thing that's missing in them is the supports and media attributes which we get "for free" in the link element and are required here - e.g. you'd want to block on a particular element only if cross-document view-transitions are supported, or block on different elements based on viewport width.

@vmpstr
Copy link
Member

vmpstr commented Jan 26, 2024

Exactly. It no longer has anything to do with the resource fetched from the href="" URL; it now has to do with the resource fetched by the element referenced by the href="" URL. That's a big semantic change. If you used normal semantics, href="#foo" would "block" until the network fetch to #foo (which is a no-op) completes.

I just want to clarify that it doesn't have to do with a resource fetched by the element referenced by the href, but rather it has to do with the element referenced by href itself being available. We can't assume the referenced element in href has been fetched from the network (aka it's not a no-op, which is indeed the point of the feature), instead we're asking to delay rendering until we're sure that element has been fetched and parsed. This doesn't have anything to do with the resources referenced by that element, but rather the element itself. Yes, we have to squint a bit at this, but this seems more or less consistent with how link is used.

FWIW, I'm fine with other proposals if they seem better, but I just wanted to defend our choice here a bit. It wasn't without reason that we went with link, and the parallel to other uses of link seems to be here.

@khushalsagar
Copy link
Contributor Author

I just want to clarify that it doesn't have to do with a resource fetched by the element referenced by the href, but rather it has to do with the element referenced by href itself being available.

+1. I'd rephrase your statement above to: "go look in the DOM for the element referenced by x, call it y. Block the stage referenced by blocking until y has been completely parsed".

I (and probably others, like the TAG and Blink API owners) would appreciate an explainer with the alternatives considered. https://github.com/WICG/view-transitions/blob/link-proposal/document-render-blocking.md lists , seemingly as a live proposal, and doesn't summarize why it wasn't chosen. Up-thread I also see discussion of a JavaScript API; documenting why that wasn't chosen would also be helpful.

I added the reasons for not choosing the blocking attribute approach here. Hope that helps.

@domenic, do the clarifications above make you more inclined towards any of the options you listed?

@noamr
Copy link
Contributor

noamr commented Jan 29, 2024

@domenic regarding href, this is consistent with how href="#..." also doesn't fetch an external resource in other places, like a or SVG references. Isn't it an established pattern that href="#fragment" is a reference to a place in the current document, while href=/external would fetch something external or navigate to it?

@domenic
Copy link
Member

domenic commented Jan 30, 2024

I just want to clarify that it doesn't have to do with a resource fetched by the element referenced by the href, but rather it has to do with the element referenced by href itself being available. We can't assume the referenced element in href has been fetched from the network (aka it's not a no-op, which is indeed the point of the feature), instead we're asking to delay rendering until we're sure that element has been fetched and parsed.

I believe this is congruent with what I'm saying. Unlike every other usage of href="#foo", which refers to the element with ID foo, you're instead doing an indirect reference to the resource referred to by the href="" or src="" or data="" of the element with ID foo.

For example, if you do

<a href="#foo">click me</a>

<a id="foo" href="https://example.com/">another anchor</a>

then clicking "click me" will take you to "another anchor", on the current page. It won't navigate you to https://example.com/. Because it's referring to "another anchor"; it is completely oblivious to and ignorant of the href="" of that other anchor.

So if, for <link rel="expect" blocking="render" href="#foo">, you were just blocking on the existence of the element with ID foo, then that would make sense. But IIUC, you're not doing that; you're reaching into the implementation of the foo element, inspecting its href="" or src="" or data="", and then waiting on that. That's what's unusual, and breaks the usual developer model of href="".

@domenic regarding href, this is consistent with how href="#..." also doesn't fetch an external resource in other places, like a or SVG references. Isn't it an established pattern that href="#fragment" is a reference to a place in the current document, while href=/external would fetch something external or navigate to it?

Yes, it's an established pattern that it refers to a place in the current document. I think "place" is a key word there: it refers to a scroll position, basically. Not to whatever's on the other end of the element referenced by the ID.

(At least in HTML. I'm less clear on what SVG has done, but IIRC SVG does several things with href="#foo" that HTML would do with for="foo" or list="foo" or headers="foo bar" or other dedicated attributes that don't invoke the URL syntax.)

@domenic, do the clarifications above make you more inclined towards any of the options you listed?

I don't think the clarifications point toward any of the options I listed in particular. In all cases, we're doing something different than hyperlinking.

Of the options I proposed, I personally think one of the <meta> options is best, or maybe the <script type="blocking">{ render: ["id1", "id2"] }</script> one.

@noamr
Copy link
Contributor

noamr commented Jan 30, 2024

So if, for <link rel="expect" blocking="render" href="#foo">, you were just blocking on the existence of the element with ID foo, then that would make sense.

This is exactly what we're proposing. I think there is a misunderstanding - in this proposal <link rel=expect href="#foo"> would block until the document sees a (closed) element with id=foo.

@domenic
Copy link
Member

domenic commented Jan 30, 2024

Really? So you block the moment you see an <img id="foo" src="image.jpg"> tag, and don't block until the image displays? The OP says

Images. Documents frequently have a hero image. While a network delay here could be too slow, fetching them from the disk cache could be worth the delay.

which implied to me you were blocking on image display.

@noamr
Copy link
Contributor

noamr commented Jan 30, 2024

Really? So you block the moment you see an <img id="foo" src="image.jpg"> tag, and don't block until the image displays? The OP says

Images. Documents frequently have a hero image. While a network delay here could be too slow, fetching them from the disk cache could be worth the delay.

which implied to me you were blocking on image display.

Right, that's the issue's original premise and I can see the source of the confusion now...,
we went through discussions until we got to this, blocking rendering until we parse the end tag of an element with an expected ID, not currently dealing with the resources that element points to.

@domenic
Copy link
Member

domenic commented Jan 31, 2024

Welp, in light of that new information, I've reviewed all of my previous arguments, and they're all wrong. I'm sorry for wasting 5 days of everyone's time, and thanks for your patience in walking me through it.

I guess we can now get back to the bikeshedding discussion for the rel. In that regard, I don't have strong opinions, but I am sympathetic to @khushalsagar's point about wanting to avoid duplicating the blocking="x" keyword space into the rel="" keyword space.

The option of omitting the rel="" feels strange, but maybe is also OK. Some notes on that:

  • It would involve pretty extensive updates to the conceptual and introductory text in the <link> element section.
  • Right now there are two types of links, rel="" links and itemprop="" links. Would we try to position this as a third type of link? How general would that type be---are there future examples of things we might put into it?
@noamr
Copy link
Contributor

noamr commented Jan 31, 2024

Welp, in light of that new information, I've reviewed all of my previous arguments, and they're all wrong. I'm sorry for wasting 5 days of everyone's time, and thanks for your patience in walking me through it.

Happens :)

I guess we can now get back to the bikeshedding discussion for the rel. In that regard, I don't have strong opinions, but I am sympathetic to @khushalsagar's point about wanting to avoid duplicating the blocking="x" keyword space into the rel="" keyword space.

The option of omitting the rel="" feels strange, but maybe is also OK.

If we add more link types that support blocking=render, e.g. preload, it can be pretty bug-prone.
e.g. <link rell=preload blocking=render> would block until the whole document is parsed. I guess we can mandate an href with a "#..." for this but it seems a bit implicit. For something as footgun-prone as document render blocking I think having an explicit rel is a good thing.

I am still in favor of what's in the PR: <link rel=expect href="#element"> would be implicitly render-blocking. My second preference would be <link rel=expect href="#element" blocking=render>

@khushalsagar
Copy link
Contributor Author

khushalsagar commented Feb 1, 2024

I feel strongly against this one: " would be implicitly render-blocking". For consistency with other render-blocking cases, we should make the blocking decision based on the value of blocking attribute. The second option SGTM: <link rel=expect href="#element" blocking=render>, I don't think we need a new link type.

Listing out the options for "rel" in the comments above: expect, essential, blockrender, maybeblock, ""(empty). rel is supposed to describe the relationship between the Document and the referenced resource. And in this case, href points to an element which will be present in the current Document. So "expectElement" sounds better?

@vmpstr
Copy link
Member

vmpstr commented Feb 1, 2024

Maybe we can do an emoji straw poll for the rel value

😄 - expect
🎉 - expectElement
❤️ - essential
🚀 - blockrender
👀 - mayblock
😕 - ""

Feel free to vote with 👎 if all of these are not good, and propose your own

@noamr
Copy link
Contributor

noamr commented Feb 2, 2024

Maybe we can do an emoji straw poll for the rel value

😄 - expect

🎉 - expectElement

❤️ - essential

🚀 - blockrender

👀 - mayblock

😕 - ""

Feel free to vote with 👎 if all of these are not good, and propose your own

Proposing a new one: 🍫 - await

@noamr
Copy link
Contributor

noamr commented Feb 5, 2024

Maybe we can do an emoji straw poll for the rel value

😄 - expect 🎉 - expectElement ❤️ - essential 🚀 - blockrender 👀 - mayblock 😕 - ""

Feel free to vote with 👎 if all of these are not good, and propose your own

OK I think this feature has been bikeshedded to the bone.
A prim and proper PR is ready, with all the checkboxes ticked: #9970

@khushalsagar
Copy link
Contributor Author

Responding to the comment at w3ctag/design-reviews#886 (comment):

I prefer using link because we need an element in the head and there's already a concept and syntax for render-blocking on link elements for stylesheets. It's better if authors don't have to learn another syntax for it.

@noamr
Copy link
Contributor

noamr commented Feb 20, 2024

Responding to the comment at w3ctag/design-reviews#886 (comment):

I prefer using link because we need an element in the head and there's already a concept and syntax for render-blocking on link elements for stylesheets. It's better if authors don't have to learn another syntax for it.

Also, link already has the media attribute (and the upcoming supports attribute), and conceptually this is no different from other same-document links, apart from being the first one that's used in a link element.

domenic pushed a commit that referenced this issue Feb 27, 2024
The main use case for this is delaying a cross-document view transition for a short period of time to let the new document be more ready.

Explainer: https://github.com/WICG/view-transitions/blob/link-proposal/document-render-blocking.md#blocking-element-id

Closes #9332.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements topic: rendering