mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 01:25:39 +00:00
7b9b1559a7
Summary: @public The bridge may send the reload event twice when reloading JS in some rare condition. The 2nd one was actually when the "new" bridge wasn't valid yet, so log an error if that happens. This may happen if the connection to Metro server gets into a weird state. Restarting Metro would solve the problem. The issue was that `RCTBridgeWillReloadNotification` fired twice for each reload with metro connected. Reviewed By: mmmulani Differential Revision: D8509448 fbshipit-source-id: 647fb98c0a97503409a10451b8f152145947ae20
1272 lines
43 KiB
Plaintext
1272 lines
43 KiB
Plaintext
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <atomic>
|
|
#include <future>
|
|
|
|
#import <React/RCTAssert.h>
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTBridgeMethod.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTCxxBridgeDelegate.h>
|
|
#import <React/RCTCxxModule.h>
|
|
#import <React/RCTCxxUtils.h>
|
|
#import <React/RCTDevSettings.h>
|
|
#import <React/RCTDisplayLink.h>
|
|
#import <React/RCTJavaScriptLoader.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTModuleData.h>
|
|
#import <React/RCTPerformanceLogger.h>
|
|
#import <React/RCTProfile.h>
|
|
#import <React/RCTRedBox.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <React/RCTFollyConvert.h>
|
|
#import <cxxreact/CxxNativeModule.h>
|
|
#import <cxxreact/Instance.h>
|
|
#import <cxxreact/JSBundleType.h>
|
|
#import <cxxreact/JSCExecutor.h>
|
|
#import <cxxreact/JSIndexedRAMBundle.h>
|
|
#import <cxxreact/ModuleRegistry.h>
|
|
#import <cxxreact/Platform.h>
|
|
#import <cxxreact/RAMBundleRegistry.h>
|
|
#import <jschelpers/Value.h>
|
|
|
|
#import "NSDataBigString.h"
|
|
#import "RCTJSCHelpers.h"
|
|
#import "RCTMessageThread.h"
|
|
#import "RCTObjcExecutor.h"
|
|
|
|
#ifdef WITH_FBSYSTRACE
|
|
#import <React/RCTFBSystrace.h>
|
|
#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<JSExecutorFactory> factory)
|
|
: bridge_(bridge)
|
|
, factory_(factory) {}
|
|
std::unique_ptr<JSExecutor> createJSExecutor(
|
|
std::shared_ptr<ExecutorDelegate> delegate,
|
|
std::shared_ptr<MessageQueueThread> 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<JSExecutorFactory> 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:
|
|
// 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<RCTPendingCall> *_pendingCalls;
|
|
std::atomic<NSInteger> _pendingCount;
|
|
|
|
// Native modules
|
|
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
|
|
NSMutableArray<RCTModuleData *> *_moduleDataByID;
|
|
NSMutableArray<Class> *_moduleClassesByID;
|
|
NSUInteger _modulesInitializedOnMainQueue;
|
|
RCTDisplayLink *_displayLink;
|
|
|
|
// JS thread management
|
|
NSThread *_jsThread;
|
|
std::shared_ptr<RCTMessageThread> _jsMessageThread;
|
|
|
|
// This is uniquely owned, but weak_ptr is used.
|
|
std::shared_ptr<Instance> _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<MessageQueueThread>)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<JSExecutorFactory> executorFactory;
|
|
if (!self.executorClass) {
|
|
if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
|
|
id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) 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<RCTJavaScriptExecutor> 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<Class> *)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<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
|
|
if ([cxxDelegate respondsToSelector:@selector(jsBoundExtraModuleForClass:)]) {
|
|
return [cxxDelegate jsBoundExtraModuleForClass:moduleClass];
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (std::shared_ptr<ModuleRegistry>)_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<ModuleRegistry>(
|
|
createNativeModules(_moduleDataByID, self, _reactInstance),
|
|
moduleNotFoundCallback);
|
|
|
|
[_performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
|
|
return registry;
|
|
}
|
|
|
|
- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
|
|
{
|
|
if (!self.valid) {
|
|
return;
|
|
}
|
|
|
|
RCTAssertJSThread();
|
|
__weak RCTCxxBridge *weakSelf = self;
|
|
_jsMessageThread = std::make_shared<RCTMessageThread>([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<GetDescAdapter>(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<RCTInstanceCallback>(self),
|
|
executorFactory,
|
|
_jsMessageThread,
|
|
[self _buildModuleRegistry]);
|
|
|
|
#if RCT_PROFILE
|
|
if (RCTProfileIsProfiling()) {
|
|
_reactInstance->setGlobalVariable(
|
|
"__RCTProfileIsProfiling",
|
|
std::make_unique<JSBigStdString>("true"));
|
|
}
|
|
#endif
|
|
|
|
[self installExtraJSBinding];
|
|
}
|
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
|
}
|
|
|
|
- (NSArray<RCTModuleData *> *)registerModulesForClasses:(NSArray<Class> *)moduleClasses
|
|
{
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
|
|
@"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);
|
|
|
|
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClasses.count];
|
|
for (Class moduleClass in moduleClasses) {
|
|
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<id<RCTBridgeModule>> *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<RCTBridgeModule> 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<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
|
|
if ([cxxDelegate respondsToSelector:@selector(installExtraJSBinding:)]) {
|
|
[cxxDelegate installExtraJSBinding:self.jsContextRef];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<id<RCTBridgeModule>> *)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<RCTModuleData *> *moduleDataById = [self registerModulesForClasses:modules];
|
|
|
|
#ifdef RCT_DEBUG
|
|
if (lazilyDiscovered) {
|
|
// 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);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
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<Class> *)modules
|
|
{
|
|
NSArray<RCTModuleData *> *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<RCTBridgeModule>)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<RCTBridgeDelegate>)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<RCTBridgeDelegate>)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<RCTInvalidating>)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<RCTPendingCall> *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<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
|
|
std::unique_ptr<const JSBigString> 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<NSDataBigString>(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<uint32_t>(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
|