Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: only intercept events with waiting handlers #6739

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

mhoran
Copy link

@mhoran mhoran commented Nov 20, 2024

Summary

Previously, NativeReanimatedModule::handleRawEvent would intercept all events received by the event listener. This resulted in an issue where onLayout would not fire in JS on the New Architecture.

Instead, only intercept events with waiting handlers. This prevents asJSIValue from being called on the Reanimated event loop and allows onLayout to bubble up in JS.

See
https://github.com/facebook/react-native/blob/v0.76.2/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.cpp#L82-L112, which prevents onLayout from being dispatched more than once.

asJSIValue evaluates the lambda above in
https://github.com/facebook/react-native/blob/v0.76.2/packages/react-native/ReactCommon/react/renderer/core/ValueFactoryEventPayload.cpp#L16.

Fixes #6684

Test plan

See reproducer in #6684. Without this change, onLayout will not fire on device rotation. With this change, onLayout will fire as expected.

@piaskowyk piaskowyk self-requested a review November 21, 2024 09:24
@tomekzaw
Copy link
Member

@mhoran Thanks for the PR. Just making sure if I understand the problem and the proposed solution correctly.

Currently, all onLayout events are intercepted by Reanimated (while still on the UI thread), and calling asJSIValue results in layoutEventState->wasDispatched being set to true. Then, once the event is processed on the JS thread, asJSIValue returns null.

After this change, the flow described above will be true only for events that pass isAnyHandlerWaitingForEvent test. For all other onLayout events (those which are not awaited by Reanimated), asJSIValue will not be called on the UI thread so asJSIValue will return the actual event data when called on the JS thread.

Is that correct?

@mhoran mhoran force-pushed the fix-onlayout-dispatch branch from 94987fe to c7e1976 Compare November 21, 2024 17:17
@mhoran
Copy link
Author

mhoran commented Nov 21, 2024

Currently, all onLayout events are intercepted by Reanimated (while still on the UI thread), and calling asJSIValue results in layoutEventState->wasDispatched being set to true. Then, once the event is processed on the JS thread, asJSIValue returns null.

Correct. While the Reanimated event listener will bail on events dispatched from the JS thread:

if (!RCTIsMainQueue()) {
// event listener called on the JS thread, let's ignore this event
// as we cannot safely access worklet runtime here
// and also we don't care about topLayout events
return false;
}

the rotation layout event dispatches from the UI thread. This results in the handler being called with the layout event, which Reanimated intercepts.

After this change, the flow described above will be true only for events that pass isAnyHandlerWaitingForEvent test. For all other onLayout events (those which are not awaited by Reanimated), asJSIValue will not be called on the UI thread so asJSIValue will return the actual event data when called on the JS thread.

Correct. This would ultimately happen when NativeReanimatedModule::handleEvent calls in to EventHandlerRegistry::processEvent, but unfortunately due to the way asJSIValue is implemented, and the debounce logic in the layout lambda, just getting the value will intercept the event, which is not desired.

I am not sure what would happen if one were to set up an onLayout handler that was intended to be handled by Reanimated. Is this even a valid use case? I think it would then get dispatched due to isAnyHandlerWaitingForEvent returning true, but then would not be dispatched on the React Native side. That seems fine, but I'm not sure how to even test this.

I added a test for this as well. Triggering a layout event by modifying a view's styles from the UI thread is equivalent to a device rotation (which dispatches on the UI thread.) The test will fail prior to this code change but passes after.

@tomekzaw
Copy link
Member

Thanks for clarification as well as adding the tests.

I am not sure what would happen if one were to set up an onLayout handler that was intended to be handled by Reanimated. Is this even a valid use case?

Sounds like a valid use case, here's a draft PR where I register an event listener for topLayout event: #5738

@mhoran mhoran force-pushed the fix-onlayout-dispatch branch from c7e1976 to 6784b78 Compare November 21, 2024 22:24
@mhoran
Copy link
Author

mhoran commented Nov 21, 2024

Sounds like a valid use case, here's a draft PR where I register an event listener for topLayout event: #5738

I've added a test to cover this use case as well.

Previously, NativeReanimatedModule::handleRawEvent would intercept all
events received by the event listener. This resulted in an issue where
onLayout would not fire in JS on the New Architecture.

Instead, only intercept events with waiting handlers. This prevents
asJSIValue from being called on the Reanimated event loop and allows
onLayout to bubble up in JS.

See
https://github.com/facebook/react-native/blob/v0.76.2/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.cpp#L82-L112,
which prevents onLayout from being dispatched more than once.

asJSIValue evaluates the lambda above in
https://github.com/facebook/react-native/blob/v0.76.2/packages/react-native/ReactCommon/react/renderer/core/ValueFactoryEventPayload.cpp#L16.

Fixes software-mansion#6684
Using notify in the runtime tests would result in test pollution. Reset
the notification registry between tests, similar to the call tracker
registry.
@mhoran mhoran force-pushed the fix-onlayout-dispatch branch from d72fcbe to 6adb380 Compare December 18, 2024 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

onLayout stops firing after rotation on iOS when Animated.View is used
2 participants