This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
42 KiB
1257 lines
42 KiB
// Copyright 2004-present Facebook. All Rights Reserved. |
|
|
|
/** |
|
* 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/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); |
|
} |
|
|
|
- (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 |
|
[self _initModules: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 |
|
, nullptr |
|
)); |
|
} |
|
} 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 = source ? @{ RCTBridgeDidDownloadScriptNotificationSourceKey: source } : nil; |
|
[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]; |
|
} |
|
} |
|
} |
|
|
|
- (void)_initModules:(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 |
|
} |
|
|
|
- (void)registerAdditionalModuleClasses:(NSArray<Class> *)modules |
|
{ |
|
[self _initModules:modules withDispatchGroup:NULL lazilyDiscovered:YES]; |
|
} |
|
|
|
- (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 |
|
{ |
|
[_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
|
|
|