Repository: facebook/react
Pull request: Cancel finished view transitions Animations manually in fire-and-forget too

Otherwise these can survive into the next View Transition and cause havoc to that transition.

This was appearing as a flash in Safari in the fixture when going from A->B. This triggers a View Transition and at the same time the scroll position updates in an effect. That fires a scroll event which starts a gesture. This shouldn't really happen and the SwipeRecognizer should ideally ignore those but it's good to surface edge cases. That gesture is blocked on the View Transition finishing and then immediately after it starts a gesture View Transition. That gesture then picked up the former Animation from the previous transition which caused issues. This PR fixes that flash.

Changed files:
filename: packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
patch: @@ -1550,6 +1550,25 @@ export function hasInstanceAffectedParent(
   return oldRect.height !== newRect.height || oldRect.width !== newRect.width;
 }
 
+function cancelAllViewTransitionAnimations(scope: Element) {
+  // In Safari, we need to manually cancel all manually start animations
+  // or it'll block or interfer with future transitions.
+  const animations = scope.getAnimations({subtree: true});
+  for (let i = 0; i < animations.length; i++) {
+    const anim = animations[i];
+    const effect: KeyframeEffect = (anim.effect: any);
+    // $FlowFixMe
+    const pseudo: ?string = effect.pseudoElement;
+    if (
+      pseudo != null &&
+      pseudo.startsWith('::view-transition') &&
+      effect.target === scope
+    ) {
+      anim.cancel();
+    }
+  }
+}
+
 // How long to wait for new fonts to load before just committing anyway.
 // This freezes the screen. It needs to be short enough that it doesn't cause too much of
 // an issue when it's a new load and slow, yet long enough that you have a chance to load
@@ -1640,6 +1659,7 @@ export function startViewTransition(
     }
     transition.ready.then(spawnedWorkCallback, spawnedWorkCallback);
     transition.finished.then(() => {
+      cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
       // $FlowFixMe[prop-missing]
       if (ownerDocument.__reactViewTransition === transition) {
         // $FlowFixMe[prop-missing]
@@ -1817,12 +1837,16 @@ export function startGestureTransition(
       }
       for (let i = 0; i < animations.length; i++) {
         const anim = animations[i];
+        if (anim.playState !== 'running') {
+          continue;
+        }
         const effect: KeyframeEffect = (anim.effect: any);
         // $FlowFixMe
         const pseudoElement: ?string = effect.pseudoElement;
         if (
           pseudoElement != null &&
-          pseudoElement.startsWith('::view-transition')
+          pseudoElement.startsWith('::view-transition') &&
+          effect.target === documentElement
         ) {
           // Ideally we could mutate the existing animation but unfortunately
           // the mutable APIs seem less tested and therefore are lacking or buggy.
@@ -1913,19 +1937,7 @@ export function startGestureTransition(
         : readyCallback;
     transition.ready.then(readyForAnimations, readyCallback);
     transition.finished.then(() => {
-      // In Safari, we need to manually cancel all manually start animations
-      // or it'll block future transitions.
-      const documentElement: Element = (ownerDocument.documentElement: any);
-      const animations = documentElement.getAnimations({subtree: true});
-      for (let i = 0; i < animations.length; i++) {
-        const anim = animations[i];
-        const effect: KeyframeEffect = (anim.effect: any);
-        // $FlowFixMe
-        const pseudo: ?string = effect.pseudoElement;
-        if (pseudo != null && pseudo.startsWith('::view-transition')) {
-          anim.cancel();
-        }
-      }
+      cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
       // $FlowFixMe[prop-missing]
       if (ownerDocument.__reactViewTransition === transition) {
         // $FlowFixMe[prop-missing]