Repository navigation
Notify surface-start listeners when an empty surface starts (#59584) - #59584
Open
bartlomiejbloniarz wants to merge 1 commit into
Open
bartlomiejbloniarz wants to merge 1 commit into
bartlomiejbloniarz wants to merge 1 commit into
Conversation
|
@bartlomiejbloniarz has imported this pull request. If you are a Meta employee, you can view this in D128154376. |
bartlomiejbloniarz
force-pushed
the
bartlomiejbloniarz/animated-props-empty-surface-start
branch
from
October 28, 2026 20:57
ad4a0d0 to
a034a22
Compare
Summary: `UIManager::startSurface` runs the callbacks registered with `UIManager::addOnSurfaceStartCallback` after adding the shadow tree. `UIManager::startEmptySurface`, which `SurfaceHandler` uses when a surface has no module name, only added the shadow tree, so those callbacks never ran for such surfaces. The shared animation backend, `ViewTransitionModule` and C++ Animated set up per-surface state in that callback. C++ Animated installs `AnimatedMountingOverrideDelegate` there. Fantom starts all its surfaces without a module name, so none of them saw a surface start in Fantom tests. `startEmptySurface` now notifies the delegate the same way `startSurface` does. ## Changelog: [GENERAL] [FIXED] - Surfaces started without a module name now run the callbacks registered with `UIManager::addOnSurfaceStartCallback` Pull Request resolved: #59584 Test Plan: - `SchedulerDelegateInvalidationTest.EmptySurfaceStartRunsSurfaceStartCallbacks`: starting a surface with no module name runs a registered surface-start callback with that surface's shadow tree. It fails without this change. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` passes. Differential Revision: D128154376 Pulled By: bartlomiejbloniarz
meta-codesync
Bot
force-pushed
the
bartlomiejbloniarz/animated-props-empty-surface-start
branch
from
October 28, 2026 21:28
a034a22 to
be16e9b
Compare
|
@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D128154376. |
Collaborator
|
This pull request was successfully merged by @bartlomiejbloniarz in be16e9b When will my fix make it into a release? | How to file a pick request? |
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
Summary: `UIManager::startSurface` runs the callbacks registered with `UIManager::addOnSurfaceStartCallback` after adding the shadow tree. `UIManager::startEmptySurface`, which `SurfaceHandler` uses when a surface has no module name, only added the shadow tree, so those callbacks never ran for such surfaces. The shared animation backend, `ViewTransitionModule` and C++ Animated set up per-surface state in that callback. C++ Animated installs `AnimatedMountingOverrideDelegate` there. Fantom starts all its surfaces without a module name, so none of them saw a surface start in Fantom tests. `startEmptySurface` now notifies the delegate the same way `startSurface` does. ## Changelog: [GENERAL] [FIXED] - Surfaces started without a module name now run the callbacks registered with `UIManager::addOnSurfaceStartCallback` Pull Request resolved: #59584 Test Plan: - `SchedulerDelegateInvalidationTest.EmptySurfaceStartRunsSurfaceStartCallbacks`: starting a surface with no module name runs a registered surface-start callback with that surface's shadow tree. It fails without this change. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` passes. Differential Revision: D128154376 Pulled By: bartlomiejbloniarz
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
Summary: Stacked on #59584. Review the last two commits: the change and its tests. Producers of animation mutations, such as C++ Animated and Reanimated, use the shared animation backend through anonymous frame callbacks. Settings that differ between producers have nowhere to live. A producer can now register once as an `AnimationFrontend` with its frame callback, then start and stop it by id with `startFrontend` and `stopFrontend`. Each frame runs the callbacks passed to `start(callback)` first, then the started frontends in registration order. `start(callback)`, `stop(callbackId)` and `pushAnimationMutations(callback)` are unchanged. C++ Animated now registers a frontend instead of starting a callback. Its frame callback therefore runs after other producers' `start(callback)` callbacks instead of in the order they were started. The next PR adds the first frontend setting. ## Changelog: [INTERNAL] [ADDED] - Let animation producers register with the animation backend as frontends Pull Request resolved: #59585 Test Plan: - `AnimationBackendFrontendTest`: a started frontend runs once per frame, starting and stopping are idempotent, frames are requested while any frontend or callback is started, a frontend without a frame callback is not started, and frames run in registration order after the callbacks. - `NativeAnimatedFrontendTests.registersAndStartsAFrontend`: C++ Animated registers and starts a frontend with the backend instead of starting a callback. - The other `animationbackend` and `animated` gtests pass. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` and `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` pass. Differential Revision: D128154522 Pulled By: bartlomiejbloniarz
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
Summary: Stacked on #59584. Review the last two commits: the change and its tests. Producers of animation mutations, such as C++ Animated and Reanimated, use the shared animation backend through anonymous frame callbacks. Settings that differ between producers have nowhere to live. A producer can now register once as an `AnimationFrontend` with its frame callback, then start and stop it by id with `startFrontend` and `stopFrontend`. Each frame runs the callbacks passed to `start(callback)` first, then the started frontends in registration order. `start(callback)`, `stop(callbackId)` and `pushAnimationMutations(callback)` are unchanged. C++ Animated now registers a frontend instead of starting a callback. Its frame callback therefore runs after other producers' `start(callback)` callbacks instead of in the order they were started. The next PR adds the first frontend setting. ## Changelog: [INTERNAL] [ADDED] - Let animation producers register with the animation backend as frontends Pull Request resolved: #59585 Test Plan: - `AnimationBackendFrontendTest`: a started frontend runs once per frame, starting and stopping are idempotent, frames are requested while any frontend or callback is started, a frontend without a frame callback is not started, and frames run in registration order after the callbacks. - `NativeAnimatedFrontendTests.registersAndStartsAFrontend`: C++ Animated registers and starts a frontend with the backend instead of starting a callback. - The other `animationbackend` and `animated` gtests pass. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` and `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` pass. Differential Revision: D128154522 Pulled By: bartlomiejbloniarz
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
…ation backend (#59586) Summary: Stacked on #59585, which is stacked on #59584. Review the last two commits: the change and its tests. With the shared animation backend, the backend's commit hook writes C++ Animated's latest values into every React commit made while it animates. C++ Animated without the backend does not do this. It writes non-layout props straight to the views, keeps them over React's mounts with `AnimatedMountingOverrideDelegate`, and commits the final values when an animation finishes. This PR adds a way to experiment with the second approach on the backend. It adds `commitDirectProps` to `AnimationFrontend` and the `enableCxxAnimatedDirectPathInBackend` flag. With the flag, C++ Animated registers with `commitDirectProps = false` and behaves as it does without the backend: - The commit hook does not write its direct values into React commits. Layout updates are unchanged. - `AnimatedMountingOverrideDelegate` keeps the values of connected props nodes over React mounts. - The last update of an animation is recorded apart, and the end-of-animation commit writes it. - One end-of-animation commit per surface is scheduled at a time. Without the flag, C++ Animated registers with `commitDirectProps = true` and the end-of-animation commit is unchanged. Turning `commitDirectProps` off without the flag asserts. ## Changelog: [INTERNAL] [ADDED] - Add `enableCxxAnimatedDirectPathInBackend` to keep C++ Animated's direct updates out of React commits with the shared animation backend Pull Request resolved: #59586 Test Plan: **Unit tests.** `AnimatedPropsRegistryTest`, `AnimationBackendFrontendTest` and `NativeAnimatedFrontendTests` cover which values the commit hook writes, when final values are written, taken and restored, that older recorded values do not overwrite a final value, and one end-of-animation commit per surface. The tests for final values and for one commit per surface fail without the code they cover. **Fantom.** - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` runs with the flag on and off and passes. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js` checks that a React commit of an animated view keeps its animated value mounted, with the flag on and off. It fails without the mount override delegate. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` passes. I also ran it locally with the backend and the flag forced on. Fantom allows at most 12 flag combinations per file, so that run is not encoded in the file. **Devices.** RNTester on an Android emulator and an iOS simulator, with the backend and C++ Animated on, once with the flag on and once with it off. I tracked a square frame by frame in screen recordings: | Check | Android, flag on | Android, flag off | iOS, flag on | iOS, flag off | |---|---|---|---|---| | React re-renders the view every 50 ms during a looping native animation | No backward jumps | No backward jumps | No backward jumps | No backward jumps | | The animation finishes, then React keeps re-rendering | Keeps the final values | Keeps the final values | Keeps the final values | Keeps the final values | | `setValue(0)`, then React keeps re-rendering | Stays reset | Stays reset | Stays reset | Stays reset | Differential Revision: D128154682 Pulled By: bartlomiejbloniarz
Contributor
Author
|
No it wasn't? |
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
Summary: `UIManager::startSurface` runs the callbacks registered with `UIManager::addOnSurfaceStartCallback` after adding the shadow tree. `UIManager::startEmptySurface`, which `SurfaceHandler` uses when a surface has no module name, only added the shadow tree, so those callbacks never ran for such surfaces. The shared animation backend, `ViewTransitionModule` and C++ Animated set up per-surface state in that callback. C++ Animated installs `AnimatedMountingOverrideDelegate` there. Fantom starts all its surfaces without a module name, so none of them saw a surface start in Fantom tests. `startEmptySurface` now notifies the delegate the same way `startSurface` does. ## Changelog: [GENERAL] [FIXED] - Surfaces started without a module name now run the callbacks registered with `UIManager::addOnSurfaceStartCallback` Pull Request resolved: #59584 Test Plan: - `SchedulerDelegateInvalidationTest.EmptySurfaceStartRunsSurfaceStartCallbacks`: starting a surface with no module name runs a registered surface-start callback with that surface's shadow tree. It fails without this change. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` passes. Differential Revision: D128154376 Pulled By: bartlomiejbloniarz
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
Summary: Stacked on #59584. Review the last two commits: the change and its tests. Producers of animation mutations, such as C++ Animated and Reanimated, use the shared animation backend through anonymous frame callbacks. Settings that differ between producers have nowhere to live. A producer can now register once as an `AnimationFrontend` with its frame callback, then start and stop it by id with `startFrontend` and `stopFrontend`. Each frame runs the callbacks passed to `start(callback)` first, then the started frontends in registration order. `start(callback)`, `stop(callbackId)` and `pushAnimationMutations(callback)` are unchanged. C++ Animated now registers a frontend instead of starting a callback. Its frame callback therefore runs after other producers' `start(callback)` callbacks instead of in the order they were started. The next PR adds the first frontend setting. ## Changelog: [INTERNAL] [ADDED] - Let animation producers register with the animation backend as frontends Pull Request resolved: #59585 Test Plan: - `AnimationBackendFrontendTest`: a started frontend runs once per frame, starting and stopping are idempotent, frames are requested while any frontend or callback is started, a frontend without a frame callback is not started, and frames run in registration order after the callbacks. - `NativeAnimatedFrontendTests.registersAndStartsAFrontend`: C++ Animated registers and starts a frontend with the backend instead of starting a callback. - The other `animationbackend` and `animated` gtests pass. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` and `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` pass. Differential Revision: D128154522 Pulled By: bartlomiejbloniarz
meta-codesync Bot
pushed a commit
that referenced
this pull request
Oct 28, 2026
…ation backend (#59586) Summary: Stacked on #59585, which is stacked on #59584. Review the last two commits: the change and its tests. With the shared animation backend, the backend's commit hook writes C++ Animated's latest values into every React commit made while it animates. C++ Animated without the backend does not do this. It writes non-layout props straight to the views, keeps them over React's mounts with `AnimatedMountingOverrideDelegate`, and commits the final values when an animation finishes. This PR adds a way to experiment with the second approach on the backend. It adds `commitDirectProps` to `AnimationFrontend` and the `enableCxxAnimatedDirectPathInBackend` flag. With the flag, C++ Animated registers with `commitDirectProps = false` and behaves as it does without the backend: - The commit hook does not write its direct values into React commits. Layout updates are unchanged. - `AnimatedMountingOverrideDelegate` keeps the values of connected props nodes over React mounts. - The last update of an animation is recorded apart, and the end-of-animation commit writes it. - One end-of-animation commit per surface is scheduled at a time. Without the flag, C++ Animated registers with `commitDirectProps = true` and the end-of-animation commit is unchanged. Turning `commitDirectProps` off without the flag asserts. ## Changelog: [INTERNAL] [ADDED] - Add `enableCxxAnimatedDirectPathInBackend` to keep C++ Animated's direct updates out of React commits with the shared animation backend Pull Request resolved: #59586 Test Plan: **Unit tests.** `AnimatedPropsRegistryTest`, `AnimationBackendFrontendTest` and `NativeAnimatedFrontendTests` cover which values the commit hook writes, when final values are written, taken and restored, that older recorded values do not overwrite a final value, and one end-of-animation commit per surface. The tests for final values and for one commit per surface fail without the code they cover. **Fantom.** - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` runs with the flag on and off and passes. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js` checks that a React commit of an animated view keeps its animated value mounted, with the flag on and off. It fails without the mount override delegate. - `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` passes. I also ran it locally with the backend and the flag forced on. Fantom allows at most 12 flag combinations per file, so that run is not encoded in the file. **Devices.** RNTester on an Android emulator and an iOS simulator, with the backend and C++ Animated on, once with the flag on and once with it off. I tracked a square frame by frame in screen recordings: | Check | Android, flag on | Android, flag off | iOS, flag on | iOS, flag off | |---|---|---|---|---| | React re-renders the view every 50 ms during a looping native animation | No backward jumps | No backward jumps | No backward jumps | No backward jumps | | The animation finishes, then React keeps re-rendering | Keeps the final values | Keeps the final values | Keeps the final values | Keeps the final values | | `setValue(0)`, then React keeps re-rendering | Stays reset | Stays reset | Stays reset | Stays reset | Differential Revision: D128154682 Pulled By: bartlomiejbloniarz
Collaborator
|
This pull request was successfully merged by @bartlomiejbloniarz in 72997cc When will my fix make it into a release? | How to file a pick request? |
Visual regression3 visual difference(s) at ios
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.









Summary:
UIManager::startSurfaceruns the callbacks registered withUIManager::addOnSurfaceStartCallbackafter adding the shadow tree.UIManager::startEmptySurface, whichSurfaceHandleruses when a surface has no module name, only added the shadow tree, so those callbacks never ran for such surfaces.The shared animation backend,
ViewTransitionModuleand C++ Animated set up per-surface state in that callback. C++ Animated installsAnimatedMountingOverrideDelegatethere. Fantom starts all its surfaces without a module name, so none of them saw a surface start in Fantom tests.startEmptySurfacenow notifies the delegate the same waystartSurfacedoes.Changelog:
[GENERAL] [FIXED] - Surfaces started without a module name now run the callbacks registered with
UIManager::addOnSurfaceStartCallbackTest Plan:
SchedulerDelegateInvalidationTest.EmptySurfaceStartRunsSurfaceStartCallbacks: starting a surface with no module name runs a registered surface-start callback with that surface's shadow tree. It fails without this change.yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.jspasses.Differential Revision: D128154376
Pulled By: bartlomiejbloniarz