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] Script event on old Document for cross-document ViewTransitions #8785

Closed
khushalsagar opened this issue May 1, 2023 · 26 comments
Labels
css-view-transitions-2 View Transitions; New feature requests

Comments

@khushalsagar
Copy link
Member

khushalsagar commented May 1, 2023

View Transition API needs to provide an event on the old Document to enable customization of the transition based on the URL for the new Document and the current Document state. For example, the Document could have changed from user interaction after its first load.

The proposed IDL is as follows:

[Exposed=Window]
interface ViewTransitionBeforeCaptureEvent : Event {
  // The URL being navigated to, after resolving server-side redirects.
  readonly attribute USVString url;

  // Opaque contextual information passed to the new Document.
  // This must be a serializable object : https://developer.mozilla.org/en-US/docs/Glossary/Serializable_object.
  attribute any context;
};

The following is sample code using this event:

document.addEventListener("viewtransitionbeforecapture", (event) => {
  // Cancel the transition (based on new URL) if needed.
  if (shouldNotTransition(event.toURL)) {
    event.preventDefault();
    return;
  }

  // Set up names on elements based on the new URL.
  if (shouldTagThumbnail(event.toURL)) {
    thumbnail.style.viewTransitionName = "full-embed";
  }

  // Add opaque contextual information to share with the new Document.
  event.context = createTransitionContext(event.url);
});
@noamr
Copy link
Collaborator

noamr commented May 24, 2023

See #8683 (comment)
This would probably not work in a potential same-site rather than same-origin scenario. We should really think through if allowing same-site in the future is a goal.

@khushalsagar
Copy link
Member Author

@noamr IIRC, the conclusion for this was authors relying on existing events (like click or navigate) where they have access to the target URL. This does mean authors don't get the URL after resolving redirects but we can always introduce an event later if we see significant use-cases needing that.

@jakearchibald for an opinion too.

@jakearchibald
Copy link
Contributor

Having the URL after redirects seems like the most important thing.

@noamr
Copy link
Collaborator

noamr commented Jun 12, 2023

Having the URL after redirects seems like the most important thing.

What's the scenario you have in mind? My thinking is that if you're in a same origin environment you shouldn't be surprised by redirects and that setting up the transition based on where you intend to go should be sufficient, but I could have a blind spot.

@jakearchibald
Copy link
Contributor

Developers want to write transitions from one page type to another, so knowing the page they're going to seems like a thing that should be reliable.

Eg, navigating to /profile may redirect to a login page, it may not. These are two very different page types likely to have different transitions.

@noamr
Copy link
Collaborator

noamr commented Jun 12, 2023

Developers want to write transitions from one page type to another, so knowing the page they're going to seems like a thing that should be reliable.

Eg, navigating to /profile may redirect to a login page, it may not. These are two very different page types likely to have different transitions.

Traditionally apps would have either a /profile or a /login link but not both, because the origin already knows if you're logged in. The redirect would happen when you try to "land" on /profile from the outside. Still looking for common scenarios where the previous page would be thinking it's going somewhere but redirected to another place.

@jakearchibald
Copy link
Contributor

jakearchibald commented Jun 12, 2023

Ok, two tabs logged out. In one tab you hit the login button and log in. What happens when you hit the login button on the other tab?

But, regardless of this, you shouldn't be betting an API design, that ideally will live for decades, will ideally outlive various frontend and backend patterns, on "well redirects probably won't happen often".

Instead, don't cut corners, design the API without easily foreseeable footguns.

@noamr
Copy link
Collaborator

noamr commented Jun 12, 2023

Ok, two tabs logged out. In one tab you hit the login button and log in. What happens when you hit the login button on the other tab?

Perhaps in this case the whole transition would be unexpected. I would expect it to be skipped. I wonder if it could be a "skip on redirect" boolean-ish though it doesn't feel complete.

But, regardless of this, you shouldn't be betting an API design, that ideally will live for decades, will ideally outlive various frontend and backend patterns, on "well it probably won't happen often".

Instead, don't cut corners, design the API without easily foreseeable footguns.

Sure, just looking to work with valid use cases and make them simple enough. We have to start with the simple common use-cases

