Skip to content

[css-view-transitions-1] Flush the callback queue before performing other view-transition operations #11947

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all 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
12 changes: 6 additions & 6 deletions css-view-transitions-1/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1256,10 +1256,6 @@ urlPrefix: https://wicg.github.io/navigation-api/; type: interface;

1. Let |document| be |transition|'s [=relevant global object's=] [=associated document=].

1. [=Flush the update callback queue=].

Note: this ensures that any changes to the DOM scheduled by other skipped transitions are done before the old state for this transition is captured.

1. [=Capture the old state=] for |transition|.

If failure is returned, then [=skip the view transition=] for |transition| with an "{{InvalidStateError}}" {{DOMException}} in |transition|'s [=relevant Realm=],
Expand Down Expand Up @@ -1663,15 +1659,19 @@ urlPrefix: https://wicg.github.io/navigation-api/; type: interface;
1. [=list/Append=] |transition| to |transition|'s [=relevant settings object=]'s [=update callback queue=].
1. [=Queue a global task=] on the [=DOM manipulation task source=],
given |transition|'s [=relevant global object=], to [=flush the update callback queue=].

</div>
<div algorithm>
To <dfn>flush the update callback queue</dfn> given a {{Document}} |document|:
To <dfn export>flush the update callback queue</dfn> given a {{Document}} |document|:
1. [=list/For each=] |transition| in |document|'s [=update callback queue=], [=call the update callback=] given |transition|.

1. Set |document|'s [=update callback queue=] to an empty list.
</div>

Note: a scheduled update callback is guaranteed to be called before the next rendering steps, regardless of whether the
Copy link
Collaborator

Choose a reason for hiding this comment

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

So... what's the behavior if you put stuff in the queue in a requestAnimationFrame callback with your proposed behavior? That would have a frame delay right? Not necessarily a deal breaker, just trying to understand the implications of this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So... what's the behavior if you put stuff in the queue in a requestAnimationFrame callback with your proposed behavior? That would have a frame delay right? Not necessarily a deal breaker, just trying to understand the implications of this.

I don't think this scenario is valid.

If you have a scheduled update callback that wasn't cancelled yet, meaning that this is an active transition waiting for the new state to settle, rendering is guaranteed to be suppressed so we won't enter the rendering loop.

The only time this would take effect is if there are skipped transitions plus a new one that was scheduled before this rendering loop and hadn't had its old state captured yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess if you started and skipped more than one view transition inside the rAF callback itself, those callbacks would be counted towards the new state rather than the old state. I think that's ok.

transition has succeeded or not. This is guaranteed by [=flush the update callback queue|flushing=] the queue in one of two situations:
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 trying to understand the implications of the rendering loop one:

  • Does this mean if I run a VT and skip it (essentially queuing up the DOM update callback), then this addition would mean that we guarantee to run that callback before the next update the rendering? (this is a new constraint IIUC)
  • If I do queue up a transition that ends up cancelling the previous one, is there an opportunity for the visuals of the first callback to appear on screen before the capture of the second callback? It sounds like yes since the update the rendering will produce a frame here.

Another comment here is that we can have a situation where you have something like

for (let i = 0; i < 10; i++) {
 document.startViewTransition(async () => { await new Promise(() => {}) });
}

which can essentially hang the page for 40 seconds (4sec timeout * 10).

Of course in the alternative I'm hoping for, we simply not start a view transition until the queue is empty and rely on existing scheduling to run the callbacks. This would mean that startViewTransition() after the above would seemingly do nothing (and the page would be interactive) and then suddenly trigger 40 seconds later.

Neither of these approaches seem great

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm trying to understand the implications of the rendering loop one:

An important note is that we're just calling those skipped update callbacks but not waiting for them to resolve.

  • Does this mean if I run a VT and skip it (essentially queuing up the DOM update callback), then this addition would mean that we guarantee to run that callback before the next update the rendering? (this is a new constraint IIUC)

It's not a new constraint exactly. It means that instead of running the callback at the end of the rendering loop like in the current spec, we'll run it in the beginning.

It's a possible scenario already if the scheduled task happens to pop before the next rendering opportunity, which is the guaranteed behavior in Safari.

  • If I do queue up a transition that ends up cancelling the previous one, is there an opportunity for the visuals of the first callback to appear on screen before the capture of the second callback? It sounds like yes since the update the rendering will produce a frame here.

The skipped one would modify the DOM at the same rendering loop as the old state capture for the new transition.
It's entirely equivalent to a scenario where the scheduled task happened to pop before the next rendering loop after starting the new transition.

Another comment here is that we can have a situation where you have something like

for (let i = 0; i < 10; i++) {
 document.startViewTransition(async () => { await new Promise(() => {}) });
}

which can essentially hang the page for 40 seconds (4sec timeout * 10).

The following would happen:

  • The first task would potentially pop, running the first 9 async functions
  • The next rendering cycle would start, immediately running the first 9 async functions if they weren't called yet
  • The old state would be captured, and the 10th async function would run, supressing rendering for 4 seconds.

Of course in the alternative I'm hoping for, we simply not start a view transition until the queue is empty and rely on existing scheduling to run the callbacks. This would mean that startViewTransition() after the above would seemingly do nothing (and the page would be interactive) and then suddenly trigger 40 seconds later.

Neither of these approaches seem great

the beginning of the rendering loop, or in a task that runs while [=document/rendering suppression for view transitions|rendering is supressed=].


## [=Skip the view transition=] ## {#skip-the-view-transition-algorithm}

<div algorithm>
Expand Down