diff --git a/.circleci/config.yml b/.circleci/config.yml index 49c0c6603f40..a9ed245dc5c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -611,11 +611,11 @@ jobs: default: false environment: - ANDROID_HOME: "C:\\Android\\android-sdk" - - ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\20.1.5948944" + - ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\19.2.5345600" - ANDROID_BUILD_VERSION: 28 - ANDROID_TOOLS_VERSION: 29.0.3 - GRADLE_OPTS: -Dorg.gradle.daemon=false - - NDK_VERSION: 20.1.5948944 + - NDK_VERSION: 19.2.5345600 steps: - checkout @@ -781,34 +781,30 @@ workflows: run_unit_tests: true requires: - setup_ios - # DISABLED: USE_FRAMEWORKS=1 not supported by Flipper - # - test_ios: - # name: test_ios_unit_frameworks_jsc - # use_frameworks: true - # run_unit_tests: true - # requires: - # - setup_ios + - test_ios: + name: test_ios_unit_frameworks_jsc + use_frameworks: true + run_unit_tests: true + requires: + - setup_ios - test_ios: name: test_ios_unit_hermes use_hermes: true run_unit_tests: true requires: - setup_ios - # DISABLED: USE_FRAMEWORKS=1 not supported by Flipper - # - test_ios: - # name: test_ios_unit_frameworks_hermes - # use_hermes: true - # use_frameworks: true - # run_unit_tests: true - # requires: - # - setup_ios - # DISABLED: Detox tests need to be fixed + - test_ios: + name: test_ios_unit_frameworks_hermes + use_hermes: true + use_frameworks: true + run_unit_tests: true + requires: + - setup_ios # - test_ios: # name: test_ios_detox # run_detox_tests: true # requires: # - setup_ios - # DISABLED: USE_FRAMEWORKS=1 not supported by Flipper # - test_ios: # name: test_ios_detox_frameworks # use_frameworks: true diff --git a/Libraries/Animated/components/AnimatedImage.js b/Libraries/Animated/components/AnimatedImage.js index 5aae7d900564..56cdb7d18f79 100644 --- a/Libraries/Animated/components/AnimatedImage.js +++ b/Libraries/Animated/components/AnimatedImage.js @@ -17,9 +17,9 @@ const createAnimatedComponent = require('../createAnimatedComponent'); import type {AnimatedComponentType} from '../createAnimatedComponent'; -module.exports = (createAnimatedComponent((Image: $FlowFixMe), { - collapsable: false, -}): AnimatedComponentType< +module.exports = (createAnimatedComponent( + (Image: $FlowFixMe), +): AnimatedComponentType< React.ElementConfig, React.ElementRef, >); diff --git a/Libraries/Animated/components/AnimatedScrollView.js b/Libraries/Animated/components/AnimatedScrollView.js index bfa75b7167b6..259cbb81b0ee 100644 --- a/Libraries/Animated/components/AnimatedScrollView.js +++ b/Libraries/Animated/components/AnimatedScrollView.js @@ -24,9 +24,9 @@ const ScrollViewWithEventThrottle = React.forwardRef((props, ref) => ( )); -module.exports = (createAnimatedComponent(ScrollViewWithEventThrottle, { - collapsable: false, -}): AnimatedComponentType< +module.exports = (createAnimatedComponent( + ScrollViewWithEventThrottle, +): AnimatedComponentType< React.ElementConfig, React.ElementRef, >); diff --git a/Libraries/Animated/components/AnimatedText.js b/Libraries/Animated/components/AnimatedText.js index 7a940093aadd..5a184e0626fe 100644 --- a/Libraries/Animated/components/AnimatedText.js +++ b/Libraries/Animated/components/AnimatedText.js @@ -17,9 +17,9 @@ const createAnimatedComponent = require('../createAnimatedComponent'); import type {AnimatedComponentType} from '../createAnimatedComponent'; -module.exports = (createAnimatedComponent((Text: $FlowFixMe), { - collapsable: false, -}): AnimatedComponentType< +module.exports = (createAnimatedComponent( + (Text: $FlowFixMe), +): AnimatedComponentType< React.ElementConfig, React.ElementRef, >); diff --git a/Libraries/Animated/components/AnimatedView.js b/Libraries/Animated/components/AnimatedView.js index d2a87807b02b..0ce54601fe99 100644 --- a/Libraries/Animated/components/AnimatedView.js +++ b/Libraries/Animated/components/AnimatedView.js @@ -17,9 +17,7 @@ const createAnimatedComponent = require('../createAnimatedComponent'); import type {AnimatedComponentType} from '../createAnimatedComponent'; -module.exports = (createAnimatedComponent(View, { - collapsable: true, -}): AnimatedComponentType< +module.exports = (createAnimatedComponent(View): AnimatedComponentType< React.ElementConfig, React.ElementRef, >); diff --git a/Libraries/Animated/createAnimatedComponent.js b/Libraries/Animated/createAnimatedComponent.js index 8e8638edb63d..230aa1642036 100644 --- a/Libraries/Animated/createAnimatedComponent.js +++ b/Libraries/Animated/createAnimatedComponent.js @@ -37,13 +37,8 @@ export type AnimatedComponentType< Instance, >; -type AnimatedComponentOptions = { - collapsable?: boolean, -}; - function createAnimatedComponent( Component: React.AbstractComponent, - options?: AnimatedComponentOptions, ): AnimatedComponentType { invariant( typeof Component !== 'function' || @@ -84,9 +79,6 @@ function createAnimatedComponent( } _isFabric = (): boolean => { - // When called during the first render, `_component` is always null. - // Therefore, even if a component is rendered in Fabric, we can't detect - // that until ref is set, which happens sometime after the first render. if (this._component == null) { return false; } @@ -221,28 +213,23 @@ function createAnimatedComponent( const {style: passthruStyle = {}, ...passthruProps} = this.props.passthroughAnimatedPropExplicitValues || {}; const mergedStyle = {...style, ...passthruStyle}; - const forceNativeId = - props.collapsable ?? - (this._propsAnimated.__isNative || - this._isFabric() || - options?.collapsable === false); - // The native driver updates views directly through the UI thread so we - // have to make sure the view doesn't get optimized away because it cannot - // go through the NativeViewHierarchyManager since it operates on the shadow - // thread. TODO: T68258846 - const collapsableProps = forceNativeId - ? { - nativeID: props.nativeID ?? 'animatedComponent', - collapsable: false, - } - : {}; return ( ); } diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 280247634303..cc8a62a381ff 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -22,7 +22,7 @@ import invariant from 'invariant'; * * See https://reactnative.dev/docs/appstate.html */ -class AppState extends NativeEventEmitter<$FlowFixMe> { +class AppState extends NativeEventEmitter { _eventHandlers: Object; _supportedEvents = ['change', 'memoryWarning', 'blur', 'focus']; currentState: ?string; @@ -142,7 +142,7 @@ function throwMissingNativeModule() { ); } -class MissingNativeAppStateShim extends EventEmitter<$FlowFixMe> { +class MissingNativeAppStateShim extends EventEmitter { // AppState isAvailable: boolean = false; currentState: ?string = null; @@ -156,7 +156,7 @@ class MissingNativeAppStateShim extends EventEmitter<$FlowFixMe> { } // EventEmitter - addListener(): any { + addListener() { throwMissingNativeModule(); } diff --git a/Libraries/BUCK b/Libraries/BUCK index cef11f41005d..2f64ec0bbd5a 100644 --- a/Libraries/BUCK +++ b/Libraries/BUCK @@ -31,7 +31,6 @@ fb_native.genrule( rn_codegen_modules( name = "FBReactNativeSpec", - android_package_name = "com.facebook.fbreact.specs", library_labels = ["supermodule:xplat/default/public.react_native.infra"], native_module_spec_name = "FBReactNativeSpec", schema_target = ":react_native_codegen_schema", diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js index 8ae146272de6..9ce8427f0dca 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js @@ -211,11 +211,9 @@ const AccessibilityInfo = { if (eventName === 'change') { listener = RCTDeviceEventEmitter.addListener( CHANGE_EVENT_NAME.screenReaderChanged, - // $FlowFixMe[incompatible-call] handler, ); } else if (CHANGE_EVENT_NAME[eventName]) { - // $FlowFixMe[incompatible-call] listener = RCTDeviceEventEmitter.addListener(eventName, handler); } diff --git a/Libraries/Components/Keyboard/Keyboard.js b/Libraries/Components/Keyboard/Keyboard.js index 3c445e04e887..a9e0a29771dd 100644 --- a/Libraries/Components/Keyboard/Keyboard.js +++ b/Libraries/Components/Keyboard/Keyboard.js @@ -15,7 +15,7 @@ import LayoutAnimation from '../../LayoutAnimation/LayoutAnimation'; import dismissKeyboard from '../../Utilities/dismissKeyboard'; import NativeKeyboardObserver from './NativeKeyboardObserver'; import invariant from 'invariant'; -const KeyboardEventEmitter: NativeEventEmitter<$FlowFixMe> = new NativeEventEmitter( +const KeyboardEventEmitter: NativeEventEmitter = new NativeEventEmitter( NativeKeyboardObserver, ); diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index c0be65511536..55571c86e08f 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -678,8 +678,8 @@ const ScrollResponderMixin = { this.preventNegativeScrollOffset = false; }, - scrollResponderTextInputFocusError: function() { - console.warn('Error measuring text field.'); + scrollResponderTextInputFocusError: function(msg: string) { + console.error('Error measuring text field: ', msg); }, /** diff --git a/Libraries/Components/StatusBar/StatusBarIOS.js b/Libraries/Components/StatusBar/StatusBarIOS.js index 8a166cd36e63..ccc561103f18 100644 --- a/Libraries/Components/StatusBar/StatusBarIOS.js +++ b/Libraries/Components/StatusBar/StatusBarIOS.js @@ -16,6 +16,6 @@ import NativeStatusBarManagerIOS from './NativeStatusBarManagerIOS'; /** * Use `StatusBar` for mutating the status bar. */ -class StatusBarIOS extends NativeEventEmitter<$FlowFixMe> {} +class StatusBarIOS extends NativeEventEmitter {} module.exports = (new StatusBarIOS(NativeStatusBarManagerIOS): StatusBarIOS); diff --git a/Libraries/Core/setUpReactRefresh.js b/Libraries/Core/setUpReactRefresh.js index ccc67ca22398..4ee10acf086f 100644 --- a/Libraries/Core/setUpReactRefresh.js +++ b/Libraries/Core/setUpReactRefresh.js @@ -45,7 +45,5 @@ if (__DEV__) { }, }; - // The metro require polyfill can not have dependencies (applies for all polyfills). - // Expose `Refresh` by assigning it to global to make it available in the polyfill. - global[(global.__METRO_GLOBAL_PREFIX__ || '') + '__ReactRefresh'] = Refresh; + (require: any).Refresh = Refresh; } diff --git a/Libraries/EventEmitter/NativeEventEmitter.js b/Libraries/EventEmitter/NativeEventEmitter.js index 3dfa20071198..298a3eea2f88 100644 --- a/Libraries/EventEmitter/NativeEventEmitter.js +++ b/Libraries/EventEmitter/NativeEventEmitter.js @@ -34,9 +34,7 @@ const DEFAULT_NATIVE_EVENT_EMITTER_OPTIONS = { * Abstract base class for implementing event-emitting modules. This implements * a subset of the standard EventEmitter node module API. */ -export default class NativeEventEmitter< - _GenericArgumentPlaceholder, -> extends EventEmitter { +export default class NativeEventEmitter extends EventEmitter { _nativeModule: ?NativeModule; _disableCallsIntoModule: boolean; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec.podspec b/Libraries/FBReactNativeSpec/FBReactNativeSpec.podspec index 0be878326215..79493bdca4eb 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec.podspec +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec.podspec @@ -4,19 +4,44 @@ # LICENSE file in the root directory of this source tree. require "json" -require_relative "../../scripts/react_native_pods.rb" package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) version = package['version'] source = { :git => 'https://github.com/facebook/react-native.git' } +codegen_path_prefix = ".." if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. source[:commit] = `git rev-parse HEAD`.strip + codegen_path_prefix = "packages" else source[:tag] = "v#{version}" end +react_native_path = File.join(__dir__, "..", "..") +srcs_dir = File.join(__dir__, "..") +codegen_script_path = File.join(react_native_path, "scripts", "generate-specs.sh") +codegen_path = File.join(react_native_path, codegen_path_prefix, "react-native-codegen") +codegen_command = "CODEGEN_PATH=#{codegen_path} sh '#{codegen_script_path}' | tee \"${SCRIPT_OUTPUT_FILE_0}\"" +modules_output_dir = File.join(__dir__, "FBReactNativeSpec") +components_output_dir = File.join(react_native_path, "ReactCommon", "react", "renderer", "components", "rncore") +generated_filenames = [ "FBReactNativeSpec.h", "FBReactNativeSpec-generated.mm" ] +generated_files = generated_filenames.map { |filename| File.join(modules_output_dir, filename) } + +if ENV['USE_FABRIC'] == '1' + components_generated_filenames = [ + "ComponentDescriptors.h", + "EventEmitters.cpp", + "EventEmitters.h", + "Props.cpp", + "Props.h", + "RCTComponentViewHelpers.h", + "ShadowNodes.cpp", + "ShadowNodes.h" + ] + generated_files = generated_files.concat(components_generated_filenames.map { |filename| File.join(components_output_dir, filename) }) +end + folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' folly_version = '2020.01.13.00' @@ -47,5 +72,12 @@ Pod::Spec.new do |s| s.dependency "React-jsi", version s.dependency "ReactCommon/turbomodule/core", version - use_react_native_codegen! (s) + s.prepare_command = "mkdir -p #{modules_output_dir} #{components_output_dir} && touch #{generated_files.reduce() { |str, file| str + " " + file }}" + s.script_phase = { + :name => 'Generate Specs', + :input_files => [srcs_dir], + :output_files => ["$(DERIVED_FILE_DIR)/codegen.log"], + :script => codegen_command, + :execution_position => :before_compile + } end diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js index 0ba50eae35e4..8aec6727e33c 100644 --- a/Libraries/Interaction/InteractionManager.js +++ b/Libraries/Interaction/InteractionManager.js @@ -21,7 +21,7 @@ import EventEmitter from '../vendor/emitter/EventEmitter'; export type Handle = number; import type {Task} from './TaskQueue'; -const _emitter = new EventEmitter<$FlowFixMe>(); +const _emitter = new EventEmitter(); const DEBUG_DELAY: 0 = 0; const DEBUG: false = false; diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js index de4172d388d1..279980101718 100644 --- a/Libraries/Linking/Linking.js +++ b/Libraries/Linking/Linking.js @@ -24,7 +24,7 @@ import nullthrows from 'nullthrows'; * * See https://reactnative.dev/docs/linking.html */ -class Linking extends NativeEventEmitter<$FlowFixMe> { +class Linking extends NativeEventEmitter { constructor() { super(Platform.OS === 'ios' ? nullthrows(NativeLinkingManager) : undefined); } diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 858886c3816c..f5d3556453b2 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -31,7 +31,7 @@ import RCTModalHostView from './RCTModalHostViewNativeComponent'; const ModalEventEmitter = Platform.OS === 'ios' && NativeModalManager != null - ? new NativeEventEmitter<$FlowFixMe>(NativeModalManager) + ? new NativeEventEmitter(NativeModalManager) : null; /** diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js index 45c7141db4a8..12864c9c9af7 100644 --- a/Libraries/Network/RCTNetworking.android.js +++ b/Libraries/Network/RCTNetworking.android.js @@ -38,7 +38,7 @@ function generateRequestId(): number { * This class is a wrapper around the native RCTNetworking module. It adds a necessary unique * requestId to each network request that can be used to abort that request later on. */ -class RCTNetworking extends NativeEventEmitter<$FlowFixMe> { +class RCTNetworking extends NativeEventEmitter { constructor() { super(NativeNetworkingAndroid); } diff --git a/Libraries/Network/RCTNetworking.ios.js b/Libraries/Network/RCTNetworking.ios.js index 5992cf84cf6d..bc1081aab357 100644 --- a/Libraries/Network/RCTNetworking.ios.js +++ b/Libraries/Network/RCTNetworking.ios.js @@ -16,7 +16,7 @@ import type {NativeResponseType} from './XMLHttpRequest'; import convertRequestBody from './convertRequestBody'; import type {RequestBody} from './convertRequestBody'; -class RCTNetworking extends NativeEventEmitter<$FlowFixMe> { +class RCTNetworking extends NativeEventEmitter { constructor() { const disableCallsIntoModule = typeof global.__disableRCTNetworkingExtraneousModuleCalls === 'function' diff --git a/Libraries/Performance/Systrace.js b/Libraries/Performance/Systrace.js index 6089016521b8..048c3d404271 100644 --- a/Libraries/Performance/Systrace.js +++ b/Libraries/Performance/Systrace.js @@ -209,9 +209,11 @@ const Systrace = { }; if (__DEV__) { - // The metro require polyfill can not have dependencies (true for all polyfills). - // Ensure that `Systrace` is available in polyfill by exposing it globally. - global[(global.__METRO_GLOBAL_PREFIX__ || '') + '__SYSTRACE'] = Systrace; + // This is needed, because require callis in polyfills are not processed as + // other files. Therefore, calls to `require('moduleId')` are not replaced + // with numeric IDs + // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) + (require: $FlowFixMe).Systrace = Systrace; } module.exports = Systrace; diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index d033f26422b0..99d2d92c975a 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -14,7 +14,7 @@ import NativeEventEmitter from '../EventEmitter/NativeEventEmitter'; import NativePushNotificationManagerIOS from './NativePushNotificationManagerIOS'; import invariant from 'invariant'; -const PushNotificationEmitter = new NativeEventEmitter<$FlowFixMe>( +const PushNotificationEmitter = new NativeEventEmitter( NativePushNotificationManagerIOS, ); diff --git a/Libraries/Renderer/shims/ReactNativeTypes.js b/Libraries/Renderer/shims/ReactNativeTypes.js index a38eaaf11417..86cb89084738 100644 --- a/Libraries/Renderer/shims/ReactNativeTypes.js +++ b/Libraries/Renderer/shims/ReactNativeTypes.js @@ -5,15 +5,10 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow */ -import type { - ElementRef, - ElementType, - MixedElement, - AbstractComponent, -} from 'react'; +import type {ElementRef, AbstractComponent} from 'react'; export type MeasureOnSuccessCallback = ( x: number, @@ -38,32 +33,22 @@ export type MeasureLayoutOnSuccessCallback = ( height: number, ) => void; -type AttributeType = +type AttributeType = | true | $ReadOnly<{| diff?: (arg1: T, arg2: T) => boolean, - process?: (arg1: V) => T, + process?: (arg1: any) => any, |}>; -// We either force that `diff` and `process` always use mixed, -// or we allow them to define specific types and use this hack -type AnyAttributeType = AttributeType<$FlowFixMe, $FlowFixMe>; - type AttributeConfiguration = $ReadOnly<{ - [propName: string]: AnyAttributeType, - style: $ReadOnly<{ - [propName: string]: AnyAttributeType, - ..., - }>, + [propName: string]: AttributeType, + style: $ReadOnly<{[propName: string]: AttributeType, ...}>, ... }>; type PartialAttributeConfiguration = $ReadOnly<{ - [propName: string]: AnyAttributeType, - style?: $ReadOnly<{ - [propName: string]: AnyAttributeType, - ..., - }>, + [propName: string]: AttributeType, + style?: $ReadOnly<{[propName: string]: AttributeType, ...}>, ... }>; @@ -109,7 +94,7 @@ export type NativeMethods = { onSuccess: MeasureLayoutOnSuccessCallback, onFail?: () => void, ): void, - setNativeProps(nativeProps: {...}): void, + setNativeProps(nativeProps: Object): void, ... }; @@ -133,11 +118,9 @@ type InspectorDataSource = $ReadOnly<{| |}>; type InspectorDataGetter = ( - ( - componentOrHandle: ElementRef | number, - ) => ?number, + (componentOrHandle: any) => ?number, ) => $ReadOnly<{| - measure: (callback: MeasureOnSuccessCallback) => void, + measure: Function, props: InspectorDataProps, source: InspectorDataSource, |}>; @@ -169,63 +152,52 @@ export type TouchedViewDataAtPoint = $ReadOnly<{| * Provide minimal Flow typing for the high-level RN API and call it a day. */ export type ReactNativeType = { - findHostInstance_DEPRECATED( - componentOrHandle: ?(ElementRef | number), + findHostInstance_DEPRECATED( + componentOrHandle: any, ): ?ElementRef>, - findNodeHandle( - componentOrHandle: ?(ElementRef | number), - ): ?number, - dispatchCommand( - handle: ElementRef>, - command: string, - args: Array, - ): void, + findNodeHandle(componentOrHandle: any): ?number, + dispatchCommand(handle: any, command: string, args: Array): void, render( - element: MixedElement, - containerTag: number, - callback: ?() => void, - ): ?ElementRef, - unmountComponentAtNode(containerTag: number): void, - unmountComponentAtNodeAndRemoveContainer(containerTag: number): void, - unstable_batchedUpdates: (fn: (T) => void, bookkeeping: T) => void, + element: React$Element, + containerTag: any, + callback: ?Function, + ): any, + unmountComponentAtNode(containerTag: number): any, + unmountComponentAtNodeAndRemoveContainer(containerTag: number): any, + // TODO (bvaughn) Add types + unstable_batchedUpdates: any, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType, ... }; export type ReactFabricType = { - findHostInstance_DEPRECATED( - componentOrHandle: ?(ElementRef | number), + findHostInstance_DEPRECATED( + componentOrHandle: any, ): ?ElementRef>, - findNodeHandle( - componentOrHandle: ?(ElementRef | number), - ): ?number, - dispatchCommand( - handle: ElementRef>, - command: string, - args: Array, - ): void, + findNodeHandle(componentOrHandle: any): ?number, + dispatchCommand(handle: any, command: string, args: Array): void, render( - element: MixedElement, - containerTag: number, - callback: ?() => void, - ): ?ElementRef, - unmountComponentAtNode(containerTag: number): void, + element: React$Element, + containerTag: any, + callback: ?Function, + ): any, + unmountComponentAtNode(containerTag: number): any, ... }; export type ReactNativeEventTarget = { - node: {...}, + node: Object, canonical: { _nativeTag: number, viewConfig: ViewConfig, - currentProps: {...}, - _internalInstanceHandle: {...}, + currentProps: Object, + _internalInstanceHandle: Object, ... }, ... }; -export type ReactFabricEventTouch = { +export type ReactFaricEventTouch = { identifier: number, locationX: number, locationY: number, @@ -239,10 +211,10 @@ export type ReactFabricEventTouch = { ... }; -export type ReactFabricEvent = { - touches: Array, - changedTouches: Array, - targetTouches: Array, +export type ReactFaricEvent = { + touches: Array, + changedTouches: Array, + targetTouches: Array, target: number, ... }; diff --git a/Libraries/TurboModule/samples/NativeSampleTurboModule.js b/Libraries/TurboModule/samples/NativeSampleTurboModule.js index d240c4ad3ab4..f9c4c6ef53e3 100644 --- a/Libraries/TurboModule/samples/NativeSampleTurboModule.js +++ b/Libraries/TurboModule/samples/NativeSampleTurboModule.js @@ -10,7 +10,6 @@ 'use strict'; -import type {UnsafeObject} from '../../Types/CodegenTypes'; import type {RootTag, TurboModule} from '../RCTExport'; import * as TurboModuleRegistry from '../TurboModuleRegistry'; @@ -27,7 +26,6 @@ export interface Spec extends TurboModule { +getString: (arg: string) => string; +getArray: (arg: Array) => Array; +getObject: (arg: Object) => Object; - +getUnsafeObject: (arg: UnsafeObject) => UnsafeObject; +getRootTag: (arg: RootTag) => RootTag; +getValue: (x: number, y: string, z: Object) => Object; +getValueWithCallback: (callback: (value: string) => void) => void; diff --git a/Libraries/Types/CodegenTypes.js b/Libraries/Types/CodegenTypes.js index 458cfe80ba88..9e33919fdd84 100644 --- a/Libraries/Types/CodegenTypes.js +++ b/Libraries/Types/CodegenTypes.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ 'use strict'; @@ -28,7 +28,6 @@ export type DirectEventHandler< export type Double = number; export type Float = number; export type Int32 = number; -export type UnsafeObject = $FlowFixMe; // Object is forbidden in strict mode type DefaultTypes = number | boolean | string | $ReadOnlyArray; // Default handling, ignore the unused value diff --git a/Libraries/Types/CoreEventTypes.js b/Libraries/Types/CoreEventTypes.js index 906219cd75dd..6bf13e0aa7cd 100644 --- a/Libraries/Types/CoreEventTypes.js +++ b/Libraries/Types/CoreEventTypes.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/Libraries/Utilities/Appearance.js b/Libraries/Utilities/Appearance.js index e898c0b1896a..314b3eaaad64 100644 --- a/Libraries/Utilities/Appearance.js +++ b/Libraries/Utilities/Appearance.js @@ -20,12 +20,10 @@ import invariant from 'invariant'; import {isAsyncDebugging} from './DebugEnvironment'; type AppearanceListener = (preferences: AppearancePreferences) => void; -const eventEmitter = new EventEmitter<$FlowFixMe>(); +const eventEmitter = new EventEmitter(); if (NativeAppearance) { - const nativeEventEmitter = new NativeEventEmitter<$FlowFixMe>( - NativeAppearance, - ); + const nativeEventEmitter = new NativeEventEmitter(NativeAppearance); nativeEventEmitter.addListener( 'appearanceChanged', (newAppearance: AppearancePreferences) => { diff --git a/Libraries/Utilities/DebugEnvironment.js b/Libraries/Utilities/DebugEnvironment.js index 3ec4eeaa4919..c2b3e8ff57ef 100644 --- a/Libraries/Utilities/DebugEnvironment.js +++ b/Libraries/Utilities/DebugEnvironment.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ 'use strict'; diff --git a/Libraries/Utilities/DevSettings.js b/Libraries/Utilities/DevSettings.js index 58054b3553b8..58494938ec65 100644 --- a/Libraries/Utilities/DevSettings.js +++ b/Libraries/Utilities/DevSettings.js @@ -17,8 +17,7 @@ interface IDevSettings { onFastRefresh(): void; } -class DevSettings extends NativeEventEmitter<$FlowFixMe> - implements IDevSettings { +class DevSettings extends NativeEventEmitter implements IDevSettings { _menuItems: Map mixed>; constructor() { diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js index 4ac8c17f6c26..b3e5822c401b 100644 --- a/Libraries/Utilities/Dimensions.js +++ b/Libraries/Utilities/Dimensions.js @@ -24,7 +24,7 @@ type DimensionsValue = { ... }; -const eventEmitter = new EventEmitter<$FlowFixMe>(); +const eventEmitter = new EventEmitter(); let dimensionsInitialized = false; let dimensions: DimensionsValue; diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js index dc47f9ffc141..65cfd09c52c3 100644 --- a/Libraries/WebSocket/WebSocket.js +++ b/Libraries/WebSocket/WebSocket.js @@ -64,7 +64,7 @@ class WebSocket extends (EventTarget(...WEBSOCKET_EVENTS): any) { CLOSED: number = CLOSED; _socketId: number; - _eventEmitter: NativeEventEmitter<$FlowFixMe>; + _eventEmitter: NativeEventEmitter; _subscriptions: Array; _binaryType: ?BinaryType; diff --git a/React/Views/ScrollView/RCTScrollContentView.m b/React/Views/ScrollView/RCTScrollContentView.m index 8006540a70f1..cf6a0b1f1795 100644 --- a/React/Views/ScrollView/RCTScrollContentView.m +++ b/React/Views/ScrollView/RCTScrollContentView.m @@ -26,7 +26,7 @@ - (void)reactSetFrame:(CGRect)frame RCTAssert([scrollView isKindOfClass:[RCTScrollView class]], @"Unexpected view hierarchy of RCTScrollView component."); - [scrollView updateContentSizeIfNeeded]; + [scrollView updateContentOffsetIfNeeded]; } @end diff --git a/React/Views/ScrollView/RCTScrollView.h b/React/Views/ScrollView/RCTScrollView.h index 765d3b3378a5..4a508ee1e9d8 100644 --- a/React/Views/ScrollView/RCTScrollView.h +++ b/React/Views/ScrollView/RCTScrollView.h @@ -28,6 +28,12 @@ */ @property (nonatomic, readonly) UIView *contentView; +/** + * If the `contentSize` is not specified (or is specified as {0, 0}, then the + * `contentSize` will automatically be determined by the size of the subview. + */ +@property (nonatomic, assign) CGSize contentSize; + /** * The underlying scrollView (TODO: can we remove this?) */ @@ -62,7 +68,7 @@ @interface RCTScrollView (Internal) -- (void)updateContentSizeIfNeeded; +- (void)updateContentOffsetIfNeeded; @end diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index c9fe67bb9edb..c52f449c7300 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -301,6 +301,7 @@ - (instancetype)initWithEventDispatcher:(id)eventDis _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; + _contentSize = CGSizeZero; _lastClippedToRect = CGRectNull; _scrollEventThrottle = 0.0; @@ -380,7 +381,7 @@ - (void)didUpdateReactSubviews - (void)didSetProps:(NSArray *)changedProps { if ([changedProps containsObject:@"contentSize"]) { - [self updateContentSizeIfNeeded]; + [self updateContentOffsetIfNeeded]; } } @@ -798,6 +799,8 @@ - (UIView *)viewForZoomingInScrollView:(__unused UIScrollView *)scrollView return _contentView; } +#pragma mark - Setters + - (CGSize)_calculateViewportSize { CGSize viewportSize = self.bounds.size; @@ -810,16 +813,71 @@ - (CGSize)_calculateViewportSize return viewportSize; } +- (CGPoint)calculateOffsetForContentSize:(CGSize)newContentSize +{ + CGPoint oldOffset = _scrollView.contentOffset; + CGPoint newOffset = oldOffset; + + CGSize oldContentSize = _scrollView.contentSize; + CGSize viewportSize = [self _calculateViewportSize]; + + BOOL fitsinViewportY = oldContentSize.height <= viewportSize.height && newContentSize.height <= viewportSize.height; + if (newContentSize.height < oldContentSize.height && !fitsinViewportY) { + CGFloat offsetHeight = oldOffset.y + viewportSize.height; + if (oldOffset.y < 0) { + // overscrolled on top, leave offset alone + } else if (offsetHeight > oldContentSize.height) { + // overscrolled on the bottom, preserve overscroll amount + newOffset.y = MAX(0, oldOffset.y - (oldContentSize.height - newContentSize.height)); + } else if (offsetHeight > newContentSize.height) { + // offset falls outside of bounds, scroll back to end of list + newOffset.y = MAX(0, newContentSize.height - viewportSize.height); + } + } + + BOOL fitsinViewportX = oldContentSize.width <= viewportSize.width && newContentSize.width <= viewportSize.width; + if (newContentSize.width < oldContentSize.width && !fitsinViewportX) { + CGFloat offsetHeight = oldOffset.x + viewportSize.width; + if (oldOffset.x < 0) { + // overscrolled at the beginning, leave offset alone + } else if (offsetHeight > oldContentSize.width && newContentSize.width > viewportSize.width) { + // overscrolled at the end, preserve overscroll amount as much as possible + newOffset.x = MAX(0, oldOffset.x - (oldContentSize.width - newContentSize.width)); + } else if (offsetHeight > newContentSize.width) { + // offset falls outside of bounds, scroll back to end + newOffset.x = MAX(0, newContentSize.width - viewportSize.width); + } + } + + // all other cases, offset doesn't change + return newOffset; +} + +/** + * Once you set the `contentSize`, to a nonzero value, it is assumed to be + * managed by you, and we'll never automatically compute the size for you, + * unless you manually reset it back to {0, 0} + */ - (CGSize)contentSize { + if (!CGSizeEqualToSize(_contentSize, CGSizeZero)) { + return _contentSize; + } + return _contentView.frame.size; } -- (void)updateContentSizeIfNeeded +- (void)updateContentOffsetIfNeeded { CGSize contentSize = self.contentSize; if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) { + // When contentSize is set manually, ScrollView internals will reset + // contentOffset to {0, 0}. Since we potentially set contentSize whenever + // anything in the ScrollView updates, we workaround this issue by manually + // adjusting contentOffset whenever this happens + CGPoint newOffset = [self calculateOffsetForContentSize:contentSize]; _scrollView.contentSize = contentSize; + _scrollView.contentOffset = newOffset; } } diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 1ed98bed6eb4..3b3f973ade27 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -242,28 +242,26 @@ def getNdkBuildName() { } def findNdkBuildFullPath() { - // android.ndkDirectory should return project.android.ndkVersion ndkDirectory - def ndkDir = android.ndkDirectory ? android.ndkDirectory.absolutePath : null - if (ndkDir) { - return new File(ndkDir, getNdkBuildName()).getAbsolutePath() - } - // we allow to provide full path to ndk-build tool if (hasProperty("ndk.command")) { return property("ndk.command") } // or just a path to the containing directory if (hasProperty("ndk.path")) { - ndkDir = property("ndk.path") + def ndkDir = property("ndk.path") return new File(ndkDir, getNdkBuildName()).getAbsolutePath() } - // @TODO ANDROID_NDK && ndk.dir is deprecated and will be removed in the future. if (System.getenv("ANDROID_NDK") != null) { - ndkDir = System.getenv("ANDROID_NDK") + def ndkDir = System.getenv("ANDROID_NDK") return new File(ndkDir, getNdkBuildName()).getAbsolutePath() } + def ndkDir = android.ndkDirectory ? android.ndkDirectory.absolutePath : null + + if (ndkDir) { + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } return null } @@ -379,7 +377,7 @@ task extractJNIFiles { android { compileSdkVersion 29 - ndkVersion ANDROID_NDK_VERSION + compileOptions { sourceCompatibility(JavaVersion.VERSION_1_8) targetCompatibility(JavaVersion.VERSION_1_8) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java index a2e4319224ab..d6693e1d87d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java @@ -12,6 +12,7 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.jni.HybridData; import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.config.ReactFeatureFlags; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -88,6 +89,64 @@ private HashMap getLocalMap() { return mLocalTypeMap; } + private Iterator> createExperimentalIterator() { + if (mKeys == null) { + mKeys = Assertions.assertNotNull(importKeys()); + } + final String[] iteratorKeys = mKeys; + final Object[] iteratorValues = Assertions.assertNotNull(importValues()); + return new Iterator>() { + int currentIndex = 0; + + @Override + public boolean hasNext() { + return currentIndex < iteratorKeys.length; + } + + @Override + public Map.Entry next() { + final int index = currentIndex++; + return new Map.Entry() { + @Override + public String getKey() { + return iteratorKeys[index]; + } + + @Override + public Object getValue() { + return iteratorValues[index]; + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException( + "Can't set a value while iterating over a ReadableNativeMap"); + } + }; + } + }; + } + + private ReadableMapKeySetIterator createExperimentalKeySetIterator() { + if (mKeys == null) { + mKeys = Assertions.assertNotNull(importKeys()); + } + final String[] iteratorKeys = mKeys; + return new ReadableMapKeySetIterator() { + int currentIndex = 0; + + @Override + public boolean hasNextKey() { + return currentIndex < iteratorKeys.length; + } + + @Override + public String nextKey() { + return iteratorKeys[currentIndex++]; + } + }; + } + private native Object[] importTypes(); @Override @@ -187,62 +246,16 @@ public int getInt(@NonNull String name) { @Override public @NonNull Iterator> getEntryIterator() { - if (mKeys == null) { - mKeys = Assertions.assertNotNull(importKeys()); - } - final String[] iteratorKeys = mKeys; - final Object[] iteratorValues = Assertions.assertNotNull(importValues()); - return new Iterator>() { - int currentIndex = 0; - - @Override - public boolean hasNext() { - return currentIndex < iteratorKeys.length; - } - - @Override - public Map.Entry next() { - final int index = currentIndex++; - return new Map.Entry() { - @Override - public String getKey() { - return iteratorKeys[index]; - } - - @Override - public Object getValue() { - return iteratorValues[index]; - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException( - "Can't set a value while iterating over a ReadableNativeMap"); - } - }; - } - }; + return ReactFeatureFlags.enableExperimentalReadableNativeMapIterator + ? createExperimentalIterator() + : getLocalMap().entrySet().iterator(); } @Override public @NonNull ReadableMapKeySetIterator keySetIterator() { - if (mKeys == null) { - mKeys = Assertions.assertNotNull(importKeys()); - } - final String[] iteratorKeys = mKeys; - return new ReadableMapKeySetIterator() { - int currentIndex = 0; - - @Override - public boolean hasNextKey() { - return currentIndex < iteratorKeys.length; - } - - @Override - public String nextKey() { - return iteratorKeys[currentIndex++]; - } - }; + return ReactFeatureFlags.enableExperimentalReadableNativeMapIterator + ? createExperimentalKeySetIterator() + : new ReadableNativeMapKeySetIterator(this); } @Override 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 f07ec720b98f..74af261f5b47 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -14,7 +14,6 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.facebook.infer.annotation.ThreadConfined; -import java.util.List; public interface UIManager extends JSIModule, PerformanceCounter { @@ -130,15 +129,4 @@ void updateRootLayoutSpecs( @Deprecated @Nullable String resolveCustomDirectEventName(@Nullable String eventName); - - /** - * Helper method to pre-initialize view managers. When using Native ViewConfigs this method will - * also pre-compute the constants for a view manager. The purpose is to ensure that we don't block - * for getting the constants for view managers during initial rendering of a surface. - * - * @deprecated this method will be removed in the future - * @param viewManagerNames {@link List } names of ViewManagers - */ - @Deprecated - void preInitializeViewManagers(List viewManagerNames); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 2fddf14715c8..c0e8f35cd001 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -57,6 +57,12 @@ public class ReactFeatureFlags { /** Feature flag to configure eager initialization of Fabric */ public static boolean eagerInitializeFabric = false; + /** Use lock-free data structures for Fabric MountItems. */ + public static boolean enableLockFreeMountInstructions = false; + + /** Temporary flag for FB-internal workaround for RN:Litho interop in non-Fabric RN. */ + public static boolean enableNonFabricRNLithoForceLayout = true; + /** Disable UI update operations in non-Fabric renderer after catalyst instance was destroyed */ public static boolean disableNonFabricViewOperationsOnCatalystDestroy = false; @@ -66,6 +72,9 @@ public class ReactFeatureFlags { */ public static boolean enableStartSurfaceRaceConditionFix = false; + /** Enables the usage of an experimental optimized iterator for ReadableNativeMaps. */ + public static boolean enableExperimentalReadableNativeMapIterator = false; + /** Enables Static ViewConfig in RN Android native code. */ public static boolean enableExperimentalStaticViewConfigs = false; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java index 86e5cf383739..91fe11b20cd5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java @@ -18,13 +18,21 @@ import com.facebook.react.fabric.events.FabricEventEmitter; import com.facebook.react.fabric.mounting.LayoutMetricsConversions; import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; +import com.facebook.react.fabric.mounting.mountitems.CreateMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchIntCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem; -import com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem; +import com.facebook.react.fabric.mounting.mountitems.InsertMountItem; import com.facebook.react.fabric.mounting.mountitems.MountItem; import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem; +import com.facebook.react.fabric.mounting.mountitems.RemoveDeleteMultiMountItem; import com.facebook.react.fabric.mounting.mountitems.SendAccessibilityEvent; +import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdatePaddingMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateStateMountItem; import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.BatchEventDispatchedListener; @@ -99,12 +107,21 @@ private static void loadClasses() { EventBeatManager.class.getClass(); EventEmitterWrapper.class.getClass(); FabricEventEmitter.class.getClass(); + BatchMountItem.class.getClass(); + CreateMountItem.class.getClass(); DispatchCommandMountItem.class.getClass(); DispatchIntCommandMountItem.class.getClass(); DispatchStringCommandMountItem.class.getClass(); + InsertMountItem.class.getClass(); MountItem.class.getClass(); PreAllocateViewMountItem.class.getClass(); + RemoveDeleteMultiMountItem.class.getClass(); SendAccessibilityEvent.class.getClass(); + UpdateEventEmitterMountItem.class.getClass(); + UpdateLayoutMountItem.class.getClass(); + UpdatePaddingMountItem.class.getClass(); + UpdatePropsMountItem.class.getClass(); + UpdateStateMountItem.class.getClass(); LayoutMetricsConversions.class.getClass(); MountingManager.class.getClass(); Binding.class.getClass(); @@ -117,6 +134,5 @@ private static void loadClasses() { StateWrapperImpl.class.getClass(); BatchEventDispatchedListener.class.getClass(); ReactNativeConfig.class.getClass(); - IntBufferBatchMountItem.class.getClass(); } } 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 60aca9feefb7..894875031946 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -26,6 +26,7 @@ import android.os.SystemClock; import android.view.View; import androidx.annotation.AnyThread; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -57,13 +58,22 @@ import com.facebook.react.fabric.events.EventEmitterWrapper; import com.facebook.react.fabric.events.FabricEventEmitter; import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; +import com.facebook.react.fabric.mounting.mountitems.CreateMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchIntCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.DispatchStringCommandMountItem; +import com.facebook.react.fabric.mounting.mountitems.InsertMountItem; import com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem; import com.facebook.react.fabric.mounting.mountitems.MountItem; import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem; +import com.facebook.react.fabric.mounting.mountitems.RemoveDeleteMultiMountItem; import com.facebook.react.fabric.mounting.mountitems.SendAccessibilityEvent; +import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdatePaddingMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateStateMountItem; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.PixelUtil; @@ -77,6 +87,7 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.text.TextLayoutManager; import com.facebook.systrace.Systrace; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -122,6 +133,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { @NonNull private final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + // Concurrent MountItem data-structures, experimental. TODO: T79662803 @NonNull private final ConcurrentLinkedQueue mViewCommandMountItemsConcurrent = new ConcurrentLinkedQueue<>(); @@ -134,6 +146,24 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { private final ConcurrentLinkedQueue mPreMountItemsConcurrent = new ConcurrentLinkedQueue<>(); + // Non-concurrent MountItem data-structures + @NonNull private final Object mViewCommandMountItemsLock = new Object(); + @NonNull private final Object mMountItemsLock = new Object(); + @NonNull private final Object mPreMountItemsLock = new Object(); + + @GuardedBy("mViewCommandMountItemsLock") + @NonNull + private List mViewCommandMountItems = new ArrayList<>(); + + @GuardedBy("mMountItemsLock") + @NonNull + private List mMountItems = new ArrayList<>(); + + @GuardedBy("mPreMountItemsLock") + @NonNull + private ArrayDeque mPreMountItems = + new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY); + @ThreadConfined(UI) @NonNull private final DispatchUIFrameCallback mDispatchUIFrameCallback; @@ -199,13 +229,6 @@ public int addRootView( return rootTag; } - @Override - public void preInitializeViewManagers(List viewManagerNames) { - for (String viewManagerName : viewManagerNames) { - mMountingManager.initializeViewManager(viewManagerName); - } - } - @Override @AnyThread @ThreadConfined(ANY) @@ -343,6 +366,100 @@ private void preallocateView( isLayoutable)); } + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem createMountItem( + String componentName, + @Nullable ReadableMap props, + @Nullable Object stateWrapper, + int reactRootTag, + int reactTag, + boolean isLayoutable) { + String component = getFabricComponentName(componentName); + + // This could be null if teardown/navigation away from a surface on the main thread happens + // while a commit is being processed in a different thread. By contract we expect this to be + // possible at teardown, but this race should *never* happen at startup. + @Nullable ThemedReactContext reactContext = mReactContextForRootTag.get(reactRootTag); + + return new CreateMountItem( + reactContext, + reactRootTag, + reactTag, + component, + props, + (StateWrapper) stateWrapper, + isLayoutable); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem insertMountItem(int reactTag, int parentReactTag, int index) { + return new InsertMountItem(reactTag, parentReactTag, index); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem removeDeleteMultiMountItem(int[] metadata) { + return new RemoveDeleteMultiMountItem(metadata); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem updateLayoutMountItem( + int reactTag, int x, int y, int width, int height, int layoutDirection) { + return new UpdateLayoutMountItem(reactTag, x, y, width, height, layoutDirection); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem updatePaddingMountItem(int reactTag, int left, int top, int right, int bottom) { + return new UpdatePaddingMountItem(reactTag, left, top, right, bottom); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem updatePropsMountItem(int reactTag, ReadableMap map) { + return new UpdatePropsMountItem(reactTag, map); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem updateStateMountItem(int reactTag, @Nullable Object stateWrapper) { + return new UpdateStateMountItem(reactTag, (StateWrapper) stateWrapper); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem updateEventEmitterMountItem(int reactTag, Object eventEmitter) { + return new UpdateEventEmitterMountItem(reactTag, (EventEmitterWrapper) eventEmitter); + } + + @DoNotStrip + @SuppressWarnings("unused") + @AnyThread + @ThreadConfined(ANY) + private MountItem createBatchMountItem( + int rootTag, MountItem[] items, int size, int commitNumber) { + return new BatchMountItem(rootTag, items, size, commitNumber); + } + @DoNotStrip @SuppressWarnings("unused") @AnyThread @@ -496,7 +613,7 @@ public void synchronouslyUpdateViewOnUIThread( @Override public void execute(@NonNull MountingManager mountingManager) { try { - mountingManager.updateProps(reactTag, props); + updatePropsMountItem(reactTag, props).execute(mountingManager); } catch (Exception ex) { // TODO T42943890: Fix animations in Fabric and remove this try/catch ReactSoftException.logSoftException( @@ -561,9 +678,12 @@ private void scheduleMountItem( // When Binding.cpp calls scheduleMountItems during a commit phase, it always calls with // a BatchMountItem. No other sites call into this with a BatchMountItem, and Binding.cpp only // calls scheduleMountItems with a BatchMountItem. - boolean isBatchMountItem = mountItem instanceof IntBufferBatchMountItem; + boolean isClassicBatchMountItem = mountItem instanceof BatchMountItem; + boolean isIntBufferMountItem = mountItem instanceof IntBufferBatchMountItem; + boolean isBatchMountItem = isClassicBatchMountItem || isIntBufferMountItem; boolean shouldSchedule = - (isBatchMountItem && ((IntBufferBatchMountItem) mountItem).shouldSchedule()) + (isClassicBatchMountItem && ((BatchMountItem) mountItem).shouldSchedule()) + || (isIntBufferMountItem && ((IntBufferBatchMountItem) mountItem).shouldSchedule()) || (!isBatchMountItem && mountItem != null); // In case of sync rendering, this could be called on the UI thread. Otherwise, @@ -680,17 +800,50 @@ private List drainConcurrentItemQueue(ConcurrentLinkedQ @UiThread @ThreadConfined(UI) private List getAndResetViewCommandMountItems() { - return drainConcurrentItemQueue(mViewCommandMountItemsConcurrent); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + return drainConcurrentItemQueue(mViewCommandMountItemsConcurrent); + } + + synchronized (mViewCommandMountItemsLock) { + List result = mViewCommandMountItems; + if (result.isEmpty()) { + return null; + } + mViewCommandMountItems = new ArrayList<>(); + return result; + } } @UiThread @ThreadConfined(UI) private List getAndResetMountItems() { - return drainConcurrentItemQueue(mMountItemsConcurrent); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + return drainConcurrentItemQueue(mMountItemsConcurrent); + } + + synchronized (mMountItemsLock) { + List result = mMountItems; + if (result.isEmpty()) { + return null; + } + mMountItems = new ArrayList<>(); + return result; + } } private Collection getAndResetPreMountItems() { - return drainConcurrentItemQueue(mPreMountItemsConcurrent); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + return drainConcurrentItemQueue(mPreMountItemsConcurrent); + } + + synchronized (mPreMountItemsLock) { + ArrayDeque result = mPreMountItems; + if (result.isEmpty()) { + return null; + } + mPreMountItems = new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY); + return result; + } } /** @@ -833,6 +986,16 @@ private boolean dispatchMountItems() { } try { + // Make sure surface associated with this MountItem has been started, and not stopped. + // TODO T68118357: clean up this logic and simplify this method overall + if (mountItem instanceof BatchMountItem) { + BatchMountItem batchMountItem = (BatchMountItem) mountItem; + if (!isSurfaceActiveForExecution( + batchMountItem.getRootTag(), "dispatchMountItems BatchMountItem")) { + continue; + } + } + // Make sure surface associated with this MountItem has been started, and not stopped. // TODO T68118357: clean up this logic and simplify this method overall if (mountItem instanceof IntBufferBatchMountItem) { @@ -881,8 +1044,16 @@ private void dispatchPreMountItems(long frameTimeNanos) { break; } - PreAllocateViewMountItem preMountItemToDispatch = mPreMountItemsConcurrent.poll(); - + PreAllocateViewMountItem preMountItemToDispatch = null; + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + preMountItemToDispatch = mPreMountItemsConcurrent.poll(); + } else { + synchronized (mPreMountItemsLock) { + if (!mPreMountItems.isEmpty()) { + preMountItemToDispatch = mPreMountItems.pollFirst(); + } + } + } // If list is empty, `poll` will return null, or var will never be set if (preMountItemToDispatch == null) { break; @@ -1090,7 +1261,13 @@ public Map getPerformanceCounters() { * @param mountItem */ private void addMountItem(MountItem mountItem) { - mMountItemsConcurrent.add(mountItem); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + mMountItemsConcurrent.add(mountItem); + } else { + synchronized (mMountItemsLock) { + mMountItems.add(mountItem); + } + } } /** @@ -1099,7 +1276,13 @@ private void addMountItem(MountItem mountItem) { * @param mountItem */ private void addPreAllocateMountItem(PreAllocateViewMountItem mountItem) { - mPreMountItemsConcurrent.add(mountItem); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + mPreMountItemsConcurrent.add(mountItem); + } else { + synchronized (mPreMountItemsLock) { + mPreMountItems.add(mountItem); + } + } } /** @@ -1108,7 +1291,13 @@ private void addPreAllocateMountItem(PreAllocateViewMountItem mountItem) { * @param mountItem */ private void addViewCommandMountItem(DispatchCommandMountItem mountItem) { - mViewCommandMountItemsConcurrent.add(mountItem); + if (ReactFeatureFlags.enableLockFreeMountInstructions) { + mViewCommandMountItemsConcurrent.add(mountItem); + } else { + synchronized (mViewCommandMountItemsLock) { + mViewCommandMountItems.add(mountItem); + } + } } private class DispatchUIFrameCallback extends GuardedFrameCallback { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index 41388f46f104..3800b495bb5d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -437,6 +437,9 @@ void Binding::installFabricUIManager( // Keep reference to config object and cache some feature flags here reactNativeConfig_ = config; + useIntBufferBatchMountItem_ = reactNativeConfig_->getBool( + "react_fabric:use_int_buffer_batch_mountitem_android"); + disablePreallocateViews_ = reactNativeConfig_->getBool( "react_fabric:disabled_view_preallocation_android"); @@ -503,7 +506,226 @@ local_ref getPlatformComponentName(const ShadowView &shadowView) { return componentName; } -void Binding::schedulerDidFinishTransaction( +local_ref createUpdateEventEmitterMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + if (!mutation.newChildShadowView.eventEmitter) { + return nullptr; + } + SharedEventEmitter eventEmitter = mutation.newChildShadowView.eventEmitter; + + // Do not hold a reference to javaEventEmitter from the C++ side. + auto javaEventEmitter = EventEmitterWrapper::newObjectJavaArgs(); + EventEmitterWrapper *cEventEmitter = cthis(javaEventEmitter); + cEventEmitter->eventEmitter = eventEmitter; + + static auto updateEventEmitterInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jint, jobject)>( + "updateEventEmitterMountItem"); + + return updateEventEmitterInstruction( + javaUIManager, mutation.newChildShadowView.tag, javaEventEmitter.get()); +} + +local_ref createUpdatePropsMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + auto shadowView = mutation.newChildShadowView; + + // TODO: move props from map to a typed object. + auto newProps = shadowView.props->rawProps; + + local_ref readableMap = + castReadableMap(ReadableNativeMap::newObjectCxxArgs(newProps)); + static auto updatePropsInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jint, ReadableMap::javaobject)>( + "updatePropsMountItem"); + + return updatePropsInstruction( + javaUIManager, mutation.newChildShadowView.tag, readableMap.get()); +} + +local_ref createUpdateLayoutMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + auto oldChildShadowView = mutation.oldChildShadowView; + auto newChildShadowView = mutation.newChildShadowView; + + if (newChildShadowView.layoutMetrics != EmptyLayoutMetrics && + oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) { + static auto updateLayoutInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod( + jint, jint, jint, jint, jint, jint)>("updateLayoutMountItem"); + auto layoutMetrics = newChildShadowView.layoutMetrics; + auto pointScaleFactor = layoutMetrics.pointScaleFactor; + auto frame = layoutMetrics.frame; + + int x = round(frame.origin.x * pointScaleFactor); + int y = round(frame.origin.y * pointScaleFactor); + int w = round(frame.size.width * pointScaleFactor); + int h = round(frame.size.height * pointScaleFactor); + auto layoutDirection = + toInt(newChildShadowView.layoutMetrics.layoutDirection); + return updateLayoutInstruction( + javaUIManager, newChildShadowView.tag, x, y, w, h, layoutDirection); + } + + return nullptr; +} + +local_ref createUpdatePaddingMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + auto oldChildShadowView = mutation.oldChildShadowView; + auto newChildShadowView = mutation.newChildShadowView; + + if (oldChildShadowView.layoutMetrics.contentInsets == + newChildShadowView.layoutMetrics.contentInsets && + mutation.type != ShadowViewMutation::Type::Insert) { + return nullptr; + } + + static auto updatePaddingInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jint, jint, jint, jint, jint)>( + "updatePaddingMountItem"); + + auto layoutMetrics = newChildShadowView.layoutMetrics; + auto pointScaleFactor = layoutMetrics.pointScaleFactor; + auto contentInsets = layoutMetrics.contentInsets; + + int left = floor(contentInsets.left * pointScaleFactor); + int top = floor(contentInsets.top * pointScaleFactor); + int right = floor(contentInsets.right * pointScaleFactor); + int bottom = floor(contentInsets.bottom * pointScaleFactor); + + return updatePaddingInstruction( + javaUIManager, newChildShadowView.tag, left, top, right, bottom); +} + +local_ref createInsertMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + static auto insertInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jint, jint, jint)>( + "insertMountItem"); + + return insertInstruction( + javaUIManager, + mutation.newChildShadowView.tag, + mutation.parentShadowView.tag, + mutation.index); +} + +local_ref createUpdateStateMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation) { + static auto updateStateInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jint, jobject)>( + "updateStateMountItem"); + + auto state = mutation.newChildShadowView.state; + + // Do not hold onto Java object from C + // We DO want to hold onto C object from Java, since we don't know the + // lifetime of the Java object + local_ref javaStateWrapper = nullptr; + if (state != nullptr) { + javaStateWrapper = StateWrapperImpl::newObjectJavaArgs(); + StateWrapperImpl *cStateWrapper = cthis(javaStateWrapper); + cStateWrapper->state_ = state; + } + + return updateStateInstruction( + javaUIManager, + mutation.newChildShadowView.tag, + (javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr)); +} + +local_ref createRemoveAndDeleteMultiMountItem( + const jni::global_ref &javaUIManager, + const std::vector &metadata) { + auto env = Environment::current(); + auto removeAndDeleteArray = env->NewIntArray(metadata.size() * 4); + int position = 0; + jint temp[4]; + for (const auto &x : metadata) { + temp[0] = x.tag; + temp[1] = x.parentTag; + temp[2] = x.index; + temp[3] = (x.shouldRemove ? 1 : 0) | (x.shouldDelete ? 2 : 0); + env->SetIntArrayRegion(removeAndDeleteArray, position, 4, temp); + position += 4; + } + + static auto removeDeleteMultiInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod(jintArray)>( + "removeDeleteMultiMountItem"); + + auto ret = removeDeleteMultiInstruction(javaUIManager, removeAndDeleteArray); + + // It is not strictly necessary to manually delete the ref here, in this + // particular case. If JNI memory is being allocated in a loop, it's easy to + // overload the localref table and crash; this is not possible in this case + // since the JNI would automatically clear this ref when it goes out of scope, + // anyway. However, this is being left here as a reminder of good hygiene and + // to be careful with JNI-allocated memory in general. + env->DeleteLocalRef(removeAndDeleteArray); + + return ret; +} + +// TODO T48019320: because we pass initial props and state to the Create (and +// preallocate) mount instruction, we technically don't need to pass the first +// Update to any components. Dedupe? +local_ref createCreateMountItem( + const jni::global_ref &javaUIManager, + const ShadowViewMutation &mutation, + const Tag surfaceId) { + static auto createJavaInstruction = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod( + jstring, ReadableMap::javaobject, jobject, jint, jint, jboolean)>( + "createMountItem"); + + auto newChildShadowView = mutation.newChildShadowView; + + local_ref componentName = + getPlatformComponentName(newChildShadowView); + + jboolean isLayoutable = + newChildShadowView.layoutMetrics != EmptyLayoutMetrics; + + local_ref props = castReadableMap( + ReadableNativeMap::newObjectCxxArgs(newChildShadowView.props->rawProps)); + + // Do not hold onto Java object from C + // We DO want to hold onto C object from Java, since we don't know the + // lifetime of the Java object + local_ref javaStateWrapper = nullptr; + if (newChildShadowView.state != nullptr) { + javaStateWrapper = StateWrapperImpl::newObjectJavaArgs(); + StateWrapperImpl *cStateWrapper = cthis(javaStateWrapper); + cStateWrapper->state_ = newChildShadowView.state; + } + + return createJavaInstruction( + javaUIManager, + componentName.get(), + props.get(), + (javaStateWrapper != nullptr ? javaStateWrapper.get() : nullptr), + surfaceId, + newChildShadowView.tag, + isLayoutable); +} + +void Binding::schedulerDidFinishTransactionIntBuffer( MountingCoordinator::Shared const &mountingCoordinator) { std::lock_guard lock(commitMutex_); @@ -961,6 +1183,251 @@ void Binding::schedulerDidFinishTransaction( env->DeleteLocalRef(intBufferArray); } +void Binding::schedulerDidFinishTransaction( + MountingCoordinator::Shared const &mountingCoordinator) { + std::lock_guard lock(commitMutex_); + + if (useIntBufferBatchMountItem_) { + return schedulerDidFinishTransactionIntBuffer(mountingCoordinator); + } + + SystraceSection s("FabricUIManagerBinding::schedulerDidFinishTransaction"); + auto finishTransactionStartTime = telemetryTimePointNow(); + + jni::global_ref localJavaUIManager = getJavaUIManager(); + if (!localJavaUIManager) { + LOG(ERROR) + << "Binding::schedulerDidFinishTransaction: JavaUIManager disappeared"; + return; + } + + auto mountingTransaction = mountingCoordinator->pullTransaction(); + + if (!mountingTransaction.has_value()) { + return; + } + + auto telemetry = mountingTransaction->getTelemetry(); + auto surfaceId = mountingTransaction->getSurfaceId(); + auto &mutations = mountingTransaction->getMutations(); + + auto revisionNumber = telemetry.getRevisionNumber(); + + std::vector> queue; + // Upper bound estimation of mount items to be delivered to Java side. + int size = mutations.size() * 3 + 42; + + local_ref> mountItemsArray = + JArrayClass::newArray(size); + + auto mountItems = *(mountItemsArray); + std::unordered_set deletedViewTags; + + // Find the set of tags that are removed and deleted in one block + std::vector toRemove; + + int position = 0; + for (const auto &mutation : mutations) { + auto oldChildShadowView = mutation.oldChildShadowView; + auto newChildShadowView = mutation.newChildShadowView; + + bool isVirtual = newChildShadowView.layoutMetrics == EmptyLayoutMetrics && + oldChildShadowView.layoutMetrics == EmptyLayoutMetrics; + + // Handle accumulated removals/deletions + if (mutation.type != ShadowViewMutation::Remove && + mutation.type != ShadowViewMutation::Delete) { + if (toRemove.size() > 0) { + mountItems[position++] = + createRemoveAndDeleteMultiMountItem(localJavaUIManager, toRemove); + toRemove.clear(); + } + } + + switch (mutation.type) { + case ShadowViewMutation::Create: { + if (disablePreallocateViews_ || + mutation.newChildShadowView.props->revision > 1 || + deletedViewTags.find(mutation.newChildShadowView.tag) != + deletedViewTags.end()) { + mountItems[position++] = + createCreateMountItem(localJavaUIManager, mutation, surfaceId); + } + break; + } + case ShadowViewMutation::Remove: { + if (!isVirtual) { + toRemove.push_back( + RemoveDeleteMetadata{mutation.oldChildShadowView.tag, + mutation.parentShadowView.tag, + mutation.index, + true, + false}); + } + break; + } + case ShadowViewMutation::Delete: { + // It is impossible to delete without removing node first + const auto &it = std::find_if( + std::begin(toRemove), + std::end(toRemove), + [&mutation](const auto &x) { + return x.tag == mutation.oldChildShadowView.tag; + }); + + if (it != std::end(toRemove)) { + it->shouldDelete = true; + } else { + toRemove.push_back(RemoveDeleteMetadata{ + mutation.oldChildShadowView.tag, -1, -1, false, true}); + } + + deletedViewTags.insert(mutation.oldChildShadowView.tag); + break; + } + case ShadowViewMutation::Update: { + if (!isVirtual) { + if (mutation.oldChildShadowView.props != + mutation.newChildShadowView.props) { + mountItems[position++] = + createUpdatePropsMountItem(localJavaUIManager, mutation); + } + if (mutation.oldChildShadowView.state != + mutation.newChildShadowView.state) { + mountItems[position++] = + createUpdateStateMountItem(localJavaUIManager, mutation); + } + + // Padding: padding mountItems must be executed before layout props + // are updated in the view. This is necessary to ensure that events + // (resulting from layout changes) are dispatched with the correct + // padding information. + auto updatePaddingMountItem = + createUpdatePaddingMountItem(localJavaUIManager, mutation); + if (updatePaddingMountItem) { + mountItems[position++] = updatePaddingMountItem; + } + + auto updateLayoutMountItem = + createUpdateLayoutMountItem(localJavaUIManager, mutation); + if (updateLayoutMountItem) { + mountItems[position++] = updateLayoutMountItem; + } + } + + if (mutation.oldChildShadowView.eventEmitter != + mutation.newChildShadowView.eventEmitter) { + auto updateEventEmitterMountItem = + createUpdateEventEmitterMountItem(localJavaUIManager, mutation); + if (updateEventEmitterMountItem) { + mountItems[position++] = updateEventEmitterMountItem; + } + } + break; + } + case ShadowViewMutation::Insert: { + if (!isVirtual) { + // Insert item + mountItems[position++] = + createInsertMountItem(localJavaUIManager, mutation); + + if (disablePreallocateViews_ || + mutation.newChildShadowView.props->revision > 1 || + deletedViewTags.find(mutation.newChildShadowView.tag) != + deletedViewTags.end()) { + mountItems[position++] = + createUpdatePropsMountItem(localJavaUIManager, mutation); + } + + // State + if (mutation.newChildShadowView.state) { + mountItems[position++] = + createUpdateStateMountItem(localJavaUIManager, mutation); + } + + // Padding: padding mountItems must be executed before layout props + // are updated in the view. This is necessary to ensure that events + // (resulting from layout changes) are dispatched with the correct + // padding information. + auto updatePaddingMountItem = + createUpdatePaddingMountItem(localJavaUIManager, mutation); + if (updatePaddingMountItem) { + mountItems[position++] = updatePaddingMountItem; + } + + // Layout + auto updateLayoutMountItem = + createUpdateLayoutMountItem(localJavaUIManager, mutation); + if (updateLayoutMountItem) { + mountItems[position++] = updateLayoutMountItem; + } + } + + // EventEmitter + auto updateEventEmitterMountItem = + createUpdateEventEmitterMountItem(localJavaUIManager, mutation); + if (updateEventEmitterMountItem) { + mountItems[position++] = updateEventEmitterMountItem; + } + + break; + } + default: { + break; + } + } + } + + // Handle remaining removals and deletions + if (toRemove.size() > 0) { + mountItems[position++] = + createRemoveAndDeleteMultiMountItem(localJavaUIManager, toRemove); + toRemove.clear(); + } + + static auto createMountItemsBatchContainer = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod( + jint, jtypeArray, jint, jint)>( + "createBatchMountItem"); + + // If there are no items, we pass a nullptr instead of passing the object + // through the JNI + auto batch = createMountItemsBatchContainer( + localJavaUIManager, + surfaceId, + position == 0 ? nullptr : mountItemsArray.get(), + position, + revisionNumber); + + static auto scheduleMountItem = + jni::findClassStatic(Binding::UIManagerJavaDescriptor) + ->getMethod("scheduleMountItem"); + + auto finishTransactionEndTime = telemetryTimePointNow(); + + scheduleMountItem( + localJavaUIManager, + batch.get(), + telemetry.getRevisionNumber(), + telemetryTimePointToMilliseconds(telemetry.getCommitStartTime()), + telemetryTimePointToMilliseconds(telemetry.getDiffStartTime()), + telemetryTimePointToMilliseconds(telemetry.getDiffEndTime()), + telemetryTimePointToMilliseconds(telemetry.getLayoutStartTime()), + telemetryTimePointToMilliseconds(telemetry.getLayoutEndTime()), + telemetryTimePointToMilliseconds(finishTransactionStartTime), + telemetryTimePointToMilliseconds(finishTransactionEndTime)); +} + void Binding::setPixelDensity(float pointScaleFactor) { pointScaleFactor_ = pointScaleFactor; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index 377762eb806a..61e2e2b783bd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -169,9 +169,14 @@ class Binding : public jni::HybridClass, float pointScaleFactor_ = 1; std::shared_ptr reactNativeConfig_{nullptr}; + bool useIntBufferBatchMountItem_{false}; bool disablePreallocateViews_{false}; bool disableVirtualNodePreallocation_{false}; bool enableFabricLogs_{false}; + + private: + void schedulerDidFinishTransactionIntBuffer( + MountingCoordinator::Shared const &mountingCoordinator); }; } // namespace react diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java index 2ea54f35e42b..be91c29c23eb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java @@ -782,10 +782,6 @@ public long measure( return viewState == null ? null : viewState.mEventEmitter; } - public void initializeViewManager(String componentName) { - mViewManagerRegistry.get(componentName); - } - /** * This class holds view state for react tags. Objects of this class are stored into the {@link * #mTagToViewState}, and they should be updated in the same thread. 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 new file mode 100644 index 000000000000..9a7e573edd05 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java @@ -0,0 +1,117 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import com.facebook.common.logging.FLog; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.ReactMarker; +import com.facebook.react.bridge.ReactMarkerConstants; +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.systrace.Systrace; + +/** + * This class represents a batch of {@link MountItem}s + * + *

