2017-02-01 22:10:41 +00:00
/**
2018-09-11 22:27:47 +00:00
* Copyright (c) Facebook, Inc. and its affiliates.
2017-02-01 22:10:41 +00:00
*
2018-02-17 02:24:55 +00:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
2017-02-01 22:10:41 +00:00
*/
#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>
2017-07-19 20:37:52 +00:00
#import <React/RCTCxxBridgeDelegate.h>
2017-02-01 22:10:41 +00:00
#import <React/RCTCxxModule.h>
#import <React/RCTCxxUtils.h>
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
#import <React/RCTDevSettings.h>
2017-02-01 22:10:41 +00:00
#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>
2017-07-10 12:21:03 +00:00
#import <React/RCTFollyConvert.h>
2017-02-01 22:10:41 +00:00
#import <cxxreact/CxxNativeModule.h>
#import <cxxreact/Instance.h>
#import <cxxreact/JSBundleType.h>
#import <cxxreact/JSCExecutor.h>
#import <cxxreact/JSIndexedRAMBundle.h>
2018-05-10 05:01:53 +00:00
#import <cxxreact/ModuleRegistry.h>
2017-02-01 22:10:41 +00:00
#import <cxxreact/Platform.h>
2017-11-08 11:50:36 +00:00
#import <cxxreact/RAMBundleRegistry.h>
2017-02-01 22:10:41 +00:00
#import <jschelpers/Value.h>
2017-02-20 12:57:54 +00:00
#import "NSDataBigString.h"
2017-03-22 01:25:00 +00:00
#import "RCTJSCHelpers.h"
2017-02-01 22:10:41 +00:00
#import "RCTMessageThread.h"
#import "RCTObjcExecutor.h"
2017-02-15 14:12:44 +00:00
#ifdef WITH_FBSYSTRACE
#import <React/RCTFBSystrace.h>
#endif
2017-05-30 18:10:20 +00:00
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
#import "RCTDevLoadingView.h"
#endif
2017-02-01 22:10:41 +00:00
#define RCTAssertJSThread() \
RCTAssert(self.executorClass || self->_jsThread == [NSThread currentThread], \
@"This method must be called on JS thread")
2017-05-13 00:57:12 +00:00
static NSString *const RCTJSThreadName = @"com.facebook.react.JavaScript";
2017-02-01 22:10:41 +00:00
2017-11-23 12:59:23 +00:00
typedef void (^RCTPendingCall)();
2017-02-01 22:10:41 +00:00
using namespace facebook::react;
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParams,
RCTBridgeFieldCallID,
};
2017-09-05 21:47:06 +00:00
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_;
};
}
2017-02-01 22:10:41 +00:00
static bool isRAMBundle(NSData *script) {
Simplifying Struct definition.
Summary:
Since we are reading from a file, we should make sure this struct is packed, just in case we change it down the line and the compiler decides it might want to introduce padding, we're now protected against that.
There was also a discussion about the fact that people might use `ptr += sizeof(BundleHeader)` as an idiom in their code, which would currently be incorrect, if padding was introduced at the end of the file. Actually, it remains incorrect to do that now, because a RAM bundle header is a different size to a BC Bundle header. If people are properly testing their code, they should spot this pretty quickly, because it will always be an incorrect thing to do with a RAM bundle, so this isn't as bad as previously thought: where the code only succeeds when the compiler deigns to not pad the struct at the end.
This diff also cleans up how headers are initialised. `BundleHeader` has a constructor that explicitly zero-initialises it so we can rely on the default initializer to do the right thing now.
Reviewed By: mhorowitz
Differential Revision: D4572032
fbshipit-source-id: 7dc50cfa9438dfdfb9f842dc39d8f15334813c63
2017-02-20 12:28:32 +00:00
BundleHeader header;
2017-02-01 22:10:41 +00:00
[script getBytes:&header length:sizeof(header)];
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
}
2017-04-25 09:29:29 +00:00
static void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger) {
__weak RCTPerformanceLogger *weakPerformanceLogger = performanceLogger;
2017-05-13 00:57:12 +00:00
ReactMarker::logTaggedMarker = [weakPerformanceLogger](const ReactMarker::ReactMarkerId markerId, const char *tag) {
2017-04-25 09:29:29 +00:00
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:
2017-06-09 13:11:43 +00:00
case ReactMarker::NATIVE_MODULE_SETUP_START:
case ReactMarker::NATIVE_MODULE_SETUP_STOP:
2018-08-10 16:26:18 +00:00
case ReactMarker::REGISTER_JS_SEGMENT_START:
case ReactMarker::REGISTER_JS_SEGMENT_STOP:
2017-04-25 09:29:29 +00:00
// These are not used on iOS.
break;
}
};
}
2017-02-01 22:10:41 +00:00
@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;
2017-09-11 11:24:29 +00:00
BOOL _didInvalidate;
2017-02-01 22:10:41 +00:00
2017-11-23 12:59:23 +00:00
NSMutableArray<RCTPendingCall> *_pendingCalls;
2017-07-31 10:15:31 +00:00
std::atomic<NSInteger> _pendingCount;
2017-02-01 22:10:41 +00:00
// Native modules
NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
2017-08-11 13:16:55 +00:00
NSMutableArray<RCTModuleData *> *_moduleDataByID;
NSMutableArray<Class> *_moduleClassesByID;
2017-02-01 22:10:41 +00:00
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;
}
2017-11-08 00:44:51 +00:00
@synthesize bridgeDescription = _bridgeDescription;
2017-02-01 22:10:41 +00:00
@synthesize loading = _loading;
@synthesize performanceLogger = _performanceLogger;
2017-11-08 00:44:51 +00:00
@synthesize valid = _valid;
2017-02-01 22:10:41 +00:00
+ (void)initialize
{
if (self == [RCTCxxBridge class]) {
2017-03-22 01:25:00 +00:00
RCTPrepareJSCExecutor();
2017-02-01 22:10:41 +00:00
}
}
2017-04-07 18:11:04 +00:00
- (JSGlobalContextRef)jsContextRef
{
2018-01-23 17:56:21 +00:00
return (JSGlobalContextRef)(_reactInstance ? _reactInstance->getJavaScriptContext() : nullptr);
2017-02-01 22:10:41 +00:00
}
2018-06-15 17:56:38 +00:00
- (std::shared_ptr<MessageQueueThread>)jsMessageThread
{
return _jsMessageThread;
}
2018-01-23 17:56:21 +00:00
- (BOOL)isInspectable
{
return _reactInstance ? _reactInstance->isInspectable() : NO;
2017-12-18 21:19:37 +00:00
}
2017-02-01 22:10:41 +00:00
- (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];
2017-04-25 09:29:29 +00:00
registerPerformanceLoggerHooks(_performanceLogger);
2017-02-01 22:10:41 +00:00
RCTLogInfo(@"Initializing %@ (parent: %@, executor: %@)", self, bridge, [self executorClass]);
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_pendingCalls = [NSMutableArray new];
_displayLink = [RCTDisplayLink new];
2017-08-11 13:16:55 +00:00
_moduleDataByName = [NSMutableDictionary new];
_moduleClassesByID = [NSMutableArray new];
_moduleDataByID = [NSMutableArray new];
2017-02-01 22:10:41 +00:00
[RCTBridge setCurrentBridge:self];
}
return self;
}
2017-09-11 11:24:29 +00:00
+ (void)runRunLoop
2017-02-01 22:10:41 +00:00
{
@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];
}
}
2017-05-16 16:40:36 +00:00
/**
* 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
2017-02-01 22:10:41 +00:00
{
RCTAssert(_jsThread, @"This method must not be called before the JS thread is created");
2017-05-16 16:40:36 +00:00
// 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.
2017-02-01 22:10:41 +00:00
if ([NSThread currentThread] == _jsThread) {
[self _tryAndHandleError:block];
} else {
[self performSelector:@selector(_tryAndHandleError:)
onThread:_jsThread
withObject:block
waitUntilDone:NO];
}
}
- (void)start
{
2017-05-13 00:57:12 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge start]", nil);
2017-02-01 22:10:41 +00:00
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge userInfo:@{@"bridge": self}];
// Set up the JS thread early
2017-09-11 11:24:29 +00:00
_jsThread = [[NSThread alloc] initWithTarget:[self class]
selector:@selector(runRunLoop)
2017-02-01 22:10:41 +00:00
object:nil];
_jsThread.name = RCTJSThreadName;
_jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
2017-07-20 19:11:57 +00:00
#if RCT_DEBUG
_jsThread.stackSize *= 2;
#endif
2017-02-01 22:10:41 +00:00
[_jsThread start];
2017-05-17 15:53:55 +00:00
dispatch_group_t prepareBridge = dispatch_group_create();
2017-02-01 22:10:41 +00:00
2017-08-11 13:16:55 +00:00
[_performanceLogger markStartForTag:RCTPLNativeModuleInit];
[self registerExtraModules];
2017-02-01 22:10:41 +00:00
// Initialize all native modules that cannot be loaded lazily
2018-04-17 14:53:19 +00:00
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
2017-08-11 13:16:55 +00:00
[_performanceLogger markStopForTag:RCTPLNativeModuleInit];
2017-02-01 22:10:41 +00:00
// This doesn't really do anything. The real work happens in initializeBridge.
_reactInstance.reset(new Instance);
__weak RCTCxxBridge *weakSelf = self;
2017-07-19 20:37:52 +00:00
// Prepare executor factory (shared_ptr for copy into block)
2017-02-01 22:10:41 +00:00
std::shared_ptr<JSExecutorFactory> executorFactory;
if (!self.executorClass) {
2017-07-19 20:37:52 +00:00
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];
2017-10-04 10:32:14 +00:00
// We use the name of the device and the app for debugging & metrics
NSString *deviceName = [[UIDevice currentDevice] name];
NSString *appName = [[NSBundle mainBundle] bundleIdentifier];
2017-07-19 20:37:52 +00:00
// The arg is a cache dir. It's not used with standard JSC.
executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object
("OwnerIdentity", "ReactNative")
2017-10-04 10:32:14 +00:00
("AppIdentity", [(appName ?: @"unknown") UTF8String])
("DeviceIdentity", [(deviceName ?: @"unknown") UTF8String])
2017-07-19 20:37:52 +00:00
("UseCustomJSC", (bool)useCustomJSC)
#if RCT_PROFILE
("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch)
#endif
));
}
2017-02-01 22:10:41 +00:00
} 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)
2017-05-17 15:53:55 +00:00
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
2017-02-01 22:10:41 +00:00
[weakSelf _initializeBridge:executorFactory];
2017-05-17 15:53:55 +00:00
dispatch_group_leave(prepareBridge);
}];
2017-02-01 22:10:41 +00:00
2017-10-25 15:05:33 +00:00
// Load the source asynchronously, then store it for later execution.
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self loadSource:^(NSError *error, RCTSource *source) {
2017-02-01 22:10:41 +00:00
if (error) {
2017-10-25 15:05:33 +00:00
[weakSelf handleError:error];
2017-02-01 22:10:41 +00:00
}
2017-10-25 15:05:33 +00:00
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
} onProgress:^(RCTLoadingProgress *progressData) {
2017-05-30 18:10:20 +00:00
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
2017-10-25 15:05:33 +00:00
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
[loadingView updateProgress:progressData];
2017-02-01 22:10:41 +00:00
#endif
2017-10-25 15:05:33 +00:00
}];
2017-02-01 22:10:41 +00:00
2017-10-25 15:05:33 +00:00
// 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];
}
});
2017-05-13 00:57:12 +00:00
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
2017-02-01 22:10:41 +00:00
}
- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
{
2017-08-18 00:09:59 +00:00
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:RCTBridgeWillDownloadScriptNotification object:_parentBridge];
2017-02-01 22:10:41 +00:00
[_performanceLogger markStartForTag:RCTPLScriptDownload];
NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil);
// Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out
(void)cookie;
RCTPerformanceLogger *performanceLogger = _performanceLogger;
2017-08-30 13:15:56 +00:00
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, RCTSource *source) {
2017-02-01 22:10:41 +00:00
RCTProfileEndAsyncEvent(0, @"native", cookie, @"JavaScript download", @"JS async");
[performanceLogger markStopForTag:RCTPLScriptDownload];
2017-08-30 13:15:56 +00:00
[performanceLogger setValue:source.length forTag:RCTPLBundleSize];
2017-08-31 12:25:19 +00:00
2018-04-11 02:23:07 +00:00
NSDictionary *userInfo = @{
RCTBridgeDidDownloadScriptNotificationSourceKey: source ?: [NSNull null],
RCTBridgeDidDownloadScriptNotificationBridgeDescriptionKey: self->_bridgeDescription ?: [NSNull null],
};
2017-08-31 12:25:19 +00:00
[center postNotificationName:RCTBridgeDidDownloadScriptNotification object:self->_parentBridge userInfo:userInfo];
2017-08-18 00:09:59 +00:00
2017-08-30 13:15:56 +00:00
_onSourceLoad(error, source);
2017-02-01 22:10:41 +00:00
};
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.");
2017-08-30 13:15:56 +00:00
onSourceLoad(error, nil);
2017-02-01 22:10:41 +00:00
} else {
2017-08-30 13:15:56 +00:00
[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) {
2017-09-12 22:40:48 +00:00
if (error) {
RCTLogError(@"Failed to load bundle(%@) with error:(%@ %@)", self.bundleURL, error.localizedDescription, error.localizedFailureReason);
return;
2017-02-01 22:10:41 +00:00
}
2017-08-30 13:15:56 +00:00
onSourceLoad(error, source);
2017-02-01 22:10:41 +00:00
}];
}
}
- (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;
}
2018-02-14 06:26:43 +00:00
- (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;
}
2017-03-17 13:55:44 +00:00
- (std::shared_ptr<ModuleRegistry>)_buildModuleRegistry
2017-02-01 22:10:41 +00:00
{
if (!self.valid) {
return {};
}
[_performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
2017-03-17 13:55:44 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge buildModuleRegistry]", nil);
2017-02-01 22:10:41 +00:00
2017-08-11 13:16:55 +00:00
__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);
2017-02-01 22:10:41 +00:00
[_performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return registry;
}
- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
if (!self.valid) {
return;
}
2017-05-17 15:53:55 +00:00
RCTAssertJSThread();
__weak RCTCxxBridge *weakSelf = self;
_jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
});
2017-02-01 22:10:41 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge initializeBridge:]", nil);
// This can only be false if the bridge was invalidated before startup completed
if (_reactInstance) {
2017-09-05 21:47:06 +00:00
#if RCT_DEV
executorFactory = std::make_shared<GetDescAdapter>(self, executorFactory);
#endif
2017-02-01 22:10:41 +00:00
// This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
_reactInstance->initializeBridge(
2017-09-11 11:24:29 +00:00
std::make_unique<RCTInstanceCallback>(self),
2017-02-01 22:10:41 +00:00
executorFactory,
_jsMessageThread,
2017-03-23 18:25:28 +00:00
[self _buildModuleRegistry]);
2017-02-01 22:10:41 +00:00
#if RCT_PROFILE
if (RCTProfileIsProfiling()) {
_reactInstance->setGlobalVariable(
"__RCTProfileIsProfiling",
std::make_unique<JSBigStdString>("true"));
}
#endif
2018-02-14 06:26:37 +00:00
[self installExtraJSBinding];
2017-02-01 22:10:41 +00:00
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
2017-08-11 13:16:55 +00:00
- (NSArray<RCTModuleData *> *)registerModulesForClasses:(NSArray<Class> *)moduleClasses
2017-02-01 22:10:41 +00:00
{
2017-08-11 13:16:55 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
@"-[RCTCxxBridge initModulesWithDispatchGroup:] autoexported moduleData", nil);
2018-07-23 21:44:20 +00:00
NSArray *moduleClassesCopy = [moduleClasses copy];
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray arrayWithCapacity:moduleClassesCopy.count];
for (Class moduleClass in moduleClassesCopy) {
2018-09-11 19:03:11 +00:00
if (RCTJSINativeModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTJSINativeModule)]) {
2018-08-29 22:36:24 +00:00
continue;
}
2017-08-11 13:16:55 +00:00
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];
2017-02-01 22:10:41 +00:00
2017-08-11 13:16:55 +00:00
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}
[_moduleDataByID addObjectsFromArray:moduleDataByID];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return moduleDataByID;
}
- (void)registerExtraModules
{
2017-02-01 22:10:41 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
@"-[RCTCxxBridge initModulesWithDispatchGroup:] extraModules", nil);
2017-08-11 13:16:55 +00:00
2017-02-01 22:10:41 +00:00
NSArray<id<RCTBridgeModule>> *extraModules = nil;
2017-08-11 13:16:55 +00:00
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
extraModules = [self.delegate extraModulesForBridge:_parentBridge];
2017-02-01 22:10:41 +00:00
} else if (self.moduleProvider) {
extraModules = self.moduleProvider();
}
2017-08-11 13:16:55 +00:00
2017-02-01 22:10:41 +00:00
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
2017-08-11 13:16:55 +00:00
RCTModuleData *moduleData = _moduleDataByName[moduleName];
2017-02-01 22:10:41 +00:00
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
2017-08-11 13:16:55 +00:00
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self];
_moduleDataByName[moduleName] = moduleData;
[_moduleClassesByID addObject:moduleClass];
[_moduleDataByID addObject:moduleData];
2017-02-01 22:10:41 +00:00
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
2017-08-11 13:16:55 +00:00
}
2017-02-01 22:10:41 +00:00
2018-02-14 06:26:37 +00:00
- (void)installExtraJSBinding
{
if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) {
id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate;
if ([cxxDelegate respondsToSelector:@selector(installExtraJSBinding:)]) {
[cxxDelegate installExtraJSBinding:self.jsContextRef];
}
}
}
2018-04-17 14:53:19 +00:00
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<id<RCTBridgeModule>> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
2017-08-11 13:16:55 +00:00
{
RCTAssert(!(RCTIsMainQueue() && lazilyDiscovered), @"Lazy discovery can only happen off the Main Queue");
2017-02-01 22:10:41 +00:00
2017-08-11 13:16:55 +00:00
// Set up moduleData for automatically-exported modules
NSArray<RCTModuleData *> *moduleDataById = [self registerModulesForClasses:modules];
if (lazilyDiscovered) {
2018-08-18 16:32:47 +00:00
#ifdef RCT_DEBUG
2017-08-11 13:16:55 +00:00
// 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);
2017-02-01 22:10:41 +00:00
}
2017-08-11 13:16:55 +00:00
#endif
2018-08-18 16:32:47 +00:00
} else {
2017-08-11 13:16:55 +00:00
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];
2017-02-01 22:10:41 +00:00
}
}
2017-08-11 13:16:55 +00:00
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
2017-02-01 22:10:41 +00:00
2017-08-11 13:16:55 +00:00
// From this point on, RCTDidInitializeModuleNotification notifications will
// be sent the first time a module is accessed.
_moduleSetupComplete = YES;
[self _prepareModulesWithDispatchGroup:dispatchGroup];
2017-02-01 22:10:41 +00:00
}
#if RCT_PROFILE
if (RCTProfileIsProfiling()) {
// Depends on moduleDataByID being loaded
RCTProfileHookModules(self);
}
#endif
2018-04-17 14:53:19 +00:00
return moduleDataById;
2017-02-01 22:10:41 +00:00
}
2017-08-11 13:16:55 +00:00
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules
{
2018-04-17 14:53:19 +00:00
NSArray<RCTModuleData *> *newModules = [self _initializeModules:modules withDispatchGroup:NULL lazilyDiscovered:YES];
if (_reactInstance) {
_reactInstance->getModuleRegistry().registerModules(createNativeModules(newModules, self, _reactInstance));
}
2017-08-11 13:16:55 +00:00
}
2017-02-01 22:10:41 +00:00
- (void)_prepareModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
2018-02-05 19:47:10 +00:00
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTCxxBridge _prepareModulesWithDispatchGroup]", nil);
2017-02-01 22:10:41 +00:00
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];
2017-08-11 13:16:55 +00:00
2017-02-01 22:10:41 +00:00
for (RCTModuleData *moduleData in _moduleDataByID) {
2017-07-17 10:36:05 +00:00
if (moduleData.requiresMainQueueSetup) {
2017-02-01 22:10:41 +00:00
// 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 = ^{
2017-12-26 22:34:05 +00:00
// Log start up metrics early before processing any other js calls
[self logStartupFinish];
2017-02-01 22:10:41 +00:00
// 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
2017-05-16 16:40:36 +00:00
[self ensureOnJavaScriptThread:^{
2017-02-01 22:10:41 +00:00
// 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
2017-10-24 06:57:46 +00:00
if (self.devSettings.isHotLoadingAvailable && self.devSettings.isHotLoadingEnabled) {
2017-02-01 22:10:41 +00:00
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);
2017-09-11 11:24:29 +00:00
2017-02-01 22:10:41 +00:00
// 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.
2017-09-11 11:24:29 +00:00
RCTRedBox *redBox = [self redBox];
2017-02-01 22:10:41 +00:00
_loading = NO;
_valid = NO;
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_jsMessageThread) {
2017-09-11 11:24:29 +00:00
// Make sure initializeBridge completed
self->_jsMessageThread->runOnQueueSync([] {});
2017-02-01 22:10:41 +00:00
}
2017-09-11 11:24:29 +00:00
self->_reactInstance.reset();
self->_jsMessageThread.reset();
2017-02-01 22:10:41 +00:00
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:self->_parentBridge userInfo:@{@"bridge": self, @"error": error}];
if ([error userInfo][RCTJSRawStackTraceKey]) {
2017-09-11 11:24:29 +00:00
[redBox showErrorMessage:[error localizedDescription]
withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
2017-02-01 22:10:41 +00:00
}
RCTFatal(error);
});
}
2017-04-07 18:11:03 +00:00
RCT_NOT_IMPLEMENTED(- (instancetype)initWithDelegate:(__unused id<RCTBridgeDelegate>)delegate
bundleURL:(__unused NSURL *)bundleURL
moduleProvider:(__unused RCTBridgeModuleListProvider)block
launchOptions:(__unused NSDictionary *)launchOptions)
2017-02-01 22:10:41 +00:00
RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL
2017-04-07 18:11:03 +00:00
moduleProvider:(__unused RCTBridgeModuleListProvider)block
launchOptions:(__unused NSDictionary *)launchOptions)
2017-02-01 22:10:41 +00:00
/**
* Prevent super from calling setUp (that'd create another batchedBridge)
*/
- (void)setUp {}
- (void)reload
{
2018-06-20 01:38:47 +00:00
if (!_valid) {
RCTLogError(@"Attempting to reload bridge before it's valid: %@. Try restarting the development server if connected.", self);
}
2017-02-01 22:10:41 +00:00
[_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) {
2017-05-16 16:40:36 +00:00
[self ensureOnJavaScriptThread:block];
2017-02-01 22:10:41 +00:00
} else if (queue) {
dispatch_async(queue, block);
}
}
#pragma mark - RCTInvalidating
- (void)invalidate
{
2017-09-11 11:24:29 +00:00
if (_didInvalidate) {
2017-02-01 22:10:41 +00:00
return;
}
RCTAssertMainQueue();
2017-09-11 11:24:29 +00:00
RCTLogInfo(@"Invalidating %@ (parent: %@, executor: %@)", self, _parentBridge, [self executorClass]);
2017-02-01 22:10:41 +00:00
_loading = NO;
_valid = NO;
2017-09-11 11:24:29 +00:00
_didInvalidate = YES;
2017-02-01 22:10:41 +00:00
if ([RCTBridge currentBridge] == self) {
[RCTBridge setCurrentBridge:nil];
}
2017-09-11 11:24:29 +00:00
// Stop JS instance and message thread
[self ensureOnJavaScriptThread:^{
[self->_displayLink invalidate];
self->_displayLink = nil;
2017-02-01 22:10:41 +00:00
2017-09-11 11:24:29 +00:00
if (RCTProfileIsProfiling()) {
RCTProfileUnhookModules(self);
2017-02-01 22:10:41 +00:00
}
2017-09-11 11:24:29 +00:00
// 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;
2017-02-01 22:10:41 +00:00
}
2017-09-11 11:24:29 +00:00
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
dispatch_group_enter(moduleInvalidation);
[self dispatchBlock:^{
[(id<RCTInvalidating>)moduleData.instance invalidate];
dispatch_group_leave(moduleInvalidation);
} queue:moduleData.methodQueue];
2017-02-01 22:10:41 +00:00
}
2017-09-11 11:24:29 +00:00
[moduleData invalidate];
}
2017-02-01 22:10:41 +00:00
2017-09-11 11:24:29 +00:00
if (dispatch_group_wait(moduleInvalidation, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) {
RCTLogError(@"Timed out waiting for modules to be invalidated");
}
2017-08-30 16:37:54 +00:00
2017-09-11 11:24:29 +00:00
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());
}];
2017-02-01 22:10:41 +00:00
}
- (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
2017-11-23 12:59:23 +00:00
- (void)_runAfterLoad:(RCTPendingCall)block
2017-02-01 22:10:41 +00:00
{
// 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.
2017-07-31 10:15:31 +00:00
_pendingCount++;
2017-02-01 22:10:41 +00:00
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();
2017-07-31 10:15:31 +00:00
self->_pendingCount--;
2017-02-01 22:10:41 +00:00
}
};
2017-05-16 16:40:36 +00:00
[self ensureOnJavaScriptThread:jsQueueBlock];
2017-02-01 22:10:41 +00:00
} else {
// Phase 2/Phase D: blocks are executed directly, adding work to the JS queue.
block();
}
}
2017-12-26 22:34:05 +00:00
- (void)logStartupFinish
2017-02-01 22:10:41 +00:00
{
2017-04-25 09:29:29 +00:00
// Log metrics about native requires during the bridge startup.
2017-12-26 22:34:05 +00:00
uint64_t nativeRequiresCount = [_performanceLogger valueForTag:RCTPLRAMNativeRequiresCount];
2017-04-25 09:29:29 +00:00
[_performanceLogger setValue:nativeRequiresCount forTag:RCTPLRAMStartupNativeRequiresCount];
2017-12-26 22:34:05 +00:00
uint64_t nativeRequires = [_performanceLogger valueForTag:RCTPLRAMNativeRequires];
2017-04-25 09:29:29 +00:00
[_performanceLogger setValue:nativeRequires forTag:RCTPLRAMStartupNativeRequires];
2017-02-01 22:10:41 +00:00
[_performanceLogger markStopForTag:RCTPLBridgeStartup];
2017-12-26 22:34:05 +00:00
}
2017-02-01 22:10:41 +00:00
2017-12-26 22:34:05 +00:00
- (void)_flushPendingCalls
{
2017-05-13 00:57:12 +00:00
RCT_PROFILE_BEGIN_EVENT(0, @"Processing pendingCalls", @{ @"count": [@(_pendingCalls.count) stringValue] });
2017-02-01 22:10:41 +00:00
// Phase B: _flushPendingCalls happens. Each block in _pendingCalls is
// executed, adding work to the queue, and _pendingCount is decremented.
// loading is set to NO.
2017-11-23 12:59:23 +00:00
NSArray<RCTPendingCall> *pendingCalls = _pendingCalls;
2017-02-01 22:10:41 +00:00
_pendingCalls = nil;
2017-11-23 12:59:23 +00:00
for (RCTPendingCall call in pendingCalls) {
2017-02-01 22:10:41 +00:00
call();
2017-07-31 10:15:31 +00:00
_pendingCount--;
2017-02-01 22:10:41 +00:00
}
_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();
2017-11-23 12:59:23 +00:00
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^(){
2017-02-01 22:10:41 +00:00
RCTProfileEndFlowEvent();
2017-11-23 12:59:23 +00:00
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
2017-02-01 22:10:41 +00:00
2017-11-23 12:59:23 +00:00
if (strongSelf->_reactInstance) {
strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
convertIdToFollyDynamic(args ?: @[]));
2017-02-01 22:10:41 +00:00
2017-05-16 16:40:36 +00:00
// ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure
// the block is invoked after callJSFunction
2017-02-01 22:10:41 +00:00
if (completion) {
2017-11-23 12:59:23 +00:00
if (strongSelf->_jsMessageThread) {
strongSelf->_jsMessageThread->runOnQueue(completion);
2017-06-22 15:17:03 +00:00
} else {
RCTLogWarn(@"Can't invoke completion without messageThread");
}
2017-02-01 22:10:41 +00:00
}
}
}];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
/**
* Called by RCTModuleMethod from any thread.
*/
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
2017-03-06 19:57:40 +00:00
if (!self.valid) {
return;
}
2017-02-01 22:10:41 +00:00
/**
* AnyThread
*/
RCTProfileBeginFlowEvent();
2017-11-23 12:59:23 +00:00
__weak __typeof(self) weakSelf = self;
[self _runAfterLoad:^(){
2017-02-01 22:10:41 +00:00
RCTProfileEndFlowEvent();
2017-11-23 12:59:23 +00:00
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
2017-02-01 22:10:41 +00:00
2017-11-23 12:59:23 +00:00
if (strongSelf->_reactInstance) {
strongSelf->_reactInstance->callJSCallback([cbID unsignedLongLongValue], convertIdToFollyDynamic(args ?: @[]));
2017-05-17 15:53:55 +00:00
}
2017-02-01 22:10:41 +00:00
}];
}
/**
* Private hack to support `setTimeout(fn, 0)`
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
RCTAssertJSThread();
2017-05-17 15:53:55 +00:00
if (_reactInstance) {
2017-06-22 16:46:30 +00:00
_reactInstance->callJSFunction("JSTimers", "callTimers",
2017-02-01 22:10:41 +00:00
folly::dynamic::array(folly::dynamic::array([timer doubleValue])));
2017-05-17 15:53:55 +00:00
}
2017-02-01 22:10:41 +00:00
}
- (void)enqueueApplicationScript:(NSData *)script
url:(NSURL *)url
onComplete:(dispatch_block_t)onComplete
{
2017-05-13 00:57:12 +00:00
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueApplicationScript]", nil);
2017-02-01 22:10:41 +00:00
2017-09-18 13:37:28 +00:00
[self executeApplicationScript:script url:url async:YES];
2017-02-01 22:10:41 +00:00
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
// Assumes that onComplete can be called when the next block on the JS thread is scheduled
2017-05-16 16:40:36 +00:00
if (onComplete) {
RCTAssert(_jsMessageThread != nullptr, @"Cannot invoke completion without jsMessageThread");
_jsMessageThread->runOnQueue(onComplete);
}
2017-02-01 22:10:41 +00:00
}
- (void)executeApplicationScriptSync:(NSData *)script url:(NSURL *)url
2017-09-18 13:37:28 +00:00
{
[self executeApplicationScript:script url:url async:NO];
}
- (void)executeApplicationScript:(NSData *)script
url:(NSURL *)url
async:(BOOL)async
2017-02-01 22:10:41 +00:00
{
[self _tryAndHandleError:^{
2017-06-14 21:06:26 +00:00
NSString *sourceUrlStr = deriveSourceURL(url);
2018-03-06 19:01:34 +00:00
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartExecutingNotification
object:self->_parentBridge userInfo:@{@"bridge": self}];
2017-02-01 22:10:41 +00:00
if (isRAMBundle(script)) {
2017-04-25 09:29:29 +00:00
[self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
2017-06-14 21:06:26 +00:00
auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
2017-02-01 22:10:41 +00:00
std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
2017-04-25 09:29:29 +00:00
[self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize];
2017-02-01 22:10:41 +00:00
if (self->_reactInstance) {
2017-11-09 19:55:39 +00:00
auto registry = RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
2017-09-22 16:56:50 +00:00
self->_reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr),
sourceUrlStr.UTF8String, !async);
2017-02-01 22:10:41 +00:00
}
} else if (self->_reactInstance) {
2017-05-24 14:27:06 +00:00
self->_reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script),
2017-09-18 13:37:28 +00:00
sourceUrlStr.UTF8String, !async);
2017-02-01 22:10:41 +00:00
} else {
2017-09-18 13:37:28 +00:00
std::string methodName = async ? "loadApplicationScript" : "loadApplicationScriptSync";
throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
2017-02-01 22:10:41 +00:00
}
}];
}
2017-11-09 19:55:39 +00:00
- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path
{
if (_reactInstance) {
_reactInstance->registerBundle(static_cast<uint32_t>(segmentId), path.UTF8String);
}
}
2017-02-01 22:10:41 +00:00
#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();
2017-05-16 16:40:36 +00:00
[self ensureOnJavaScriptThread:^{
2017-03-30 13:36:21 +00:00
#if WITH_FBSYSTRACE
[RCTFBSystrace registerCallbacks];
#endif
2017-02-01 22:10:41 +00:00
RCTProfileInit(self);
2017-06-14 18:11:24 +00:00
[self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[@YES] completion:NULL];
2017-02-01 22:10:41 +00:00
}];
}
- (void)stopProfiling:(void (^)(NSData *))callback
{
RCTAssertMainQueue();
2017-05-16 16:40:36 +00:00
[self ensureOnJavaScriptThread:^{
2017-06-14 18:11:24 +00:00
[self enqueueJSCall:@"Systrace" method:@"setEnabled" args:@[@NO] completion:NULL];
2017-02-01 22:10:41 +00:00
RCTProfileEnd(self, ^(NSString *log) {
NSData *logData = [log dataUsingEncoding:NSUTF8StringEncoding];
callback(logData);
2017-03-30 13:36:21 +00:00
#if WITH_FBSYSTRACE
[RCTFBSystrace unregisterCallbacks];
#endif
2017-02-01 22:10:41 +00:00
});
}];
}
- (BOOL)isBatchActive
{
return _wasBatchActive;
}
@end