From 95ce9ba617e04a0396e3d01f20e562b2dc77fb28 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 5 Aug 2026 09:08:40 -0700 Subject: [PATCH] Construct the shared animation backend after the UIManager has a delegate (#57817) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: ## Changelog: [Internal] - Construct the shared animation backend after the UIManager has a delegate `AnimationBackend`'s constructor calls `UIManager::addOnSurfaceStartCallback` so that `AnimatedPropsRegistry::initializeSurface` runs for each surface as it starts. That call is a no-op unless the `UIManager` already has a delegate: ``` void UIManager::addOnSurfaceStartCallback( UIManagerDelegate::OnSurfaceStartCallback&& callback) { if (delegate_ != nullptr) { delegate_->uiManagerShouldAddOnSurfaceStartCallback(std::move(callback)); } } ``` `Scheduler`'s constructor was building the backend immediately after constructing the `UIManager` and roughly seventy lines before `uiManager->setDelegate(this)`, so the callback was dropped every time and `Scheduler::onSurfaceStartCallbacks_` never received it. `AnimatedPropsRegistry::update` skips surfaces missing from `surfaceContexts_`, so correctness was left resting on `getMap()` default-constructing the entry via `operator[]` — that is, on a commit hook happening to run before the first animated update on a freshly started surface. When that ordering does not hold, early animated updates on a new surface are dropped. Move the backend construction to just after `setDelegate`, and add a comment recording the ordering constraint. This is still well before any surface can start, so the registry is populated for every surface from the first frame. The neighbouring `getShadowTreeRegistry().enumerate(...)` in the same constructor is also dead today — the `UIManager` is two lines old and its registry is necessarily empty — but it is left in place deliberately. It mirrors the enumerate-then-register pattern in `NativeAnimatedNodesManagerProvider` and `ViewTransitionModule`, both of which are constructed lazily and do need it, and it keeps `AnimationBackend`'s constructor correct if it is ever moved to a lazy call site. Reviewed By: christophpurrer Differential Revision: D114759538 --- .../react/renderer/scheduler/Scheduler.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 77f7719dcc0..b5fa421a35a 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -59,16 +59,6 @@ Scheduler::Scheduler( auto uiManager = std::make_shared(runtimeExecutor_, contextContainer_); - if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - auto animationBackend = std::make_shared( - schedulerToolbox.animationChoreographer, uiManager); - - schedulerToolbox.animationChoreographer->setAnimationBackend( - animationBackend); - - uiManager->unstable_setAnimationBackend(animationBackend); - } - auto eventOwnerBox = std::make_shared(); eventOwnerBox->owner = eventDispatcher_; @@ -131,6 +121,19 @@ Scheduler::Scheduler( uiManager->setDelegate(this); uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_); + // Must come after `setDelegate`: the backend's constructor registers a + // surface-start callback, and `UIManager` silently drops those while it has + // no delegate. + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + auto animationBackend = std::make_shared( + schedulerToolbox.animationChoreographer, uiManager); + + schedulerToolbox.animationChoreographer->setAnimationBackend( + animationBackend); + + uiManager->unstable_setAnimationBackend(animationBackend); + } + auto bindingsExecutor = schedulerToolbox.bridgelessBindingsExecutor.has_value() ? schedulerToolbox.bridgelessBindingsExecutor.value()