A MountItem batch contains an array of {@link MountItem} and a size. The size determines the + * amount of items that needs to be processed in the array. + * + *

The purpose of encapsulating the array of MountItems this way, is to reduce the amount of + * allocations in C++ + */ +@DoNotStrip +public class BatchMountItem implements MountItem { + static final String TAG = "FabricBatchMountItem"; + + private final int mRootTag; + @NonNull private final MountItem[] mMountItems; + + private final int mSize; + + private final int mCommitNumber; + + public BatchMountItem(int rootTag, MountItem[] items, int size, int commitNumber) { + int itemsLength = (items == null ? 0 : items.length); + if (size < 0 || size > itemsLength) { + throw new IllegalArgumentException( + "Invalid size received by parameter size: " + size + " items.size = " + itemsLength); + } + mRootTag = rootTag; + mMountItems = items; + mSize = size; + mCommitNumber = commitNumber; + } + + private void beginMarkers(String reason) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricUIManager::" + reason + " - " + mSize + " items"); + + if (mCommitNumber > 0) { + ReactMarker.logFabricMarker( + ReactMarkerConstants.FABRIC_BATCH_EXECUTION_START, null, mCommitNumber); + } + } + + private void endMarkers() { + if (mCommitNumber > 0) { + ReactMarker.logFabricMarker( + ReactMarkerConstants.FABRIC_BATCH_EXECUTION_END, null, mCommitNumber); + } + + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + beginMarkers("mountViews"); + + int mountItemIndex = 0; + try { + for (; mountItemIndex < mSize; mountItemIndex++) { + mMountItems[mountItemIndex].execute(mountingManager); + } + } catch (RuntimeException e) { + FLog.e( + TAG, + "Caught exception executing mountItem @" + + mountItemIndex + + ": " + + mMountItems[mountItemIndex].toString(), + e); + throw e; + } + + endMarkers(); + } + + public int getRootTag() { + return mRootTag; + } + + public boolean shouldSchedule() { + return mSize != 0; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < mSize; i++) { + if (s.length() > 0) { + s.append("\n"); + } + s.append("BatchMountItem [S:" + mRootTag + "] (") + .append(i + 1) + .append("/") + .append(mSize) + .append("): ") + .append(mMountItems[i]); + } + return s.toString(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java new file mode 100644 index 000000000000..d96bab3326e0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.uimanager.StateWrapper; +import com.facebook.react.uimanager.ThemedReactContext; + +public class CreateMountItem implements MountItem { + + @NonNull private final String mComponent; + private final int mRootTag; + private final int mReactTag; + @NonNull private final ThemedReactContext mContext; + private final @Nullable ReadableMap mProps; + private final @Nullable StateWrapper mStateWrapper; + private final boolean mIsLayoutable; + + public CreateMountItem( + @Nullable ThemedReactContext context, + int rootTag, + int reactTag, + @NonNull String component, + @Nullable ReadableMap props, + @NonNull StateWrapper stateWrapper, + boolean isLayoutable) { + mContext = context; + mComponent = component; + mRootTag = rootTag; + mReactTag = reactTag; + mProps = props; + mStateWrapper = stateWrapper; + mIsLayoutable = isLayoutable; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + if (mContext == null) { + throw new IllegalStateException( + "Cannot execute PreAllocateViewMountItem without Context for ReactTag: " + + mReactTag + + " and rootTag: " + + mRootTag); + } + mountingManager.createView( + mContext, mComponent, mReactTag, mProps, mStateWrapper, mIsLayoutable); + } + + @Override + public String toString() { + return "CreateMountItem [" + + mReactTag + + "] - component: " + + mComponent + + " - rootTag: " + + mRootTag + + " - isLayoutable: " + + mIsLayoutable; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java new file mode 100644 index 000000000000..edfc46ac7109 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/InsertMountItem.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import com.facebook.react.fabric.mounting.MountingManager; + +public class InsertMountItem implements MountItem { + + private int mReactTag; + private int mParentReactTag; + private int mIndex; + + public InsertMountItem(int reactTag, int parentReactTag, int index) { + mReactTag = reactTag; + mParentReactTag = parentReactTag; + mIndex = index; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.addViewAt(mParentReactTag, mReactTag, mIndex); + } + + public int getParentReactTag() { + return mParentReactTag; + } + + public int getIndex() { + return mIndex; + } + + @Override + public String toString() { + return "InsertMountItem [" + + mReactTag + + "] - parentTag: [" + + mParentReactTag + + "] - index: " + + mIndex; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveDeleteMultiMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveDeleteMultiMountItem.java new file mode 100644 index 000000000000..daf3ed5ca75d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/RemoveDeleteMultiMountItem.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import com.facebook.react.fabric.mounting.MountingManager; + +public class RemoveDeleteMultiMountItem implements MountItem { + + // Metadata is an array of ints, grouped into 4 ints per instruction (so the length of metadata + // is always divisible by 4): + // + // `instruction*4 + 0`: react tag of view instruction + // `instruction*4 + 1`: react tag of view's parent + // `instruction*4 + 2`: index of view in parents' children instruction + // `instruction*4 + 3`: flags indicating if the view should be removed, and/or deleted + @NonNull private int[] mMetadata; + + // Bitfields of "flag", indicating if a view should be removed and/or deleted + private static final int REMOVE_FLAG = 1; + private static final int DELETE_FLAG = 2; + + // Indices for each parameter within an "instruction" + private static final int INSTRUCTION_FIELDS_LEN = 4; + private static final int TAG_INDEX = 0; + private static final int PARENT_TAG_INDEX = 1; + private static final int VIEW_INDEX_INDEX = 2; + private static final int FLAGS_INDEX = 3; + + public RemoveDeleteMultiMountItem(@NonNull int[] metadata) { + mMetadata = metadata; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + // First, go through instructions and remove all views that are marked + // for removal. + // Not all views that are removed are deleted, and not all deleted views + // are removed first. + // *All* views must be removed here before we can delete any views. + // Removal of a view from a parent is based on indices within the parents' children, + // and deletion causes reordering; so we must perform all removals first. + for (int i = 0; i < mMetadata.length; i += INSTRUCTION_FIELDS_LEN) { + int flags = mMetadata[i + FLAGS_INDEX]; + if ((flags & REMOVE_FLAG) != 0) { + int parentTag = mMetadata[i + PARENT_TAG_INDEX]; + int tag = mMetadata[i + TAG_INDEX]; + int index = mMetadata[i + VIEW_INDEX_INDEX]; + mountingManager.removeViewAt(tag, parentTag, index); + } + } + + // After removing all views, delete all views marked for deletion. + for (int i = 0; i < mMetadata.length; i += 4) { + int flags = mMetadata[i + FLAGS_INDEX]; + if ((flags & DELETE_FLAG) != 0) { + int tag = mMetadata[i + TAG_INDEX]; + mountingManager.deleteView(tag); + } + } + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < mMetadata.length; i += 4) { + if (s.length() > 0) { + s.append("\n"); + } + s.append("RemoveDeleteMultiMountItem (") + .append(i / INSTRUCTION_FIELDS_LEN + 1) + .append("/") + .append(mMetadata.length / INSTRUCTION_FIELDS_LEN) + .append("): [") + .append(mMetadata[i + TAG_INDEX]) + .append("] parent [") + .append(mMetadata[i + PARENT_TAG_INDEX]) + .append("] idx ") + .append(mMetadata[i + VIEW_INDEX_INDEX]) + .append(" ") + .append(mMetadata[i + FLAGS_INDEX]); + } + return s.toString(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java new file mode 100644 index 000000000000..72331c6b9634 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateEventEmitterMountItem.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import com.facebook.react.fabric.events.EventEmitterWrapper; +import com.facebook.react.fabric.mounting.MountingManager; + +public class UpdateEventEmitterMountItem implements MountItem { + + @NonNull private final EventEmitterWrapper mEventHandler; + private final int mReactTag; + + public UpdateEventEmitterMountItem(int reactTag, @NonNull EventEmitterWrapper EventHandler) { + mReactTag = reactTag; + mEventHandler = EventHandler; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.updateEventEmitter(mReactTag, mEventHandler); + } + + @Override + public String toString() { + return "UpdateEventEmitterMountItem [" + mReactTag + "]"; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java new file mode 100644 index 000000000000..cd8edd887e20 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLayoutMountItem.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.LayoutDirection; +import androidx.annotation.NonNull; +import com.facebook.react.fabric.mounting.MountingManager; + +public class UpdateLayoutMountItem implements MountItem { + + private final int mReactTag; + private final int mX; + private final int mY; + private final int mWidth; + private final int mHeight; + private final int mLayoutDirection; + + public UpdateLayoutMountItem( + int reactTag, int x, int y, int width, int height, int layoutDirection) { + mReactTag = reactTag; + mX = x; + mY = y; + mWidth = width; + mHeight = height; + mLayoutDirection = convertLayoutDirection(layoutDirection); + } + + // TODO move this from here + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static int convertLayoutDirection(int layoutDirection) { + switch (layoutDirection) { + case 0: + return LayoutDirection.INHERIT; + case 1: + return LayoutDirection.LTR; + case 2: + return LayoutDirection.RTL; + default: + throw new IllegalArgumentException("Unsupported layout direction: " + layoutDirection); + } + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.updateLayout(mReactTag, mX, mY, mWidth, mHeight); + } + + public int getX() { + return mX; + } + + public int getY() { + return mY; + } + + public int getHeight() { + return mHeight; + } + + public int getWidth() { + return mWidth; + } + + public int getLayoutDirection() { + return mLayoutDirection; + } + + @Override + public String toString() { + return "UpdateLayoutMountItem [" + + mReactTag + + "] - x: " + + mX + + " - y: " + + mY + + " - height: " + + mHeight + + " - width: " + + mWidth + + " - layoutDirection: " + + +mLayoutDirection; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePaddingMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePaddingMountItem.java new file mode 100644 index 000000000000..e0403627bbd9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePaddingMountItem.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import androidx.annotation.NonNull; +import com.facebook.react.fabric.mounting.MountingManager; + +/** + * A MountItem that represents setting the padding properties of a view (left, top, right, bottom). + * This is distinct from layout because it happens far less frequently from layout; so it is a perf + * optimization to transfer this data and execute the methods strictly less than the layout-related + * properties. + */ +public class UpdatePaddingMountItem implements MountItem { + + private final int mReactTag; + private final int mLeft; + private final int mTop; + private final int mRight; + private final int mBottom; + + public UpdatePaddingMountItem(int reactTag, int left, int top, int right, int bottom) { + mReactTag = reactTag; + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.updatePadding(mReactTag, mLeft, mTop, mRight, mBottom); + } + + @Override + public String toString() { + return "UpdatePaddingMountItem [" + + mReactTag + + "] - left: " + + mLeft + + " - top: " + + mTop + + " - right: " + + mRight + + " - bottom: " + + mBottom; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java new file mode 100644 index 000000000000..8b980705e9dc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdatePropsMountItem.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.fabric.mounting.MountingManager; + +public class UpdatePropsMountItem implements MountItem { + + private final int mReactTag; + @NonNull private final ReadableMap mUpdatedProps; + + public UpdatePropsMountItem(int reactTag, @NonNull ReadableMap updatedProps) { + mReactTag = reactTag; + mUpdatedProps = updatedProps; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.updateProps(mReactTag, mUpdatedProps); + } + + @Override + public String toString() { + StringBuilder result = + new StringBuilder("UpdatePropsMountItem [").append(mReactTag).append("]"); + + if (IS_DEVELOPMENT_ENVIRONMENT) { + result.append(" props: ").append(mUpdatedProps); + } + + return result.toString(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java new file mode 100644 index 000000000000..4544e8f935e2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.mounting.mountitems; + +import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.uimanager.StateWrapper; + +public class UpdateStateMountItem implements MountItem { + + private final int mReactTag; + @Nullable private final StateWrapper mStateWrapper; + + public UpdateStateMountItem(int reactTag, @Nullable StateWrapper stateWrapper) { + mReactTag = reactTag; + mStateWrapper = stateWrapper; + } + + @Override + public void execute(@NonNull MountingManager mountingManager) { + mountingManager.updateState(mReactTag, mStateWrapper); + } + + @Override + public String toString() { + StringBuilder result = + new StringBuilder("UpdateStateMountItem [").append(mReactTag).append("]"); + + if (IS_DEVELOPMENT_ENVIRONMENT) { + result.append(" state: ").append(mStateWrapper != null ? mStateWrapper.getState() : ""); + } + + return result.toString(); + } +} 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 51f84ebc9400..9d982ee29d5a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -15,7 +15,6 @@ import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.view.View; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.collection.ArrayMap; import com.facebook.common.logging.FLog; @@ -209,7 +208,7 @@ public UIImplementation getUIImplementation() { } @Override - public @NonNull String getName() { + public String getName() { return NAME; } @@ -294,16 +293,14 @@ private static Map createConstants( * Helper method to pre-compute the constants for a view manager. This method ensures that we * don't block for getting the constants for view managers during TTI * - * @deprecated this method will be removed in the future + * @deprecated this method will not be available in FabricUIManager class. * @param viewManagerNames {@link List} names of ViewManagers */ @Deprecated - @Override - public void preInitializeViewManagers(List viewManagerNames) { + public void preComputeConstantsForViewManager(List viewManagerNames) { + // TODO T81145457 - Implement pre-initialization of ViewManagers in Fabric Android if (ReactFeatureFlags.enableExperimentalStaticViewConfigs) { - for (String viewManagerName : viewManagerNames) { - mUIImplementation.resolveViewManager(viewManagerName); - } + preInitializeViewManagers(viewManagerNames); // When Static view configs are enabled it is not necessary to pre-compute the constants for // viewManagers, although the pre-initialization of viewManager objects is still necessary // for performance reasons. @@ -328,6 +325,12 @@ public void preInitializeViewManagers(List viewManagerNames) { mViewManagerConstantsCache = Collections.unmodifiableMap(constantsMap); } + private void preInitializeViewManagers(List viewManagerNames) { + for (String viewManagerName : viewManagerNames) { + mUIImplementation.resolveViewManager(viewManagerName); + } + } + @ReactMethod(isBlockingSynchronousMethod = true) public @Nullable WritableMap getConstantsForViewManager(@Nullable String viewManagerName) { if (mViewManagerConstantsCache != null diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java index 5401620f7157..a806b87a380a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java @@ -39,6 +39,7 @@ public abstract class ViewManager * * @param viewToUpdate * @param props + * @param stateWrapper */ public void updateProperties(@NonNull T viewToUpdate, ReactStylesDiffMap props) { final ViewManagerDelegate delegate; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index 9869fe878ded..68ada3dfc706 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -23,7 +23,6 @@ import android.net.Uri; import android.widget.Toast; import androidx.annotation.Nullable; -import com.facebook.common.internal.Objects; import com.facebook.common.references.CloseableReference; import com.facebook.common.util.UriUtil; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; @@ -91,10 +90,8 @@ public class ReactImageView extends GenericDraweeView { private ImageResizeMethod mResizeMethod = ImageResizeMethod.AUTO; public void updateCallerContext(@Nullable Object callerContext) { - if (!Objects.equal(mCallerContext, callerContext)) { - mCallerContext = callerContext; - mIsDirty = true; - } + mCallerContext = callerContext; + mIsDirty = true; } private class RoundedCornerPostprocessor extends BasePostprocessor { @@ -232,11 +229,6 @@ public ReactImageView( } public void setShouldNotifyLoadEvents(boolean shouldNotify) { - // Skip update if shouldNotify is already in sync with the download listener - if (shouldNotify == (mDownloadListener != null)) { - return; - } - if (!shouldNotify) { mDownloadListener = null; } else { @@ -303,25 +295,18 @@ public void setBackgroundColor(int backgroundColor) { } public void setBorderColor(int borderColor) { - if (mBorderColor != borderColor) { - mBorderColor = borderColor; - mIsDirty = true; - } + mBorderColor = borderColor; + mIsDirty = true; } public void setOverlayColor(int overlayColor) { - if (mOverlayColor != overlayColor) { - mOverlayColor = overlayColor; - mIsDirty = true; - } + mOverlayColor = overlayColor; + mIsDirty = true; } public void setBorderWidth(float borderWidth) { - float newBorderWidth = PixelUtil.toPixelFromDIP(borderWidth); - if (!FloatUtil.floatsEqual(mBorderWidth, newBorderWidth)) { - mBorderWidth = newBorderWidth; - mIsDirty = true; - } + mBorderWidth = PixelUtil.toPixelFromDIP(borderWidth); + mIsDirty = true; } public void setBorderRadius(float borderRadius) { @@ -344,39 +329,32 @@ public void setBorderRadius(float borderRadius, int position) { } public void setScaleType(ScalingUtils.ScaleType scaleType) { - if (mScaleType != scaleType) { - mScaleType = scaleType; - mIsDirty = true; - } + mScaleType = scaleType; + mIsDirty = true; } public void setTileMode(Shader.TileMode tileMode) { - if (mTileMode != tileMode) { - mTileMode = tileMode; - mIsDirty = true; - } + mTileMode = tileMode; + mIsDirty = true; } public void setResizeMethod(ImageResizeMethod resizeMethod) { - if (mResizeMethod != resizeMethod) { - mResizeMethod = resizeMethod; - mIsDirty = true; - } + mResizeMethod = resizeMethod; + mIsDirty = true; } public void setSource(@Nullable ReadableArray sources) { - List tmpSources = new LinkedList<>(); - + mSources.clear(); if (sources == null || sources.size() == 0) { ImageSource imageSource = new ImageSource(getContext(), REMOTE_TRANSPARENT_BITMAP_URI); - tmpSources.add(imageSource); + mSources.add(imageSource); } else { // Optimize for the case where we have just one uri, case in which we don't need the sizes if (sources.size() == 1) { ReadableMap source = sources.getMap(0); String uri = source.getString("uri"); ImageSource imageSource = new ImageSource(getContext(), uri); - tmpSources.add(imageSource); + mSources.add(imageSource); if (Uri.EMPTY.equals(imageSource.getUri())) { warnImageSource(uri); } @@ -387,44 +365,28 @@ public void setSource(@Nullable ReadableArray sources) { ImageSource imageSource = new ImageSource( getContext(), uri, source.getDouble("width"), source.getDouble("height")); - tmpSources.add(imageSource); + mSources.add(imageSource); if (Uri.EMPTY.equals(imageSource.getUri())) { warnImageSource(uri); } } } } - - // Don't reset sources and dirty node if sources haven't changed - if (mSources.equals(tmpSources)) { - return; - } - - mSources.clear(); - for (ImageSource src : tmpSources) { - mSources.add(src); - } mIsDirty = true; } public void setDefaultSource(@Nullable String name) { - Drawable newDefaultDrawable = + mDefaultImageDrawable = ResourceDrawableIdHelper.getInstance().getResourceDrawable(getContext(), name); - if (!Objects.equal(mDefaultImageDrawable, newDefaultDrawable)) { - mDefaultImageDrawable = newDefaultDrawable; - mIsDirty = true; - } + mIsDirty = true; } public void setLoadingIndicatorSource(@Nullable String name) { Drawable drawable = ResourceDrawableIdHelper.getInstance().getResourceDrawable(getContext(), name); - Drawable newLoadingIndicatorSource = + mLoadingImageDrawable = drawable != null ? (Drawable) new AutoRotateDrawable(drawable, 1000) : null; - if (!Objects.equal(mLoadingImageDrawable, newLoadingIndicatorSource)) { - mLoadingImageDrawable = newLoadingIndicatorSource; - mIsDirty = true; - } + mIsDirty = true; } public void setProgressiveRenderingEnabled(boolean enabled) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.java b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.java index 065ebbe63668..9f95b5bae9af 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.java @@ -11,7 +11,6 @@ import android.net.Uri; import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; -import java.util.Objects; /** Class describing an image source (network URI or resource) and size. */ public class ImageSource { @@ -30,22 +29,6 @@ public ImageSource(Context context, String source, double width, double height) mUri = computeUri(context); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ImageSource that = (ImageSource) o; - return Double.compare(that.mSize, mSize) == 0 - && isResource == that.isResource - && Objects.equals(mUri, that.mUri) - && Objects.equals(mSource, that.mSource); - } - - @Override - public int hashCode() { - return Objects.hash(mUri, mSource, mSize, isResource); - } - public ImageSource(Context context, String source) { this(context, source, 0.0d, 0.0d); } diff --git a/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp index 61f686c44044..c1ad15082beb 100644 --- a/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp @@ -64,15 +64,14 @@ local_ref> ReadableNativeMap::importKeys() { return JArrayClass::newArray(0); } auto pairs = map_.items(); - jint size = map_.size(); - auto jarray = JArrayClass::newArray(size); - jint i = 0; for (auto &pair : pairs) { - auto value = pair.first.asString(); - keys_.value().push_back(value); - (*jarray)[i++] = make_jstring(value); + keys_.value().push_back(pair.first.asString()); + } + jint size = keys_.value().size(); + auto jarray = JArrayClass::newArray(size); + for (jint ii = 0; ii < size; ii++) { + (*jarray)[ii] = make_jstring(keys_.value()[ii].getString()); } - return jarray; } diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h index 30add1d265ba..030d3fac9783 100644 --- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h +++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h @@ -10,6 +10,7 @@ #include #include +#include #include #include diff --git a/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java b/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java index f992e5d6ae1c..8475fbc47112 100644 --- a/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java +++ b/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java @@ -42,9 +42,6 @@ public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) { @ReactMethod(isBlockingSynchronousMethod = true) public abstract WritableMap getObject(ReadableMap arg); - @ReactMethod(isBlockingSynchronousMethod = true) - public abstract WritableMap getUnsafeObject(ReadableMap arg); - @ReactMethod public abstract void voidFunc(); diff --git a/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java b/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java index f919eb5bd2dd..bc357d2baa3d 100644 --- a/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java +++ b/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java @@ -109,16 +109,6 @@ public WritableMap getObject(ReadableMap arg) { return map; } - @DoNotStrip - @Override - @SuppressWarnings("unused") - public WritableMap getUnsafeObject(ReadableMap arg) { - WritableNativeMap map = new WritableNativeMap(); - map.merge(arg); - log("getUnsafeObject", arg, map); - return map; - } - @DoNotStrip @SuppressWarnings("unused") @Override diff --git a/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.h b/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.h index b14289e1cd1b..4738cff8d10a 100644 --- a/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.h +++ b/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.h @@ -26,7 +26,6 @@ - (NSString *)getString:(NSString *)arg; - (NSArray> *)getArray:(NSArray *)arg; - (NSDictionary *)getObject:(NSDictionary *)arg; -- (NSDictionary *)getUnsafeObject:(NSDictionary *)arg; - (NSNumber *)getRootTag:(double)arg; - (NSDictionary *)getValue:(double)x y:(NSString *)y z:(NSDictionary *)z; - (void)getValueWithCallback:(RCTResponseSenderBlock)callback; diff --git a/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.mm b/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.mm index 7337f63d5181..70f69b107bab 100644 --- a/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.mm +++ b/ReactCommon/react/nativemodule/samples/platform/ios/RCTNativeSampleTurboModuleSpec.mm @@ -70,16 +70,6 @@ .invokeObjCMethod(rt, ObjectKind, "getObject", @selector(getObject:), args, count); } -static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject( - facebook::jsi::Runtime &rt, - TurboModule &turboModule, - const facebook::jsi::Value *args, - size_t count) -{ - return static_cast(turboModule) - .invokeObjCMethod(rt, ObjectKind, "getUnsafeObject", @selector(getUnsafeObject:), args, count); -} - static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag( facebook::jsi::Runtime &rt, TurboModule &turboModule, @@ -140,7 +130,6 @@ methodMap_["getString"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString}; methodMap_["getArray"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray}; methodMap_["getObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject}; - methodMap_["getUnsafeObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject}; methodMap_["getRootTag"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag}; methodMap_["getValue"] = MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue}; methodMap_["getValueWithCallback"] = diff --git a/ReactCommon/react/nativemodule/samples/platform/ios/RCTSampleTurboModule.mm b/ReactCommon/react/nativemodule/samples/platform/ios/RCTSampleTurboModule.mm index ac1d236ed7dc..3e019c791eab 100644 --- a/ReactCommon/react/nativemodule/samples/platform/ios/RCTSampleTurboModule.mm +++ b/ReactCommon/react/nativemodule/samples/platform/ios/RCTSampleTurboModule.mm @@ -96,11 +96,6 @@ - (NSDictionary *)constantsToExport return arg; } -RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg) -{ - return arg; -} - RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg) { return @(arg); diff --git a/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.cpp b/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.cpp index a66b01ef2c16..5eea42c3d805 100644 --- a/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.cpp +++ b/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.cpp @@ -69,12 +69,6 @@ std::vector SampleTurboCxxModuleLegacyImpl::getMethods() { return getObject(xplat::jsArgAsObject(args, 0)); }, CxxModule::SyncTag), - CxxModule::Method( - "getUnsafeObject", - [this](folly::dynamic args) { - return getUnsafeObject(xplat::jsArgAsObject(args, 0)); - }, - CxxModule::SyncTag), CxxModule::Method( "getRootTag", [this](folly::dynamic args) { @@ -132,11 +126,6 @@ folly::dynamic SampleTurboCxxModuleLegacyImpl::getObject( return arg; } -folly::dynamic SampleTurboCxxModuleLegacyImpl::getUnsafeObject( - const folly::dynamic &arg) { - return arg; -} - double SampleTurboCxxModuleLegacyImpl::getRootTag(double arg) { return arg; } diff --git a/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.h b/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.h index a3aa44062f57..72d0e3b4adbf 100644 --- a/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.h +++ b/ReactCommon/react/nativemodule/samples/platform/ios/SampleTurboCxxModuleLegacyImpl.h @@ -31,7 +31,6 @@ class SampleTurboCxxModuleLegacyImpl std::string getString(const std::string &arg); folly::dynamic getArray(const folly::dynamic &arg); folly::dynamic getObject(const folly::dynamic &arg); - folly::dynamic getUnsafeObject(const folly::dynamic &arg); double getRootTag(double arg); folly::dynamic getValue(double x, const std::string &y, const folly::dynamic &z); diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index ba068284c4cc..48d652328d81 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -1623,15 +1623,7 @@ ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView( } ComponentDescriptor const &componentDescriptor = getComponentDescriptorForShadowView(startingView); - - // Base the mutated view on the finalView, so that the following stay - // consistent: - // - state - // - eventEmitter - // For now, we do not allow interpolation of state. And we probably never - // will, so make sure we always keep the mounting layer consistent with the - // "final" state. - auto mutatedShadowView = ShadowView(finalView); + auto mutatedShadowView = ShadowView(startingView); if (startingView.props == nullptr || finalView.props == nullptr) { return finalView; diff --git a/ReactCommon/react/renderer/components/view/ViewComponentDescriptor.h b/ReactCommon/react/renderer/components/view/ViewComponentDescriptor.h index 4ec013c7fb5b..fd078e656c89 100644 --- a/ReactCommon/react/renderer/components/view/ViewComponentDescriptor.h +++ b/ReactCommon/react/renderer/components/view/ViewComponentDescriptor.h @@ -25,15 +25,7 @@ class ViewComponentDescriptor float animationProgress, const SharedProps &props, const SharedProps &newProps) const override { -#ifdef ANDROID - // On Android only, the merged props should have the same RawProps as the - // final props struct - SharedProps interpolatedPropsShared = - (newProps != nullptr ? cloneProps(newProps, newProps->rawProps) - : cloneProps(newProps, {})); -#else SharedProps interpolatedPropsShared = cloneProps(newProps, {}); -#endif interpolateViewProps( animationProgress, props, newProps, interpolatedPropsShared); diff --git a/ReactCommon/react/renderer/components/view/ViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/ViewEventEmitter.cpp index fd2e83420b6b..a81f85af595c 100644 --- a/ReactCommon/react/renderer/components/view/ViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/ViewEventEmitter.cpp @@ -35,81 +35,42 @@ void ViewEventEmitter::onAccessibilityEscape() const { #pragma mark - Layout void ViewEventEmitter::onLayout(const LayoutMetrics &layoutMetrics) const { - // A copy of a shared pointer (`layoutEventState_`) establishes shared - // ownership that will be captured by lambda. - auto layoutEventState = layoutEventState_; - - // Dispatched `frame` values to JavaScript thread are throttled here. - // Basic ideas: - // - Scheduling a lambda with some value that already was dispatched, does - // nothing. - // - If some lambda is already in flight, we don't schedule another; - // - When a lambda is being executed on the JavaScript thread, the *most - // recent* `frame` value is used (not the value that was current at the - // moment of scheduling the lambda). - // - // This implies the following caveats: - // - Some events can be skipped; - // - When values change rapidly, even events with different values - // can be skipped (only the very last will be delivered). - // - Ordering is preserved. - + // Due to State Reconciliation, `onLayout` can be called potentially many + // times with identical layoutMetrics. Ensure that the JS event is only + // dispatched when the value changes. { - std::lock_guard guard(layoutEventState->mutex); - - // If a *particular* `frame` was already dispatched to the JavaScript side, - // no other work is required. - if (layoutEventState->frame == layoutMetrics.frame && - layoutEventState->wasDispatched) { + std::lock_guard guard(layoutMetricsMutex_); + if (lastLayoutMetrics_ == layoutMetrics) { return; } - - // If the *particular* `frame` was not already dispatched *or* - // some *other* `frame` was dispatched before, - // we need to schedule the dispatching. - layoutEventState->wasDispatched = false; - layoutEventState->frame = layoutMetrics.frame; - - // Something is already in flight, dispatching another event is not - // required. - if (layoutEventState->isDispatching) { - return; - } - - layoutEventState->isDispatching = true; + lastLayoutMetrics_ = layoutMetrics; } - dispatchEvent("layout", [layoutEventState](jsi::Runtime &runtime) { - auto frame = Rect{}; - - { - std::lock_guard guard(layoutEventState->mutex); - - layoutEventState->isDispatching = false; - - // If some *particular* `frame` was already dispatched before, - // and since then there were no other new values of the `frame` observed, - // do nothing. - if (layoutEventState->wasDispatched) { - return jsi::Value::null(); - } - - frame = layoutEventState->frame; - - // If some *particular* `frame` was *not* already dispatched before, - // it's time to dispatch it and mark as dispatched. - layoutEventState->wasDispatched = true; - } - - auto layout = jsi::Object(runtime); - layout.setProperty(runtime, "x", frame.origin.x); - layout.setProperty(runtime, "y", frame.origin.y); - layout.setProperty(runtime, "width", frame.size.width); - layout.setProperty(runtime, "height", frame.size.height); - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "layout", std::move(layout)); - return jsi::Value(std::move(payload)); - }); + auto expectedEventCount = ++*eventCounter_; + + // dispatchUniqueEvent only drops consecutive onLayout events to the same + // node. We want to drop *any* unprocessed onLayout events when there's a + // newer one. + dispatchEvent( + "layout", + [frame = layoutMetrics.frame, + expectedEventCount, + eventCounter = eventCounter_](jsi::Runtime &runtime) { + auto actualEventCount = eventCounter->load(); + if (expectedEventCount != actualEventCount) { + // Drop stale events + return jsi::Value::null(); + } + + auto layout = jsi::Object(runtime); + layout.setProperty(runtime, "x", frame.origin.x); + layout.setProperty(runtime, "y", frame.origin.y); + layout.setProperty(runtime, "width", frame.size.width); + layout.setProperty(runtime, "height", frame.size.height); + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "layout", std::move(layout)); + return jsi::Value(std::move(payload)); + }); } } // namespace react diff --git a/ReactCommon/react/renderer/components/view/ViewEventEmitter.h b/ReactCommon/react/renderer/components/view/ViewEventEmitter.h index 1a4e17e84e51..c00802431a18 100644 --- a/ReactCommon/react/renderer/components/view/ViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/ViewEventEmitter.h @@ -38,35 +38,11 @@ class ViewEventEmitter : public TouchEventEmitter { void onLayout(const LayoutMetrics &layoutMetrics) const; private: - /* - * Contains the most recent `frame` and a `mutex` protecting access to it. - */ - struct LayoutEventState { - /* - * Protects an access to other fields of the struct. - */ - std::mutex mutex; + mutable std::mutex layoutMetricsMutex_; + mutable LayoutMetrics lastLayoutMetrics_; - /* - * Last dispatched `frame` value or value that's being dispatched right now. - */ - Rect frame{}; - - /* - * Indicates that the `frame` value was already dispatched (and dispatching - * of the *same* value is not needed). - */ - bool wasDispatched{false}; - - /* - * Indicates that some lambda is already being dispatching (and dispatching - * another one is not needed). - */ - bool isDispatching{false}; - }; - - mutable std::shared_ptr layoutEventState_{ - std::make_shared()}; + mutable std::shared_ptr eventCounter_{ + std::make_shared(0)}; }; } // namespace react diff --git a/ReactCommon/react/renderer/core/BatchedEventQueue.cpp b/ReactCommon/react/renderer/core/BatchedEventQueue.cpp index 55bc9c7b0aab..055e9c59ec41 100644 --- a/ReactCommon/react/renderer/core/BatchedEventQueue.cpp +++ b/ReactCommon/react/renderer/core/BatchedEventQueue.cpp @@ -13,8 +13,10 @@ namespace react { BatchedEventQueue::BatchedEventQueue( EventPipe eventPipe, StatePipe statePipe, - std::unique_ptr eventBeat) - : EventQueue(eventPipe, statePipe, std::move(eventBeat)) {} + std::unique_ptr eventBeat, + bool enableV2EventCoalescing) + : EventQueue(eventPipe, statePipe, std::move(eventBeat)), + enableV2EventCoalescing_(enableV2EventCoalescing) {} void BatchedEventQueue::onEnqueue() const { EventQueue::onEnqueue(); @@ -26,26 +28,38 @@ void BatchedEventQueue::enqueueUniqueEvent(RawEvent const &rawEvent) const { { std::lock_guard lock(queueMutex_); - auto repeatedEvent = eventQueue_.rend(); - - for (auto it = eventQueue_.rbegin(); it != eventQueue_.rend(); ++it) { - if (it->type == rawEvent.type && - it->eventTarget == rawEvent.eventTarget) { - repeatedEvent = it; - break; - } else if (it->eventTarget == rawEvent.eventTarget) { - // It is necessary to maintain order of different event types - // for the same target. If the same target has event types A1, B1 - // in the event queue and event A2 occurs. A1 has to stay in the - // queue. - break; + if (enableV2EventCoalescing_) { + auto repeatedEvent = eventQueue_.rend(); + + for (auto it = eventQueue_.rbegin(); it != eventQueue_.rend(); ++it) { + if (it->type == rawEvent.type && + it->eventTarget == rawEvent.eventTarget) { + repeatedEvent = it; + break; + } else if (it->eventTarget == rawEvent.eventTarget) { + // It is necessary to maintain order of different event types + // for the same target. If the same target has event types A1, B1 + // in the event queue and event A2 occurs. A1 has to stay in the + // queue. + break; + } } - } - if (repeatedEvent == eventQueue_.rend()) { - eventQueue_.push_back(rawEvent); + if (repeatedEvent == eventQueue_.rend()) { + eventQueue_.push_back(rawEvent); + } else { + *repeatedEvent = std::move(rawEvent); + } } else { - *repeatedEvent = std::move(rawEvent); + if (!eventQueue_.empty()) { + auto const position = eventQueue_.back(); + if (position.type == rawEvent.type && + position.eventTarget == rawEvent.eventTarget) { + eventQueue_.pop_back(); + } + } + + eventQueue_.push_back(rawEvent); } } diff --git a/ReactCommon/react/renderer/core/BatchedEventQueue.h b/ReactCommon/react/renderer/core/BatchedEventQueue.h index 827ef87886ef..85f7c8bca459 100644 --- a/ReactCommon/react/renderer/core/BatchedEventQueue.h +++ b/ReactCommon/react/renderer/core/BatchedEventQueue.h @@ -21,7 +21,8 @@ class BatchedEventQueue final : public EventQueue { BatchedEventQueue( EventPipe eventPipe, StatePipe statePipe, - std::unique_ptr eventBeat); + std::unique_ptr eventBeat, + bool enableV2EventCoalescing); void onEnqueue() const override; @@ -31,6 +32,9 @@ class BatchedEventQueue final : public EventQueue { * Can be called on any thread. */ void enqueueUniqueEvent(const RawEvent &rawEvent) const; + + private: + bool const enableV2EventCoalescing_; }; } // namespace react diff --git a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index 18217e60e1e5..497cf561e3d8 100644 --- a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -127,14 +127,6 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { const SharedProps &props, const SharedProps &newProps) const override { // By default, this does nothing. -#ifdef ANDROID - // On Android only, the merged props should have the same RawProps as the - // final props struct - if (newProps != nullptr) { - return cloneProps(newProps, newProps->rawProps); - } -#endif - return cloneProps(newProps, {}); }; diff --git a/ReactCommon/react/renderer/core/EventDispatcher.cpp b/ReactCommon/react/renderer/core/EventDispatcher.cpp index 8ece2ab3a07a..75d046082871 100644 --- a/ReactCommon/react/renderer/core/EventDispatcher.cpp +++ b/ReactCommon/react/renderer/core/EventDispatcher.cpp @@ -21,7 +21,8 @@ EventDispatcher::EventDispatcher( StatePipe const &statePipe, EventBeat::Factory const &synchonousEventBeatFactory, EventBeat::Factory const &asynchonousEventBeatFactory, - EventBeat::SharedOwnerBox const &ownerBox) + EventBeat::SharedOwnerBox const &ownerBox, + bool enableV2EventCoalescing) : synchronousUnbatchedQueue_(std::make_unique( eventPipe, statePipe, @@ -29,7 +30,8 @@ EventDispatcher::EventDispatcher( synchronousBatchedQueue_(std::make_unique( eventPipe, statePipe, - synchonousEventBeatFactory(ownerBox))), + synchonousEventBeatFactory(ownerBox), + enableV2EventCoalescing)), asynchronousUnbatchedQueue_(std::make_unique( eventPipe, statePipe, @@ -37,7 +39,8 @@ EventDispatcher::EventDispatcher( asynchronousBatchedQueue_(std::make_unique( eventPipe, statePipe, - asynchonousEventBeatFactory(ownerBox))) {} + asynchonousEventBeatFactory(ownerBox), + enableV2EventCoalescing)) {} void EventDispatcher::dispatchEvent( RawEvent const &rawEvent, diff --git a/ReactCommon/react/renderer/core/EventDispatcher.h b/ReactCommon/react/renderer/core/EventDispatcher.h index d3fb37c13275..eb0abf52eba7 100644 --- a/ReactCommon/react/renderer/core/EventDispatcher.h +++ b/ReactCommon/react/renderer/core/EventDispatcher.h @@ -37,7 +37,8 @@ class EventDispatcher { StatePipe const &statePipe, EventBeat::Factory const &synchonousEventBeatFactory, EventBeat::Factory const &asynchonousEventBeatFactory, - EventBeat::SharedOwnerBox const &ownerBox); + EventBeat::SharedOwnerBox const &ownerBox, + bool enableV2EventCoalescing); /* * Dispatches a raw event with given priority using event-delivery pipe. diff --git a/ReactCommon/react/renderer/mapbuffer/BUCK b/ReactCommon/react/renderer/mapbuffer/BUCK index dc2462158457..5bd4038921ef 100644 --- a/ReactCommon/react/renderer/mapbuffer/BUCK +++ b/ReactCommon/react/renderer/mapbuffer/BUCK @@ -22,7 +22,7 @@ rn_xplat_cxx_library( [ ("", "*.h"), ], - prefix = "react/renderer/mapbuffer", + prefix = "react", ), compiler_flags = [ "-fexceptions", @@ -63,6 +63,5 @@ fb_xplat_cxx_test( deps = [ "//xplat/folly:molly", "//xplat/third-party/gmock:gtest", - react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"), ], ) diff --git a/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp index 2113ecf41e0a..085ccae66c97 100644 --- a/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp +++ b/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp @@ -14,9 +14,5 @@ MapBuffer::MapBuffer() {} MapBuffer::~MapBuffer() {} -int MapBuffer::getSize() { - return 0; -} - } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index 1a3e5b12e3b4..3ce365990d52 100644 --- a/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -31,8 +31,6 @@ class MapBuffer { public: MapBuffer(); virtual ~MapBuffer(); - - int getSize(); }; } // namespace react diff --git a/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp b/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp index eba9018f9097..4785f09ae7e4 100644 --- a/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp +++ b/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp @@ -7,14 +7,8 @@ #include -#include #include -#include -using namespace facebook::react; - -// Dummy test to create setup of tests -TEST(MapBufferTest, testMapCreation) { - auto buffer = MapBuffer(); - assert(buffer.getSize() == 0); +TEST(MapBufferTest, testSomething) { + // TODO } diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 648b1116844d..313b4e201ec6 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -67,7 +67,8 @@ Scheduler::Scheduler( statePipe, schedulerToolbox.synchronousEventBeatFactory, schedulerToolbox.asynchronousEventBeatFactory, - eventOwnerBox); + eventOwnerBox, + reactNativeConfig_->getBool("react_fabric:enable_v2_event_coalescing")); // Casting to `std::shared_ptr`. auto eventDispatcher = diff --git a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp index 18b1aaa34e46..56a72d3881fe 100644 --- a/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp +++ b/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp @@ -10,7 +10,6 @@ #include #include #include -#include using namespace facebook::jni; @@ -41,31 +40,65 @@ TextMeasurement TextLayoutManager::measureCachedSpannableById( int64_t cacheId, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const { + const jni::global_ref &fabricUIManager = + contextContainer_->at>("FabricUIManager"); + auto env = Environment::current(); auto attachmentPositions = env->NewFloatArray(0); + + static auto measure = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + auto minimumSize = layoutConstraints.minimumSize; auto maximumSize = layoutConstraints.maximumSize; + local_ref componentName = make_jstring("RCTText"); folly::dynamic cacheIdMap = folly::dynamic::object; cacheIdMap["cacheId"] = cacheId; + local_ref attributedStringRNM = + ReadableNativeMap::newObjectCxxArgs(cacheIdMap); + local_ref paragraphAttributesRNM = + ReadableNativeMap::newObjectCxxArgs(toDynamic(paragraphAttributes)); - auto size = measureAndroidComponent( - contextContainer_, + local_ref attributedStringRM = make_local( + reinterpret_cast(attributedStringRNM.get())); + local_ref paragraphAttributesRM = make_local( + reinterpret_cast(paragraphAttributesRNM.get())); + auto size = yogaMeassureToSize(measure( + fabricUIManager, -1, // TODO: we should pass rootTag in - "RCTText", - cacheIdMap, - toDynamic(paragraphAttributes), + componentName.get(), + attributedStringRM.get(), + paragraphAttributesRM.get(), nullptr, minimumSize.width, maximumSize.width, minimumSize.height, maximumSize.height, - attachmentPositions); + attachmentPositions)); // Clean up allocated ref - it still takes up space in the JNI ref table even // though it's 0 length env->DeleteLocalRef(attachmentPositions); + // Explicitly release smart pointers to free up space faster in JNI tables + componentName.reset(); + attributedStringRM.reset(); + attributedStringRNM.reset(); + paragraphAttributesRM.reset(); + paragraphAttributesRNM.reset(); + // TODO: currently we do not support attachments for cached IDs - should we? auto attachments = TextMeasurement::Attachments{}; @@ -126,6 +159,9 @@ TextMeasurement TextLayoutManager::doMeasure( AttributedString attributedString, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const { + const jni::global_ref &fabricUIManager = + contextContainer_->at>("FabricUIManager"); + int attachmentsCount = 0; for (auto fragment : attributedString.getFragments()) { if (fragment.isAttachment()) { @@ -135,22 +171,46 @@ TextMeasurement TextLayoutManager::doMeasure( auto env = Environment::current(); auto attachmentPositions = env->NewFloatArray(attachmentsCount * 2); + static auto measure = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + auto minimumSize = layoutConstraints.minimumSize; auto maximumSize = layoutConstraints.maximumSize; auto serializedAttributedString = toDynamic(attributedString); - auto size = measureAndroidComponent( - contextContainer_, + local_ref componentName = make_jstring("RCTText"); + local_ref attributedStringRNM = + ReadableNativeMap::newObjectCxxArgs(serializedAttributedString); + local_ref paragraphAttributesRNM = + ReadableNativeMap::newObjectCxxArgs(toDynamic(paragraphAttributes)); + + local_ref attributedStringRM = make_local( + reinterpret_cast(attributedStringRNM.get())); + local_ref paragraphAttributesRM = make_local( + reinterpret_cast(paragraphAttributesRNM.get())); + auto size = yogaMeassureToSize(measure( + fabricUIManager, -1, // TODO: we should pass rootTag in - "RCTText", - serializedAttributedString, - toDynamic(paragraphAttributes), + componentName.get(), + attributedStringRM.get(), + paragraphAttributesRM.get(), nullptr, minimumSize.width, maximumSize.width, minimumSize.height, maximumSize.height, - attachmentPositions); + attachmentPositions)); jfloat *attachmentData = env->GetFloatArrayElements(attachmentPositions, 0); @@ -179,6 +239,13 @@ TextMeasurement TextLayoutManager::doMeasure( attachmentPositions, attachmentData, JNI_ABORT); env->DeleteLocalRef(attachmentPositions); + // Explicitly release smart pointers to free up space faster in JNI tables + componentName.reset(); + attributedStringRM.reset(); + attributedStringRNM.reset(); + paragraphAttributesRM.reset(); + paragraphAttributesRNM.reset(); + return TextMeasurement{size, attachments}; } diff --git a/ReactCommon/react/utils/LayoutManager.h b/ReactCommon/react/utils/LayoutManager.h deleted file mode 100644 index 9789fc87d997..000000000000 --- a/ReactCommon/react/utils/LayoutManager.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -#ifdef ANDROID - -using namespace facebook::jni; - -Size measureAndroidComponent( - const ContextContainer::Shared &contextContainer, - Tag rootTag, - std::string componentName, - folly::dynamic localData, - folly::dynamic props, - folly::dynamic state, - float minWidth, - float maxWidth, - float minHeight, - float maxHeight, - jfloatArray attachmentPositions) { - const jni::global_ref &fabricUIManager = - contextContainer->at>("FabricUIManager"); - - static auto measure = - jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") - ->getMethod("measure"); - - auto componentNameRef = make_jstring(componentName); - local_ref localDataRNM = - ReadableNativeMap::newObjectCxxArgs(localData); - local_ref propsRNM = - ReadableNativeMap::newObjectCxxArgs(props); - local_ref stateRNM = - ReadableNativeMap::newObjectCxxArgs(state); - - local_ref localDataRM = - make_local(reinterpret_cast(localDataRNM.get())); - local_ref propsRM = - make_local(reinterpret_cast(propsRNM.get())); - local_ref stateRM = - make_local(reinterpret_cast(stateRNM.get())); - - auto size = yogaMeassureToSize(measure( - fabricUIManager, - rootTag, - componentNameRef.get(), - localDataRM.get(), - propsRM.get(), - stateRM.get(), - minWidth, - maxWidth, - minHeight, - maxHeight, - attachmentPositions)); - - // Explicitly release smart pointers to free up space faster in JNI tables - componentNameRef.reset(); - localDataRM.reset(); - localDataRNM.reset(); - propsRM.reset(); - propsRNM.reset(); - stateRM.reset(); - stateRNM.reset(); - - return size; -} - -#endif - -} // namespace react -} // namespace facebook diff --git a/build.gradle.kts b/build.gradle.kts index 532c996c131d..4d2c53b0189e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,9 +34,4 @@ allprojects { google() jcenter() } - - // used to override ndk path on CI - if (System.getenv("LOCAL_ANDROID_NDK_VERSION") != null) { - setProperty("ANDROID_NDK_VERSION", System.getenv("LOCAL_ANDROID_NDK_VERSION")) - } } diff --git a/gradle.properties b/gradle.properties index dd6698ad75bd..f0a3fdf5d79f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.parallel=true - -ANDROID_NDK_VERSION=20.1.5948944 diff --git a/packages/eslint-config-react-native-community/index.js b/packages/eslint-config-react-native-community/index.js index b388eb4db03c..9f03ade38b7d 100644 --- a/packages/eslint-config-react-native-community/index.js +++ b/packages/eslint-config-react-native-community/index.js @@ -81,6 +81,7 @@ module.exports = { __DEV__: true, __dirname: false, __fbBatchedBridgeConfig: false, + AbortController: false, alert: false, cancelAnimationFrame: false, cancelIdleCallback: false, diff --git a/packages/eslint-plugin-codegen/__tests__/react-native-modules-test.js b/packages/eslint-plugin-codegen/__tests__/react-native-modules-test.js index 7591ffb86a8d..18880a57eac5 100644 --- a/packages/eslint-plugin-codegen/__tests__/react-native-modules-test.js +++ b/packages/eslint-plugin-codegen/__tests__/react-native-modules-test.js @@ -18,20 +18,7 @@ const NATIVE_MODULES_DIR = __dirname; const eslintTester = new ESLintTester(); -const VALID_SPECS = [ - { - code: ` -import {TurboModuleRegistry, type TurboModule} from 'react-native'; -import type {UnsafeObject} from 'react-native/Libraries/Types/CodegenTypes'; - -export interface Spec extends TurboModule { - func1(a: string): UnsafeObject, -} -export default TurboModuleRegistry.get('XYZ'); - `, - filename: `${NATIVE_MODULES_DIR}/NativeXYZ.js`, - }, -]; +const VALID_SPECS = []; const INVALID_SPECS = [ // Untyped NativeModule require diff --git a/packages/react-native-codegen/BUCK b/packages/react-native-codegen/BUCK index d9da90a41f0e..07b517fc2f71 100644 --- a/packages/react-native-codegen/BUCK +++ b/packages/react-native-codegen/BUCK @@ -28,7 +28,6 @@ rn_codegen_components( rn_codegen_modules( name = "codegen_tests", - android_package_name = "com.facebook.fbreact.specs", native_module_spec_name = "FBReactNativeTestSpec", schema_target = ":codegen_tests_schema", ) diff --git a/packages/react-native-codegen/DEFS.bzl b/packages/react-native-codegen/DEFS.bzl index 7cd02e2c0e2e..06a9ef6c9371 100644 --- a/packages/react-native-codegen/DEFS.bzl +++ b/packages/react-native-codegen/DEFS.bzl @@ -105,7 +105,6 @@ def rn_codegen_cli(): def rn_codegen_modules( name, native_module_spec_name, - android_package_name, library_labels = [], schema_target = ""): generate_fixtures_rule_name = "generate_fixtures_modules-{}".format(name) @@ -119,13 +118,7 @@ def rn_codegen_modules( fb_native.genrule( name = generate_fixtures_rule_name, srcs = native.glob(["src/generators/**/*.js"]), - cmd = "$(exe {generator_script}) $(location {schema_target}) {library_name} $OUT {native_module_spec_name} {android_package_name}".format( - generator_script = react_native_root_target("packages/react-native-codegen:generate_all_from_schema"), - schema_target = schema_target, - library_name = name, - native_module_spec_name = native_module_spec_name, - android_package_name = android_package_name, - ), + cmd = "$(exe {}) $(location {}) {} $OUT {}".format(react_native_root_target("packages/react-native-codegen:generate_all_from_schema"), schema_target, name, native_module_spec_name), out = "codegenfiles-{}".format(name), labels = ["codegen_rule"], ) @@ -135,10 +128,9 @@ def rn_codegen_modules( ################## fb_native.genrule( name = generate_module_java_name, - cmd = "mkdir -p $OUT/{spec_path} && cp -r $(location {generator_target})/java/{spec_path}/* $OUT/{spec_path}/".format( - spec_path = android_package_name.replace(".", "/"), - generator_target = ":" + generate_fixtures_rule_name, - ), + # TODO: support different package name internally. + # Right now, it's hardcoded to `com.facebook.fbreact.specs`. + cmd = "mkdir -p $OUT/com/facebook/fbreact/specs && cp -r $(location :{})/java/com/facebook/fbreact/specs/* $OUT/com/facebook/fbreact/specs/".format(generate_fixtures_rule_name), out = "src", labels = ["codegen_rule"], ) diff --git a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/resolver/FunctionResolvedType.java b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/resolver/FunctionResolvedType.java index 7896494eebb2..2a7468ce11e2 100644 --- a/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/resolver/FunctionResolvedType.java +++ b/packages/react-native-codegen/android/gradlePlugin-build/gradlePlugin/src/main/java/com/facebook/react/codegen/generator/resolver/FunctionResolvedType.java @@ -103,8 +103,6 @@ public MethodSpec getGeneratedMethodWithReactAnnotation(String methodName) { // React methods need special `@ReactMethod` annotation for now. methodBuilder.addAnnotation(annotationBuilder.build()); - // TODO(T82242829) Add @DoNotStrip annotation - return methodBuilder.build(); } diff --git a/packages/react-native-codegen/src/cli/generators/generate-all.js b/packages/react-native-codegen/src/cli/generators/generate-all.js index 6b7e8509e958..613eec58c0bf 100644 --- a/packages/react-native-codegen/src/cli/generators/generate-all.js +++ b/packages/react-native-codegen/src/cli/generators/generate-all.js @@ -19,7 +19,7 @@ const fs = require('fs'); const mkdirp = require('mkdirp'); const args = process.argv.slice(2); -if (args.length < 4) { +if (args.length !== 4) { throw new Error( `Expected to receive path to schema, library name, output directory and module spec name. Received ${args.join( ', ', diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 8739e09bafad..b8238cf4786d 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -371,11 +371,10 @@ module.exports = { packageName?: string, ): FilesOutput { const files = new Map(); - const nativeModules = getModules(schema); - const normalizedPackageName = - packageName == null ? 'com.facebook.fbreact.specs' : packageName; + packageName != null ? packageName : 'com.facebook.fbreact.specs'; const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`; + const nativeModules = getModules(schema); Object.keys(nativeModules).forEach(hasteModuleName => { const { @@ -396,7 +395,6 @@ module.exports = { 'com.facebook.react.bridge.ReactMethod', 'com.facebook.react.bridge.ReactModuleWithSpec', 'com.facebook.react.turbomodule.core.interfaces.TurboModule', - 'com.facebook.proguard.annotations.DoNotStrip', ]); const methods = properties.map(method => { @@ -445,7 +443,7 @@ module.exports = { const methodJavaAnnotation = `@ReactMethod${ isSyncMethod ? '(isBlockingSynchronousMethod = true)' : '' - }\n @DoNotStrip`; + }`; const methodBody = method.optional ? getFalsyReturnStatementFromReturnType( methodTypeAnnotation.returnTypeAnnotation, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleCpp-test.js index 6904c804baf9..6dbe02f952f1 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleCpp-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleCpp-test.js @@ -22,12 +22,7 @@ describe('GenerateModuleCpp', () => { it(`can generate fixture ${fixtureName}`, () => { expect( - generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ), + generator.generate(fixtureName, fixture, 'SampleSpec'), ).toMatchSnapshot(); }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleH-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleH-test.js index ffe05d6880de..658221b56298 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleH-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleH-test.js @@ -22,12 +22,7 @@ describe('GenerateModuleH', () => { it(`can generate fixture ${fixtureName}`, () => { expect( - generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ), + generator.generate(fixtureName, fixture, 'SampleSpec'), ).toMatchSnapshot(); }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js index fc148e2efc47..3c4943971857 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js @@ -21,12 +21,7 @@ describe('GenerateModuleHObjCpp', () => { const fixture = fixtures[fixtureName]; it(`can generate fixture ${fixtureName}`, () => { - const output = generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ); + const output = generator.generate(fixtureName, fixture, 'SampleSpec'); expect( new Map([['SampleSpec.h', output.get('SampleSpec.h')]]), ).toMatchSnapshot(); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js index e7c77457dd7e..5ccb87d2d4b6 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js @@ -22,12 +22,7 @@ describe('GenerateModuleJavaSpec', () => { it(`can generate fixture ${fixtureName}`, () => { expect( - generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ), + generator.generate(fixtureName, fixture, 'SampleSpec'), ).toMatchSnapshot(); }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js index d3dae2784407..e6030bca0fc2 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js @@ -22,12 +22,7 @@ describe('GenerateModuleJniCpp', () => { it(`can generate fixture ${fixtureName}`, () => { expect( - generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ), + generator.generate(fixtureName, fixture, 'SampleSpec'), ).toMatchSnapshot(); }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniH-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniH-test.js index d5ac7ecfb328..dcbd78a42c60 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniH-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniH-test.js @@ -22,12 +22,7 @@ describe('GenerateModuleJniH', () => { it(`can generate fixture ${fixtureName}`, () => { expect( - generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ), + generator.generate(fixtureName, fixture, 'SampleSpec'), ).toMatchSnapshot(); }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleMm-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleMm-test.js index 3c0e56865854..ec8f4842a106 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleMm-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleMm-test.js @@ -21,12 +21,7 @@ describe('GenerateModuleMm', () => { const fixture = fixtures[fixtureName]; it(`can generate fixture ${fixtureName}`, () => { - const output = generator.generate( - fixtureName, - fixture, - 'SampleSpec', - 'com.facebook.fbreact.specs', - ); + const output = generator.generate(fixtureName, fixture, 'SampleSpec'); expect( new Map([ ['SampleSpec-generated.mm', output.get('SampleSpec-generated.mm')], diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap index 5e314758e47c..a5564d2fbaa1 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap @@ -16,7 +16,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -33,19 +32,15 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract WritableMap difficult(ReadableMap A); @ReactMethod - @DoNotStrip public abstract void optionals(ReadableMap A); @ReactMethod - @DoNotStrip public void optionalMethod(ReadableMap options, Callback callback, ReadableArray extras) {} @ReactMethod - @DoNotStrip public abstract void getArrays(ReadableMap options); } ", @@ -68,7 +63,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -102,7 +96,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -116,7 +109,6 @@ public abstract class AliasTurboModuleSpec extends ReactContextBaseJavaModule im } @ReactMethod - @DoNotStrip public abstract void cropImage(ReadableMap cropData); } ", @@ -139,7 +131,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -155,15 +146,12 @@ public abstract class NativeCameraRollManagerSpec extends ReactContextBaseJavaMo } @ReactMethod - @DoNotStrip public abstract void getPhotos(ReadableMap params, Promise promise); @ReactMethod - @DoNotStrip public abstract void saveToCameraRoll(String uri, String type, Promise promise); @ReactMethod - @DoNotStrip public abstract void deletePhotos(ReadableArray assets, Promise promise); } ", @@ -181,7 +169,6 @@ public abstract class NativeCameraRollManagerSpec extends ReactContextBaseJavaMo package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -196,23 +183,18 @@ public abstract class NativeExceptionsManagerSpec extends ReactContextBaseJavaMo } @ReactMethod - @DoNotStrip public abstract void reportFatalException(String message, ReadableArray stack, double exceptionId); @ReactMethod - @DoNotStrip public abstract void reportSoftException(String message, ReadableArray stack, double exceptionId); @ReactMethod - @DoNotStrip public void reportException(ReadableMap data) {} @ReactMethod - @DoNotStrip public abstract void updateExceptionMessage(String message, ReadableArray stack, double exceptionId); @ReactMethod - @DoNotStrip public void dismissRedbox() {} } ", @@ -235,7 +217,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; @@ -287,43 +268,33 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } @ReactMethod - @DoNotStrip public abstract void voidFunc(); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract boolean getBool(boolean arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract double getNumber(double arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract String getString(String arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract WritableArray getArray(ReadableArray arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract WritableMap getObject(ReadableMap arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract double getRootTag(double arg); @ReactMethod(isBlockingSynchronousMethod = true) - @DoNotStrip public abstract WritableMap getValue(double x, String y, ReadableMap z); @ReactMethod - @DoNotStrip public abstract void getValueWithCallback(Callback callback); @ReactMethod - @DoNotStrip public abstract void getValueWithPromise(boolean error, Promise promise); } ", @@ -346,7 +317,6 @@ Map { package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -359,7 +329,6 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } @ReactMethod - @DoNotStrip public abstract void voidFunc(); } ", @@ -377,7 +346,6 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo package com.facebook.fbreact.specs; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -390,7 +358,6 @@ public abstract class NativeSampleTurboModule2Spec extends ReactContextBaseJavaM } @ReactMethod - @DoNotStrip public abstract void voidFunc(); } ", diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index e05e185232ca..9ef58d4d0973 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -257,31 +257,6 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; -const NATIVE_MODULE_WITH_UNSAFE_OBJECT = ` -/** - * 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. - * - * @flow strict - * @format - */ - -'use strict'; - -import type {TurboModule} from '../RCTExport'; -import * as TurboModuleRegistry from '../TurboModuleRegistry'; -import type {UnsafeObject} from 'react-native/Libraries/Types/CodegenTypes'; - -export interface Spec extends TurboModule { - +getUnsafeObject: (o: UnsafeObject) => UnsafeObject, -} - -export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); - -`; - const NATIVE_MODULE_WITH_ROOT_TAG = ` /** * Copyright (c) Facebook, Inc. and its affiliates. @@ -580,7 +555,6 @@ module.exports = { NATIVE_MODULE_WITH_COMPLEX_OBJECTS, NATIVE_MODULE_WITH_COMPLEX_OBJECTS_WITH_NULLABLE_KEY, NATIVE_MODULE_WITH_SIMPLE_OBJECT, - NATIVE_MODULE_WITH_UNSAFE_OBJECT, NATIVE_MODULE_WITH_ROOT_TAG, NATIVE_MODULE_WITH_NULLABLE_PARAM, NATIVE_MODULE_WITH_BASIC_ARRAY, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index 2527921cea9f..1ce7e4797dda 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -1297,40 +1297,3 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_SIMPLE_O } }" `; - -exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_UNSAFE_OBJECT 1`] = ` -"{ - 'modules': { - 'NativeSampleTurboModule': { - 'type': 'NativeModule', - 'aliases': {}, - 'spec': { - 'properties': [ - { - 'name': 'getUnsafeObject', - 'optional': false, - 'typeAnnotation': { - 'type': 'FunctionTypeAnnotation', - 'returnTypeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - }, - 'params': [ - { - 'name': 'o', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] - } - } - ] - }, - 'moduleNames': [ - 'SampleTurboModule' - ] - } - } -}" -`; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/index.js b/packages/react-native-codegen/src/parsers/flow/modules/index.js index e00d157743a1..955f8358d5ab 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -197,7 +197,6 @@ function translateTypeAnnotation( type: 'FloatTypeAnnotation', }); } - case 'UnsafeObject': case 'Object': { return wrapNullable(nullable, { type: 'GenericObjectTypeAnnotation', diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 35822c377965..f3ddbec6b868 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -798,7 +798,7 @@ SPEC CHECKSUMS: CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f DoubleConversion: cde416483dac037923206447da6e1454df403714 FBLazyVector: fe973c09b2299b5e8154186ecf1f6554b4f70987 - FBReactNativeSpec: d0504078deb2ffa0fbee5032382f4ef165a1c8a8 + FBReactNativeSpec: 259a715466e53b411664fdfbe166dbde93ece5b6 Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: e4493b013c02d9347d5e0cb4d128680239f6c78a diff --git a/packages/rn-tester/android/app/build.gradle b/packages/rn-tester/android/app/build.gradle index 59a2e64275a3..8855fff3fd2b 100644 --- a/packages/rn-tester/android/app/build.gradle +++ b/packages/rn-tester/android/app/build.gradle @@ -127,7 +127,7 @@ def useIntlJsc = false android { compileSdkVersion 29 - ndkVersion ANDROID_NDK_VERSION + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index 09fdd06f9af7..bc227cdc94c3 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -68,8 +68,6 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { ]), getObject: () => NativeSampleTurboModule.getObject({a: 1, b: 'foo', c: null}), - getUnsafeObject: () => - NativeSampleTurboModule.getObject({a: 1, b: 'foo', c: null}), getRootTag: () => NativeSampleTurboModule.getRootTag(this.context), getValue: () => NativeSampleTurboModule.getValue(5, 'test', {a: 1, b: 'foo'}), diff --git a/scripts/generate-specs.sh b/scripts/generate-specs.sh index cd4ef2902d2b..69de08b3dcc3 100755 --- a/scripts/generate-specs.sh +++ b/scripts/generate-specs.sh @@ -7,18 +7,16 @@ # This script collects the JavaScript spec definitions for core # native modules and components, then uses react-native-codegen # to generate native code. -# -# Optionally, set these envvars to override defaults: -# - SRCS_DIR: Path to JavaScript sources -# - CODEGEN_MODULES_LIBRARY_NAME: Defaults to FBReactNativeSpec -# - CODEGEN_MODULES_OUTPUT_DIR: Defaults to Libraries/$CODEGEN_MODULES_LIBRARY_NAME/$CODEGEN_MODULES_LIBRARY_NAME -# - CODEGEN_COMPONENTS_LIBRARY_NAME: Defaults to rncore -# - CODEGEN_COMPONENTS_OUTPUT_DIR: Defaults to ReactCommon/react/renderer/components/$CODEGEN_COMPONENTS_LIBRARY_NAME +# The script will use the local react-native-codegen package by +# default. Optionally, set the CODEGEN_PATH to point to the +# desired codegen library (e.g. when using react-native-codegen +# from npm). # # Usage: # ./scripts/generate-specs.sh -# SRCS_DIR=myapp/js CODEGEN_MODULES_LIBRARY_NAME=MySpecs CODEGEN_MODULES_OUTPUT_DIR=myapp/MySpecs ./scripts/generate-specs.sh # +# Examples: +# CODEGEN_PATH=.. ./scripts/generate-specs.sh # shellcheck disable=SC2038 @@ -27,6 +25,7 @@ set -e THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) TEMP_DIR=$(mktemp -d /tmp/react-native-codegen-XXXXXXXX) RN_DIR=$(cd "$THIS_DIR/.." && pwd) +CODEGEN_PATH="${CODEGEN_PATH:-$(cd "$RN_DIR/packages/react-native-codegen" && pwd)}" YARN_BINARY="${YARN_BINARY:-$(command -v yarn)}" USE_FABRIC="${USE_FABRIC:-0}" @@ -41,30 +40,14 @@ describe () { } main() { - SRCS_DIR=${SRCS_DIR:-$(cd "$RN_DIR/Libraries" && pwd)} - CODEGEN_MODULES_LIBRARY_NAME=${CODEGEN_MODULES_LIBRARY_NAME:-FBReactNativeSpec} + SRCS_DIR=$(cd "$RN_DIR/Libraries" && pwd) - CODEGEN_COMPONENTS_LIBRARY_NAME=${CODEGEN_COMPONENTS_LIBRARY_NAME:-rncore} - CODEGEN_MODULES_OUTPUT_DIR=${CODEGEN_MODULES_OUTPUT_DIR:-"$RN_DIR/Libraries/$CODEGEN_MODULES_LIBRARY_NAME/$CODEGEN_MODULES_LIBRARY_NAME"} - # TODO: $CODEGEN_COMPONENTS_PATH should be programmatically specified, and may change with use_frameworks! support. - CODEGEN_COMPONENTS_PATH="ReactCommon/react/renderer/components" - CODEGEN_COMPONENTS_OUTPUT_DIR=${CODEGEN_COMPONENTS_OUTPUT_DIR:-"$RN_DIR/$CODEGEN_COMPONENTS_PATH/$CODEGEN_COMPONENTS_LIBRARY_NAME"} + OUTPUT_DIR="$TEMP_DIR/out" + COMPONENTS_DIR="$RN_DIR/ReactCommon/react/renderer/components/rncore" + MODULES_DIR="$RN_DIR/Libraries/FBReactNativeSpec/FBReactNativeSpec" - TEMP_OUTPUT_DIR="$TEMP_DIR/out" SCHEMA_FILE="$TEMP_DIR/schema.json" - CODEGEN_REPO_PATH="$RN_DIR/packages/react-native-codegen" - CODEGEN_NPM_PATH="$RN_DIR/../react-native-codegen" - - if [ -d "$CODEGEN_REPO_PATH" ]; then - CODEGEN_PATH=$(cd "$CODEGEN_REPO_PATH" && pwd) - elif [ -d "$CODEGEN_NPM_PATH" ]; then - CODEGEN_PATH=$(cd "$CODEGEN_NPM_PATH" && pwd) - else - echo "Error: Could not determine react-native-codegen location. Try running 'yarn install' or 'npm install' in your project root." 1>&2 - exit 1 - fi - if [ ! -d "$CODEGEN_PATH/lib" ]; then describe "Building react-native-codegen package" pushd "$CODEGEN_PATH" >/dev/null || exit @@ -78,13 +61,13 @@ main() { describe "Generating native code from schema (iOS)" pushd "$RN_DIR" >/dev/null || exit - "$YARN_BINARY" --silent node scripts/generate-specs-cli.js ios "$SCHEMA_FILE" "$TEMP_OUTPUT_DIR" "$CODEGEN_MODULES_LIBRARY_NAME" + USE_FABRIC="$USE_FABRIC" "$YARN_BINARY" --silent node scripts/generate-specs-cli.js ios "$SCHEMA_FILE" "$OUTPUT_DIR" popd >/dev/null || exit - mkdir -p "$CODEGEN_COMPONENTS_OUTPUT_DIR" "$CODEGEN_MODULES_OUTPUT_DIR" - mv "$TEMP_OUTPUT_DIR/$CODEGEN_MODULES_LIBRARY_NAME.h" "$TEMP_OUTPUT_DIR/$CODEGEN_MODULES_LIBRARY_NAME-generated.mm" "$CODEGEN_MODULES_OUTPUT_DIR" - find "$TEMP_OUTPUT_DIR" -type f | xargs sed -i '' "s/$CODEGEN_MODULES_LIBRARY_NAME/$CODEGEN_COMPONENTS_LIBRARY_NAME/g" - cp -R "$TEMP_OUTPUT_DIR/." "$CODEGEN_COMPONENTS_OUTPUT_DIR" + mkdir -p "$COMPONENTS_DIR" "$MODULES_DIR" + mv "$OUTPUT_DIR/FBReactNativeSpec.h" "$OUTPUT_DIR/FBReactNativeSpec-generated.mm" "$MODULES_DIR" + find "$OUTPUT_DIR" -type f | xargs sed -i '' 's/FBReactNativeSpec/rncore/g' + cp -R "$OUTPUT_DIR/." "$COMPONENTS_DIR" } trap cleanup EXIT diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 2e01cfaebfae..1e28bee9e594 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -4,7 +4,7 @@ # LICENSE file in the root directory of this source tree. def use_react_native! (options={}) - # The prefix to react-native + # The prefix to the react-native prefix = options[:path] ||= "../node_modules/react-native" # Include Fabric dependencies @@ -144,57 +144,3 @@ def react_native_post_install(installer) exclude_architectures(installer) end - -def use_react_native_codegen!(spec, options={}) - # The path to react-native (e.g. react_native_path) - prefix = options[:path] ||= File.join(__dir__, "..") - - # The path to JavaScript files - srcs_dir = options[:srcs_dir] ||= File.join(prefix, "Libraries") - - # Library name (e.g. FBReactNativeSpec) - codegen_modules_library_name = spec.name - codegen_modules_output_dir = options[:codegen_modules_output_dir] ||= File.join(prefix, "Libraries/#{codegen_modules_library_name}/#{codegen_modules_library_name}") - - # Run the codegen as part of the Xcode build pipeline. - env_vars = "SRCS_DIR=#{srcs_dir}" - env_vars += " CODEGEN_MODULES_OUTPUT_DIR=#{codegen_modules_output_dir}" - if ENV['USE_FABRIC'] == '1' - # We use a different library name for components, as well as an additional set of files. - # Eventually, we want these to be part of the same library as #{codegen_modules_library_name} above. - codegen_components_library_name = "rncore" - codegen_components_output_dir = File.join(prefix, "ReactCommon/react/renderer/components/#{codegen_components_library_name}") - env_vars += " CODEGEN_COMPONENTS_OUTPUT_DIR=#{codegen_components_output_dir}" - end - spec.script_phase = { - :name => 'Generate Specs', - :input_files => [srcs_dir], - :output_files => ["$(DERIVED_FILE_DIR)/codegen-#{codegen_modules_library_name}.log"], - :script => "bash -c '#{env_vars} CODEGEN_MODULES_LIBRARY_NAME=#{codegen_modules_library_name} #{File.join(__dir__, "generate-specs.sh")}' | tee \"${SCRIPT_OUTPUT_FILE_0}\"", - :execution_position => :before_compile, - :show_env_vars_in_log => true - } - - # Since the generated files are not guaranteed to exist when CocoaPods is run, we need to create - # empty files to ensure the references are included in the resulting Pods Xcode project. - mkdir_command = "mkdir -p #{codegen_modules_output_dir}" - generated_filenames = [ "#{codegen_modules_library_name}.h", "#{codegen_modules_library_name}-generated.mm" ] - generated_files = generated_filenames.map { |filename| File.join(codegen_modules_output_dir, filename) } - - if ENV['USE_FABRIC'] == '1' - mkdir_command += " #{codegen_components_output_dir}" - components_generated_filenames = [ - "ComponentDescriptors.h", - "EventEmitters.cpp", - "EventEmitters.h", - "Props.cpp", - "Props.h", - "RCTComponentViewHelpers.h", - "ShadowNodes.cpp", - "ShadowNodes.h" - ] - generated_files = generated_files.concat(components_generated_filenames.map { |filename| File.join(codegen_components_output_dir, filename) }) - end - - spec.prepare_command = "#{mkdir_command} && touch #{generated_files.reduce() { |str, file| str + " " + file }}" -end diff --git a/tools/build_defs/oss/rn_defs.bzl b/tools/build_defs/oss/rn_defs.bzl index 23fb85c5eb15..30b911c2ec42 100644 --- a/tools/build_defs/oss/rn_defs.bzl +++ b/tools/build_defs/oss/rn_defs.bzl @@ -122,7 +122,6 @@ def rn_extra_build_flags(): # React property preprocessor def rn_android_library(name, deps = [], plugins = [], *args, **kwargs): - _ = kwargs.pop("autoglob", False) _ = kwargs.pop("is_androidx", False) if react_native_target( "java/com/facebook/react/uimanager/annotations:annotations", @@ -166,7 +165,6 @@ def rn_apple_library(*args, **kwargs): kwargs.setdefault("target_sdk_version", "10.0") # Unsupported kwargs - _ = kwargs.pop("autoglob", False) _ = kwargs.pop("plugins_only", False) _ = kwargs.pop("enable_exceptions", False) _ = kwargs.pop("extension_api_only", False) @@ -193,8 +191,7 @@ def rn_genrule(*args, **kwargs): def rn_robolectric_test(name, srcs, vm_args = None, *args, **kwargs): vm_args = vm_args or [] - _ = kwargs.pop("autoglob", False) - _ = kwargs.pop("is_androidx", False) + is_androidx = kwargs.pop("is_androidx", False) kwargs["deps"] = kwargs.pop("deps", []) + [ react_native_android_toplevel_dep("third-party/java/mockito2:mockito2"),