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

[css-view-transitions-2] Dealing with promises on old VT object #9888

Closed
khushalsagar opened this issue Feb 1, 2024 · 5 comments · Fixed by #9954
Closed

[css-view-transitions-2] Dealing with promises on old VT object #9888

khushalsagar opened this issue Feb 1, 2024 · 5 comments · Fixed by #9954
Labels
css-view-transitions-2 View Transitions; New feature requests

Comments

@khushalsagar
Copy link
Member

What should happen with all the promises once capture is done.

@khushalsagar
Copy link
Member Author

This is a tricky one that could use author feedback: @mirisuzanne @jakearchibald @bramus. The following is a summary of discussion and tentative proposed resolution based on that.

The old Document will have an event when the navigation is about to be committed but before the browser renders/caches the last frame. We want authors to be able to set up CSS, including type, based on the destination page or skip the transition. The question is how should that API be exposed.

  1. Provide a ViewTransition object on this event. The skipTransition() API lets the author abort the transition. We're also planning to add a mutable types parameter to toggle types used in active-view-transition. The other use-case of providing the VT object is to enable sharing code for SPA/MPA cases.

    But the ViewTransition IDL has promises to track the lifecycle of transition animations. Since the old Document has no animation, promises like ready/finish conceptually don't make sense. We could model it as the ViewTransition on the old Document started off like an SPA transition but was skipped once the old DOM was captured and its state was copied over to the new DOM. So ready would reject and updateCallbackDone+finish would resolve, potentially when the Document exits BFCache (if it was persisted).

    It's unclear to us whether providing a VT object here with this default behaviour of promises is ergonomic or error prone, shared code mistakenly assumes the transition failed because ready rejected.

  2. The other option is to introduce a new IDL (SnapshotViewTransition) which is the ViewTransition equivalent for the Document which provides snapshots for the start state but doesn't run animations. This would have a subset of the ViewTransition APIs but no class relationship.

    The pro of this is that its future proof. If we add more APIs to ViewTransition, we don't have to shoehorn a meaning for the old Document's object which might not make sense. The con is that sharing code with SPA libraries might be harder.

Proposed Resolution: Option 1, use the VT IDL on the old Doc with default behaviour for promises (and new APIs as they are introduced).

@bramus
Copy link
Contributor

bramus commented Feb 6, 2024

This is a tricky one that could use author feedback

I would need to get my hands on a prototype to be sure but my gut feeling tells me none of the promises make sense at all. IUC the vt object you receive inside pageconceal still incomplete. Only after the logic inside that handler has been executed, the viewTransition will actually start.

