From e185cf80102714de228320a381403af1036103ca Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 10 Oct 2023 05:59:54 -0700 Subject: [PATCH] Make the Fabric Interop layer work in Bridgeless (#40732) Summary: This change allows the Fabric Interop Layer to work in bridgeless mode. Given that the legacy components requires a Bridge to send events and use commands, this change simulates a bridge when it doesn't exists (i.e.: we are in bridgeless mode). In order to make it work, we had to simulate a few elements from the Bridge and the UIManager. ## Changelog: [iOS][Fixed] - Make the Fabric interop layer works in bridgeless mode. ## Facebook: As an alternative approach, we could have created a `protocol RCTBridging`, have the `RCTBridge` conform to that protocol, and create a new type for it. Practically this would have been much more cumbersome: 1. The [RCTBridge](https://www.internalfb.com/code/fbsource/[916531b9bf7a9943036807f7563c925b4c3e0101]/xplat/js/react-native-github/packages/react-native/React/Base/RCTBridge.h?lines=87-238) interface is quite big. All the props and method should be part of the protocol. 2. Extensions declared on RCTBridge would not have worked. For example, [`RCTBridge (RCTUIManager)`](https://www.internalfb.com/code/fbsource/[916531b9bf7a9943036807f7563c925b4c3e0101]/xplat/js/react-native-github/packages/react-native/React/Modules/RCTUIManager.h?lines=170-174). 3. It would require a major overhaul of the APIs, returning `id` in place of **every** function that takes/return an instance of `RCTBridge *`. Clearly, not a feasible way to go. Differential Revision: D50079929 --- .../react-native/React/Base/RCTBridgeProxy.mm | 14 +++-- .../react-native/React/Modules/RCTUIManager.h | 7 +++ .../React/Views/RCTComponentData.m | 2 +- ...cyViewManagerInteropComponentDescriptor.mm | 8 +++ .../RCTLegacyViewManagerInteropCoordinator.h | 4 +- .../RCTLegacyViewManagerInteropCoordinator.mm | 52 +++++++++++++++---- .../platform/ios/ReactCommon/RCTInstance.mm | 34 ++++++------ 7 files changed, 89 insertions(+), 32 deletions(-) diff --git a/packages/react-native/React/Base/RCTBridgeProxy.mm b/packages/react-native/React/Base/RCTBridgeProxy.mm index 8b49d8d9e72397..e4ece263b12bad 100644 --- a/packages/react-native/React/Base/RCTBridgeProxy.mm +++ b/packages/react-native/React/Base/RCTBridgeProxy.mm @@ -351,6 +351,11 @@ - (RCTUIManager *)uiManager return (RCTUIManager *)_uiManagerProxy; } +- (RCTBridgeProxy *)object +{ + return self; +} + /** * NSProxy setup */ @@ -387,12 +392,14 @@ - (void)logError:(NSString *)message cmd:(SEL)cmd @implementation RCTUIManagerProxy { RCTViewRegistry *_viewRegistry; + NSMutableDictionary *_legacyViewRegistry; } - (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry { self = [super self]; if (self) { _viewRegistry = viewRegistry; + _legacyViewRegistry = [NSMutableDictionary new]; } return self; } @@ -404,20 +411,21 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag { [self logWarning:@"Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." cmd:_cmd]; - return [_viewRegistry viewForReactTag:reactTag]; + return [_viewRegistry viewForReactTag:reactTag] ? [_viewRegistry viewForReactTag:reactTag] + : [_legacyViewRegistry objectForKey:reactTag]; } - (void)addUIBlock:(RCTViewManagerUIBlock)block { [self logWarning: - @"This method isn't implemented faithfully: the viewRegistry passed to RCTViewManagerUIBlock is nil. Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." + @"This method isn't implemented faithfully. Please migrate to RCTViewRegistry if possible: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." cmd:_cmd]; __weak __typeof(self) weakSelf = self; RCTExecuteOnMainQueue(^{ __typeof(self) strongSelf = weakSelf; if (strongSelf) { - block((RCTUIManager *)strongSelf, nil); + block((RCTUIManager *)strongSelf, strongSelf->_legacyViewRegistry); } }); } diff --git a/packages/react-native/React/Modules/RCTUIManager.h b/packages/react-native/React/Modules/RCTUIManager.h index 67fc4289b62395..0979c965f8fdca 100644 --- a/packages/react-native/React/Modules/RCTUIManager.h +++ b/packages/react-native/React/Modules/RCTUIManager.h @@ -9,6 +9,7 @@ #import #import +#import #import #import #import @@ -173,6 +174,12 @@ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplier @end +@interface RCTBridgeProxy (RCTUIManager) + +@property (nonatomic, readonly) RCTUIManager *uiManager; + +@end + RCT_EXTERN NSMutableDictionary *RCTModuleConstantsForDestructuredComponent( NSMutableDictionary *directEvents, NSMutableDictionary *bubblingEvents, diff --git a/packages/react-native/React/Views/RCTComponentData.m b/packages/react-native/React/Views/RCTComponentData.m index 888cad59b88e27..52efad6e159d21 100644 --- a/packages/react-native/React/Views/RCTComponentData.m +++ b/packages/react-native/React/Views/RCTComponentData.m @@ -71,7 +71,7 @@ - (RCTViewManager *)manager object:nil userInfo:@{@"module" : _bridgelessViewManager}]; } - return _manager ?: _bridgelessViewManager; + return _manager ? _manager : _bridgelessViewManager; } RCT_NOT_IMPLEMENTED(-(instancetype)init) diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm index df08d6e29fa899..c0f3c2f8028210 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm @@ -8,6 +8,7 @@ #include "LegacyViewManagerInteropComponentDescriptor.h" #include #include +#include #include #include #include @@ -86,6 +87,12 @@ static Class getViewManagerFromComponentName(const std::string &componentName) bridge = unwrapManagedObjectWeakly(optionalBridge.value()); } + RCTBridgeProxy *bridgeProxy; + auto optionalBridgeProxy = contextContainer->find>("RCTBridgeProxy"); + if (optionalBridgeProxy) { + bridgeProxy = unwrapManagedObjectWeakly(optionalBridgeProxy.value()); + } + auto optionalEventDispatcher = contextContainer->find>("RCTEventDispatcher"); RCTEventDispatcher *eventDispatcher; if (optionalEventDispatcher) { @@ -104,6 +111,7 @@ static Class getViewManagerFromComponentName(const std::string &componentName) return wrapManagedObject([[RCTLegacyViewManagerInteropCoordinator alloc] initWithComponentData:componentData bridge:bridge + bridgeProxy:bridgeProxy bridgelessInteropData:bridgeModuleDecorator]); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.h b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.h index 4b7d333103f5b8..4d7289a1f94710 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.h +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.h @@ -14,13 +14,15 @@ NS_ASSUME_NONNULL_BEGIN @class RCTComponentData; @class RCTBridge; +@class RCTBridgeProxy; typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event); @interface RCTLegacyViewManagerInteropCoordinator : NSObject - (instancetype)initWithComponentData:(RCTComponentData *)componentData - bridge:(RCTBridge *)bridge + bridge:(nullable RCTBridge *)bridge + bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData; - (UIView *)createPaperViewWithTag:(NSInteger)tag; diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm index 72e17982dd0b32..f114594fd3ed07 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm @@ -8,6 +8,7 @@ #include "RCTLegacyViewManagerInteropCoordinator.h" #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include @@ -25,6 +27,8 @@ @implementation RCTLegacyViewManagerInteropCoordinator { RCTComponentData *_componentData; __weak RCTBridge *_bridge; __weak RCTBridgeModuleDecorator *_bridgelessInteropData; + __weak RCTBridgeProxy *_bridgeProxy; + /* Each instance of `RCTLegacyViewManagerInteropComponentView` registers a block to which events are dispatched. This is the container that maps unretained UIView pointer to a block to which the event is dispatched. @@ -40,13 +44,16 @@ @implementation RCTLegacyViewManagerInteropCoordinator { } - (instancetype)initWithComponentData:(RCTComponentData *)componentData - bridge:(RCTBridge *)bridge + bridge:(nullable RCTBridge *)bridge + bridgeProxy:(nullable RCTBridgeProxy *)bridgeProxy bridgelessInteropData:(RCTBridgeModuleDecorator *)bridgelessInteropData; { if (self = [super init]) { _componentData = componentData; _bridge = bridge; _bridgelessInteropData = bridgelessInteropData; + _bridgeProxy = bridgeProxy; + if (bridgelessInteropData) { // During bridge mode, RCTBridgeModules will be decorated with these APIs by the bridge. RCTAssert( @@ -62,7 +69,9 @@ - (instancetype)initWithComponentData:(RCTComponentData *)componentData if (strongSelf) { InterceptorBlock block = [strongSelf->_eventInterceptors objectForKey:reactTag]; if (block) { - block(std::string([RCTNormalizeInputEventName(eventName) UTF8String]), convertIdToFollyDynamic(event ?: @{})); + block( + std::string([RCTNormalizeInputEventName(eventName) UTF8String]), + convertIdToFollyDynamic(event ? event : @{})); } } }; @@ -131,15 +140,9 @@ - (void)handleCommand:(NSString *)commandName NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args]; if (_bridge) { - [_bridge.batchedBridge - dispatchBlock:^{ - [method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs]; - [self->_bridge.uiManager setNeedsLayout]; - } - queue:RCTGetUIManagerQueue()]; + [self _handleCommandsOnBridge:method withArgs:newArgs]; } else { - // TODO T86826778 - Figure out which queue this should be dispatched to. - [method invokeWithBridge:nil module:self->_componentData.manager arguments:newArgs]; + [self _handleCommandsOnBridgeless:method withArgs:newArgs]; } } @@ -169,8 +172,37 @@ - (void)removeViewFromRegistryWithTag:(NSInteger)tag } #pragma mark - Private +- (void)_handleCommandsOnBridge:(id)method withArgs:(NSArray *)newArgs +{ + [_bridge.batchedBridge + dispatchBlock:^{ + [method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs]; + [self->_bridge.uiManager setNeedsLayout]; + } + queue:RCTGetUIManagerQueue()]; +} + +- (void)_handleCommandsOnBridgeless:(id)method withArgs:(NSArray *)newArgs +{ + RCTViewManager *componentViewManager = self->_componentData.manager; + [componentViewManager setValue:_bridgeProxy forKey:@"bridge"]; + + [self->_bridgeProxy.uiManager + addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + [method invokeWithBridge:nil module:componentViewManager arguments:newArgs]; + }]; +} - (void)_addUIBlock:(RCTViewManagerUIBlock)block +{ + if (_bridge) { + [self _addUIBlockOnBridge:block]; + } else { + [self->_bridgeProxy.uiManager addUIBlock:block]; + } +} + +- (void)_addUIBlockOnBridge:(RCTViewManagerUIBlock)block { __weak __typeof__(self) weakSelf = self; [_bridge.batchedBridge diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 027e24b182ba9b..f73100ae5fdc53 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -227,24 +227,23 @@ - (void)_start RuntimeExecutor bufferedRuntimeExecutor = _reactInstance->getBufferedRuntimeExecutor(); timerManager->setRuntimeExecutor(bufferedRuntimeExecutor); - RCTBridgeProxy *bridgeProxy = RCTTurboModuleInteropEnabled() && RCTTurboModuleInteropBridgeProxyEnabled() - ? [[RCTBridgeProxy alloc] initWithViewRegistry:_bridgeModuleDecorator.viewRegistry_DEPRECATED - moduleRegistry:_bridgeModuleDecorator.moduleRegistry - bundleManager:_bridgeModuleDecorator.bundleManager - callableJSModules:_bridgeModuleDecorator.callableJSModules - dispatchToJSThread:^(dispatch_block_t block) { - __strong __typeof(self) strongSelf = weakSelf; - if (strongSelf && strongSelf->_valid) { - strongSelf->_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) { block(); }); - } + RCTBridgeProxy *bridgeProxy = + [[RCTBridgeProxy alloc] initWithViewRegistry:_bridgeModuleDecorator.viewRegistry_DEPRECATED + moduleRegistry:_bridgeModuleDecorator.moduleRegistry + bundleManager:_bridgeModuleDecorator.bundleManager + callableJSModules:_bridgeModuleDecorator.callableJSModules + dispatchToJSThread:^(dispatch_block_t block) { + __strong __typeof(self) strongSelf = weakSelf; + if (strongSelf && strongSelf->_valid) { + strongSelf->_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) { block(); }); } - registerSegmentWithId:^(NSNumber *segmentId, NSString *path) { - __strong __typeof(self) strongSelf = weakSelf; - if (strongSelf && strongSelf->_valid) { - [strongSelf registerSegmentWithId:segmentId path:path]; - } - }] - : nil; + } + registerSegmentWithId:^(NSNumber *segmentId, NSString *path) { + __strong __typeof(self) strongSelf = weakSelf; + if (strongSelf && strongSelf->_valid) { + [strongSelf registerSegmentWithId:segmentId path:path]; + } + }]; // Set up TurboModules _turboModuleManager = [[RCTTurboModuleManager alloc] @@ -269,6 +268,7 @@ - (void)_start facebook::react::wrapManagedObject([_turboModuleManager moduleForName:"RCTEventDispatcher"])); contextContainer->insert("RCTBridgeModuleDecorator", facebook::react::wrapManagedObject(_bridgeModuleDecorator)); contextContainer->insert("RuntimeScheduler", std::weak_ptr(_reactInstance->getRuntimeScheduler())); + contextContainer->insert("RCTBridgeProxy", facebook::react::wrapManagedObject(bridgeProxy)); _surfacePresenter = [[RCTSurfacePresenter alloc] initWithContextContainer:contextContainer