diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 5e4ef0be3..7eafce75e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -133,8 +133,8 @@ RCT_EXPORT_MODULE(); (void)bridge; } - // Sleep on the main thread so the deallocation can happen on the JS thread. - sleep(DEFAULT_TIMEOUT); + RUN_RUNLOOP_WHILE(weakExecutor, 1); + sleep(1); XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released"); } @@ -151,7 +151,8 @@ RCT_EXPORT_MODULE(); (void)bridge; } - sleep(DEFAULT_TIMEOUT); + RUN_RUNLOOP_WHILE(weakContext, 1); + sleep(1); XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated"); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index ff4b8cae3..390459b1b 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -25,6 +25,10 @@ @implementation TestExecutor +RCT_EXPORT_MODULE() + +- (void)setUp {} + - (instancetype)init { if (self = [super init]) { diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index 534f9ad91..1ba3eaf56 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -13,18 +13,28 @@ @end @implementation RCTContextExecutorTests +{ + RCTContextExecutor *_executor; +} + +- (void)setUp +{ + [super setUp]; + _executor = [[RCTContextExecutor alloc] init]; + RCTSetExecutorID(_executor); + [_executor setUp]; +} - (void)testNativeLoggingHookExceptionBehavior { - RCTContextExecutor *executor = [[RCTContextExecutor alloc] init]; dispatch_semaphore_t doneSem = dispatch_semaphore_create(0); - [executor executeApplicationScript:@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);" + [_executor executeApplicationScript:@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);" sourceURL:[NSURL URLWithString:@"file://"] onComplete:^(id error){ dispatch_semaphore_signal(doneSem); }]; dispatch_semaphore_wait(doneSem, DISPATCH_TIME_FOREVER); - [executor invalidate]; + [_executor invalidate]; } static uint64_t _get_time_nanoseconds(void) @@ -91,7 +101,6 @@ static uint64_t _get_time_nanoseconds(void) }; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - RCTContextExecutor *executor = RCTCreateExecutor([RCTContextExecutor class]); NSString *script = @" \ var modules = { \ module: { \ @@ -105,7 +114,7 @@ static uint64_t _get_time_nanoseconds(void) } \ "; - [executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(NSError *error) { + [_executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(NSError *error) { NSMutableArray *params = [[NSMutableArray alloc] init]; id data = @1; for (int i = 0; i < 4; i++) { @@ -115,10 +124,10 @@ static uint64_t _get_time_nanoseconds(void) for (int j = 0; j < runs; j++) { @autoreleasepool { double start = _get_time_nanoseconds(); - [executor executeJSCall:@"module" + [_executor executeJSCall:@"module" method:@"method" arguments:params - context:RCTGetExecutorID(executor) + context:RCTGetExecutorID(_executor) callback:^(id json, NSError *__error) { RCTAssert([json isEqual:@YES], @"Invalid return"); }]; diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 1966c6045..12d6ba456 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -121,7 +121,7 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) { - UIUserNotificationType types = UIRemoteNotificationTypeNone; + UIUserNotificationType types = UIUserNotificationTypeNone; if (permissions) { if ([permissions[@"alert"] boolValue]) { types |= UIUserNotificationTypeAlert; diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 97095a5c9..a8cf9ae5e 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -31,8 +31,11 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); RCTSparseArray *_callbacks; dispatch_semaphore_t _socketOpenSemaphore; NSMutableDictionary *_injectedObjects; + NSURL *_url; } +RCT_EXPORT_MODULE() + - (instancetype)init { return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; @@ -41,41 +44,45 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); - (instancetype)initWithURL:(NSURL *)URL { if (self = [super init]) { - - _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); - _socket = [[RCTSRWebSocket alloc] initWithURL:URL]; - _socket.delegate = self; - _callbacks = [[RCTSparseArray alloc] init]; - _injectedObjects = [[NSMutableDictionary alloc] init]; - [_socket setDelegateDispatchQueue:_jsQueue]; - - NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:URL]; - [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; - - if (![self connectToProxy]) { - RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \ - you are running on the device, check if you have the right IP \ - address in `RCTWebSocketExecutor.m`.", URL); - [self invalidate]; - return nil; - } - - NSInteger retries = 3; - BOOL runtimeIsReady = [self prepareJSRuntime]; - while (!runtimeIsReady && retries > 0) { - runtimeIsReady = [self prepareJSRuntime]; - retries--; - } - if (!runtimeIsReady) { - RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not " - "paused on a breakpoint or exception and try reloading again."); - [self invalidate]; - return nil; - } + _url = URL; } return self; } +- (void)setUp +{ + _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); + _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; + _socket.delegate = self; + _callbacks = [[RCTSparseArray alloc] init]; + _injectedObjects = [[NSMutableDictionary alloc] init]; + [_socket setDelegateDispatchQueue:_jsQueue]; + + NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url]; + [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; + + if (![self connectToProxy]) { + RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \ + you are running on the device, check if you have the right IP \ + address in `RCTWebSocketExecutor.m`.", _url); + [self invalidate]; + return; + } + + NSInteger retries = 3; + BOOL runtimeIsReady = [self prepareJSRuntime]; + while (!runtimeIsReady && retries > 0) { + runtimeIsReady = [self prepareJSRuntime]; + retries--; + } + if (!runtimeIsReady) { + RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not " + "paused on a breakpoint or exception and try reloading again."); + [self invalidate]; + return; + } +} + - (BOOL)connectToProxy { _socketOpenSemaphore = dispatch_semaphore_create(0); diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index f79026fda..841d7c0b1 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -899,7 +899,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin @implementation RCTBatchedBridge { BOOL _loading; - id _javaScriptExecutor; + __weak id _javaScriptExecutor; RCTSparseArray *_modulesByID; RCTSparseArray *_queuesByID; dispatch_queue_t _methodQueue; @@ -936,13 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin [_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - /** - * Initialize executor to allow enqueueing calls - */ - Class executorClass = self.executorClass ?: [RCTContextExecutor class]; - _javaScriptExecutor = RCTCreateExecutor(executorClass); - _latestJSExecutor = _javaScriptExecutor; - /** * Initialize and register bridge modules *before* adding the display link * so we don't have threading issues @@ -980,7 +973,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (Class)executorClass { - return _parentBridge.executorClass; + return _parentBridge.executorClass ?: [RCTContextExecutor class]; } - (void)setExecutorClass:(Class)executorClass @@ -1045,6 +1038,16 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin // Store modules _modulesByName = [modulesByName copy]; + /** + * The executor is a bridge module, wait for it to be created and set it before + * any other module has access to the bridge + */ + _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; + _latestJSExecutor = _javaScriptExecutor; + RCTSetExecutorID(_javaScriptExecutor); + + [_javaScriptExecutor setUp]; + // Set bridge for (id module in _modulesByName.allValues) { if ([module respondsToSelector:@selector(setBridge:)]) { diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 8f1eb8a98..7a3aa2870 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -11,6 +11,7 @@ #import +#import "RCTBridgeModule.h" #import "RCTInvalidating.h" typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); @@ -20,7 +21,13 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); * Abstracts away a JavaScript execution context - we may be running code in a * web view (for debugging purposes), or may be running code in a `JSContext`. */ -@protocol RCTJavaScriptExecutor +@protocol RCTJavaScriptExecutor + +/** + * Used to set up the executor after the bridge has been fully initialized. + * Do any expensive setup in this method instead of `-init`. + */ +- (void)setUp; /** * Executes given method with arguments on JS thread and calls the given callback @@ -61,14 +68,12 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @end static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; -__used static id RCTCreateExecutor(Class executorClass) +__used static void RCTSetExecutorID(id executor) { static NSUInteger executorID = 0; - id executor = [[executorClass alloc] init]; if (executor) { objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); } - return executor; } __used static NSNumber *RCTGetExecutorID(id executor) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 627255c37..d17791c9a 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -68,6 +68,8 @@ NSThread *_javaScriptThread; } +RCT_EXPORT_MODULE() + /** * The one tiny pure native hook that we implement is a native logging hook. * You could even argue that this is not necessary - we could plumb logging @@ -233,37 +235,43 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) JSGlobalContextRef ctx; if (context) { ctx = JSGlobalContextRetain(context); - } else { - JSContextGroupRef group = JSContextGroupCreate(); - ctx = JSGlobalContextCreateInGroup(group, NULL); -#if FB_JSC_HACK - JSContextGroupBindToCurrentThread(group); -#endif - JSContextGroupRelease(group); + strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; } - - strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; - [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; - [strongSelf _addNativeHook:RCTNoop withName:"noop"]; - -#if RCT_DEV - [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; - [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; - - for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(toggleProfilingFlag:) - name:event - object:nil]; - } -#endif - }]; } return self; } +- (void)setUp +{ + __weak RCTContextExecutor *weakSelf = self; + [self executeBlockOnJavaScriptQueue:^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (!strongSelf->_context) { + JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); + JSGlobalContextRetain(ctx); + strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; + } + [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; + [strongSelf _addNativeHook:RCTNoop withName:"noop"]; +#if RCT_DEV + [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; + [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; + + for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(toggleProfilingFlag:) + name:event + object:nil]; + } +#endif + }]; +} + - (void)toggleProfilingFlag:(NSNotification *)notification { JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index 56323fb99..fb3c6f031 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -46,16 +46,14 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) NSRegularExpression *_scriptTagsRegex; } +RCT_EXPORT_MODULE() + @synthesize valid = _valid; - (instancetype)initWithWebView:(UIWebView *)webView { if ((self = [super init])) { - _objectsToInject = [[NSMutableDictionary alloc] init]; - _webView = webView ?: [[UIWebView alloc] init]; - _commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL], - _scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL], - _webView.delegate = self; + _webView = webView; } return self; } @@ -65,6 +63,18 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) return [self initWithWebView:nil]; } +- (void)setUp +{ + if (!_webView) { + _webView = [[UIWebView alloc] init]; + } + + _objectsToInject = [[NSMutableDictionary alloc] init]; + _commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL], + _scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL], + _webView.delegate = self; +} + - (void)invalidate { _valid = NO;