Looking at existing code that I use in an SPA+VT, looking to see see what I could carry over to MPA/pageconceal, I would only carry over the stuff that happens before document.startViewTransition() ever gets called. The stuff after document.startViewTransition() would be used on the new page.

  • SPA:

    navigation.addEventListener("navigate", (e) => {
      // … If VT MPA Support: return
    
      e.intercept({
        handler: async () => {
          // … Determine transition type
          
          // … Give some elements a v-t-name if needed
        
          // Create and start VT
          const t = document.startViewTransition({
            update,
            type,
          });
          
          await t.updateCallbackDone;
          // … Analytics, or whatever
          
          await t.finished;
          // … Cleanup
        },
    });
  • MPA (old page):

    document.addEventListener(`pageconceal`, (e) => {
      // … Determine transition type
    
      // … Give some elements a v-t-name
    });

In the MPA code I would only need to determine the VT type to use (or maybe skip the transition) and have no need for any of the promises.

Maybe it makes sense to expose something like a PendingViewTransition instance in pageconceal to make it easily distinguishable for authors? You can’t really do anything with, besides changing some properties or skipping it. It would also have no promises at all. I think that is what is meant with option 2?

This PendingViewTransition instance would then be converted to a full ViewTransition instance on the new page, which an author gets passed into the pagereveal event.

The old Document will have an event when the navigation is about to be committed but before the browser renders/caches the last frame.

Hmm. How would authors then easily get to know what the target URL is in order to determine the transition type? In the SPA, when intercepting the navigation, the navigation is already committed and the navigation.currentEntry updated. But here in pageconceal it is not.

It would be handy if the navigation already was committed, so that authors can then reuse their SPA code: read navigation.currentEntry, knowing that it’s the up-to-date destination URL. With it not being committed yet, authors would need to keep track of destination in the navigate event, store it in a variable, and then have pageconceal use that variable.

@khushalsagar
Copy link
Member Author

I think that is what is meant with option 2?

Yup. Regarding naming, I went with SnapshotViewTransition because this Document only provides the snapshot but no animations. PendingViewTransition is also fine.

How would authors then easily get to know what the target URL is in order to determine the transition type?

The pageconceal event has a NavigationActivation to provide the final URL, nav type and a reference to the destination entry. We can't commit the navigation, i.e. change the current entry, because the old Document has to remain active so it can produce one last frame which the browser will cache.

@noamr
Copy link
Collaborator

noamr commented Feb 8, 2024

Proposed resolution from internal sync:
The outgoing event would have an ordinary ViewTransition object with all the promises.
The promises would behave as if the view transition was skipped after the page was unloaded, so would only be observable if restored from BFCache.

Alternative considered: a new interface with just skipViewTransition & types.

@astearns astearns added this to unsorted FTF in Feb 2024 Agenda Feb 11, 2024
@astearns astearns moved this from unsorted FTF to Monday morning in Feb 2024 Agenda Feb 11, 2024
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-view-transitions-2] Dealing with promises on old VT object, and agreed to the following:

  • RESOLVED: Keep the existing VT object for MPA transitions; the promises resolve when back from BFCache, ready rejects. acts like a skipped transition when document hides
The full IRC log of that discussion <TabAtkins> noamr: Again, about the old document, when we start a cross-doc VT
<TabAtkins> noamr: We fire an event when the doc is about the commit
<TabAtkins> noamr: That even tgives an object that lets you control the VT
<TabAtkins> noamr: ONe thing it's clear to use you can do is skip the transition
<TabAtkins> noamr: Probably also to change the types, once there's an API for that
<TabAtkins> noamr: question we ahven't answered is what to do with the promises - finished, ready, updatecallbackdone
<TabAtkins> noamr: so one option is that this isn't acutally a VT object, just a controller with a few abilities
<TabAtkins> noamr: second option is to give you a proper VT object, the promises resolve when you can no longer run script
<TabAtkins> noamr: So if page is persisted and restroed in bfcache, you'd get them resolved/rejected
<TabAtkins> noamr: advantage of this is there's no weird new objects, it's just all VT objects.
<TabAtkins> noamr: If you want to do cleanup, you can do it
<TabAtkins> noamr: And the other VT things just behave as if it was a skipped VT
<TabAtkins> noamr: Since we decided we're skipping a VT when it's hiding, bfcache hides the doc, so it'll be consistent there to skip
<TabAtkins> noamr: so as far as the old doc is concenred, the transition is skipped when then page is hidden
<khush> q+
<miriam> ack khush
<TabAtkins> khush: i think right now the interface we hav e for script is build with the idea that the old and new docs, and the doc running the animation, are the same. with cross-doc that breaks
<TabAtkins> khush: so question is how to expose the new transition to script in the old page where many of the parts don't make sense
<bramus> q+
<TabAtkins> khush: I think if I was a web author, i'd be more inclined to want an API taht only has the options that make sense
<TabAtkins> khush: So I favored an option tha tintroduced a new interface that only has APIs for skipping and setting the types
<TabAtkins> khush: So I think the call should be based on what devs find easiest. if it's easier to share code that operates on a full VT object, we can design it so that the nonsense options still do something resonable.
<miriam> ack bramus
<TabAtkins> khush: but i lean toward new interface
<TabAtkins> bramus: i'd like to back up khushal, i'd like a new object
<bramus> https://github.com//issues/9888#issuecomment-1930801630
<TabAtkins> bramus: I think as an author it doesnt' make sense to expose promises that don't mean anything at all
<TabAtkins> bramus: If you converted this code to MPA, only the stuff before document.startViewTransition will be used in the old page; stuff after is in the new page
<TabAtkins> bramus: So I think it makes sense to have a different object.
<TabAtkins> bramus: I proposed PartialViewTranstion or PendingVT
<TabAtkins> bramus: If we do want to keep the full VT object, maybe shoudl expose a property that tells authors what type of VT they're looking at
<miriam> ack fantasai
<TabAtkins> fantasai: I don't write a lot of JS, but I think what i'd expect is when you're doing single-page VT, you ahve this VT object you hang onto the whole time
<TabAtkins> fantasai: I'd expect the multipage to have the same idea conceptually - there's a single VT object that the old page has until it's destroyed then the new page has it
<TabAtkins> fantasai: it's not actually the same object, obviously, but it should behave as
<TabAtkins> fantasai: So the promises will resolve at the right time of the transition, etc
<TabAtkins> fantasai: it's just that your doc will stop existing before some promises resolve
<TabAtkins> fantasai: and new doc will start existing after you've called startVT()
<vmpstr> q+
<khush> q+
<TabAtkins> fantasai: But otherwise you can conceptually treate it like it was one object the whole time
<TabAtkins> bramus: In that case then I would like some property to tell the author that this is a VT that was carried over from another doc, etc
<TabAtkins> fantasai: Yeah that makes sens
<TabAtkins> fantasai: but if you're just setting up some listeners, it's okay
<TabAtkins> vmpstr: if you bfcache restore, you will restore the actual object, so those listeners will get called with skipped VT
<TabAtkins> khush: so you're saying let's pretend the object is persistent across pages
<TabAtkins> khush: so when the pseudo-dom is ready, the new page resolves the ready promise, but you want the ready promise to be resolved (canceled) on the old page too
<flackr> q+
<TabAtkins> khush: it feels odd to have this mental model where events are replicated on old page when they're no longer relevant
<TabAtkins> fantasai: so you're suggesting behaving as if you'd ksipped the transitio?
<TabAtkins> khush: yes
<TabAtkins> fantasai: ah yeah that makes sense
<TabAtkins> (treat the old page like it was skipped)
<TabAtkins> khush: this is why I thought it was reasonable to only offer the useful APIs on th eold page. Having a promise or callback that only ever fires on the new page seems funky.
<TabAtkins> fantasai: Seems simpler to just ahve a single model, then.
<khush> q?
<khush> q-
<fantasai> s/model/API model/
<miriam> ack vmpstr
<TabAtkins> vmpstr: whatever we decide, it's the ready promise that's a little confusing
<TabAtkins> vmpstr: whether that resolves or rejects is a function of whether we ran the animation or not
<TabAtkins> vmpstr: and the old doc doesn't know this, only the new doc does
<TabAtkins> vmpstr: So i support just not exposing it
<noamr> q+
<TabAtkins> khush: elika was agreeing with Noam, just consistently reject on the old doc
<flackr> q-
<TabAtkins> vmpstr: i'm fine with it either way because this is an edge case, it' sjust, pedantically speaking, the consistency isn't there
<miriam> ack flackr
<TabAtkins> flackr: i do think it makes some sense to attach promises to the event even if it's run on the new page - cleanup, for example, can run on the old page to restore stuff you fiddled with for the transition
<TabAtkins> flackr: i'm okay with the ready promise rejecting
<TabAtkins> flackr: you woujldn't be doing the cleanup in the ready promise, you'd be waiting for the transition to finish completely
<miriam> ack noamr
<TabAtkins> noamr: i disagree with the VT object not making sense
<TabAtkins> noamr: I think they do make sense
<TabAtkins> noamr: It just may be that ready() is about "can you animate this transition", and on the old doc you never can, it'll reject
<TabAtkins> noamr: updateCallbackDone() does make sense, it's done because there is none
<TabAtkins> noamr: and finished() has a use-case - you might at the start set a bunch of names, and want to remove them on finish
<TabAtkins> noamr: so if we don't have a finihed promise you ahve to listen for pagehide() yourself
<TabAtkins> noamr: that could be buggy - the event could fire multiple times, etc
<flackr> +1
<bramus> q+
<TabAtkins> noamr: so in general i think APIs do make sense
<miriam> ack bramus
<TabAtkins> bramus: wouldn't authors do setup of things...
<TabAtkins> bramus: you went from old page to new page, then Back Button to return to old page, authors would use pagereveal event to set things up, rather than relying on finished promise?
<TabAtkins> noamr: Unrelated, you might not have a transition there, might not do anything on pagereveal.
<khush> q+
<TabAtkins> noamr: If you have both, we resolve the promises then fire pagereveal, that makes sense
<TabAtkins> noamr: You just add a transition, respond to it being finished by undoing things, and ohter bits are consistently redundant
<miriam> ack khush
<TabAtkins> khush: +1 to finished use-case, i didn't think about it that way
<TabAtkins> khush: for bramus's, when you return b->a, you might have code to set up that specific transition, but it might not line up with the setup you did from a->b
<TabAtkins> khush: So being able to setup your cleanup immediately and make sure it's correct is good
<TabAtkins> khush: Also, bramus's comment about having a property to tell you what half of the transition you're in, that sounds like a good idea
<TabAtkins> noamr: if you're in the cross-doc case, you'll get this VT in a pageswap event. We can add the boolean there if we want.
<TabAtkins> bramus: I imagine passing the VT to a generic handler that handles both SPA and MPA.
<TabAtkins> noamr: The handler there could just take a second parameter itself. but i do find the boolean redundant but harmless
<TabAtkins> khush: we could just not have it for now, and if we see it become a common pattern we can add
<TabAtkins> noamr: yeah, can resolve separately
<TabAtkins> miriam: coming to a resolution?
<noamr> Proposed resolution: the promises resolve when back from BFCache, ready rejects. acts like a skipped transition when document hides
<TabAtkins> fantasai: want to additionally clarify that we are still using the existing VT interface, not making a new types
<TabAtkins> noamr: yup
<TabAtkins> miriam: objections?
<TabAtkins> RESOLVED: Keep the existing VT object for MPA transitions; the promises resolve when back from BFCache, ready rejects. acts like a skipped transition when document hides

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-view-transitions-2 View Transitions; New feature requests
Projects
Feb 2024 Agenda
Monday morning
4 participants