-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Comments
I was just prototyping a version of this yesterday with a pair of style tags:
There are several uses I see for this:
JS APII 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 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 APII 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.
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. |
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.
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);
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. |
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. |
As spec'd, startViewTransition will do the following:
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. |
I wonder if for the view transitions case this can be a CSS property rather than an HTML property, e.g. putting |
An explainer clarifying the use-case and a concrete proposal is now available here. |
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? |
For the partial rendering case, this proposal expects authors to:
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). |
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:
The targeted use-case for partial rendering is View Transitions. The author knows exactly which DOM nodes need to animate (will have a |
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. |
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
Adding a concrete use-case from a real world site. Say you have a header or footer which is |
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?
|
+1. That's already how the render-blocking concept is spec'd.
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. |
Couple of Qs as I continue to wrap my head around this:
|
For regular (non-transition) blocking, I don't see why not.
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. |
@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. |
@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.
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). |
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. |
Another update to the explainer based on some offline feedback. Latest version is here. The interesting open questions are:
|
Leaving a note for bikeshedding, the decision for whether the id specified by I could go with |
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. |
The reason why |
I don't see why that's true? If you add a future |
The proposed syntax if we don't use
Later on if we add "loadingindicator", it will be the following instead:
I'm just not seeing the advantage of parallel keywords in The other thing is that
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. |
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 <link blocking=render href="#foo"> |
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.
Interesting. Unless a link with no rel is already meaningful, we could consider this. |
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 Currently, This proposal changes the behavior to say: "go look in the DOM for the element referenced by This is strange on multiple levels:
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 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 Given that this needs to exist in the head tag, we only have a few reasonable choices: <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 |
Same as many other rels, like alternate/help.
Yes, this is consistent with other links. Many of the
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".
I don't think this needs <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 |
Those are saying to non-browser user agents, you should fetch the URL indicated by
This is a bit different, as it's taking all existing attributes and saying that they don't apply. Except for one specific attribute,
Exactly. It no longer has anything to do with the resource fetched from the
|
That's fair. I am ok with either these alternatives (meta/new script type). |
I just want to clarify that it doesn't have to do with a resource fetched by the element referenced by the 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. |
+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
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? |
@domenic regarding |
I believe this is congruent with what I'm saying. Unlike every other usage of 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 So if, for
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
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 |
This is exactly what we're proposing. I think there is a misunderstanding - in this proposal |
Really? So you block the moment you see an
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..., |
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 The option of omitting the
|
Happens :)
If we add more link types that support I am still in favor of what's in the PR: |
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 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, |
Maybe we can do an emoji straw poll for the rel value 😄 - expect Feel free to vote with 👎 if all of these are not good, and propose your own |
Proposing a new one: 🍫 - await |
OK I think this feature has been bikeshedded to the bone. |
Responding to the comment at w3ctag/design-reviews#886 (comment): I prefer using link because we need an element in the |
Also, |
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.
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:
It would be good to extend this concept to cover the above use-cases. Likely with a generic promise based API like:
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
The text was updated successfully, but these errors were encountered: