Skip to content

Commit 61331f3

Browse files
authored
Fix ViewTransition crash in Mobile Safari (#35337)
Speculative fix to #35336 written by Claude. I have verified that applying a similar patch locally to the repro from #35336 does fix the crash. I'm not familiar enough with the underlying APIs to tell whether the fix is correct or sufficient.
1 parent 55480b4 commit 61331f3

File tree

1 file changed

+17
-22
lines changed

1 file changed

+17
-22
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,26 +1996,6 @@ export function hasInstanceAffectedParent(
19961996
return oldRect.height !== newRect.height || oldRect.width !== newRect.width;
19971997
}
19981998

1999-
function cancelAllViewTransitionAnimations(scope: Element) {
2000-
// In Safari, we need to manually cancel all manually start animations
2001-
// or it'll block or interfer with future transitions.
2002-
// $FlowFixMe[prop-missing]
2003-
const animations = scope.getAnimations({subtree: true});
2004-
for (let i = 0; i < animations.length; i++) {
2005-
const anim = animations[i];
2006-
const effect: KeyframeEffect = (anim.effect: any);
2007-
// $FlowFixMe
2008-
const pseudo: ?string = effect.pseudoElement;
2009-
if (
2010-
pseudo != null &&
2011-
pseudo.startsWith('::view-transition') &&
2012-
effect.target === scope
2013-
) {
2014-
anim.cancel();
2015-
}
2016-
}
2017-
}
2018-
20191999
// How long to wait for new fonts to load before just committing anyway.
20202000
// This freezes the screen. It needs to be short enough that it doesn't cause too much of
20212001
// an issue when it's a new load and slow, yet long enough that you have a chance to load
@@ -2210,6 +2190,8 @@ export function startViewTransition(
22102190
// $FlowFixMe[prop-missing]
22112191
ownerDocument.__reactViewTransition = transition;
22122192

2193+
const viewTransitionAnimations: Array<Animation> = [];
2194+
22132195
const readyCallback = () => {
22142196
const documentElement: Element = (ownerDocument.documentElement: any);
22152197
// Loop through all View Transition Animations.
@@ -2224,6 +2206,7 @@ export function startViewTransition(
22242206
pseudoElement != null &&
22252207
pseudoElement.startsWith('::view-transition')
22262208
) {
2209+
viewTransitionAnimations.push(animation);
22272210
const keyframes = effect.getKeyframes();
22282211
// Next, we're going to try to optimize this animation in case the auto-generated
22292212
// width/height keyframes are unnecessary.
@@ -2315,7 +2298,12 @@ export function startViewTransition(
23152298
};
23162299
transition.ready.then(readyCallback, handleError);
23172300
transition.finished.finally(() => {
2318-
cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
2301+
for (let i = 0; i < viewTransitionAnimations.length; i++) {
2302+
// In Safari, we need to manually cancel all manually started animations
2303+
// or it'll block or interfer with future transitions.
2304+
// We can't use getAnimations() due to #35336 so we collect them in an array.
2305+
viewTransitionAnimations[i].cancel();
2306+
}
23192307
// $FlowFixMe[prop-missing]
23202308
if (ownerDocument.__reactViewTransition === transition) {
23212309
// $FlowFixMe[prop-missing]
@@ -2549,6 +2537,7 @@ export function startGestureTransition(
25492537
// $FlowFixMe[prop-missing]
25502538
ownerDocument.__reactViewTransition = transition;
25512539
const customTimelineCleanup: Array<() => void> = []; // Cleanup Animations started in a CustomTimeline
2540+
const viewTransitionAnimations: Array<Animation> = [];
25522541
const readyCallback = () => {
25532542
const documentElement: Element = (ownerDocument.documentElement: any);
25542543
// Loop through all View Transition Animations.
@@ -2566,6 +2555,7 @@ export function startGestureTransition(
25662555
const pseudoElement: ?string = effect.pseudoElement;
25672556
if (pseudoElement == null) {
25682557
} else if (pseudoElement.startsWith('::view-transition')) {
2558+
viewTransitionAnimations.push(animations[i]);
25692559
const timing = effect.getTiming();
25702560
const duration =
25712561
// $FlowFixMe[prop-missing]
@@ -2743,7 +2733,12 @@ export function startGestureTransition(
27432733
};
27442734
transition.ready.then(readyForAnimations, handleError);
27452735
transition.finished.finally(() => {
2746-
cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
2736+
for (let i = 0; i < viewTransitionAnimations.length; i++) {
2737+
// In Safari, we need to manually cancel all manually started animations
2738+
// or it'll block or interfer with future transitions.
2739+
// We can't use getAnimations() due to #35336 so we collect them in an array.
2740+
viewTransitionAnimations[i].cancel();
2741+
}
27472742
for (let i = 0; i < customTimelineCleanup.length; i++) {
27482743
const cleanup = customTimelineCleanup[i];
27492744
cleanup();

0 commit comments

Comments
 (0)