diff --git a/Libraries/WebPerformance/NativePerformanceObserver.h b/Libraries/WebPerformance/NativePerformanceObserver.h index 9bd015d572ebf7..3a3250dcbcc6e7 100644 --- a/Libraries/WebPerformance/NativePerformanceObserver.h +++ b/Libraries/WebPerformance/NativePerformanceObserver.h @@ -13,10 +13,34 @@ #include #include #include -#include "NativePerformanceObserver_RawPerformanceEntry.h" namespace facebook::react { +#pragma mark - Structs + +using RawPerformanceEntry = NativePerformanceObserverCxxBaseRawPerformanceEntry< + std::string, + int32_t, + double, + double, + // For "event" entries only: + std::optional, + std::optional, + std::optional>; + +template <> +struct Bridging + : NativePerformanceObserverCxxBaseRawPerformanceEntryBridging< + std::string, + int32_t, + double, + double, + std::optional, + std::optional, + std::optional> {}; + +#pragma mark - implementation + class NativePerformanceObserver : public NativePerformanceObserverCxxSpec, std::enable_shared_from_this { diff --git a/Libraries/WebPerformance/NativePerformanceObserver.js b/Libraries/WebPerformance/NativePerformanceObserver.js index 78b86b099aea4c..b3d6fdefc8b7f7 100644 --- a/Libraries/WebPerformance/NativePerformanceObserver.js +++ b/Libraries/WebPerformance/NativePerformanceObserver.js @@ -18,7 +18,7 @@ export const RawPerformanceEntryTypeValues = { export type RawPerformanceEntryType = number; -export type RawPerformanceEntry = $ReadOnly<{ +export type RawPerformanceEntry = {| name: string, entryType: RawPerformanceEntryType, startTime: number, @@ -27,7 +27,7 @@ export type RawPerformanceEntry = $ReadOnly<{ processingStart?: number, processingEnd?: number, interactionId?: number, -}>; +|}; export interface Spec extends TurboModule { +startReporting: (entryType: string) => void; diff --git a/Libraries/WebPerformance/NativePerformanceObserver_RawPerformanceEntry.h b/Libraries/WebPerformance/NativePerformanceObserver_RawPerformanceEntry.h deleted file mode 100644 index 1469bf196e3156..00000000000000 --- a/Libraries/WebPerformance/NativePerformanceObserver_RawPerformanceEntry.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and 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 - -namespace facebook::react { - -struct RawPerformanceEntry { - std::string name; - int32_t entryType; - double startTime; - double duration; - // For "event" entries only: - std::optional processingStart; - std::optional processingEnd; - std::optional interactionId; -}; - -template <> -struct Bridging { - static RawPerformanceEntry fromJs( - jsi::Runtime &rt, - const jsi::Object &value, - const std::shared_ptr &jsInvoker) { - RawPerformanceEntry result{ - bridging::fromJs( - rt, value.getProperty(rt, "name"), jsInvoker), - bridging::fromJs( - rt, value.getProperty(rt, "entryType"), jsInvoker), - bridging::fromJs( - rt, value.getProperty(rt, "startTime"), jsInvoker), - bridging::fromJs( - rt, value.getProperty(rt, "duration"), jsInvoker), - bridging::fromJs>( - rt, value.getProperty(rt, "processingStart"), jsInvoker), - bridging::fromJs>( - rt, value.getProperty(rt, "processingEnd"), jsInvoker), - bridging::fromJs>( - rt, value.getProperty(rt, "interactionId"), jsInvoker), - }; - return result; - } - - static jsi::Object toJs(jsi::Runtime &rt, const RawPerformanceEntry &value) { - auto result = facebook::jsi::Object(rt); - result.setProperty(rt, "name", bridging::toJs(rt, value.name)); - result.setProperty(rt, "entryType", bridging::toJs(rt, value.entryType)); - result.setProperty(rt, "startTime", bridging::toJs(rt, value.startTime)); - result.setProperty(rt, "duration", bridging::toJs(rt, value.duration)); - if (value.processingStart) { - result.setProperty( - rt, - "processingStart", - bridging::toJs(rt, value.processingStart.value())); - } - if (value.processingEnd) { - result.setProperty( - rt, "processingEnd", bridging::toJs(rt, value.processingEnd.value())); - } - if (value.interactionId) { - result.setProperty( - rt, "interactionId", bridging::toJs(rt, value.interactionId.value())); - } - return result; - } -}; - -} // namespace facebook::react diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 865f60b63f30c9..7076ab94131171 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -16,6 +16,7 @@ import type { NativeModuleTypeAnnotation, NativeModuleFunctionTypeAnnotation, NativeModulePropertyShape, + NativeModuleAliasMap, } from '../../CodegenSchema'; import type {AliasResolver} from './Utils'; @@ -28,8 +29,13 @@ type FilesOutput = Map; const ModuleClassDeclarationTemplate = ({ hasteModuleName, moduleProperties, -}: $ReadOnly<{hasteModuleName: string, moduleProperties: string[]}>) => { - return `class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule { + structs, +}: $ReadOnly<{ + hasteModuleName: string, + moduleProperties: string[], + structs: string, +}>) => { + return `${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule { protected: ${hasteModuleName}CxxSpecJSI(std::shared_ptr jsInvoker); @@ -186,6 +192,76 @@ function translatePrimitiveJSTypeToCpp( } } +function createStructs( + moduleName: string, + aliasMap: NativeModuleAliasMap, + resolveAlias: AliasResolver, +): string { + return Object.keys(aliasMap) + .map(alias => { + const value = aliasMap[alias]; + if (value.properties.length === 0) { + return ''; + } + const structName = `${moduleName}Base${alias}`; + const templateParameterWithTypename = value.properties + .map((v, i) => 'typename P' + i) + .join(', '); + const templateParameter = value.properties + .map((v, i) => 'P' + i) + .join(', '); + return `#pragma mark - ${structName} + +template <${templateParameterWithTypename}> +struct ${structName} { +${value.properties.map((v, i) => ' P' + i + ' ' + v.name).join(';\n')}; + bool operator==(const ${structName} &other) const { + return ${value.properties + .map(v => `${v.name} == other.${v.name}`) + .join(' && ')}; + } +}; + +template <${templateParameterWithTypename}> +struct ${structName}Bridging { + static ${structName}<${templateParameter}> fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + ${structName}<${templateParameter}> result{ +${value.properties + .map( + (v, i) => + ` bridging::fromJs(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`, + ) + .join(',\n')}}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const ${structName}<${templateParameter}> &value) { + auto result = facebook::jsi::Object(rt); +${value.properties + .map((v, i) => { + if (v.optional) { + return ` if (value.${v.name}) { + result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value())); + }`; + } else { + return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}));`; + } + }) + .join('\n')} + return result; + } +}; + +`; + }) + .join('\n'); +} + function translatePropertyToCpp( prop: NativeModulePropertyShape, resolveAlias: AliasResolver, @@ -251,6 +327,7 @@ module.exports = { moduleNames: [moduleName], } = nativeModules[hasteModuleName]; const resolveAlias = createAliasResolver(aliases); + const structs = createStructs(moduleName, aliases, resolveAlias); return [ ModuleClassDeclarationTemplate({ @@ -258,6 +335,7 @@ module.exports = { moduleProperties: properties.map(prop => translatePropertyToCpp(prop, resolveAlias, true), ), + structs, }), ModuleSpecClassDeclarationTemplate({ hasteModuleName, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 2e7955758b4d4e..679072d878ebe3 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -202,6 +202,36 @@ Map { namespace facebook { namespace react { +#pragma mark - SampleTurboModuleCxxBaseObjectAlias + +template +struct SampleTurboModuleCxxBaseObjectAlias { + P0 x; + bool operator==(const SampleTurboModuleCxxBaseObjectAlias &other) const { + return x == other.x; + } +}; + +template +struct SampleTurboModuleCxxBaseObjectAliasBridging { + static SampleTurboModuleCxxBaseObjectAlias fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + SampleTurboModuleCxxBaseObjectAlias result{ + bridging::fromJs(rt, value.getProperty(rt, \\"x\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const SampleTurboModuleCxxBaseObjectAlias &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"x\\", bridging::toJs(rt, value.x)); + return result; + } +}; + class JSI_EXPORT NativeSampleTurboModuleCxxSpecJSI : public TurboModule { protected: NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker); @@ -358,6 +388,54 @@ Map { namespace facebook { namespace react { +#pragma mark - AliasTurboModuleBaseOptions + +template +struct AliasTurboModuleBaseOptions { + P0 offset; + P1 size; + P2 displaySize; + P3 resizeMode; + P4 allowExternalStorage; + bool operator==(const AliasTurboModuleBaseOptions &other) const { + return offset == other.offset && size == other.size && displaySize == other.displaySize && resizeMode == other.resizeMode && allowExternalStorage == other.allowExternalStorage; + } +}; + +template +struct AliasTurboModuleBaseOptionsBridging { + static AliasTurboModuleBaseOptions fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + AliasTurboModuleBaseOptions result{ + bridging::fromJs(rt, value.getProperty(rt, \\"offset\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"size\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"displaySize\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"resizeMode\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"allowExternalStorage\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const AliasTurboModuleBaseOptions &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"offset\\", bridging::toJs(rt, value.offset)); + result.setProperty(rt, \\"size\\", bridging::toJs(rt, value.size)); + if (value.displaySize) { + result.setProperty(rt, \\"displaySize\\", bridging::toJs(rt, value.displaySize.value())); + } + if (value.resizeMode) { + result.setProperty(rt, \\"resizeMode\\", bridging::toJs(rt, value.resizeMode.value())); + } + if (value.allowExternalStorage) { + result.setProperty(rt, \\"allowExternalStorage\\", bridging::toJs(rt, value.allowExternalStorage.value())); + } + return result; + } +}; + class JSI_EXPORT AliasTurboModuleCxxSpecJSI : public TurboModule { protected: AliasTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker); @@ -435,6 +513,179 @@ Map { namespace facebook { namespace react { +#pragma mark - CameraRollManagerBasePhotoIdentifierImage + +template +struct CameraRollManagerBasePhotoIdentifierImage { + P0 uri; + P1 playableDuration; + P2 width; + P3 height; + P4 isStored; + P5 filename; + bool operator==(const CameraRollManagerBasePhotoIdentifierImage &other) const { + return uri == other.uri && playableDuration == other.playableDuration && width == other.width && height == other.height && isStored == other.isStored && filename == other.filename; + } +}; + +template +struct CameraRollManagerBasePhotoIdentifierImageBridging { + static CameraRollManagerBasePhotoIdentifierImage fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + CameraRollManagerBasePhotoIdentifierImage result{ + bridging::fromJs(rt, value.getProperty(rt, \\"uri\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"playableDuration\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"width\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"height\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"isStored\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"filename\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const CameraRollManagerBasePhotoIdentifierImage &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"uri\\", bridging::toJs(rt, value.uri)); + result.setProperty(rt, \\"playableDuration\\", bridging::toJs(rt, value.playableDuration)); + result.setProperty(rt, \\"width\\", bridging::toJs(rt, value.width)); + result.setProperty(rt, \\"height\\", bridging::toJs(rt, value.height)); + if (value.isStored) { + result.setProperty(rt, \\"isStored\\", bridging::toJs(rt, value.isStored.value())); + } + result.setProperty(rt, \\"filename\\", bridging::toJs(rt, value.filename)); + return result; + } +}; + + +#pragma mark - CameraRollManagerBasePhotoIdentifier + +template +struct CameraRollManagerBasePhotoIdentifier { + P0 node; + bool operator==(const CameraRollManagerBasePhotoIdentifier &other) const { + return node == other.node; + } +}; + +template +struct CameraRollManagerBasePhotoIdentifierBridging { + static CameraRollManagerBasePhotoIdentifier fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + CameraRollManagerBasePhotoIdentifier result{ + bridging::fromJs(rt, value.getProperty(rt, \\"node\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const CameraRollManagerBasePhotoIdentifier &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"node\\", bridging::toJs(rt, value.node)); + return result; + } +}; + + +#pragma mark - CameraRollManagerBasePhotoIdentifiersPage + +template +struct CameraRollManagerBasePhotoIdentifiersPage { + P0 edges; + P1 page_info; + bool operator==(const CameraRollManagerBasePhotoIdentifiersPage &other) const { + return edges == other.edges && page_info == other.page_info; + } +}; + +template +struct CameraRollManagerBasePhotoIdentifiersPageBridging { + static CameraRollManagerBasePhotoIdentifiersPage fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + CameraRollManagerBasePhotoIdentifiersPage result{ + bridging::fromJs(rt, value.getProperty(rt, \\"edges\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"page_info\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const CameraRollManagerBasePhotoIdentifiersPage &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"edges\\", bridging::toJs(rt, value.edges)); + result.setProperty(rt, \\"page_info\\", bridging::toJs(rt, value.page_info)); + return result; + } +}; + + +#pragma mark - CameraRollManagerBaseGetPhotosParams + +template +struct CameraRollManagerBaseGetPhotosParams { + P0 first; + P1 after; + P2 groupName; + P3 groupTypes; + P4 assetType; + P5 maxSize; + P6 mimeTypes; + bool operator==(const CameraRollManagerBaseGetPhotosParams &other) const { + return first == other.first && after == other.after && groupName == other.groupName && groupTypes == other.groupTypes && assetType == other.assetType && maxSize == other.maxSize && mimeTypes == other.mimeTypes; + } +}; + +template +struct CameraRollManagerBaseGetPhotosParamsBridging { + static CameraRollManagerBaseGetPhotosParams fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + CameraRollManagerBaseGetPhotosParams result{ + bridging::fromJs(rt, value.getProperty(rt, \\"first\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"after\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"groupName\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"groupTypes\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"assetType\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"maxSize\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"mimeTypes\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const CameraRollManagerBaseGetPhotosParams &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"first\\", bridging::toJs(rt, value.first)); + if (value.after) { + result.setProperty(rt, \\"after\\", bridging::toJs(rt, value.after.value())); + } + if (value.groupName) { + result.setProperty(rt, \\"groupName\\", bridging::toJs(rt, value.groupName.value())); + } + if (value.groupTypes) { + result.setProperty(rt, \\"groupTypes\\", bridging::toJs(rt, value.groupTypes.value())); + } + if (value.assetType) { + result.setProperty(rt, \\"assetType\\", bridging::toJs(rt, value.assetType.value())); + } + if (value.maxSize) { + result.setProperty(rt, \\"maxSize\\", bridging::toJs(rt, value.maxSize.value())); + } + if (value.mimeTypes) { + result.setProperty(rt, \\"mimeTypes\\", bridging::toJs(rt, value.mimeTypes.value())); + } + return result; + } +}; + class JSI_EXPORT NativeCameraRollManagerCxxSpecJSI : public TurboModule { protected: NativeCameraRollManagerCxxSpecJSI(std::shared_ptr jsInvoker); @@ -505,6 +756,108 @@ private: Delegate delegate_; }; +#pragma mark - ExceptionsManagerBaseStackFrame + +template +struct ExceptionsManagerBaseStackFrame { + P0 column; + P1 file; + P2 lineNumber; + P3 methodName; + P4 collapse; + bool operator==(const ExceptionsManagerBaseStackFrame &other) const { + return column == other.column && file == other.file && lineNumber == other.lineNumber && methodName == other.methodName && collapse == other.collapse; + } +}; + +template +struct ExceptionsManagerBaseStackFrameBridging { + static ExceptionsManagerBaseStackFrame fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + ExceptionsManagerBaseStackFrame result{ + bridging::fromJs(rt, value.getProperty(rt, \\"column\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"file\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"lineNumber\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"methodName\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"collapse\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const ExceptionsManagerBaseStackFrame &value) { + auto result = facebook::jsi::Object(rt); + if (value.column) { + result.setProperty(rt, \\"column\\", bridging::toJs(rt, value.column.value())); + } + result.setProperty(rt, \\"file\\", bridging::toJs(rt, value.file)); + if (value.lineNumber) { + result.setProperty(rt, \\"lineNumber\\", bridging::toJs(rt, value.lineNumber.value())); + } + result.setProperty(rt, \\"methodName\\", bridging::toJs(rt, value.methodName)); + if (value.collapse) { + result.setProperty(rt, \\"collapse\\", bridging::toJs(rt, value.collapse.value())); + } + return result; + } +}; + + +#pragma mark - ExceptionsManagerBaseExceptionData + +template +struct ExceptionsManagerBaseExceptionData { + P0 message; + P1 originalMessage; + P2 name; + P3 componentStack; + P4 stack; + P5 id; + P6 isFatal; + P7 extraData; + bool operator==(const ExceptionsManagerBaseExceptionData &other) const { + return message == other.message && originalMessage == other.originalMessage && name == other.name && componentStack == other.componentStack && stack == other.stack && id == other.id && isFatal == other.isFatal && extraData == other.extraData; + } +}; + +template +struct ExceptionsManagerBaseExceptionDataBridging { + static ExceptionsManagerBaseExceptionData fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + ExceptionsManagerBaseExceptionData result{ + bridging::fromJs(rt, value.getProperty(rt, \\"message\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"originalMessage\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"name\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"componentStack\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"stack\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"id\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"isFatal\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"extraData\\"), jsInvoker)}; + return result; + } + + static jsi::Object toJs( + jsi::Runtime &rt, + const ExceptionsManagerBaseExceptionData &value) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"message\\", bridging::toJs(rt, value.message)); + result.setProperty(rt, \\"originalMessage\\", bridging::toJs(rt, value.originalMessage)); + result.setProperty(rt, \\"name\\", bridging::toJs(rt, value.name)); + result.setProperty(rt, \\"componentStack\\", bridging::toJs(rt, value.componentStack)); + result.setProperty(rt, \\"stack\\", bridging::toJs(rt, value.stack)); + result.setProperty(rt, \\"id\\", bridging::toJs(rt, value.id)); + result.setProperty(rt, \\"isFatal\\", bridging::toJs(rt, value.isFatal)); + if (value.extraData) { + result.setProperty(rt, \\"extraData\\", bridging::toJs(rt, value.extraData.value())); + } + return result; + } +}; + class JSI_EXPORT NativeExceptionsManagerCxxSpecJSI : public TurboModule { protected: NativeExceptionsManagerCxxSpecJSI(std::shared_ptr jsInvoker); diff --git a/packages/rn-tester/BUCK b/packages/rn-tester/BUCK index 610c5247756e3e..dae1544ba4ede9 100644 --- a/packages/rn-tester/BUCK +++ b/packages/rn-tester/BUCK @@ -1,3 +1,4 @@ +load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob") load("@fbsource//xplat/hermes/defs:hermes.bzl", "HERMES_BYTECODE_VERSION") load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") load("//tools/build_defs:fb_xplat_platform_specific_rule.bzl", "fb_xplat_platform_specific_rule") @@ -10,6 +11,7 @@ load("//tools/build_defs/apple:flag_defs.bzl", "get_objc_arc_preprocessor_flags" load("//tools/build_defs/oss:metro_defs.bzl", "rn_library") load( "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", "APPLE", "YOGA_APPLE_TARGET", "js_library_glob", @@ -49,6 +51,7 @@ rn_library( srcs = js_library_glob( [ "js", + "NativeCxxModuleExample", "NativeModuleExample", "NativeComponentExample", "RCTTest", @@ -61,10 +64,13 @@ rn_library( ], ), codegen_components = True, + codegen_modules = True, labels = [ "pfh:ReactNative_CommonInfrastructurePlaceholder", ], native_component_spec_name = "AppSpecs", + native_module_android_package_name = "com.facebook.fbreact.specs", + native_module_spec_name = "AppSpecs", skip_processors = True, visibility = ["PUBLIC"], deps = [ @@ -319,3 +325,24 @@ rn_xplat_cxx_library2( "//xplat/js/react-native-github:RCTFabricComponentViewsBase", ], ) + +rn_xplat_cxx_library2( + name = "NativeCxxModuleExample", + srcs = glob(["NativeCxxModuleExample/*.cpp"]), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("NativeCxxModuleExample", "*.h"), + ], + prefix = "NativeCxxModuleExample", + ), + fbandroid_compiler_flags = [ + "-fexceptions", + "-frtti", + ], + platforms = (ANDROID, APPLE), + visibility = ["PUBLIC"], + deps = [ + ":AppSpecsJSI", + ], +) diff --git a/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt b/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt new file mode 100644 index 00000000000000..ce1fa2d8980137 --- /dev/null +++ b/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++17 + -Wall + -Wpedantic + -Wno-gnu-zero-variadic-macro-arguments + -DFOLLY_NO_CONFIG=1 + -DLOG_TAG=\"ReactNative\") + +file(GLOB nativecxxmoduleexample_SRC CONFIGURE_DEPENDS *.cpp) +add_library(nativecxxmoduleexample STATIC ${nativecxxmoduleexample_SRC}) + +target_include_directories(nativecxxmoduleexample PUBLIC .) +target_include_directories(react_codegen_AppSpecs PUBLIC .) + +target_link_libraries(nativecxxmoduleexample + fbjni + jsi + react_nativemodule_core + react_codegen_AppSpecs) diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp new file mode 100644 index 00000000000000..78c9c1e3a27c17 --- /dev/null +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NativeCxxModuleExample.h" + +namespace facebook::react { + +NativeCxxModuleExample::NativeCxxModuleExample( + std::shared_ptr jsInvoker) + : NativeCxxModuleExampleCxxSpec(std::move(jsInvoker)) {} + +void NativeCxxModuleExample::getValueWithCallback( + jsi::Runtime &rt, + AsyncCallback callback) { + callback({"value from callback!"}); +} + +std::vector> NativeCxxModuleExample::getArray( + jsi::Runtime &rt, + std::vector> arg) { + return arg; +} + +bool NativeCxxModuleExample::getBool(jsi::Runtime &rt, bool arg) { + return arg; +} + +ConstantsStruct NativeCxxModuleExample::getConstants(jsi::Runtime &rt) { + return ConstantsStruct{true, 69, "react-native"}; +} + +int32_t NativeCxxModuleExample::getEnum(jsi::Runtime &rt, int32_t arg) { + return arg; +} + +std::map> NativeCxxModuleExample::getMap( + jsi::Runtime &rt, + std::map> arg) { + return arg; +} + +double NativeCxxModuleExample::getNumber(jsi::Runtime &rt, double arg) { + return arg; +} + +ObjectStruct NativeCxxModuleExample::getObject( + jsi::Runtime &rt, + ObjectStruct arg) { + return arg; +} + +std::set NativeCxxModuleExample::getSet( + jsi::Runtime &rt, + std::set arg) { + return arg; +} + +std::string NativeCxxModuleExample::getString( + jsi::Runtime &rt, + std::string arg) { + return arg; +} + +std::string NativeCxxModuleExample::getUnion( + jsi::Runtime &rt, + float x, + std::string y, + jsi::Object z) { + std::string result = "x: " + std::to_string(x) + ", y: " + y + ", z: { "; + if (z.hasProperty(rt, "value")) { + result += "value: "; + result += std::to_string(z.getProperty(rt, "value").getNumber()); + } else if (z.hasProperty(rt, "low")) { + result += "low: "; + result += z.getProperty(rt, "low").getString(rt).utf8(rt); + } + result += " }"; + return result; +} + +ValueStruct NativeCxxModuleExample::getValue( + jsi::Runtime &rt, + double x, + std::string y, + ObjectStruct z) { + ValueStruct result{x, y, z}; + return result; +} + +AsyncPromise NativeCxxModuleExample::getValueWithPromise( + jsi::Runtime &rt, + bool error) { + auto promise = AsyncPromise(rt, jsInvoker_); + if (error) { + promise.reject("intentional promise rejection"); + } else { + promise.resolve("result!"); + } + return promise; +} + +void NativeCxxModuleExample::voidFunc(jsi::Runtime &rt) { + // Nothing to do +} + +} // namespace facebook::react diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h new file mode 100644 index 00000000000000..e968db5848ee2e --- /dev/null +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 + +#if __has_include() // CocoaPod headers on Apple +#include +#elif __has_include("AppSpecsJSI.h") // Cmake headers on Android +#include "AppSpecsJSI.h" +#else // BUCK headers +#include +#endif +#include +#include +#include +#include + +namespace facebook::react { + +#pragma mark - Structs +using ConstantsStruct = + NativeCxxModuleExampleCxxBaseConstantsStruct; + +template <> +struct Bridging + : NativeCxxModuleExampleCxxBaseConstantsStructBridging< + bool, + int32_t, + std::string> {}; + +using ObjectStruct = NativeCxxModuleExampleCxxBaseObjectStruct< + int32_t, + std::string, + std::optional>; + +template <> +struct Bridging + : NativeCxxModuleExampleCxxBaseObjectStructBridging< + int32_t, + std::string, + std::optional> {}; + +using ValueStruct = + NativeCxxModuleExampleCxxBaseValueStruct; + +template <> +struct Bridging : NativeCxxModuleExampleCxxBaseValueStructBridging< + double, + std::string, + ObjectStruct> {}; + +#pragma mark - implementation +class NativeCxxModuleExample + : public NativeCxxModuleExampleCxxSpec { + public: + NativeCxxModuleExample(std::shared_ptr jsInvoker); + + void getValueWithCallback( + jsi::Runtime &rt, + AsyncCallback callback); + + std::vector> getArray( + jsi::Runtime &rt, + std::vector> arg); + + bool getBool(jsi::Runtime &rt, bool arg); + + ConstantsStruct getConstants(jsi::Runtime &rt); + + int32_t getEnum(jsi::Runtime &rt, int32_t arg); + + std::map> getMap( + jsi::Runtime &rt, + std::map> arg); + + double getNumber(jsi::Runtime &rt, double arg); + + ObjectStruct getObject(jsi::Runtime &rt, ObjectStruct arg); + + std::set getSet(jsi::Runtime &rt, std::set arg); + + std::string getString(jsi::Runtime &rt, std::string arg); + + std::string getUnion(jsi::Runtime &rt, float x, std::string y, jsi::Object z); + + ValueStruct + getValue(jsi::Runtime &rt, double x, std::string y, ObjectStruct z); + + AsyncPromise getValueWithPromise(jsi::Runtime &rt, bool error); + + void voidFunc(jsi::Runtime &rt); +}; + +} // namespace facebook::react diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js new file mode 100644 index 00000000000000..d0effa1d796462 --- /dev/null +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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-local + * @format + */ + +import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; + +import {TurboModuleRegistry} from 'react-native'; + +/** Flow enum support will be added when the sun rises in the west +export enum EnumInt { + A = 23, + B = 42, +} +*/ + +export type UnionFloat = 1.44 | 2.88 | 5.76; +export type UnionString = 'One' | 'Two' | 'Three'; +export type UnionObject = {value: number} | {low: string}; + +export type ConstantsStruct = {| + const1: boolean, + const2: number, + const3: string, +|}; + +export type ObjectStruct = {| + a: number, + b: string, + c?: ?string, +|}; + +export type ValueStruct = {| + x: number, + y: string, + z: ObjectStruct, +|}; + +export interface Spec extends TurboModule { + +getArray: (arg: Array) => Array; + +getBool: (arg: boolean) => boolean; + +getConstants: () => ConstantsStruct; + +getEnum: (arg: number /*EnumInt*/) => number /*EnumInt*/; + +getMap: (arg: {[key: string]: ?number}) => {[key: string]: ?number}; + +getNumber: (arg: number) => number; + +getObject: (arg: ObjectStruct) => ObjectStruct; + +getSet: (arg: Array) => Array; + +getString: (arg: string) => string; + +getUnion: (x: UnionFloat, y: UnionString, z: UnionObject) => string; + +getValue: (x: number, y: string, z: ObjectStruct) => ValueStruct; + +getValueWithCallback: (callback: (value: string) => void) => void; + +getValueWithPromise: (error: boolean) => Promise; + +voidFunc: () => void; +} + +export default (TurboModuleRegistry.get( + 'NativeCxxModuleExampleCxx', +): ?Spec); diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.podspec b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.podspec new file mode 100644 index 00000000000000..4eaef3a3978898 --- /dev/null +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.podspec @@ -0,0 +1,31 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "../package.json"))) + +Pod::Spec.new do |s| + s.name = "NativeCxxModuleExample" + s.version = package["version"] + s.summary = package["description"] + s.description = "NativeCxxModuleExample" + s.homepage = "https://github.com/facebook/react-native.git" + s.license = "MIT" + s.platforms = { :ios => "12.4" } + s.compiler_flags = '-Wno-nullability-completeness' + s.author = "Meta Platforms, Inc. and its affiliates" + s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "#{s.version}" } + s.source_files = "**/*.{h,cpp}" + s.requires_arc = true + s.pod_target_xcconfig = { + "USE_HEADERMAP" => "YES", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + install_modules_dependencies(s) + + s.dependency "ReactCommon/turbomodule/core" +end diff --git a/packages/rn-tester/Podfile b/packages/rn-tester/Podfile index 45ff904de3b762..3e6f2c798d1cab 100644 --- a/packages/rn-tester/Podfile +++ b/packages/rn-tester/Podfile @@ -48,6 +48,9 @@ def pods(target_name, options = {}, use_flipper: !IN_CI && !USE_FRAMEWORKS) # RNTester native modules and components pod 'ScreenshotManager', :path => "NativeModuleExample" + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' + pod 'NativeCxxModuleExample', :path => "NativeCxxModuleExample" + end end target 'RNTester' do diff --git a/packages/rn-tester/RNTester/RNTesterTurboModuleProvider.mm b/packages/rn-tester/RNTester/RNTesterTurboModuleProvider.mm index b8532eab9db215..32e5c3418013a9 100644 --- a/packages/rn-tester/RNTester/RNTesterTurboModuleProvider.mm +++ b/packages/rn-tester/RNTester/RNTesterTurboModuleProvider.mm @@ -7,6 +7,9 @@ #import "RNTesterTurboModuleProvider.h" +#ifdef RCT_NEW_ARCH_ENABLED +#import +#endif #import #import #import @@ -28,7 +31,11 @@ Class RNTesterTurboModuleClassProvider(const char *name) if (name == "SampleTurboCxxModule") { return std::make_shared(jsInvoker); } - +#ifdef RCT_NEW_ARCH_ENABLED + if (name == "NativeCxxModuleExampleCxx") { + return std::make_shared(jsInvoker); + } +#endif return nullptr; } diff --git a/packages/rn-tester/android/app/src/main/jni/CMakeLists.txt b/packages/rn-tester/android/app/src/main/jni/CMakeLists.txt index d1af2d92e230ca..e5bd4dde4bd31d 100644 --- a/packages/rn-tester/android/app/src/main/jni/CMakeLists.txt +++ b/packages/rn-tester/android/app/src/main/jni/CMakeLists.txt @@ -11,7 +11,11 @@ project(appmodules) include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake) add_subdirectory(${REACT_COMMON_DIR}/react/nativemodule/samples/platform/android/ sampleturbomodule_build) +add_subdirectory(${REACT_COMMON_DIR}/../packages/rn-tester/NativeCxxModuleExample/ nativecxxmoduleexample_build) # RN Tester needs to link against the sample turbomobule target_link_libraries(${CMAKE_PROJECT_NAME} sampleturbomodule) +# RN Tester needs to link against the NativeCxxModuleExample +target_link_libraries(${CMAKE_PROJECT_NAME} + nativecxxmoduleexample) diff --git a/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp b/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp index 81b8450dfee885..be31e906ef0273 100644 --- a/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp +++ b/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,9 @@ void registerComponents( std::shared_ptr cxxModuleProvider( const std::string &name, const std::shared_ptr &jsInvoker) { + if (name == "NativeCxxModuleExampleCxx") { + return std::make_shared(jsInvoker); + } return nullptr; } diff --git a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js new file mode 100644 index 00000000000000..ba62ba6e64ef4e --- /dev/null +++ b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js @@ -0,0 +1,222 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +import type {RootTag} from 'react-native/Libraries/ReactNative/RootTag'; + +import { + StyleSheet, + Text, + View, + FlatList, + Platform, + TouchableOpacity, + RootTagContext, +} from 'react-native'; +import * as React from 'react'; +import NativeCxxModuleExample /*EnumInt,*/ from '../../../NativeCxxModuleExample/NativeCxxModuleExample'; + +type State = {| + testResults: { + [string]: { + type: string, + value: mixed, + ... + }, + ... + }, +|}; + +type Examples = + | 'callback' + | 'getArray' + | 'getBool' + | 'getConstants' + | 'getEnum' + | 'getMap' + | 'getNumber' + | 'getObject' + | 'getSet' + | 'getString' + | 'getUnion' + | 'getValue' + | 'promise' + | 'rejectPromise' + | 'voidFunc'; + +class NativeCxxModuleExampleExample extends React.Component<{||}, State> { + static contextType: React$Context = RootTagContext; + + state: State = { + testResults: {}, + }; + + // Add calls to methods in TurboModule here + // $FlowFixMe[missing-local-annot] + _tests = { + callback: () => + NativeCxxModuleExample?.getValueWithCallback(callbackValue => + this._setResult('callback', callbackValue), + ), + getArray: () => + NativeCxxModuleExample?.getArray([ + {a: 1, b: 'foo'}, + {a: 2, b: 'bar'}, + null, + ]), + getBool: () => NativeCxxModuleExample?.getBool(true), + getConstants: () => NativeCxxModuleExample?.getConstants(), + getEnum: () => NativeCxxModuleExample?.getEnum(/*EnumInt.A*/ 2), + getMap: () => NativeCxxModuleExample?.getMap({a: 1, b: null, c: 3}), + getNumber: () => NativeCxxModuleExample?.getNumber(99.95), + getObject: () => + NativeCxxModuleExample?.getObject({a: 1, b: 'foo', c: null}), + getSet: () => NativeCxxModuleExample?.getSet([1, 1.1, 1.1, 1.1, 2]), + getString: () => NativeCxxModuleExample?.getString('Hello'), + getUnion: () => NativeCxxModuleExample?.getUnion(1.44, 'Two', {low: '12'}), + getValue: () => + NativeCxxModuleExample?.getValue(5, 'test', {a: 1, b: 'foo'}), + promise: () => + NativeCxxModuleExample?.getValueWithPromise(false).then(valuePromise => + this._setResult('promise', valuePromise), + ), + rejectPromise: () => + NativeCxxModuleExample?.getValueWithPromise(true) + .then(() => {}) + .catch(e => this._setResult('rejectPromise', e.message)), + voidFunc: () => NativeCxxModuleExample?.voidFunc(), + }; + + _setResult( + name: string | Examples, + result: + | $FlowFixMe + | void + | Array<$FlowFixMe> + | boolean + | {const1: boolean, const2: number, const3: string} + | number + | {[key: string]: ?number} + | Promise + | number + | string, + ) { + this.setState(({testResults}) => ({ + testResults: { + ...testResults, + /* $FlowFixMe[invalid-computed-prop] (>=0.111.0 site=react_native_fb) + * This comment suppresses an error found when Flow v0.111 was + * deployed. To see the error, delete this comment and run Flow. */ + [name]: {value: result, type: typeof result}, + }, + })); + } + + _renderResult(name: Examples): React.Node { + const result = this.state.testResults[name] || {}; + return ( + + {JSON.stringify(result.value)} + {result.type} + + ); + } + + componentDidMount(): void { + if (global.__turboModuleProxy == null) { + throw new Error( + 'Cannot load this example because TurboModule is not configured.', + ); + } + Object.keys(this._tests).forEach(item => + this._setResult(item, this._tests[item]()), + ); + } + + render(): React.Node { + return ( + + + + Object.keys(this._tests).forEach(item => + this._setResult(item, this._tests[item]()), + ) + }> + Run all tests + + this.setState({testResults: {}})} + style={[styles.column, styles.button]}> + Clear results + + + item} + renderItem={({item}) => ( + + this._setResult(item, this._tests[item]())}> + {item} + + {this._renderResult(item)} + + )} + /> + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + item: { + flexDirection: 'row', + margin: 6, + }, + column: { + flex: 2, + justifyContent: 'center', + padding: 3, + }, + result: { + alignItems: 'stretch', + justifyContent: 'space-between', + }, + value: { + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + fontSize: 12, + }, + type: { + color: '#333', + fontSize: 10, + }, + button: { + borderColor: '#444', + padding: 3, + flex: 1, + }, + buttonTextLarge: { + textAlign: 'center', + color: 'rgb(0,122,255)', + fontSize: 16, + padding: 6, + }, + buttonText: { + color: 'rgb(0,122,255)', + textAlign: 'center', + }, +}); + +module.exports = NativeCxxModuleExampleExample; diff --git a/packages/rn-tester/js/examples/TurboModule/TurboCxxModuleExample.js b/packages/rn-tester/js/examples/TurboModule/TurboCxxModuleExample.js new file mode 100644 index 00000000000000..18e9f53ae249d9 --- /dev/null +++ b/packages/rn-tester/js/examples/TurboModule/TurboCxxModuleExample.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const React = require('react'); +const NativeCxxModuleExampleExample = require('./NativeCxxModuleExampleExample'); + +exports.displayName = (undefined: ?string); +exports.title = 'Cxx TurboModule'; +exports.category = 'Basic'; +exports.description = 'Usage of Cxx TurboModule'; +exports.examples = [ + { + title: 'TurboCxxModuleExample', + render: function (): React.Element { + return ; + }, + }, +]; diff --git a/packages/rn-tester/js/utils/RNTesterList.android.js b/packages/rn-tester/js/utils/RNTesterList.android.js index 97e4b43cec6602..3e590bb405fc05 100644 --- a/packages/rn-tester/js/utils/RNTesterList.android.js +++ b/packages/rn-tester/js/utils/RNTesterList.android.js @@ -288,6 +288,11 @@ const APIs: Array = [ category: 'Basic', module: require('../examples/TurboModule/TurboModuleExample'), }, + { + key: 'TurboCxxModuleExample', + category: 'Basic', + module: require('../examples/TurboModule/TurboCxxModuleExample'), + }, ]; if (ReactNativeFeatureFlags.shouldEmitW3CPointerEvents()) { diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index af266e7ea0efdb..d0536191ec8a8d 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -258,6 +258,10 @@ const APIs: Array = [ key: 'TurboModuleExample', module: require('../examples/TurboModule/TurboModuleExample'), }, + { + key: 'TurboCxxModuleExample', + module: require('../examples/TurboModule/TurboCxxModuleExample'), + }, { key: 'VibrationExample', module: require('../examples/Vibration/VibrationExample'),