From 7f22db8ea034d1aed74103542b04af2a8a11caa1 Mon Sep 17 00:00:00 2001 From: Dmitry Rykun Date: Tue, 30 May 2023 08:04:23 -0700 Subject: [PATCH] Introduce __nativeComponentRegistry__getNativeViewConfig (#37522) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37522 This diff adds cross-platform Cxx binding helper and iOS specific implementation of `__nativeComponentRegistry__getNativeViewConfig` to JS runtime. This function provides native view config for a native component in bridgeless mode. Changelog: [Internal] - Introduce `__nativeComponentRegistry__getNativeViewConfig` in iOS. Reviewed By: javache Differential Revision: D43538804 fbshipit-source-id: 0aeca40c1c2a625eca5d60e466eb24df30453ba7 --- .../react-native/React/Base/RCTConstants.h | 6 ++ .../react-native/React/Base/RCTConstants.m | 15 +++++ .../react-native/React/Modules/RCTUIManager.h | 7 +++ .../react-native/React/Modules/RCTUIManager.m | 28 ++++++--- .../React/Views/RCTComponentData.h | 3 + .../React/Views/RCTComponentData.m | 29 ++++++---- .../NativeViewConfigProviderBinding.cpp | 30 ++++++++++ .../NativeViewConfigProviderBinding.h | 20 +++++++ .../platform/ios/Core/RCTInstance.mm | 6 ++ .../RCTNativeViewConfigProvider.h | 17 ++++++ .../RCTNativeViewConfigProvider.mm | 58 +++++++++++++++++++ 11 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.cpp create mode 100644 packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.h create mode 100644 packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.h create mode 100644 packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.mm diff --git a/packages/react-native/React/Base/RCTConstants.h b/packages/react-native/React/Base/RCTConstants.h index 217b7aabc21..b1c244ac93c 100644 --- a/packages/react-native/React/Base/RCTConstants.h +++ b/packages/react-native/React/Base/RCTConstants.h @@ -81,3 +81,9 @@ RCT_EXTERN void RCTSetMemoryPressureUnloadLevel(int value); */ RCT_EXTERN BOOL RCTGetParseUnhandledJSErrorStackNatively(void); RCT_EXTERN void RCTSetParseUnhandledJSErrorStackNatively(BOOL value); + +/* + * Use native view configs in bridgeless mode + */ +RCT_EXTERN BOOL RCTGetUseNativeViewConfigsInBridgelessMode(void); +RCT_EXTERN void RCTSetUseNativeViewConfigsInBridgelessMode(BOOL value); diff --git a/packages/react-native/React/Base/RCTConstants.m b/packages/react-native/React/Base/RCTConstants.m index de1095f0018..81b8741d583 100644 --- a/packages/react-native/React/Base/RCTConstants.m +++ b/packages/react-native/React/Base/RCTConstants.m @@ -81,3 +81,18 @@ void RCTSetParseUnhandledJSErrorStackNatively(BOOL value) { RCTParseUnhandledJSErrorStackNatively = value; } + +/* + * Use native view configs in bridgeless mode + */ +static BOOL RCTUseNativeViewConfigsInBridgelessMode = NO; + +BOOL RCTGetUseNativeViewConfigsInBridgelessMode(void) +{ + return RCTUseNativeViewConfigsInBridgelessMode; +} + +void RCTSetUseNativeViewConfigsInBridgelessMode(BOOL value) +{ + RCTUseNativeViewConfigsInBridgelessMode = value; +} diff --git a/packages/react-native/React/Modules/RCTUIManager.h b/packages/react-native/React/Modules/RCTUIManager.h index 77472622f3f..67fc4289b62 100644 --- a/packages/react-native/React/Modules/RCTUIManager.h +++ b/packages/react-native/React/Modules/RCTUIManager.h @@ -172,3 +172,10 @@ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplier @property (nonatomic, readonly) RCTUIManager *uiManager; @end + +RCT_EXTERN NSMutableDictionary *RCTModuleConstantsForDestructuredComponent( + NSMutableDictionary *directEvents, + NSMutableDictionary *bubblingEvents, + Class managerClass, + NSString *name, + NSDictionary *viewConfig); diff --git a/packages/react-native/React/Modules/RCTUIManager.m b/packages/react-native/React/Modules/RCTUIManager.m index 3b8a8518c68..6f03df143f1 100644 --- a/packages/react-native/React/Modules/RCTUIManager.m +++ b/packages/react-native/React/Modules/RCTUIManager.m @@ -1463,10 +1463,12 @@ RCT_EXPORT_METHOD(clearJSResponder) }]; } -static NSMutableDictionary *moduleConstantsForComponent( +NSMutableDictionary *RCTModuleConstantsForDestructuredComponent( NSMutableDictionary *directEvents, NSMutableDictionary *bubblingEvents, - RCTComponentData *componentData) + Class managerClass, + NSString *name, + NSDictionary *viewConfig) { NSMutableDictionary *moduleConstants = [NSMutableDictionary new]; @@ -1476,10 +1478,9 @@ static NSMutableDictionary *moduleConstantsForComponent( NSMutableDictionary *directEventTypes = [NSMutableDictionary new]; // Add manager class - moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass); + moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(managerClass); // Add native props - NSDictionary *viewConfig = [componentData viewConfig]; moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"]; moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"]; moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes; @@ -1497,7 +1498,7 @@ static NSMutableDictionary *moduleConstantsForComponent( RCTLogError( @"Component '%@' re-registered bubbling event '%@' as a " "direct event", - componentData.name, + name, eventName); } } @@ -1518,7 +1519,7 @@ static NSMutableDictionary *moduleConstantsForComponent( RCTLogError( @"Component '%@' re-registered direct event '%@' as a " "bubbling event", - componentData.name, + name, eventName); } } @@ -1540,7 +1541,7 @@ static NSMutableDictionary *moduleConstantsForComponent( RCTLogError( @"Component '%@' re-registered direct event '%@' as a " "bubbling event", - componentData.name, + name, eventName); } } @@ -1548,6 +1549,15 @@ static NSMutableDictionary *moduleConstantsForComponent( return moduleConstants; } +static NSMutableDictionary *moduleConstantsForComponentData( + NSMutableDictionary *directEvents, + NSMutableDictionary *bubblingEvents, + RCTComponentData *componentData) +{ + return RCTModuleConstantsForDestructuredComponent( + directEvents, bubblingEvents, componentData.managerClass, componentData.name, componentData.viewConfig); +} + - (NSDictionary *)constantsToExport { return [self getConstants]; @@ -1563,7 +1573,7 @@ static NSMutableDictionary *moduleConstantsForComponent( enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) { RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name); NSMutableDictionary *moduleConstants = - moduleConstantsForComponent(directEvents, bubblingEvents, componentData); + moduleConstantsForComponentData(directEvents, bubblingEvents, componentData); constants[name] = moduleConstants; }]; @@ -1611,7 +1621,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView : (NSString *)name) NSMutableDictionary *directEvents = [NSMutableDictionary new]; NSMutableDictionary *bubblingEvents = [NSMutableDictionary new]; NSMutableDictionary *moduleConstants = - moduleConstantsForComponent(directEvents, bubblingEvents, componentData); + moduleConstantsForComponentData(directEvents, bubblingEvents, componentData); return @{ @"viewConfig" : moduleConstants, }; diff --git a/packages/react-native/React/Views/RCTComponentData.h b/packages/react-native/React/Views/RCTComponentData.h index 352f8262506..1dd11977835 100644 --- a/packages/react-native/React/Views/RCTComponentData.h +++ b/packages/react-native/React/Views/RCTComponentData.h @@ -42,8 +42,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy, nullable) void (^eventInterceptor) (NSString *eventName, NSDictionary *event, NSNumber *reactTag); ++ (NSDictionary *)viewConfigForViewMangerClass:(Class)managerClass; - (NSDictionary *)viewConfig; @end +RCT_EXTERN NSString *RCTViewManagerModuleNameForClass(Class managerClass); + NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Views/RCTComponentData.m b/packages/react-native/React/Views/RCTComponentData.m index 8b345905809..94b30a63480 100644 --- a/packages/react-native/React/Views/RCTComponentData.m +++ b/packages/react-native/React/Views/RCTComponentData.m @@ -55,7 +55,7 @@ static SEL selectorForType(NSString *type) _viewPropBlocks = [NSMutableDictionary new]; _shadowPropBlocks = [NSMutableDictionary new]; - _name = moduleNameForClass(managerClass); + _name = RCTViewManagerModuleNameForClass(managerClass); } return self; } @@ -385,7 +385,7 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S }]; } -- (NSDictionary *)viewConfig ++ (NSDictionary *)viewConfigForViewMangerClass:(Class)managerClass { NSMutableArray *bubblingEvents = [NSMutableArray new]; NSMutableArray *capturingEvents = [NSMutableArray new]; @@ -393,8 +393,8 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) { - NSArray *events = [self.manager customBubblingEventTypes]; + if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) { + NSArray *events = [[managerClass new] customBubblingEventTypes]; for (NSString *event in events) { [bubblingEvents addObject:RCTNormalizeInputEventName(event)]; } @@ -403,7 +403,7 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S unsigned int count = 0; NSMutableDictionary *propTypes = [NSMutableDictionary new]; - Method *methods = class_copyMethodList(object_getClass(_managerClass), &count); + Method *methods = class_copyMethodList(object_getClass(managerClass), &count); for (unsigned int i = 0; i < count; i++) { SEL selector = method_getName(methods[i]); const char *selectorName = sel_getName(selector); @@ -418,13 +418,13 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S } NSString *name = @(underscorePos + 1); - NSString *type = ((NSArray * (*)(id, SEL)) objc_msgSend)(_managerClass, selector)[0]; + NSString *type = ((NSArray * (*)(id, SEL)) objc_msgSend)(managerClass, selector)[0]; if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) { RCTLogError( @"Property '%@' of component '%@' redefined from '%@' " "to '%@'", name, - _name, + RCTViewManagerModuleNameForClass(managerClass), propTypes[name], type); } @@ -450,24 +450,31 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S RCTLogError( @"Component '%@' registered '%@' as both a bubbling event " "and a direct event", - _name, + RCTViewManagerModuleNameForClass(managerClass), event); } } #endif - Class superClass = [_managerClass superclass]; + Class superClass = [managerClass superclass]; return @{ @"propTypes" : propTypes, @"directEvents" : directEvents, @"bubblingEvents" : bubblingEvents, @"capturingEvents" : capturingEvents, - @"baseModuleName" : superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass), + @"baseModuleName" : superClass == [NSObject class] ? (id)kCFNull : RCTViewManagerModuleNameForClass(superClass), }; } -static NSString *moduleNameForClass(Class managerClass) +- (NSDictionary *)viewConfig +{ + // Make sure the manager is initialized before accessing view config. + [self manager]; + return [RCTComponentData viewConfigForViewMangerClass:_managerClass]; +} + +NSString *RCTViewManagerModuleNameForClass(Class managerClass) { // Hackety hack, this partially re-implements RCTBridgeModuleNameForClass // We want to get rid of RCT and RK prefixes, but a lot of JS code still references diff --git a/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.cpp b/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.cpp new file mode 100644 index 00000000000..d29eca015d1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.cpp @@ -0,0 +1,30 @@ +/* + * 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 "NativeViewConfigProviderBinding.h" + +namespace facebook::react::NativeViewConfigProviderBinding { + +void install(jsi::Runtime &runtime, ProviderType &&provider) { + auto name = "RN$NativeComponentRegistry_getNativeViewConfig"; + auto hostFunction = [provider = std::move(provider)]( + jsi::Runtime &runtime, + jsi::Value const & /*thisValue*/, + jsi::Value const *args, + size_t count) -> jsi::Value { + if (count != 1 || !args[0].isString()) { + throw new jsi::JSError(runtime, "1 argument of type String expected."); + } + return provider(args[0].getString(runtime).utf8(runtime)); + }; + + auto jsiFunction = jsi::Function::createFromHostFunction( + runtime, jsi::PropNameID::forAscii(runtime, name), 2, hostFunction); + + runtime.global().setProperty(runtime, name, jsiFunction); +} +} // namespace facebook::react::NativeViewConfigProviderBinding diff --git a/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.h b/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.h new file mode 100644 index 00000000000..b635eab8777 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridgeless/nativeviewconfig/NativeViewConfigProviderBinding.h @@ -0,0 +1,20 @@ +/* + * 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 + +namespace facebook::react::NativeViewConfigProviderBinding { + +using ProviderType = std::function; + +/* + * Installs native view config provider into JavaScript runtime. + */ +void install(jsi::Runtime &runtime, ProviderType &&provider); +} // namespace facebook::react::NativeViewConfigProviderBinding diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm index 3873592bf54..20b4d77c8da 100644 --- a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm @@ -14,6 +14,7 @@ #import #import #import +#import #import #import #import @@ -25,6 +26,7 @@ #import #import #import +#import #import #import #import @@ -283,6 +285,10 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags) }); RCTInstallNativeComponentRegistryBinding(runtime); + if (RCTGetUseNativeViewConfigsInBridgelessMode()) { + installNativeViewConfigProviderBinding(runtime); + } + if (strongSelf->_bindingsInstallFunc) { strongSelf->_bindingsInstallFunc(runtime); } diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.h b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.h new file mode 100644 index 00000000000..2f5c555581a --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.h @@ -0,0 +1,17 @@ +/* + * 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 + +namespace facebook::react { +/* + * Installs native view config provider into JavaScript runtime. + */ +void installNativeViewConfigProviderBinding(jsi::Runtime &runtime); +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.mm b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.mm new file mode 100644 index 00000000000..97108934347 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/NativeViewConfig/RCTNativeViewConfigProvider.mm @@ -0,0 +1,58 @@ +/* + * 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 "RCTNativeViewConfigProvider.h" + +#import +#import +#import +#import +#import +#import + +namespace facebook::react { +namespace { + +// This function eagerly loads module constants for every RCTViewManager subclass. +// This is not compatible with lazily loaded modules, but we don't have them in OSS, so that's fine for now. +NSDictionary *eagerViewConfigs() +{ + static NSMutableDictionary *result = [NSMutableDictionary new]; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + auto directEvents = [NSMutableDictionary new]; + auto bubblingEvents = [NSMutableDictionary new]; + for (Class moduleClass in RCTGetModuleClasses()) { + if ([moduleClass isSubclassOfClass:RCTViewManager.class]) { + auto name = RCTViewManagerModuleNameForClass(moduleClass); + auto viewConfig = [RCTComponentData viewConfigForViewMangerClass:moduleClass]; + auto moduleConstants = + RCTModuleConstantsForDestructuredComponent(directEvents, bubblingEvents, moduleClass, name, viewConfig); + result[name] = moduleConstants; + } + } + }); + return result; +} + +jsi::Value provideNativeViewConfig(facebook::jsi::Runtime &runtime, std::string const &name) +{ + auto componentName = [NSString stringWithCString:name.c_str() encoding:NSASCIIStringEncoding]; + auto viewConfig = eagerViewConfigs()[componentName]; + return TurboModuleConvertUtils::convertObjCObjectToJSIValue(runtime, viewConfig); +}; + +} // namespace + +void installNativeViewConfigProviderBinding(jsi::Runtime &runtime) +{ + auto nativeViewConfigProvider = [&runtime](std::string const &name) -> jsi::Value { + return provideNativeViewConfig(runtime, name); + }; + NativeViewConfigProviderBinding::install(runtime, std::move(nativeViewConfigProvider)); +} +} // namespace facebook::react