From 04ea9762e2013dcebf9f8a51d8974fa799e41cd5 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Fri, 2 Nov 2018 00:14:13 -0700 Subject: [PATCH] iOS: register lazy nativemodules on startup when Chrome is attached Summary: @public This allows apps to specify custom lazy modules that they need to load during chrome debugging. This is because lazy modules won't be available on start up, hence these modules will be missing in JS land, causing problems. Reviewed By: shergin Differential Revision: D12899408 fbshipit-source-id: dca313648e994b22e3ee5afec856ef76470065f9 --- React/Base/RCTBridgeDelegate.h | 5 ++++ React/CxxBridge/RCTCxxBridge.mm | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h index c237b9880fa7be..5133d6fb06002b 100644 --- a/React/Base/RCTBridgeDelegate.h +++ b/React/Base/RCTBridgeDelegate.h @@ -72,4 +72,9 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback; +/** + * Retrieve the list of lazy-native-modules names for the given bridge. + */ +- (NSDictionary *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge; + @end diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 9124fc185aaea8..29d5361bd5662b 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -301,6 +301,7 @@ - (void)start [self registerExtraModules]; // Initialize all native modules that cannot be loaded lazily (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; + [self registerExtraLazyModules]; [_performanceLogger markStopForTag:RCTPLNativeModuleInit]; @@ -600,6 +601,57 @@ - (void)registerExtraModules RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } +- (void)registerExtraLazyModules +{ +#if RCT_DEBUG + // This is debug-only and only when Chrome is attached, since it expects all modules to be already + // available on start up. Otherwise, we can let the lazy module discovery to load them on demand. + Class executorClass = [_parentBridge executorClass]; + if (executorClass && [NSStringFromClass(executorClass) isEqualToString:@"RCTWebSocketExecutor"]) { + NSDictionary *moduleClasses = nil; + if ([self.delegate respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) { + moduleClasses = [self.delegate extraLazyModuleClassesForBridge:_parentBridge]; + } + + if (!moduleClasses) { + return; + } + + // This logic is mostly copied from `registerModulesForClasses:`, but with one difference: + // we must use the names provided by the delegate method here. + for (NSString *moduleName in moduleClasses) { + Class moduleClass = moduleClasses[moduleName]; + if (RCTJSINativeModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTJSINativeModule)]) { + continue; + } + + // Check for module name collisions + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (moduleData) { + if (moduleData.hasInstance) { + // Existing module was preregistered, so it takes precedence + continue; + } else if ([moduleClass new] == nil) { + // The new module returned nil from init, so use the old module + continue; + } else if ([moduleData.moduleClass new] != nil) { + // Both modules were non-nil, so it's unclear which should take precedence + RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the " + "name '%@', but name was already registered by class %@", + moduleClass, moduleName, moduleData.moduleClass); + } + } + + moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; + + _moduleDataByName[moduleName] = moduleData; + [_moduleClassesByID addObject:moduleClass]; + [_moduleDataByID addObject:moduleData]; + } + } +#endif +} + - (NSArray *)_initializeModules:(NSArray> *)modules withDispatchGroup:(dispatch_group_t)dispatchGroup lazilyDiscovered:(BOOL)lazilyDiscovered