fix crash when view with event driven animation is unmounted (#52807)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52807

changelog: [internal]

Fixes a crash that occurs when event driven animation is attached to a view that is unmounted.

Reviewed By: zeyap

Differential Revision: D78889689

fbshipit-source-id: 6bc534c3d80a7ed2fc5e0ac378e58343fef45430
This commit is contained in:
Samuel Susla
2025-07-25 07:37:16 -07:00
committed by Facebook GitHub Bot
parent 0d13474161
commit dbdf39a8fe
3 changed files with 100 additions and 36 deletions
@@ -151,6 +151,66 @@ test('animation driven by onScroll event', () => {
expect(viewElement.getBoundingClientRect().y).toBe(100);
});
test('animation driven by onScroll event when animated view is unmounted', () => {
const scrollViewRef = createRef<HostInstance>();
component PressableWithNativeDriver(mountAnimatedView: boolean) {
const currScroll = useAnimatedValue(0);
return (
<View style={{flex: 1}}>
{mountAnimatedView ? (
<Animated.View
style={{
position: 'absolute',
width: 10,
height: 10,
transform: [{translateY: currScroll}],
}}
/>
) : null}
<Animated.ScrollView
ref={scrollViewRef}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: currScroll,
},
},
},
],
{useNativeDriver: true},
)}>
<View style={{height: 1000, width: 100}} />
</Animated.ScrollView>
</View>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<PressableWithNativeDriver mountAnimatedView={true} />);
});
Fantom.runTask(() => {
root.render(<PressableWithNativeDriver mountAnimatedView={false} />);
});
const scrollViewelement = ensureInstance(
scrollViewRef.current,
ReactNativeElement,
);
Fantom.scrollTo(scrollViewelement, {
x: 0,
y: 50,
});
Fantom.runWorkLoop();
});
test('animated opacity', () => {
let _opacity;
let _opacityAnimation;
@@ -406,47 +406,51 @@ void NativeAnimatedNodesManager::handleAnimatedEvent(
if (!isOnRenderThread_) {
return;
}
if (eventDrivers_.empty()) {
return;
}
if (!eventDrivers_.empty()) {
bool foundAtLeastOneDriver = false;
bool foundAtLeastOneDriver = false;
const auto key = EventAnimationDriverKey{
.viewTag = viewTag,
.eventName = EventEmitter::normalizeEventType(eventName)};
if (auto driversIter = eventDrivers_.find(key);
driversIter != eventDrivers_.end()) {
auto& drivers = driversIter->second;
if (!drivers.empty()) {
foundAtLeastOneDriver = true;
}
for (const auto& driver : drivers) {
if (auto value = driver->getValueFromPayload(eventPayload)) {
auto node =
getAnimatedNode<ValueAnimatedNode>(driver->getAnimatedNodeTag());
stopAnimationsForNode(node->tag());
if (node->setRawValue(value.value())) {
updatedNodeTags_.insert(node->tag());
}
const auto key = EventAnimationDriverKey{
.viewTag = viewTag,
.eventName = EventEmitter::normalizeEventType(eventName)};
if (auto driversIter = eventDrivers_.find(key);
driversIter != eventDrivers_.end()) {
auto& drivers = driversIter->second;
if (!drivers.empty()) {
foundAtLeastOneDriver = true;
}
for (const auto& driver : drivers) {
if (auto value = driver->getValueFromPayload(eventPayload)) {
auto node =
getAnimatedNode<ValueAnimatedNode>(driver->getAnimatedNodeTag());
if (node == nullptr) {
continue;
}
stopAnimationsForNode(node->tag());
if (node->setRawValue(value.value())) {
updatedNodeTags_.insert(node->tag());
}
}
}
}
if (foundAtLeastOneDriver && !isEventAnimationInProgress_) {
// There is an animation driver handling this event and
// event driven animation has not been started yet.
isEventAnimationInProgress_ = true;
// Some platforms (e.g. iOS) have UI tick listener disable
// when there are no active animations. Calling
// `startRenderCallbackIfNeeded` will call platform specific code to
// register UI tick listener.
startRenderCallbackIfNeeded();
// Calling startOnRenderCallback_ will register a UI tick listener.
// The UI ticker listener will not be called until the next frame.
// That's why, in case this is called from the UI thread, we need to
// proactivelly trigger the animation loop to avoid showing stale
// frames.
onRender();
}
if (foundAtLeastOneDriver && !isEventAnimationInProgress_) {
// There is an animation driver handling this event and
// event driven animation has not been started yet.
isEventAnimationInProgress_ = true;
// Some platforms (e.g. iOS) have UI tick listener disable
// when there are no active animations. Calling
// `startRenderCallbackIfNeeded` will call platform specific code to
// register UI tick listener.
startRenderCallbackIfNeeded();
// Calling startOnRenderCallback_ will register a UI tick listener.
// The UI ticker listener will not be called until the next frame.
// That's why, in case this is called from the UI thread, we need to
// proactivelly trigger the animation loop to avoid showing stale
// frames.
onRender();
}
}
@@ -37,7 +37,7 @@ class EventAnimationDriver {
protected:
std::vector<std::string> eventPath_;
Tag animatedValueTag_;
const Tag animatedValueTag_;
};
struct EventAnimationDriverKey {