diff --git a/Libraries/Alert/NativeAlertManager.js b/Libraries/Alert/NativeAlertManager.js index 778c89083cb81f..08b2c2d648669c 100644 --- a/Libraries/Alert/NativeAlertManager.js +++ b/Libraries/Alert/NativeAlertManager.js @@ -17,7 +17,7 @@ import type {DefaultInputsArray} from './AlertMacOS'; // TODO(macOS GH#774) export type Args = {| title?: string, message?: string, - buttons?: Array, // TODO: have a better type + buttons?: Array, // TODO(T67565166): have a better type type?: string, defaultValue?: string, cancelButtonKey?: string, diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js index 099e71c8faaaaf..4851399fd1bb9f 100644 --- a/Libraries/Animated/src/createAnimatedComponent.js +++ b/Libraries/Animated/src/createAnimatedComponent.js @@ -10,6 +10,7 @@ 'use strict'; +const View = require('../../Components/View/View'); const {AnimatedEvent} = require('./AnimatedEvent'); const AnimatedProps = require('./nodes/AnimatedProps'); const React = require('react'); @@ -20,7 +21,18 @@ const setAndForwardRef = require('../../Utilities/setAndForwardRef'); export type AnimatedComponentType< Props: {+[string]: mixed, ...}, Instance, -> = React.AbstractComponent<$ObjMap any>, Instance>; +> = React.AbstractComponent< + $ObjMap< + Props & + $ReadOnly<{ + passthroughAnimatedPropExplicitValues?: React.ElementConfig< + typeof View, + >, + }>, + () => any, + >, + Instance, +>; function createAnimatedComponent( Component: React.AbstractComponent, @@ -65,6 +77,7 @@ function createAnimatedComponent( // However, setNativeProps can only be implemented on leaf native // components. If you want to animate a composite component, you need to // re-render it. In this case, we have a fallback that uses forceUpdate. + // This fallback is also called in Fabric. _animatedPropsCallback = () => { if (this._component == null) { // AnimatedProps is created in will-mount because it's used in render. @@ -119,6 +132,10 @@ function createAnimatedComponent( _attachProps(nextProps) { const oldPropsAnimated = this._propsAnimated; + if (nextProps === oldPropsAnimated) { + return; + } + this._propsAnimated = new AnimatedProps( nextProps, this._animatedPropsCallback, @@ -160,10 +177,15 @@ function createAnimatedComponent( }); render() { - const props = this._propsAnimated.__getValue(); + const {style = {}, ...props} = this._propsAnimated.__getValue() || {}; + const {style: passthruStyle = {}, ...passthruProps} = + this.props.passthroughAnimatedPropExplicitValues || {}; + const mergedStyle = {...style, ...passthruStyle}; return ( { return ( this._setStickyHeaderRef(key, ref)} nextHeaderLayoutY={this._headerLayoutYs.get( this._getKeyForIndex(nextIndex, childArray), diff --git a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js index 0bc51bbfaecbec..76485c76af2081 100644 --- a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +++ b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -14,6 +14,7 @@ const AnimatedImplementation = require('../../Animated/src/AnimatedImplementatio const React = require('react'); const StyleSheet = require('../../StyleSheet/StyleSheet'); const View = require('../View/View'); +const Platform = require('../../Utilities/Platform'); import type {LayoutEvent} from '../../Types/CoreEventTypes'; @@ -29,6 +30,7 @@ export type Props = { inverted: ?boolean, // The height of the parent ScrollView. Currently only set when inverted. scrollViewHeight: ?number, + nativeID?: ?string, ... }; @@ -37,6 +39,7 @@ type State = { layoutY: number, layoutHeight: number, nextHeaderLayoutY: ?number, + translateY: ?number, ... }; @@ -46,17 +49,121 @@ class ScrollViewStickyHeader extends React.Component { layoutY: 0, layoutHeight: 0, nextHeaderLayoutY: this.props.nextHeaderLayoutY, + translateY: null, }; + _translateY: ?AnimatedImplementation.Interpolation = null; + _shouldRecreateTranslateY: boolean = true; + _haveReceivedInitialZeroTranslateY: boolean = true; + _ref: any; // TODO T53738161: flow type this, and the whole file + + // Fabric-only: + _timer: ?TimeoutID; + _animatedValueListenerId: string; + _animatedValueListener: (valueObject: $ReadOnly<{|value: number|}>) => void; + _debounceTimeout: number = Platform.OS === 'android' ? 15 : 64; + setNextHeaderY(y: number) { this.setState({nextHeaderLayoutY: y}); } + UNSAFE_componentWillReceiveProps(nextProps: Props) { + if ( + nextProps.scrollViewHeight !== this.props.scrollViewHeight || + nextProps.scrollAnimatedValue !== this.props.scrollAnimatedValue || + nextProps.inverted !== this.props.inverted + ) { + this._shouldRecreateTranslateY = true; + } + } + + updateTranslateListener( + translateY: AnimatedImplementation.Interpolation, + isFabric: boolean, + ) { + if (this._translateY != null && this._animatedValueListenerId != null) { + this._translateY.removeListener(this._animatedValueListenerId); + } + + this._translateY = translateY; + this._shouldRecreateTranslateY = false; + + if (!isFabric) { + return; + } + + if (!this._animatedValueListener) { + // This is called whenever the (Interpolated) Animated Value + // updates, which is several times per frame during scrolling. + // To ensure that the Fabric ShadowTree has the most recent + // translate style of this node, we debounce the value and then + // pass it through to the underlying node during render. + // This is: + // 1. Only an issue in Fabric. + // 2. Worse in Android than iOS. In Android, but not iOS, you + // can touch and move your finger slightly and still trigger + // a "tap" event. In iOS, moving will cancel the tap in + // both Fabric and non-Fabric. On Android when you move + // your finger, the hit-detection moves from the Android + // platform to JS, so we need the ShadowTree to have knowledge + // of the current position. + this._animatedValueListener = ({value}) => { + // When the AnimatedInterpolation is recreated, it always initializes + // to a value of zero and emits a value change of 0 to its listeners. + if (value === 0 && !this._haveReceivedInitialZeroTranslateY) { + this._haveReceivedInitialZeroTranslateY = true; + return; + } + if (this._timer) { + clearTimeout(this._timer); + } + this._timer = setTimeout(() => { + if (value !== this.state.translateY) { + this.setState({ + translateY: value, + }); + // This fixes jank on iOS, especially around paging, + // but causes jank on Android. + // It seems that Native Animated Driver on iOS has + // more conflicts with values passed through the ShadowTree + // especially when connecting new Animated nodes + disconnecting + // old ones, compared to Android where that process seems fine. + if (Platform.OS === 'ios') { + setTimeout(() => { + this.setState({ + translateY: null, + }); + }, 0); + } + } + }, this._debounceTimeout); + }; + } + if (this.state.translateY !== 0 && this.state.translateY != null) { + this._haveReceivedInitialZeroTranslateY = false; + } + this._animatedValueListenerId = translateY.addListener( + this._animatedValueListener, + ); + } + _onLayout = event => { + const layoutY = event.nativeEvent.layout.y; + const layoutHeight = event.nativeEvent.layout.height; + const measured = true; + + if ( + layoutY !== this.state.layoutY || + layoutHeight !== this.state.layoutHeight || + measured !== this.state.measured + ) { + this._shouldRecreateTranslateY = true; + } + this.setState({ - measured: true, - layoutY: event.nativeEvent.layout.y, - layoutHeight: event.nativeEvent.layout.height, + measured, + layoutY, + layoutHeight, }); this.props.onLayout(event); @@ -66,85 +173,124 @@ class ScrollViewStickyHeader extends React.Component { } }; + _setComponentRef = ref => { + this._ref = ref; + }; + render(): React.Node { - const {inverted, scrollViewHeight} = this.props; - const {measured, layoutHeight, layoutY, nextHeaderLayoutY} = this.state; - const inputRange: Array = [-1, 0]; - const outputRange: Array = [0, 0]; - - if (measured) { - if (inverted) { - // The interpolation looks like: - // - Negative scroll: no translation - // - `stickStartPoint` is the point at which the header will start sticking. - // It is calculated using the ScrollView viewport height so it is a the bottom. - // - Headers that are in the initial viewport will never stick, `stickStartPoint` - // will be negative. - // - From 0 to `stickStartPoint` no translation. This will cause the header - // to scroll normally until it reaches the top of the scroll view. - // - From `stickStartPoint` to when the next header y hits the bottom edge of the header: translate - // equally to scroll. This will cause the header to stay at the top of the scroll view. - // - Past the collision with the next header y: no more translation. This will cause the - // header to continue scrolling up and make room for the next sticky header. - // In the case that there is no next header just translate equally to - // scroll indefinitely. - if (scrollViewHeight != null) { - const stickStartPoint = layoutY + layoutHeight - scrollViewHeight; - if (stickStartPoint > 0) { - inputRange.push(stickStartPoint); - outputRange.push(0); - inputRange.push(stickStartPoint + 1); - outputRange.push(1); - // If the next sticky header has not loaded yet (probably windowing) or is the last - // we can just keep it sticked forever. - const collisionPoint = - (nextHeaderLayoutY || 0) - layoutHeight - scrollViewHeight; - if (collisionPoint > stickStartPoint) { - inputRange.push(collisionPoint, collisionPoint + 1); - outputRange.push( - collisionPoint - stickStartPoint, - collisionPoint - stickStartPoint, - ); + // Fabric Detection + // eslint-disable-next-line dot-notation + const isFabric = !!( + this._ref && this._ref['_internalInstanceHandle']?.stateNode?.canonical + ); + + // Initially and in the case of updated props or layout, we + // recreate this interpolated value. Otherwise, we do not recreate + // when there are state changes. + if (this._shouldRecreateTranslateY) { + const {inverted, scrollViewHeight} = this.props; + const {measured, layoutHeight, layoutY, nextHeaderLayoutY} = this.state; + const inputRange: Array = [-1, 0]; + const outputRange: Array = [0, 0]; + + if (measured) { + if (inverted) { + // The interpolation looks like: + // - Negative scroll: no translation + // - `stickStartPoint` is the point at which the header will start sticking. + // It is calculated using the ScrollView viewport height so it is a the bottom. + // - Headers that are in the initial viewport will never stick, `stickStartPoint` + // will be negative. + // - From 0 to `stickStartPoint` no translation. This will cause the header + // to scroll normally until it reaches the top of the scroll view. + // - From `stickStartPoint` to when the next header y hits the bottom edge of the header: translate + // equally to scroll. This will cause the header to stay at the top of the scroll view. + // - Past the collision with the next header y: no more translation. This will cause the + // header to continue scrolling up and make room for the next sticky header. + // In the case that there is no next header just translate equally to + // scroll indefinitely. + if (scrollViewHeight != null) { + const stickStartPoint = layoutY + layoutHeight - scrollViewHeight; + if (stickStartPoint > 0) { + inputRange.push(stickStartPoint); + outputRange.push(0); + inputRange.push(stickStartPoint + 1); + outputRange.push(1); + // If the next sticky header has not loaded yet (probably windowing) or is the last + // we can just keep it sticked forever. + const collisionPoint = + (nextHeaderLayoutY || 0) - layoutHeight - scrollViewHeight; + if (collisionPoint > stickStartPoint) { + inputRange.push(collisionPoint, collisionPoint + 1); + outputRange.push( + collisionPoint - stickStartPoint, + collisionPoint - stickStartPoint, + ); + } } } - } - } else { - // The interpolation looks like: - // - Negative scroll: no translation - // - From 0 to the y of the header: no translation. This will cause the header - // to scroll normally until it reaches the top of the scroll view. - // - From header y to when the next header y hits the bottom edge of the header: translate - // equally to scroll. This will cause the header to stay at the top of the scroll view. - // - Past the collision with the next header y: no more translation. This will cause the - // header to continue scrolling up and make room for the next sticky header. - // In the case that there is no next header just translate equally to - // scroll indefinitely. - inputRange.push(layoutY); - outputRange.push(0); - // If the next sticky header has not loaded yet (probably windowing) or is the last - // we can just keep it sticked forever. - const collisionPoint = (nextHeaderLayoutY || 0) - layoutHeight; - if (collisionPoint >= layoutY) { - inputRange.push(collisionPoint, collisionPoint + 1); - outputRange.push(collisionPoint - layoutY, collisionPoint - layoutY); } else { - inputRange.push(layoutY + 1); - outputRange.push(1); + // The interpolation looks like: + // - Negative scroll: no translation + // - From 0 to the y of the header: no translation. This will cause the header + // to scroll normally until it reaches the top of the scroll view. + // - From header y to when the next header y hits the bottom edge of the header: translate + // equally to scroll. This will cause the header to stay at the top of the scroll view. + // - Past the collision with the next header y: no more translation. This will cause the + // header to continue scrolling up and make room for the next sticky header. + // In the case that there is no next header just translate equally to + // scroll indefinitely. + inputRange.push(layoutY); + outputRange.push(0); + // If the next sticky header has not loaded yet (probably windowing) or is the last + // we can just keep it sticked forever. + const collisionPoint = (nextHeaderLayoutY || 0) - layoutHeight; + if (collisionPoint >= layoutY) { + inputRange.push(collisionPoint, collisionPoint + 1); + outputRange.push( + collisionPoint - layoutY, + collisionPoint - layoutY, + ); + } else { + inputRange.push(layoutY + 1); + outputRange.push(1); + } } } + + this.updateTranslateListener( + this.props.scrollAnimatedValue.interpolate({ + inputRange, + outputRange, + }), + isFabric, + ); } - const translateY = this.props.scrollAnimatedValue.interpolate({ - inputRange, - outputRange, - }); const child = React.Children.only(this.props.children); + // TODO T68319535: remove this if NativeAnimated is rewritten for Fabric + const passthroughAnimatedPropExplicitValues = + isFabric && this.state.translateY != null + ? { + style: {transform: [{translateY: this.state.translateY}]}, + } + : null; + return ( + ref={this._setComponentRef} + style={[ + child.props.style, + styles.header, + {transform: [{translateY: this._translateY}]}, + ]} + passthroughAnimatedPropExplicitValues={ + passthroughAnimatedPropExplicitValues + }> {React.cloneElement(child, { style: styles.fill, // We transfer the child style to the wrapper. onLayout: undefined, // we call this manually through our this._onLayout diff --git a/Libraries/Core/NativeExceptionsManager.js b/Libraries/Core/NativeExceptionsManager.js index db982abc037c6f..b36eb0cd74370b 100644 --- a/Libraries/Core/NativeExceptionsManager.js +++ b/Libraries/Core/NativeExceptionsManager.js @@ -47,7 +47,6 @@ export interface Spec extends TurboModule { stack: Array, exceptionId: number, ) => void; - // TODO(T53311281): This is a noop on iOS now. Implement it. +reportException?: (data: ExceptionData) => void; +updateExceptionMessage: ( message: string, diff --git a/Libraries/Linking/NativeLinking.js b/Libraries/Linking/NativeLinking.js index a73a19fe8fb878..18b3e7128cd570 100644 --- a/Libraries/Linking/NativeLinking.js +++ b/Libraries/Linking/NativeLinking.js @@ -26,7 +26,7 @@ export interface Spec extends TurboModule { action: string, extras: ?Array<{ key: string, - value: string | number | boolean, + value: string | number | boolean, // TODO(T67672788): Union types are not type safe ... }>, ) => Promise; diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index dcc524da53ca06..9eeb2f2a1dead7 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -1175,9 +1175,10 @@ class VirtualizedList extends React.PureComponent { this.context == null ) { // TODO (T46547044): use React.warn once 16.9 is sync'd: https://github.com/facebook/react/pull/15170 - console.warn( + console.error( 'VirtualizedLists should never be nested inside plain ScrollViews with the same ' + - 'orientation - use another VirtualizedList-backed container instead.', + 'orientation because it can break windowing and other functionality - use another ' + + 'VirtualizedList-backed container instead.', ); this._hasWarned.nesting = true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index c1247471729ba2..80c0163ebbe5e2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -431,6 +431,23 @@ private void updateRootLayoutSpecs(final int widthMeasureSpec, final int heightM @ThreadConfined(UI) public void unmountReactApplication() { UiThreadUtil.assertOnUiThread(); + // Stop surface in Fabric. + // Calling FabricUIManager.stopSurface causes the C++ Binding.stopSurface + // to be called synchronously over the JNI, which causes an empty tree + // to be committed via the Scheduler, which will cause mounting instructions + // to be queued up and synchronously executed to delete and remove + // all the views in the hierarchy. + if (mReactInstanceManager != null) { + final ReactContext reactApplicationContext = mReactInstanceManager.getCurrentReactContext(); + if (reactApplicationContext != null && getUIManagerType() == FABRIC) { + @Nullable + UIManager uiManager = + UIManagerHelper.getUIManager(reactApplicationContext, getUIManagerType()); + if (uiManager != null) { + uiManager.stopSurface(this.getId()); + } + } + } if (mReactInstanceManager != null && mIsAttachedToInstance) { mReactInstanceManager.detachRootView(this); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java index 4edebdcf00ea54..1707c623edbbc8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -32,6 +32,13 @@ int startSurface( int widthMeasureSpec, int heightMeasureSpec); + /** + * Stop a surface from running in JS and clears up native memory usage. Assumes that the native + * View hierarchy has already been cleaned up. Fabric-only. + */ + @AnyThread + void stopSurface(final int surfaceId); + /** * Updates the layout specs of the RootShadowNode based on the Measure specs received by * parameters. @@ -106,4 +113,14 @@ int startSurface( * @param listener */ void removeUIManagerEventListener(UIManagerListener listener); + + /** + * This method dispatches events from RN Android code to JS. The delivery of this event will not + * be queued in EventDispatcher class. + * + * @param reactTag tag + * @param eventName name of the event + * @param event parameters + */ + void receiveEvent(int reactTag, String eventName, @Nullable WritableMap event); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 4c863b702096f8..e644fe69e3c256 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -134,13 +134,19 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { @GuardedBy("mPreMountItemsLock") @NonNull - private ArrayDeque mPreMountItems = + private ArrayDeque mPreMountItems = new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY); @ThreadConfined(UI) @NonNull private final DispatchUIFrameCallback mDispatchUIFrameCallback; + @ThreadConfined(UI) + private int mLastExecutedMountItemSurfaceId = -1; + + @ThreadConfined(UI) + private boolean mLastExecutedMountItemSurfaceIdActive = false; + /** * This is used to keep track of whether or not the FabricUIManager has been destroyed. Once the * Catalyst instance is being destroyed, we should cease all operation here. @@ -242,6 +248,7 @@ public void onRequestEventBeat() { @AnyThread @ThreadConfined(ANY) + @Override public void stopSurface(int surfaceID) { mBinding.stopSurface(surfaceID); mReactContextForRootTag.remove(surfaceID); @@ -673,9 +680,9 @@ private List getAndResetMountItems() { } } - private ArrayDeque getAndResetPreMountItems() { + private ArrayDeque getAndResetPreMountItems() { synchronized (mPreMountItemsLock) { - ArrayDeque result = mPreMountItems; + ArrayDeque result = mPreMountItems; if (result.isEmpty()) { return null; } @@ -684,6 +691,38 @@ private ArrayDeque getAndResetPreMountItems() { } } + /** + * Check if a surfaceId is active and ready for MountItems to be executed against it. It is safe + * and cheap to call this repeatedly because we expect many operations to be batched with the same + * surfaceId in a row and we memoize the parameters and results. + * + * @param surfaceId + * @param context + * @return + */ + @UiThread + @ThreadConfined(UI) + private boolean surfaceActiveForExecution(int surfaceId, String context) { + if (mLastExecutedMountItemSurfaceId != surfaceId) { + mLastExecutedMountItemSurfaceId = surfaceId; + mLastExecutedMountItemSurfaceIdActive = mReactContextForRootTag.get(surfaceId) != null; + + // If there are many MountItems with the same SurfaceId, we only + // log a warning for the first one that is skipped. + if (!mLastExecutedMountItemSurfaceIdActive) { + ReactSoftException.logSoftException( + TAG, + new ReactNoCrashSoftException( + "dispatchMountItems: skipping " + + context + + ", because surface not available: " + + surfaceId)); + } + } + + return mLastExecutedMountItemSurfaceIdActive; + } + @UiThread @ThreadConfined(UI) /** Nothing should call this directly except for `tryDispatchMountItems`. */ @@ -755,7 +794,7 @@ private boolean dispatchMountItems() { // If there are MountItems to dispatch, we make sure all the "pre mount items" are executed // first - ArrayDeque mPreMountItemsToDispatch = getAndResetPreMountItems(); + ArrayDeque mPreMountItemsToDispatch = getAndResetPreMountItems(); if (mPreMountItemsToDispatch != null) { Systrace.beginSection( @@ -764,7 +803,11 @@ private boolean dispatchMountItems() { + mPreMountItemsToDispatch.size()); while (!mPreMountItemsToDispatch.isEmpty()) { - mPreMountItemsToDispatch.pollFirst().execute(mMountingManager); + PreAllocateViewMountItem mountItem = mPreMountItemsToDispatch.pollFirst(); + if (surfaceActiveForExecution( + mountItem.getRootTag(), "dispatchMountItems PreAllocateViewMountItem")) { + mountItem.execute(mMountingManager); + } } Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); @@ -791,14 +834,8 @@ private boolean dispatchMountItems() { // TODO T68118357: clean up this logic and simplify this method overall if (mountItem instanceof BatchMountItem) { BatchMountItem batchMountItem = (BatchMountItem) mountItem; - int rootTag = batchMountItem.getRootTag(); - if (mReactContextForRootTag.get(rootTag) == null) { - ReactSoftException.logSoftException( - TAG, - new ReactNoCrashSoftException( - "dispatchMountItems: skipping batched item: surface not available [" - + rootTag - + "]")); + if (!surfaceActiveForExecution( + batchMountItem.getRootTag(), "dispatchMountItems BatchMountItem")) { continue; } } @@ -854,15 +891,18 @@ private void dispatchPreMountItems(long frameTimeNanos) { break; } - MountItem preMountItemsToDispatch; + PreAllocateViewMountItem preMountItemToDispatch; synchronized (mPreMountItemsLock) { if (mPreMountItems.isEmpty()) { break; } - preMountItemsToDispatch = mPreMountItems.pollFirst(); + preMountItemToDispatch = mPreMountItems.pollFirst(); } - preMountItemsToDispatch.execute(mMountingManager); + if (surfaceActiveForExecution( + preMountItemToDispatch.getRootTag(), "dispatchPreMountItems")) { + preMountItemToDispatch.execute(mMountingManager); + } } } finally { mInDispatch = false; @@ -906,6 +946,7 @@ public void updateRootLayoutSpecs( doLeftAndRightSwapInRTL); } + @Override public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { EventEmitterWrapper eventEmitter = mMountingManager.getEventEmitter(reactTag); if (eventEmitter == null) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java index 85034aa043247e..3ab5b864cca193 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java @@ -81,7 +81,7 @@ public String toString() { if (s.length() > 0) { s.append("\n"); } - s.append("BatchMountItem (") + s.append("BatchMountItem [S:" + mRootTag + "] (") .append(i + 1) .append("/") .append(mSize) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java index fad513a9ea0b67..7f519e9bc6f2f2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java @@ -47,6 +47,10 @@ public PreAllocateViewMountItem( mIsLayoutable = isLayoutable; } + public int getRootTag() { + return mRootTag; + } + @Override public void execute(@NonNull MountingManager mountingManager) { if (ENABLE_FABRIC_LOGS) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java index 912fcbcc756969..22b82bed0ca1d5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java @@ -31,7 +31,10 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.RCTEventEmitter; import java.util.HashMap; @@ -42,6 +45,7 @@ public class ReactAccessibilityDelegate extends AccessibilityDelegateCompat { private static final String TAG = "ReactAccessibilityDelegate"; + public static final String TOP_ACCESSIBILITY_ACTION_EVENT = "topAccessibilityAction"; private static int sCounter = 0x3f000000; private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200; private static final int SEND_EVENT = 1; @@ -281,9 +285,24 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) { event.putString("actionName", mAccessibilityActionsMap.get(action)); ReactContext reactContext = (ReactContext) host.getContext(); if (reactContext.hasActiveCatalystInstance()) { - reactContext - .getJSModule(RCTEventEmitter.class) - .receiveEvent(host.getId(), "topAccessibilityAction", event); + final int reactTag = host.getId(); + UIManager uiManager = UIManagerHelper.getUIManager(reactContext, reactTag); + if (uiManager != null) { + uiManager + .getEventDispatcher() + .dispatchEvent( + new Event(reactTag) { + @Override + public String getEventName() { + return TOP_ACCESSIBILITY_ACTION_EVENT; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(reactTag, TOP_ACCESSIBILITY_ACTION_EVENT, event); + } + }); + } } else { ReactSoftException.logSoftException( TAG, new ReactNoCrashSoftException("Cannot get RCTEventEmitter, no CatalystInstance")); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 1f51e272ac9179..52750844ed8d26 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -437,6 +437,11 @@ public int startSurface( throw new UnsupportedOperationException(); } + @Override + public void stopSurface(final int surfaceId) { + throw new UnsupportedOperationException(); + } + /** Unregisters a new root view. */ @ReactMethod public void removeRootView(int rootViewTag) { @@ -945,4 +950,11 @@ public View resolveView(int tag) { .getNativeViewHierarchyManager() .resolveView(tag); } + + @Override + public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event) { + getReactApplicationContext() + .getJSModule(RCTEventEmitter.class) + .receiveEvent(targetTag, eventName, event); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 3d7988087ffaeb..f7a0a18995fe75 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -37,7 +37,6 @@ import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; -import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.react.views.view.ReactViewBackgroundManager; import java.util.ArrayList; import java.util.Collections; @@ -100,8 +99,9 @@ private ReactContext getReactContext() { protected void onLayout( boolean changed, int textViewLeft, int textViewTop, int textViewRight, int textViewBottom) { // TODO T62882314: Delete this method when Fabric is fully released in OSS + int reactTag = getId(); if (!(getText() instanceof Spanned) - || ViewUtil.getUIManagerType(getId()) == UIManagerType.FABRIC) { + || ViewUtil.getUIManagerType(reactTag) == UIManagerType.FABRIC) { /** * In general, {@link #setText} is called via {@link ReactTextViewManager#updateExtraData} * before we are laid out. This ordering is a requirement because we utilize the data from @@ -257,9 +257,9 @@ public int compare(Object o1, Object o2) { WritableMap event = Arguments.createMap(); event.putArray("inlineViews", inlineViewInfoArray2); - reactContext - .getJSModule(RCTEventEmitter.class) - .receiveEvent(getId(), "topInlineViewLayout", event); + if (uiManager != null) { + uiManager.receiveEvent(reactTag, "topInlineViewLayout", event); + } } } diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 0db1caf4366450..9fffd1a414cc2a 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -84,6 +84,7 @@ rn_xplat_cxx_library( CXXREACT_PUBLIC_HEADERS = [ "CxxNativeModule.h", + "ErrorUtils.h", "Instance.h", "JSBundleType.h", "JSDeltaBundleClient.h", diff --git a/ReactCommon/cxxreact/ErrorUtils.h b/ReactCommon/cxxreact/ErrorUtils.h new file mode 100644 index 00000000000000..aebd3918629c3c --- /dev/null +++ b/ReactCommon/cxxreact/ErrorUtils.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + +inline static void handleJSError( + jsi::Runtime &runtime, + const jsi::JSError &error) { + auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils"); + if (errorUtils.isUndefined() || !errorUtils.isObject() || + !errorUtils.getObject(runtime).hasProperty(runtime, "reportFatalError")) { + // ErrorUtils was not set up. This probably means the bundle didn't + // load properly. + throw jsi::JSError( + runtime, + "ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " + + error.getMessage(), + error.getStack()); + } + // TODO(janzer): Rewrite this function to return the processed error + // instead of just reporting it through the native module + auto func = errorUtils.asObject(runtime).getPropertyAsFunction( + runtime, "reportFatalError"); + func.call(runtime, error.value(), jsi::Value(true)); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/cxxreact/NativeToJsBridge.cpp b/ReactCommon/cxxreact/NativeToJsBridge.cpp index 58e99c9922152e..2729997c7cb1ec 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.cpp +++ b/ReactCommon/cxxreact/NativeToJsBridge.cpp @@ -14,6 +14,7 @@ #include #include +#include "ErrorUtils.h" #include "Instance.h" #include "JSBigString.h" #include "MessageQueueThread.h" @@ -26,6 +27,8 @@ #ifdef WITH_FBSYSTRACE #include +#include + using fbsystrace::FbSystraceAsyncFlow; #endif @@ -338,37 +341,23 @@ std::shared_ptr NativeToJsBridge::getDecoratedNativeCallInvoker( } RuntimeExecutor NativeToJsBridge::getRuntimeExecutor() { - auto runtimeExecutor = [this, isDestroyed = m_destroyed]( - std::function - &&callback) { - if (*isDestroyed) { - return; - } - runOnExecutorQueue([callback = std::move(callback)](JSExecutor *executor) { - jsi::Runtime *runtime = (jsi::Runtime *)executor->getJavaScriptContext(); - try { - callback(*runtime); - } catch (jsi::JSError &originalError) { - auto errorUtils = runtime->global().getProperty(*runtime, "ErrorUtils"); - if (errorUtils.isUndefined() || !errorUtils.isObject() || - !errorUtils.getObject(*runtime).hasProperty( - *runtime, "reportFatalError")) { - // ErrorUtils was not set up. This probably means the bundle didn't - // load properly. - throw jsi::JSError( - *runtime, - "ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " + - originalError.getMessage(), - originalError.getStack()); + auto runtimeExecutor = + [this, isDestroyed = m_destroyed]( + std::function &&callback) { + if (*isDestroyed) { + return; } - // TODO(janzer): Rewrite this function to return the processed error - // instead of just reporting it through the native module - auto func = errorUtils.asObject(*runtime).getPropertyAsFunction( - *runtime, "reportFatalError"); - func.call(*runtime, originalError.value(), jsi::Value(true)); - } - }); - }; + runOnExecutorQueue( + [callback = std::move(callback)](JSExecutor *executor) { + jsi::Runtime *runtime = + (jsi::Runtime *)executor->getJavaScriptContext(); + try { + callback(*runtime); + } catch (jsi::JSError &originalError) { + handleJSError(*runtime, originalError); + } + }); + }; return runtimeExecutor; } diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 17fb0d7df9c09d..8706592132f3f0 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -235,7 +235,7 @@ export type FunctionTypeAnnotationReturnArrayElementType = FunctionTypeAnnotatio export type ObjectParamTypeAnnotation = $ReadOnly<{| optional: boolean, name: string, - typeAnnotation: FunctionTypeAnnotationParamTypeAnnotation, + typeAnnotation?: FunctionTypeAnnotationParamTypeAnnotation, // TODO (T67898313): Workaround for NativeLinking's use of union type, typeAnnotations should not be optional |}>; export type FunctionTypeAnnotationReturn = diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js index 474927f779940e..f5502f2a839f6a 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js @@ -181,6 +181,31 @@ function translatePrimitiveJSTypeToObjCTypeForReturn( } } +function handleArrayOfObjects( + objectForGeneratingStructs: Array, + param: FunctionTypeAnnotationParam, + name: string, +) { + if ( + param.typeAnnotation.type === 'ArrayTypeAnnotation' && + param.typeAnnotation.elementType && + param.typeAnnotation.elementType.type === 'ObjectTypeAnnotation' && + param.typeAnnotation.elementType.properties + ) { + const {elementType} = param.typeAnnotation; + const {properties} = elementType; + if (properties && properties.length > 0) { + objectForGeneratingStructs.push({ + name, + object: { + type: 'ObjectTypeAnnotation', + properties, + }, + }); + } + } +} + const methodImplementationTemplate = '- (::_RETURN_VALUE_::) ::_PROPERTY_NAME_::::_ARGS_::;'; @@ -232,12 +257,31 @@ module.exports = { }, }); paramObjCType = `JS::Native::_MODULE_NAME_::::Spec${variableName}&`; + + param.typeAnnotation.properties.map(aProp => + handleArrayOfObjects( + objectForGeneratingStructs, + aProp, + capitalizeFirstLetter(prop.name) + + capitalizeFirstLetter(param.name) + + capitalizeFirstLetter(aProp.name) + + 'Element', + ), + ); } else { paramObjCType = translatePrimitiveJSTypeToObjCType( param, typeName => `Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`, ); + + handleArrayOfObjects( + objectForGeneratingStructs, + param, + capitalizeFirstLetter(prop.name) + + capitalizeFirstLetter(param.name) + + 'Element', + ); } return `${i === 0 ? '' : param.name}:(${paramObjCType})${ param.name @@ -285,7 +329,7 @@ module.exports = { return protocolTemplate .replace( /::_STRUCTS_::/g, - translateObjectsForStructs(objectForGeneratingStructs), + translateObjectsForStructs(objectForGeneratingStructs, name), ) .replace(/::_MODULE_PROPERTIES_::/g, implementations) .replace(/::_MODULE_NAME_::/g, name) diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js index f6ff56e544cdca..a90d98a3be8671 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js @@ -60,8 +60,17 @@ function getNamespacedStructName(structName: string, propertyName: string) { function getElementTypeForArray( property: ObjectParamTypeAnnotation, name: string, + moduleName: string, ): string { const {typeAnnotation} = property; + + // TODO(T67898313): Workaround for NativeLinking's use of union type. This check may be removed once typeAnnotation is non-optional. + if (!typeAnnotation) { + throw new Error( + `Cannot get array element type, property ${property.name} does not contain a type annotation`, + ); + } + if (typeAnnotation.type !== 'ArrayTypeAnnotation') { throw new Error( `Cannot get array element type for non-array type ${typeAnnotation.type}`, @@ -82,9 +91,12 @@ function getElementTypeForArray( case 'Int32TypeAnnotation': return 'double'; case 'ObjectTypeAnnotation': - return getNamespacedStructName(name, property.name); + return getNamespacedStructName(name, property.name) + 'Element'; case 'GenericObjectTypeAnnotation': - // TODO T67565166: Generic objects are not type safe and should be disallowed in the schema. + // TODO(T67565166): Generic objects are not type safe and should be disallowed in the schema. This case should throw an error once it is disallowed in schema. + console.error( + `Warning: Generic objects are not type safe and should be avoided whenever possible (see '${property.name}' in ${moduleName}'s ${name})`, + ); return 'id'; case 'BooleanTypeAnnotation': case 'AnyObjectTypeAnnotation': @@ -104,6 +116,7 @@ function getElementTypeForArray( function getInlineMethodSignature( property: ObjectParamTypeAnnotation, name: string, + moduleName: string, ): string { const {typeAnnotation} = property; function markOptionalTypeIfNecessary(type: string) { @@ -112,6 +125,15 @@ function getInlineMethodSignature( } return type; } + + // TODO(T67672788): Workaround for values key in NativeLinking which lacks a typeAnnotation. id is not type safe! + if (!typeAnnotation) { + console.error( + `Warning: Unsafe type found (see '${property.name}' in ${moduleName}'s ${name})`, + ); + return `id ${getSafePropertyName(property.name)}() const;`; + } + switch (typeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': switch (typeAnnotation.name) { @@ -149,6 +171,7 @@ function getInlineMethodSignature( `facebook::react::LazyVector<${getElementTypeForArray( property, name, + moduleName, )}>`, )} ${getSafePropertyName(property.name)}() const;`; case 'FunctionTypeAnnotation': @@ -160,6 +183,7 @@ function getInlineMethodSignature( function getInlineMethodImplementation( property: ObjectParamTypeAnnotation, name: string, + moduleName: string, ): string { const {typeAnnotation} = property; function markOptionalTypeIfNecessary(type: string): string { @@ -175,6 +199,13 @@ function getInlineMethodImplementation( return `RCTBridgingTo${capitalizeFirstLetter(value)}`; } function bridgeArrayElementValueIfNecessary(element: string): string { + // TODO(T67898313): Workaround for NativeLinking's use of union type + if (!typeAnnotation) { + throw new Error( + `Cannot get array element type, property ${property.name} does not contain a type annotation`, + ); + } + if (typeAnnotation.type !== 'ArrayTypeAnnotation') { throw new Error( `Cannot get array element type for non-array type ${typeAnnotation.type}`, @@ -197,7 +228,10 @@ function getInlineMethodImplementation( case 'BooleanTypeAnnotation': return `RCTBridgingToBool(${element})`; case 'ObjectTypeAnnotation': - return `${getNamespacedStructName(name, property.name)}(${element})`; + return `${getNamespacedStructName( + name, + property.name, + )}Element(${element})`; case 'GenericObjectTypeAnnotation': return element; case 'AnyObjectTypeAnnotation': @@ -215,6 +249,19 @@ function getInlineMethodImplementation( } } + // TODO(T67672788): Workaround for values key in NativeLinking which lacks a typeAnnotation. id is not type safe! + if (!typeAnnotation) { + console.error( + `Warning: Unsafe type found (see '${property.name}' in ${moduleName}'s ${name})`, + ); + return inlineTemplate + .replace( + /::_RETURN_TYPE_::/, + property.optional ? 'id _Nullable ' : 'id ', + ) + .replace(/::_RETURN_VALUE_::/, 'p'); + } + switch (typeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': switch (typeAnnotation.name) { @@ -279,6 +326,7 @@ function getInlineMethodImplementation( `facebook::react::LazyVector<${getElementTypeForArray( property, name, + moduleName, )}>`, ), ) @@ -287,6 +335,7 @@ function getInlineMethodImplementation( `${markOptionalValueIfNecessary('vec')}(p, ^${getElementTypeForArray( property, name, + moduleName, )}(id itemValue_0) { return ${bridgeArrayElementValueIfNecessary( 'itemValue_0', )}; })`, @@ -307,6 +356,7 @@ function translateObjectsForStructs( |}>, |}>, >, + moduleName: string, ): string { const flattenObjects = flatObjects(annotations); @@ -315,7 +365,7 @@ function translateObjectsForStructs( (acc, object) => acc.concat( object.properties.map(property => - getInlineMethodImplementation(property, object.name) + getInlineMethodImplementation(property, object.name, moduleName) .replace( /::_PROPERTY_NAME_::/g, getSafePropertyName(property.name), @@ -333,7 +383,9 @@ function translateObjectsForStructs( .replace( /::_STRUCT_PROPERTIES_::/g, object.properties - .map(property => getInlineMethodSignature(property, object.name)) + .map(property => + getInlineMethodSignature(property, object.name, moduleName), + ) .join('\n '), ) .replace(/::_STRUCT_NAME_::/g, object.name), diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js index adf71dff20d49f..d43ab794e7c5cf 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js @@ -62,6 +62,14 @@ function getBuilderInputFieldDeclaration( return 'folly::Optional<' + annotation + '> ' + property.name + ';'; } const {typeAnnotation} = property; + + // TODO(T67898313): Workaround for NativeLinking's use of union type. This check may be removed once typeAnnotation is non-optional. + if (!typeAnnotation) { + throw new Error( + `Cannot get array element type, property ${property.name} does not contain a type annotation`, + ); + } + switch (typeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': switch (typeAnnotation.name) { @@ -153,6 +161,14 @@ function unsafeGetter(name: string, optional: boolean) { function getObjectProperty(property: ObjectParamTypeAnnotation): string { const {typeAnnotation} = property; + + // TODO(T67898313): Workaround for NativeLinking's use of union type. This check may be removed once typeAnnotation is non-optional. + if (!typeAnnotation) { + throw new Error( + `Cannot get array element type, property ${property.name} does not contain a type annotation`, + ); + } + switch (typeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': switch (typeAnnotation.name) { diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js index ee0f928690895a..df7bff1abb36f5 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js @@ -66,6 +66,7 @@ function flatObjects( objectTypesToFlatten = objectTypesToFlatten.concat( properties.reduce((acc, curr) => { if ( + curr.typeAnnotation && curr.typeAnnotation.type === 'ObjectTypeAnnotation' && curr.typeAnnotation.properties ) { diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 110476acc5f7f1..3739531ef48032 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -601,6 +601,10 @@ const COMPLEX_OBJECTS: SchemaType = { type: 'StringTypeAnnotation', }, }, + { + optional: false, + name: 'value', + }, ], }, }, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js index ef865d1ef80c8c..4791308f586b89 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js @@ -23,7 +23,7 @@ describe('GenerateStructs', () => { it(`can generate fixture ${fixtureName}`, () => { expect( generator - .translateObjectsForStructs(fixture) + .translateObjectsForStructs(fixture, fixtureName) .replace(/::_MODULE_NAME_::/g, 'SampleTurboModule'), ).toMatchSnapshot(); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index 83c96819594ef0..0d92adac32c8aa 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -72,6 +72,23 @@ namespace JS { @end +namespace JS { + namespace NativeSampleTurboModule { + struct SpecGetArraysOptionsArrayOfObjectsElement { + double numberProperty() const; + + SpecGetArraysOptionsArrayOfObjectsElement(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecGetArraysOptionsArrayOfObjectsElement) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecGetArraysOptionsArrayOfObjectsElement:(id)json; +@end + + namespace JS { namespace NativeSampleTurboModule { struct SpecGetArraysOptions { @@ -79,7 +96,7 @@ namespace JS { folly::Optional> optionalArrayOfNumbers() const; facebook::react::LazyVector arrayOfStrings() const; folly::Optional> optionalArrayOfStrings() const; - facebook::react::LazyVector arrayOfObjects() const; + facebook::react::LazyVector arrayOfObjects() const; SpecGetArraysOptions(NSDictionary *const v) : _v(v) {} private: @@ -93,6 +110,24 @@ namespace JS { @end +namespace JS { + namespace NativeSampleTurboModule { + struct SpecOptionalMethodExtrasElement { + NSString *key() const; + id value() const; + + SpecOptionalMethodExtrasElement(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecOptionalMethodExtrasElement) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecOptionalMethodExtrasElement:(id)json; +@end + + namespace JS { namespace NativeSampleTurboModule { struct SpecOptionalsA { @@ -228,6 +263,20 @@ inline folly::Optional JS::NativeSampleTurboModule::SpecOptionalsA::option } +inline NSString *JS::NativeSampleTurboModule::SpecOptionalMethodExtrasElement::key() const +{ + id const p = _v[@\\"key\\"]; + return RCTBridgingToString(p); +} + + +inline id JS::NativeSampleTurboModule::SpecOptionalMethodExtrasElement::value() const +{ + id const p = _v[@\\"value\\"]; + return p; +} + + inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfNumbers() const { id const p = _v[@\\"arrayOfNumbers\\"]; @@ -256,10 +305,17 @@ inline folly::Optional> JS::NativeSample } -inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfObjects() const +inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfObjects() const { id const p = _v[@\\"arrayOfObjects\\"]; - return RCTBridgingToVec(p, ^JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjects(id itemValue_0) { return JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjects(itemValue_0); }); + return RCTBridgingToVec(p, ^JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjectsElement(id itemValue_0) { return JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjectsElement(itemValue_0); }); +} + + +inline double JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjectsElement::numberProperty() const +{ + id const p = _v[@\\"numberProperty\\"]; + return RCTBridgingToDouble(p); }