/** * 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. */ #include #include #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "NSDataBigString.h" #import "RCTJSCHelpers.h" #import "RCTMessageThread.h" #import "RCTObjcExecutor.h" #ifdef WITH_FBSYSTRACE #import #endif #if RCT_DEV && __has_include("RCTDevLoadingView.h") #import "RCTDevLoadingView.h" #endif #define RCTAssertJSThread() \ RCTAssert(self.executorClass || self->_jsThread == [NSThread currentThread], \ @"This method must be called on JS thread") static NSString *const RCTJSThreadName = @"com.facebook.react.JavaScript"; typedef void (^RCTPendingCall)(); using namespace facebook::react; /** * Must be kept in sync with `MessageQueue.js`. */ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldRequestModuleIDs = 0, RCTBridgeFieldMethodIDs, RCTBridgeFieldParams, RCTBridgeFieldCallID, }; namespace { class GetDescAdapter : public JSExecutorFactory { public: GetDescAdapter(RCTCxxBridge *bridge, std::shared_ptr factory) : bridge_(bridge) , factory_(factory) {} std::unique_ptr createJSExecutor( std::shared_ptr delegate, std::shared_ptr jsQueue) override { auto ret = factory_->createJSExecutor(delegate, jsQueue); bridge_.bridgeDescription = [NSString stringWithFormat:@"RCTCxxBridge %s", ret->getDescription().c_str()]; return std::move(ret); } private: RCTCxxBridge *bridge_; std::shared_ptr factory_; }; } static bool isRAMBundle(NSData *script) { BundleHeader header; [script getBytes:&header length:sizeof(header)]; return parseTypeFromHeader(header) == ScriptTag::RAMBundle; } static void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger) { __weak RCTPerformanceLogger *weakPerformanceLogger = performanceLogger; ReactMarker::logTaggedMarker = [weakPerformanceLogger](const ReactMarker::ReactMarkerId markerId, const char *tag) { switch (markerId) { case ReactMarker::RUN_JS_BUNDLE_START: [weakPerformanceLogger markStartForTag:RCTPLScriptExecution]; break; case ReactMarker::RUN_JS_BUNDLE_STOP: [weakPerformanceLogger markStopForTag:RCTPLScriptExecution]; break; case ReactMarker::NATIVE_REQUIRE_START: [weakPerformanceLogger appendStartForTag:RCTPLRAMNativeRequires]; break; case ReactMarker::NATIVE_REQUIRE_STOP: [weakPerformanceLogger appendStopForTag:RCTPLRAMNativeRequires]; [weakPerformanceLogger addValue:1 forTag:RCTPLRAMNativeRequiresCount]; break; case ReactMarker::CREATE_REACT_CONTEXT_STOP: case ReactMarker::JS_BUNDLE_STRING_CONVERT_START: case ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP: case ReactMarker::NATIVE_MODULE_SETUP_START: case ReactMarker::NATIVE_MODULE_SETUP_STOP: case ReactMarker::REGISTER_JS_SEGMENT_START: case ReactMarker::REGISTER_JS_SEGMENT_STOP: // These are not used on iOS. break; } }; } @interface RCTCxxBridge () @property (nonatomic, weak, readonly) RCTBridge *parentBridge; @property (nonatomic, assign, readonly) BOOL moduleSetupComplete; - (instancetype)initWithParentBridge:(RCTBridge *)bridge; - (void)partialBatchDidFlush; - (void)batchDidComplete; @end struct RCTInstanceCallback : public InstanceCallback { __weak RCTCxxBridge *bridge_; RCTInstanceCallback(RCTCxxBridge *bridge): bridge_(bridge) {}; void onBatchComplete() override { // There's no interface to call this per partial batch [bridge_ partialBatchDidFlush]; [bridge_ batchDidComplete]; } }; @implementation RCTCxxBridge { BOOL _wasBatchActive; BOOL _didInvalidate; NSMutableArray *_pendingCalls; std::atomic _pendingCount; // Native modules NSMutableDictionary *_moduleDataByName; NSMutableArray *_moduleDataByID; NSMutableArray *_moduleClassesByID; NSUInteger _modulesInitializedOnMainQueue; RCTDisplayLink *_displayLink; // JS thread management NSThread *_jsThread; std::shared_ptr _jsMessageThread; // This is uniquely owned, but weak_ptr is used. std::shared_ptr _reactInstance; } @synthesize bridgeDescription = _bridgeDescription; @synthesize loading = _loading; @synthesize performanceLogger = _performanceLogger; @synthesize valid = _valid; + (void)initialize { if (self == [RCTCxxBridge class]) { RCTPrepareJSCExecutor(); } } - (JSGlobalContextRef)jsContextRef { return (JSGlobalContextRef)(_reactInstance ? _reactInstance->getJavaScriptContext() : nullptr); } - (std::shared_ptr)jsMessageThread { return _jsMessageThread; } - (BOOL)isInspectable { return _reactInstance ? _reactInstance->isInspectable() : NO; } - (instancetype)initWithParentBridge:(RCTBridge *)bridge { RCTAssertParam(bridge); if ((self = [super initWithDelegate:bridge.delegate bundleURL:bridge.bundleURL moduleProvider:bridge.moduleProvider launchOptions:bridge.launchOptions])) { _parentBridge = bridge; _performanceLogger = [bridge performanceLogger]; registerPerformanceLoggerHooks(_performanceLogger); RCTLogInfo(@"Initializing %@ (parent: %@, executor: %@)", self, bridge, [self executorClass]); /** * Set Initial State */ _valid = YES; _loading = YES; _pendingCalls = [NSMutableArray new]; _displayLink = [RCTDisplayLink new]; _moduleDataByName = [NSMutableDictionary new]; _moduleClassesByID = [NSMutableArray new]; _moduleDataByID = [NSMutableArray new]; [RCTBridge setCurrentBridge:self]; } return self; } + (void)runRunLoop { @autoreleasepool { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge runJSRunLoop] setup", nil); // copy thread name to pthread name pthread_setname_np([NSThread currentThread].name.UTF8String); // Set up a dummy runloop source to avoid spinning CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx); CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode); CFRelease(noSpinSource); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); // run the run loop while (kCFRunLoopRunStopped != CFRunLoopRunInMode(kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) { RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad. } } } - (void)_tryAndHandleError:(dispatch_block_t)block { NSError *error = tryAndReturnError(block); if (error) { [self handleError:error]; } } /** * Ensure block is run on the JS thread. If we're already on the JS thread, the block will execute synchronously. * If we're not on the JS thread, the block is dispatched to that thread. Any errors encountered while executing * the block will go through handleError: */ - (void)ensureOnJavaScriptThread:(dispatch_block_t)block { RCTAssert(_jsThread, @"This method must not be called before the JS thread is created"); // This does not use _jsMessageThread because it may be called early before the runloop reference is captured // and _jsMessageThread is valid. _jsMessageThread also doesn't allow us to shortcut the dispatch if we're // already on the correct thread. if ([NSThread currentThread] == _jsThread) { [self _tryAndHandleError:block]; } else { [self performSelector:@selector(_tryAndHandleError:) onThread:_jsThread withObject:block waitUntilDone:NO]; } } - (void)start { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge start]", nil); [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification object:_parentBridge userInfo:@{@"bridge": self}]; // Set up the JS thread early _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil]; _jsThread.name = RCTJSThreadName; _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive; #if RCT_DEBUG _jsThread.stackSize *= 2; #endif [_jsThread start]; dispatch_group_t prepareBridge = dispatch_group_create(); [_performanceLogger markStartForTag:RCTPLNativeModuleInit]; [self registerExtraModules]; // Initialize all native modules that cannot be loaded lazily (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; [_performanceLogger markStopForTag:RCTPLNativeModuleInit]; // This doesn't really do anything. The real work happens in initializeBridge. _reactInstance.reset(new Instance); __weak RCTCxxBridge *weakSelf = self; // Prepare executor factory (shared_ptr for copy into block) std::shared_ptr executorFactory; if (!self.executorClass) { if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { id cxxDelegate = (id) self.delegate; executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self]; } if (!executorFactory) { BOOL useCustomJSC = [self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] && [self.delegate shouldBridgeUseCustomJSC:self]; // We use the name of the device and the app for debugging & metrics NSString *deviceName = [[UIDevice currentDevice] name]; NSString *appName = [[NSBundle mainBundle] bundleIdentifier]; // The arg is a cache dir. It's not used with standard JSC. executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object ("OwnerIdentity", "ReactNative") ("AppIdentity", [(appName ?: @"unknown") UTF8String]) ("DeviceIdentity", [(deviceName ?: @"unknown") UTF8String]) ("UseCustomJSC", (bool)useCustomJSC) #if RCT_PROFILE ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch) #endif )); } } else { id objcExecutor = [self moduleForClass:self.executorClass]; executorFactory.reset(new RCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { if (error) { [weakSelf handleError:error]; } })); } // Dispatch the instance initialization as soon as the initial module metadata has // been collected (see initModules) dispatch_group_enter(prepareBridge); [self ensureOnJavaScriptThread:^{ [weakSelf _initializeBridge:executorFactory]; dispatch_group_leave(prepareBridge); }]; // Load the source asynchronously, then store it for later execution. dispatch_group_enter(prepareBridge); __block NSData *sourceCode; [self loadSource:^(NSError *error, RCTSource *source) { if (error) { [weakSelf handleError:error]; } sourceCode = source.data; dispatch_group_leave(prepareBridge); } onProgress:^(RCTLoadingProgress *progressData) { #if RCT_DEV && __has_include("RCTDevLoadingView.h") RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]]; [loadingView updateProgress:progressData]; #endif }]; // Wait for both the modules and source code to have finished loading dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ RCTCxxBridge *strongSelf = weakSelf; if (sourceCode && strongSelf.loading) { [strongSelf executeSourceCode:sourceCode sync:NO]; } }); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge]; [_performanceLogger markStartForTag:RCTPLScriptDownload]; NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil); // Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out (void)cookie; RCTPerformanceLogger *performanceLogger = _performanceLogger; RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) { RCTProfileEndAsyncEvent(0, @"native", cookie, @"JavaScript download", @"JS async"); [performanceLogger markStopForTag:RCTPLScriptDownload]; [performanceLogger setValue:source.length forTag:RCTPLBundleSize]; NSDictionary *userInfo = @{ RCTBridgeDidDownloadScriptNotificationSourceKey: source ?: [NSNull null], RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey: self->_bridgeDescription ?: [NSNull null], }; [center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo]; _onSourceLoad(error, source); }; if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) { [self.delegate loadSourceForBridge:_parentBridge onProgress:onProgress onComplete:onSourceLoad]; } else if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { [self.delegate loadSourceForBridge:_parentBridge withBlock:onSourceLoad]; } else if (!self.bundleURL) { NSError *error = RCTErrorWithMessage(@"No bundle URL present.\n\nMake sure you're running a packager " \ "server or have included a .jsbundle file in your application bundle."); onSourceLoad(error, nil); } else { [RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) { if (error) { RCTLogError(@"Failed to load bundle(%@) with error:(%@ %@)", self.bundleURL, error.localizedDescription, error.localizedFailureReason); return; } onSourceLoad(error, source); }]; } } - (NSArray *)moduleClasses { if (RCT_DEBUG && _valid && _moduleClassesByID == nil) { RCTLogError(@"Bridge modules have not yet been initialized. You may be " "trying to access a module too early in the startup procedure."); } return _moduleClassesByID; } /** * Used by RCTUIManager */ - (RCTModuleData *)moduleDataForName:(NSString *)moduleName { return _moduleDataByName[moduleName]; } - (id)moduleForName:(NSString *)moduleName { return _moduleDataByName[moduleName].instance; } - (BOOL)moduleIsInitialized:(Class)moduleClass { return _moduleDataByName[RCTBridgeModuleNameForClass(moduleClass)].hasInstance; } - (id)jsBoundExtraModuleForClass:(Class)moduleClass { if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { id cxxDelegate = (id) self.delegate; if ([cxxDelegate respondsToSelector:@selector(jsBoundExtraModuleForClass:)]) { return [cxxDelegate jsBoundExtraModuleForClass:moduleClass]; } } return nil; } - (std::shared_ptr)_buildModuleRegistry { if (!self.valid) { return {}; } [_performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig]; RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge buildModuleRegistry]", nil); __weak __typeof(self) weakSelf = self; ModuleRegistry::ModuleNotFoundCallback moduleNotFoundCallback = ^bool(const std::string &name) { __strong __typeof(weakSelf) strongSelf = weakSelf; return [strongSelf.delegate respondsToSelector:@selector(bridge:didNotFindModule:)] && [strongSelf.delegate bridge:strongSelf didNotFindModule:@(name.c_str())]; }; auto registry = std::make_shared( createNativeModules(_moduleDataByID, self, _reactInstance), moduleNotFoundCallback); [_performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); return registry; } - (void)_initializeBridge:(std::shared_ptr)executorFactory { if (!self.valid) { return; } RCTAssertJSThread(); __weak RCTCxxBridge *weakSelf = self; _jsMessageThread = std::make_shared([NSRunLoop currentRunLoop], ^(NSError *error) { if (error) { [weakSelf handleError:error]; } }); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil); // This can only be false if the bridge was invalidated before startup completed if (_reactInstance) { #if RCT_DEV executorFactory = std::make_shared(self, executorFactory); #endif // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance _reactInstance->initializeBridge( std::make_unique(self), executorFactory, _jsMessageThread, [self _buildModuleRegistry]); #if RCT_PROFILE if (RCTProfileIsProfiling()) { _reactInstance->setGlobalVariable( "__RCTProfileIsProfiling", std::make_unique("true")); } #endif [self installExtraJSBinding]; } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } - (NSArray *)registerModulesForClasses:(NSArray *)moduleClasses { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil); NSArray *moduleClassesCopy = [moduleClasses copy]; NSMutableArray *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count]; for (Class moduleClass in moduleClassesCopy) { if (RCTJSINativeModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTJSINativeModule)]) { continue; } NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); // 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); } } // Instantiate moduleData // TODO #13258411: can we defer this until config generation? moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } [_moduleDataByID addObjectsFromArray:moduleDataByID]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); return moduleDataByID; } - (void)registerExtraModules { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] extraModules", nil); NSArray> *extraModules = nil; if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { extraModules = [self.delegate extraModulesForBridge:_parentBridge]; } else if (self.moduleProvider) { extraModules = self.moduleProvider(); } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); #if RCT_DEBUG static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ RCTVerifyAllModulesExported(extraModules); }); #endif RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] preinitialized moduleData", nil); // Set up moduleData for pre-initialized module instances for (id module in extraModules) { Class moduleClass = [module class]; NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); if (RCT_DEBUG) { // Check for name collisions between preregistered modules RCTModuleData *moduleData = _moduleDataByName[moduleName]; if (moduleData) { RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the " "name '%@', but name was already registered by class %@", moduleClass, moduleName, moduleData.moduleClass); continue; } } // Instantiate moduleData container RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self]; _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; [_moduleDataByID addObject:moduleData]; } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } - (void)installExtraJSBinding { if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { id cxxDelegate = (id) self.delegate; if ([cxxDelegate respondsToSelector:@selector(installExtraJSBinding:)]) { [cxxDelegate installExtraJSBinding:self.jsContextRef]; } } } - (NSArray *)_initializeModules:(NSArray> *)modules withDispatchGroup:(dispatch_group_t)dispatchGroup lazilyDiscovered:(BOOL)lazilyDiscovered { RCTAssert(!(RCTIsMainQueue() && lazilyDiscovered), @"Lazy discovery can only happen off the Main Queue"); // Set up moduleData for automatically-exported modules NSArray *moduleDataById = [self registerModulesForClasses:modules]; if (lazilyDiscovered) { #ifdef RCT_DEBUG // Lazily discovered modules do not require instantiation here, // as they are not allowed to have pre-instantiated instance // and must not require the main queue. for (RCTModuleData *moduleData in moduleDataById) { RCTAssert(!(moduleData.requiresMainQueueSetup || moduleData.hasInstance), @"Module \'%@\' requires initialization on the Main Queue or has pre-instantiated, which is not supported for the lazily discovered modules.", moduleData.name); } #endif } else { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initModulesWithDispatchGroup:] moduleData.hasInstance", nil); // Dispatch module init onto main thread for those modules that require it // For non-lazily discovered modules we run through the entire set of modules // that we have, otherwise some modules coming from the delegate // or module provider block, will not be properly instantiated. for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) { // Modules that were pre-initialized should ideally be set up before // bridge init has finished, otherwise the caller may try to access the // module directly rather than via `[bridge moduleForClass:]`, which won't // trigger the lazy initialization process. If the module cannot safely be // set up on the current thread, it will instead be async dispatched // to the main thread to be set up in _prepareModulesWithDispatchGroup:. (void)[moduleData instance]; } } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); // From this point on, RCTDidInitializeModuleNotification notifications will // be sent the first time a module is accessed. _moduleSetupComplete = YES; [self _prepareModulesWithDispatchGroup:dispatchGroup]; } #if RCT_PROFILE if (RCTProfileIsProfiling()) { // Depends on moduleDataByID being loaded RCTProfileHookModules(self); } #endif return moduleDataById; } - (void)registerAdditionalModuleClasses:(NSArray *)modules { NSArray *newModules = [self _initializeModules:modules withDispatchGroup:NULL lazilyDiscovered:YES]; if (_reactInstance) { _reactInstance->getModuleRegistry().registerModules(createNativeModules(newModules, self, _reactInstance)); } } - (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup { RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTCxxBridge _prepareModulesWithDispatchGroup]", nil); BOOL initializeImmediately = NO; if (dispatchGroup == NULL) { // If no dispatchGroup is passed in, we must prepare everything immediately. // We better be on the right thread too. RCTAssertMainQueue(); initializeImmediately = YES; } // Set up modules that require main thread init or constants export [_performanceLogger setValue:0 forTag:RCTPLNativeModuleMainThread]; for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.requiresMainQueueSetup) { // Modules that need to be set up on the main thread cannot be initialized // lazily when required without doing a dispatch_sync to the main thread, // which can result in deadlock. To avoid this, we initialize all of these // modules on the main thread in parallel with loading the JS code, so // they will already be available before they are ever required. dispatch_block_t block = ^{ if (self.valid && ![moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) { [self->_performanceLogger appendStartForTag:RCTPLNativeModuleMainThread]; (void)[moduleData instance]; [moduleData gatherConstants]; [self->_performanceLogger appendStopForTag:RCTPLNativeModuleMainThread]; } }; if (initializeImmediately && RCTIsMainQueue()) { block(); } else { // We've already checked that dispatchGroup is non-null, but this satisifies the // Xcode analyzer if (dispatchGroup) { dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), block); } } _modulesInitializedOnMainQueue++; } } [_performanceLogger setValue:_modulesInitializedOnMainQueue forTag:RCTPLNativeModuleMainThreadUsesCount]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } - (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData { [_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData]; } - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync { // This will get called from whatever thread was actually executing JS. dispatch_block_t completion = ^{ // Log start up metrics early before processing any other js calls [self logStartupFinish]; // Flush pending calls immediately so we preserve ordering [self _flushPendingCalls]; // Perform the state update and notification on the main thread, so we can't run into // timing issues with RCTRootView dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; // Starting the display link is not critical to startup, so do it last [self ensureOnJavaScriptThread:^{ // Register the display link to start sending js calls after everything is setup [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; }]; }); }; if (sync) { [self executeApplicationScriptSync:sourceCode url:self.bundleURL]; completion(); } else { [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion]; } #if RCT_DEV if (self.devSettings.isHotLoadingAvailable && self.devSettings.isHotLoadingEnabled) { NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash NSString *host = self.bundleURL.host; NSNumber *port = self.bundleURL.port; [self enqueueJSCall:@"HMRClient" method:@"enable" args:@[@"ios", path, host, RCTNullIfNil(port)] completion:NULL]; } #endif } - (void)handleError:(NSError *)error { // This is generally called when the infrastructure throws an // exception while calling JS. Most product exceptions will not go // through this method, but through RCTExceptionManager. // There are three possible states: // 1. initializing == _valid && _loading // 2. initializing/loading finished (success or failure) == _valid && !_loading // 3. invalidated == !_valid && !_loading // !_valid && _loading can't happen. // In state 1: on main queue, move to state 2, reset the bridge, and RCTFatal. // In state 2: go directly to RCTFatal. Do not enqueue, do not collect $200. // In state 3: do nothing. if (self->_valid && !self->_loading) { if ([error userInfo][RCTJSRawStackTraceKey]) { [self.redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; } RCTFatal(error); // RN will stop, but let the rest of the app keep going. return; } if (!_valid || !_loading) { return; } // Hack: once the bridge is invalidated below, it won't initialize any new native // modules. Initialize the redbox module now so we can still report this error. RCTRedBox *redBox = [self redBox]; _loading = NO; _valid = NO; dispatch_async(dispatch_get_main_queue(), ^{ if (self->_jsMessageThread) { // Make sure initializeBridge completed self->_jsMessageThread->runOnQueueSync([] {}); } self->_reactInstance.reset(); self->_jsMessageThread.reset(); [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification object:self->_parentBridge userInfo:@{@"bridge": self, @"error": error}]; if ([error userInfo][RCTJSRawStackTraceKey]) { [redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; } RCTFatal(error); }); } RCT_NOT_IMPLEMENTED(- (instancetype)initWithDelegate:(__unused id)delegate bundleURL:(__unused NSURL *)bundleURL moduleProvider:(__unused RCTBridgeModuleListProvider)block launchOptions:(__unused NSDictionary *)launchOptions) RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL moduleProvider:(__unused RCTBridgeModuleListProvider)block launchOptions:(__unused NSDictionary *)launchOptions) /** * Prevent super from calling setUp (that'd create another batchedBridge) */ - (void)setUp {} - (void)reload { if (!_valid) { RCTLogError(@"Attempting to reload bridge before it's valid: %@. Try restarting the development server if connected.", self); } [_parentBridge reload]; } - (Class)executorClass { return _parentBridge.executorClass; } - (void)setExecutorClass:(Class)executorClass { RCTAssertMainQueue(); _parentBridge.executorClass = executorClass; } - (NSURL *)bundleURL { return _parentBridge.bundleURL; } - (void)setBundleURL:(NSURL *)bundleURL { _parentBridge.bundleURL = bundleURL; } - (id)delegate { return _parentBridge.delegate; } - (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue { if (queue == RCTJSThread) { [self ensureOnJavaScriptThread:block]; } else if (queue) { dispatch_async(queue, block); } } #pragma mark - RCTInvalidating - (void)invalidate { if (_didInvalidate) { return; } RCTAssertMainQueue(); RCTLogInfo(@"Invalidating %@ (parent: %@, executor: %@)", self, _parentBridge, [self executorClass]); _loading = NO; _valid = NO; _didInvalidate = YES; if ([RCTBridge currentBridge] == self) { [RCTBridge setCurrentBridge:nil]; } // Stop JS instance and message thread [self ensureOnJavaScriptThread:^{ [self->_displayLink invalidate]; self->_displayLink = nil; if (RCTProfileIsProfiling()) { RCTProfileUnhookModules(self); } // Invalidate modules // We're on the JS thread (which we'll be suspending soon), so no new calls will be made to native modules after // this completes. We must ensure all previous calls were dispatched before deallocating the instance (and module // wrappers) or we may have invalid pointers still in flight. dispatch_group_t moduleInvalidation = dispatch_group_create(); for (RCTModuleData *moduleData in self->_moduleDataByID) { // Be careful when grabbing an instance here, we don't want to instantiate // any modules just to invalidate them. if (![moduleData hasInstance]) { continue; } if ([moduleData.instance respondsToSelector:@selector(invalidate)]) { dispatch_group_enter(moduleInvalidation); [self dispatchBlock:^{ [(id)moduleData.instance invalidate]; dispatch_group_leave(moduleInvalidation); } queue:moduleData.methodQueue]; } [moduleData invalidate]; } if (dispatch_group_wait(moduleInvalidation, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) { RCTLogError(@"Timed out waiting for modules to be invalidated"); } self->_reactInstance.reset(); self->_jsMessageThread.reset(); self->_moduleDataByName = nil; self->_moduleDataByID = nil; self->_moduleClassesByID = nil; self->_pendingCalls = nil; [self->_jsThread cancel]; self->_jsThread = nil; CFRunLoopStop(CFRunLoopGetCurrent()); }]; } - (void)logMessage:(NSString *)message level:(NSString *)level { if (RCT_DEBUG && _valid) { [self enqueueJSCall:@"RCTLog" method:@"logIfNoNativeHook" args:@[level, message] completion:NULL]; } } #pragma mark - RCTBridge methods - (void)_runAfterLoad:(RCTPendingCall)block { // Ordering here is tricky. Ideally, the C++ bridge would provide // functionality to defer calls until after the app is loaded. Until that // happens, we do this. _pendingCount keeps a count of blocks which have // been deferred. It is incremented using an atomic barrier call before each // block is added to the js queue, and decremented using an atomic barrier // call after the block is executed. If _pendingCount is zero, there is no // work either in the js queue, or in _pendingCalls, so it is safe to add new // work to the JS queue directly. if (self.loading || _pendingCount > 0) { // From the callers' perspecive: // Phase 1: jsQueueBlocks are added to the queue; _pendingCount is // incremented for each. If the first block is created after self.loading is // true, phase 1 will be nothing. _pendingCount++; dispatch_block_t jsQueueBlock = ^{ // From the perspective of the JS queue: if (self.loading) { // Phase A: jsQueueBlocks are executed. self.loading is true, so they // are added to _pendingCalls. [self->_pendingCalls addObject:block]; } else { // Phase C: More jsQueueBlocks are executed. self.loading is false, so // each block is executed, adding work to the queue, and _pendingCount is // decremented. block(); self->_pendingCount--; } }; [self ensureOnJavaScriptThread:jsQueueBlock]; } else { // Phase 2/Phase D: blocks are executed directly, adding work to the JS queue. block(); } } - (void)logStartupFinish { // Log metrics about native requires during the bridge startup. uint64_t nativeRequiresCount = [_performanceLogger valueForTag:RCTPLRAMNativeRequiresCount]; [_performanceLogger setValue:nativeRequiresCount forTag:RCTPLRAMStartupNativeRequiresCount]; uint64_t nativeRequires = [_performanceLogger valueForTag:RCTPLRAMNativeRequires]; [_performanceLogger setValue:nativeRequires forTag:RCTPLRAMStartupNativeRequires]; [_performanceLogger markStopForTag:RCTPLBridgeStartup]; } - (void)_flushPendingCalls { RCT_PROFILE_BEGIN_EVENT(0, @"Processing pendingCalls", @{ @"count": [@(_pendingCalls.count) stringValue] }); // Phase B: _flushPendingCalls happens. Each block in _pendingCalls is // executed, adding work to the queue, and _pendingCount is decremented. // loading is set to NO. NSArray *pendingCalls = _pendingCalls; _pendingCalls = nil; for (RCTPendingCall call in pendingCalls) { call(); _pendingCount--; } _loading = NO; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } /** * Public. Can be invoked from any thread. */ - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion { if (!self.valid) { return; } /** * AnyThread */ RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil); RCTProfileBeginFlowEvent(); __weak __typeof(self) weakSelf = self; [self _runAfterLoad:^(){ RCTProfileEndFlowEvent(); __strong __typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) { return; } if (strongSelf->_reactInstance) { strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[])); // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure // the block is invoked after callJSFunction if (completion) { if (strongSelf->_jsMessageThread) { strongSelf->_jsMessageThread->runOnQueue(completion); } else { RCTLogWarn(@"Can't invoke completion without messageThread"); } } } }]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } /** * Called by RCTModuleMethod from any thread. */ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args { if (!self.valid) { return; } /** * AnyThread */ RCTProfileBeginFlowEvent(); __weak __typeof(self) weakSelf = self; [self _runAfterLoad:^(){ RCTProfileEndFlowEvent(); __strong __typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) { return; } if (strongSelf->_reactInstance) { strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[])); } }]; } /** * Private hack to support `setTimeout(fn, 0)` */ - (void)_immediatelyCallTimer:(NSNumber *)timer { RCTAssertJSThread(); if (_reactInstance) { _reactInstance->callJSFunction("JSTimers", "callTimers", folly::dynamic::array(folly::dynamic::array([timer doubleValue]))); } } - (void)enqueueApplicationScript:(NSData *)script url:(NSURL *)url onComplete:(dispatch_block_t)onComplete { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueApplicationScript]", nil); [self executeApplicationScript:script url:url async:YES]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); // Assumes that onComplete can be called when the next block on the JS thread is scheduled if (onComplete) { RCTAssert(_jsMessageThread != nullptr, @"Cannot invoke completion without jsMessageThread"); _jsMessageThread->runOnQueue(onComplete); } } - (void)executeApplicationScriptSync:(NSData *)script url:(NSURL *)url { [self executeApplicationScript:script url:url async:NO]; } - (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async { [self _tryAndHandleError:^{ NSString *sourceUrlStr = deriveSourceURL(url); [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartExecutingNotification object:self->_parentBridge userInfo:@{@"bridge": self}]; if (isRAMBundle(script)) { [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad]; auto ramBundle = std::make_unique(sourceUrlStr.UTF8String); std::unique_ptr scriptStr = ramBundle->getStartupCode(); [self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad]; [self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize]; if (self->_reactInstance) { auto registry = RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory()); self->_reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async); } } else if (self->_reactInstance) { self->_reactInstance->loadScriptFromString(std::make_unique(script), sourceUrlStr.UTF8String, !async); } else { std::string methodName = async ? "loadApplicationScript" : "loadApplicationScriptSync"; throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge"); } }]; } - (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path { if (_reactInstance) { _reactInstance->registerBundle(static_cast(segmentId), path.UTF8String); } } #pragma mark - Payload Processing - (void)partialBatchDidFlush { for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.implementsPartialBatchDidFlush) { [self dispatchBlock:^{ [moduleData.instance partialBatchDidFlush]; } queue:moduleData.methodQueue]; } } } - (void)batchDidComplete { // TODO #12592471: batchDidComplete is only used by RCTUIManager, // can we eliminate this special case? for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.implementsBatchDidComplete) { [self dispatchBlock:^{ [moduleData.instance batchDidComplete]; } queue:moduleData.methodQueue]; } } } - (void)startProfiling { RCTAssertMainQueue(); [self ensureOnJavaScriptThread:^{ #if WITH_FBSYSTRACE [RCTFBSystrace registerCallbacks]; #endif RCTProfileInit(self); [self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[@YES] completion:NULL]; }]; } - (void)stopProfiling:(void (^)(NSData *))callback { RCTAssertMainQueue(); [self ensureOnJavaScriptThread:^{ [self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[@NO] completion:NULL]; RCTProfileEnd(self, ^(NSString *log) { NSData *logData = [log dataUsingEncoding:NSUTF8StringEncoding]; callback(logData); #if WITH_FBSYSTRACE [RCTFBSystrace unregisterCallbacks]; #endif }); }]; } - (BOOL)isBatchActive { return _wasBatchActive; } @end