What I'm struggling with here is the feasibility of making the final URL observable when we enable cross-origin same-site, and if we want to enable something that's incompatible with that.
Perhaps if we use the navigation-matchers (#8925) we can make something that affects the style (mainly transition-names) but is not observable to the original document.

@jakearchibald
Copy link
Contributor

I wonder if it could be a "skip on redirect" boolean-ish

I don't think that's a good idea, considering how common it is to redirect from eg /articles to /articles/ (or vice versa)

@jakearchibald
Copy link
Contributor

Redirects are also common on form submissions. I don't think this feature should pretend they aren't part of the platform, or hope that part of the platform isn't used.

@noamr
Copy link
Collaborator

noamr commented Jun 12, 2023

Redirects are also common on form submissions.
I don't think this feature should pretend they aren't part of the platform, or hope that part of the platform isn't used.

That's a good use-case, e.g. submitting to/submit redirects to /success and /failure.
I wonder in this case if it's necessary for the original document to differentiate what it captures / under what names.
Perhaps the only difference this would make is some redundant captures?

The concept is not to pretend that redirects don't exist, but to find a solution for this that's forward-compatible with same-site, and explore if the complexity it adds is useful enough.

Having something that affects the style/transition names but is not observable by the old document might be a feasible direction (e.g. the media query proposed in #8925 applies the final URL to the style but there's no JS event).

@jakearchibald
Copy link
Contributor

jakearchibald commented Jun 12, 2023

That's a good use-case, e.g. submitting to/submit redirects to /success and /failure.
I wonder in this case if it's necessary for the original document to differentiate what it captures / under what names.

I don't know, but that's only one case. It's also common for /post-thing to redirect to the posted thing (or back to the form with error messages), and in that case it is important for the old document to capture particular things.

The concept is not to pretend that redirects don't exist, but to find a solution for this that's forward-compatible with same-site, and explore if the complexity it adds is useful enough.

I'd avoid limiting same-origin use-cases due to cross-origin limitations.

@noamr
Copy link
Collaborator

noamr commented Jun 12, 2023

I'd avoid limiting same-origin use-cases due to cross-origin limitations.

It's a valid opinion but would not be a light decision and requires further thinking. same-site has been a big request so far.

@noamr
Copy link
Collaborator

noamr commented Nov 17, 2023

I want us to consider how this could create a gotcha with BFCache.
Let's use the example in the OP:

document.addEventListener("viewtransitionbeforecapture", (event) => {
  // Cancel the transition (based on new URL) if needed.
 /* ... */

  // Set up names on elements based on the new URL.
  if (shouldTagThumbnail(event.toURL)) {
    thumbnail.style.viewTransitionName = "full-embed";
  }
});

If after doing this the document is saved to BFCache and then restored, it would be restored with `thumbnail.style.viewTransitionName === "full-embed".

Not sure if there is a way around this or if this should stop us from doing this, but wanted it to be documented.

@bokand
Copy link
Contributor

bokand commented Nov 17, 2023

What does the event give us that we don't get from URL matching in @view-transition and types? Doesn't the example below effectively capture the example in OP?

@view-transition {
  navigation: auto;
  to: url('destination.html');
  type: addFullEmbedName;
}

@view-transition {
  navigation: none;
  to: url('notransition.html');
}

:root:active-view-transition(addFullEmbedName) #thumbnail {
  view-transition-name: full-embed;
}

The example also allows adding opaque context (as mentioned in #9526) this can be achieved in other ways, e.g. session storage.

If the page really wants to run some JS before the transition capture, couldn't they do so by listening to navigations from the navigation API?

@khushalsagar
Copy link
Member Author

If after doing this the document is saved to BFCache and then restored, it would be restored with `thumbnail.style.viewTransitionName === "full-embed". Not sure if there is a way around this or if this should stop us from doing this, but wanted it to be documented.

There's 2 modes with which authors can add names and both work with BFCache.

  • You're using script events to add names to inline style (that case you mentioned). You can use pageHide to reset names when the Document enters BFCache or reveal when it is restored. This means you'll have to track which elements are currently tagged.
  • You're using script events or just CSS to tag names via type + active-view-transition. In this case the styles are automatically updated by the browser when the Document enters BFCache.

@khushalsagar
Copy link
Member Author

What does the event give us that we don't get from URL matching in @view-transition and types?

We haven't yet decided whether the URL matching for @view-transition will be based on the initial URL or post redirect one. Since we're not adding the "to" property yet, the question can be deferred. If we decide to use the post redirect URL for this then we should have an easy equivalent script hook.

If the page really wants to run some JS before the transition capture, couldn't they do so by listening to navigations from the navigation API?

The navigate event runs before we dispatch the navigation request with the initial URL. I don't know of any script API today which provides the post redirect URL.

@noamr
Copy link
Collaborator

noamr commented Nov 17, 2023

  • You're using script events to add names to inline style (that case you mentioned). You can use pageHide to reset names when the Document enters BFCache or reveal when it is restored. This means you'll have to track which elements are currently tagged.

Yes, I know this is possible, I meant that it creates a "gotcha" because you have to remember to clear names you set on this event.

@khushalsagar
Copy link
Member Author

I meant that it creates a "gotcha" because you have to remember to clear names you set on this event.

Agreed. That's why developers should use types instead. It avoids having to manually track transition lifetime for updating styles.

@noamr
Copy link
Collaborator

noamr commented Nov 17, 2023

Actually you'd have the same gotcha with the navigate event.
What I'm contemplating is whether we should have a VT-specific event here or a more general same-origin-redirect event in the navigation API. The latter would be less of an encouragement to run last-minute JS for view-transition but would still work for those use cases.

Also if it's in the navigation API people won't be tempted to use a VT just to get the redirect info.

@khushalsagar
Copy link
Member Author

I think the gotcha of remembering to reset names is always going to happen if you're using inline styles. Irrespective of whether we have a script event with post redirect info. Since capturing will always be async, it'll run at the next rendering loop after this event.

What I'm contemplating is whether we should have a VT-specific event here or a more general same-origin-redirect event in the navigation API.

That's a good question. I was wondering whether VT-specific is nicer from a performance standpoint. We don't have to delay the navigation on dispatch of this event unless there is a transition. In the transition case we have to go back to the page to capture old snapshots anyway so the additional event has no perf impact.

But you can do the above with a generic event as well. The browser can track whether an event handler is registered or allow making the event passive so event dispatch doesn't block the navigation from being committed. Given that, navigation API seems like a better spot for it.

@noamr
Copy link
Collaborator

noamr commented Nov 17, 2023

I think the gotcha of remembering to reset names is always going to happen if you're using inline styles. Irrespective of whether we have a script event with post redirect info. Since capturing will always be async, it'll run at the next rendering loop after this event.

What I'm contemplating is whether we should have a VT-specific event here or a more general same-origin-redirect event in the navigation API.

That's a good question. I was wondering whether VT-specific is nicer from a performance standpoint. We don't have to delay the navigation on dispatch of this event unless there is a transition. In the transition case we have to go back to the page to capture old snapshots anyway so the additional event has no perf impact.

But you can do the above with a generic event as well. The browser can track whether an event handler is registered or allow making the event passive so event dispatch doesn't block the navigation from being committed. Given that, navigation API seems like a better spot for it.

As long as this is constrained to same-origin redirects, aren't the performance characteristics of this event the same as pagehide as committing is serial (in the same renderer)?

@khushalsagar
Copy link
Member Author

committing is serial (in the same renderer)

Is that a spec requirement or an implementation detail. I could see a browser implementation switching to the new Document before dispatching events like pagehide on the old Document.

@noamr
Copy link
Collaborator

noamr commented Nov 17, 2023

committing is serial (in the same renderer)

Is that a spec requirement or an implementation detail. I could see a browser implementation switching to the new Document before dispatching events like pagehide on the old Document.

Spec requirement,. See https://html.spec.whatwg.org/#unload-a-document step 4.
Without this, sessionStorage would be racy if accessed from pagehide, or directly accessing other windows etc.

@khushalsagar
Copy link
Member Author

Spec requirement,. See https://html.spec.whatwg.org/#unload-a-document step 4.
Without this, sessionStorage would be racy if accessed from pagehide, or directly accessing other windows etc.

Ah. In that case, don't see any perf reason to have another navigation event later in the navigation's lifecycle. While we're thinking about this, we likely don't need this event to be cancellable like navigate.

@khushalsagar
Copy link
Member Author

Handled by pageconceal in progress.

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
None yet
Development

No branches or pull requests

4 participants