Skip to content

Commit fd0bc69

Browse files
zeyapmeta-codesync[bot]
authored andcommitted
Suspend overlapping view transitions (#56462)
Summary: Pull Request resolved: #56462 ## Changelog: [Internal] [Added] - Suspend overlapping view transitions When a new view transition starts while another is still active, queue it instead of running immediately. The queued transition runs after the current one finishes via `startViewTransitionEnd` (triggered when transition truly finishes). - Add `suspendOnActiveViewTransition()` to `UIManagerViewTransitionDelegate`, exposed as a method on `nativeFabricUIManager` so the reconciler can signal suspension - `ViewTransitionModule` queues pending transitions in a `std::queue<PendingTransition>` when `suspendNextTransition_` is set - `startViewTransitionEnd` drains the queue sequentially, each transition triggering the next on completion Reviewed By: sammy-SC Differential Revision: D99366975 fbshipit-source-id: 24bc26b31088d6c46f94fd0805178771fc637df3
1 parent 2247778 commit fd0bc69

4 files changed

Lines changed: 72 additions & 0 deletions

File tree

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,24 @@ jsi::Value UIManagerBinding::get(
10741074
});
10751075
}
10761076

1077+
if (methodName == "suspendOnActiveViewTransition") {
1078+
return jsi::Function::createFromHostFunction(
1079+
runtime,
1080+
name,
1081+
0,
1082+
[uiManager](
1083+
jsi::Runtime& runtime,
1084+
const jsi::Value& /*thisValue*/,
1085+
const jsi::Value* /*arguments*/,
1086+
size_t /*count*/) -> jsi::Value {
1087+
auto* viewTransitionDelegate = uiManager->getViewTransitionDelegate();
1088+
if (viewTransitionDelegate != nullptr) {
1089+
viewTransitionDelegate->suspendOnActiveViewTransition();
1090+
}
1091+
return jsi::Value::undefined();
1092+
});
1093+
}
1094+
10771095
if (methodName == "startViewTransition") {
10781096
auto paramCount = 1;
10791097
return jsi::Function::createFromHostFunction(

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class UIManagerViewTransitionDelegate {
6767
{
6868
return nullptr;
6969
}
70+
71+
// Called by the reconciler to signal that the next view transition should
72+
// be suspended until the currently active one finishes.
73+
virtual void suspendOnActiveViewTransition() {}
7074
};
7175

7276
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,21 @@ void ViewTransitionModule::startViewTransition(
328328
std::function<void()> mutationCallback,
329329
std::function<void()> onReadyCallback,
330330
std::function<void()> onCompleteCallback) {
331+
// If the reconciler signalled suspension and a transition is still active,
332+
// queue this transition to run after the current one finishes.
333+
// Only queue if the previous transition is still running; if it already
334+
// finished, the flag is stale and we should run normally.
335+
if (suspendNextTransition_ && transitionStarted_) {
336+
suspendNextTransition_ = false;
337+
pendingTransitions_.push(
338+
PendingTransition{
339+
std::move(mutationCallback),
340+
std::move(onReadyCallback),
341+
std::move(onCompleteCallback)});
342+
return;
343+
}
344+
suspendNextTransition_ = false;
345+
331346
// Mark transition as started
332347
transitionStarted_ = true;
333348

@@ -351,6 +366,15 @@ void ViewTransitionModule::startViewTransition(
351366
}
352367
}
353368

369+
void ViewTransitionModule::suspendOnActiveViewTransition() {
370+
// Signal that the next transition should be suspended until the current
371+
// one finishes. The actual queueing happens in startViewTransition.
372+
if (transitionStarted_) {
373+
// if there's no active transition, suspendOnActiveViewTransition is no-op
374+
suspendNextTransition_ = true;
375+
}
376+
}
377+
354378
void ViewTransitionModule::startViewTransitionEnd() {
355379
for (const auto& [tag, names] : nameRegistry_) {
356380
for (const auto& name : names) {
@@ -370,6 +394,18 @@ void ViewTransitionModule::startViewTransitionEnd() {
370394
}
371395

372396
transitionStarted_ = false;
397+
398+
if (!pendingTransitions_.empty()) {
399+
auto pendingTransition = pendingTransitions_.front();
400+
pendingTransitions_.pop();
401+
startViewTransition(
402+
std::move(pendingTransition.mutationCallback),
403+
std::move(pendingTransition.onReadyCallback),
404+
std::move(pendingTransition.onCompleteCallback));
405+
// when this transition finishes, it'll call startViewTransitionEnd
406+
// during its complete callback and pendingTransitions_ will be processed
407+
// again
408+
}
373409
}
374410

375411
std::optional<UIManagerViewTransitionDelegate::ViewTransitionInstance>

packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#pragma once
99

10+
#include <queue>
1011
#include <unordered_set>
1112

1213
#include <react/renderer/core/LayoutMetrics.h>
@@ -78,6 +79,8 @@ class ViewTransitionModule : public UIManagerViewTransitionDelegate,
7879

7980
std::shared_ptr<const ShadowNode> findPseudoElementShadowNodeByTag(Tag tag) const override;
8081

82+
void suspendOnActiveViewTransition() override;
83+
8184
// Animation state structure for storing minimal view data
8285
struct AnimationKeyFrameViewLayoutMetrics {
8386
Point originFromRoot;
@@ -124,6 +127,17 @@ class ViewTransitionModule : public UIManagerViewTransitionDelegate,
124127
UIManager *uiManager_{nullptr};
125128

126129
bool transitionStarted_{false};
130+
131+
// When suspendNextTransition_ is true and a transition is active, the next
132+
// startViewTransition calls are queued instead of running immediately.
133+
bool suspendNextTransition_{false};
134+
135+
struct PendingTransition {
136+
std::function<void()> mutationCallback;
137+
std::function<void()> onReadyCallback;
138+
std::function<void()> onCompleteCallback;
139+
};
140+
std::queue<PendingTransition> pendingTransitions_{};
127141
};
128142

129143
} // namespace facebook::react

0 commit comments

Comments
 (0)