[ReactNative] Make JavaScript executors bridge modules

Summary:
@public

This is the first of a few diffs that change the way the executors are handled
by the bridge.

For they are just promoted to modules, so they are automatically loaded by the bridge.

Test Plan:
Tested on UIExplorer, Catalyst and MAdMan.
Tested all the 3 executors, everything looks fine.
This commit is contained in:
Tadeu Zagallo 2015-06-09 15:42:10 -07:00
parent 68bb3a7e71
commit 847dff8d75
9 changed files with 131 additions and 84 deletions

View File

@ -133,8 +133,8 @@ RCT_EXPORT_MODULE();
(void)bridge; (void)bridge;
} }
// Sleep on the main thread so the deallocation can happen on the JS thread. RUN_RUNLOOP_WHILE(weakExecutor, 1);
sleep(DEFAULT_TIMEOUT); sleep(1);
XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released"); XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released");
} }
@ -151,7 +151,8 @@ RCT_EXPORT_MODULE();
(void)bridge; (void)bridge;
} }
sleep(DEFAULT_TIMEOUT); RUN_RUNLOOP_WHILE(weakContext, 1);
sleep(1);
XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated"); XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated");
} }

View File

@ -25,6 +25,10 @@
@implementation TestExecutor @implementation TestExecutor
RCT_EXPORT_MODULE()
- (void)setUp {}
- (instancetype)init - (instancetype)init
{ {
if (self = [super init]) { if (self = [super init]) {

View File

@ -13,18 +13,28 @@
@end @end
@implementation RCTContextExecutorTests @implementation RCTContextExecutorTests
{
RCTContextExecutor *_executor;
}
- (void)setUp
{
[super setUp];
_executor = [[RCTContextExecutor alloc] init];
RCTSetExecutorID(_executor);
[_executor setUp];
}
- (void)testNativeLoggingHookExceptionBehavior - (void)testNativeLoggingHookExceptionBehavior
{ {
RCTContextExecutor *executor = [[RCTContextExecutor alloc] init];
dispatch_semaphore_t doneSem = dispatch_semaphore_create(0); 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://"] sourceURL:[NSURL URLWithString:@"file://"]
onComplete:^(id error){ onComplete:^(id error){
dispatch_semaphore_signal(doneSem); dispatch_semaphore_signal(doneSem);
}]; }];
dispatch_semaphore_wait(doneSem, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(doneSem, DISPATCH_TIME_FOREVER);
[executor invalidate]; [_executor invalidate];
} }
static uint64_t _get_time_nanoseconds(void) 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); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
RCTContextExecutor *executor = RCTCreateExecutor([RCTContextExecutor class]);
NSString *script = @" \ NSString *script = @" \
var modules = { \ var modules = { \
module: { \ 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]; NSMutableArray *params = [[NSMutableArray alloc] init];
id data = @1; id data = @1;
for (int i = 0; i < 4; i++) { 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++) { for (int j = 0; j < runs; j++) {
@autoreleasepool { @autoreleasepool {
double start = _get_time_nanoseconds(); double start = _get_time_nanoseconds();
[executor executeJSCall:@"module" [_executor executeJSCall:@"module"
method:@"method" method:@"method"
arguments:params arguments:params
context:RCTGetExecutorID(executor) context:RCTGetExecutorID(_executor)
callback:^(id json, NSError *__error) { callback:^(id json, NSError *__error) {
RCTAssert([json isEqual:@YES], @"Invalid return"); RCTAssert([json isEqual:@YES], @"Invalid return");
}]; }];

View File

@ -121,7 +121,7 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions)
{ {
UIUserNotificationType types = UIRemoteNotificationTypeNone; UIUserNotificationType types = UIUserNotificationTypeNone;
if (permissions) { if (permissions) {
if ([permissions[@"alert"] boolValue]) { if ([permissions[@"alert"] boolValue]) {
types |= UIUserNotificationTypeAlert; types |= UIUserNotificationTypeAlert;

View File

@ -31,8 +31,11 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply);
RCTSparseArray *_callbacks; RCTSparseArray *_callbacks;
dispatch_semaphore_t _socketOpenSemaphore; dispatch_semaphore_t _socketOpenSemaphore;
NSMutableDictionary *_injectedObjects; NSMutableDictionary *_injectedObjects;
NSURL *_url;
} }
RCT_EXPORT_MODULE()
- (instancetype)init - (instancetype)init
{ {
return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]];
@ -41,23 +44,29 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply);
- (instancetype)initWithURL:(NSURL *)URL - (instancetype)initWithURL:(NSURL *)URL
{ {
if (self = [super init]) { if (self = [super init]) {
_url = URL;
}
return self;
}
- (void)setUp
{
_jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
_socket = [[RCTSRWebSocket alloc] initWithURL:URL]; _socket = [[RCTSRWebSocket alloc] initWithURL:_url];
_socket.delegate = self; _socket.delegate = self;
_callbacks = [[RCTSparseArray alloc] init]; _callbacks = [[RCTSparseArray alloc] init];
_injectedObjects = [[NSMutableDictionary alloc] init]; _injectedObjects = [[NSMutableDictionary alloc] init];
[_socket setDelegateDispatchQueue:_jsQueue]; [_socket setDelegateDispatchQueue:_jsQueue];
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:URL]; NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
if (![self connectToProxy]) { if (![self connectToProxy]) {
RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \ RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \
you are running on the device, check if you have the right IP \ you are running on the device, check if you have the right IP \
address in `RCTWebSocketExecutor.m`.", URL); address in `RCTWebSocketExecutor.m`.", _url);
[self invalidate]; [self invalidate];
return nil; return;
} }
NSInteger retries = 3; NSInteger retries = 3;
@ -70,11 +79,9 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply);
RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not " RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not "
"paused on a breakpoint or exception and try reloading again."); "paused on a breakpoint or exception and try reloading again.");
[self invalidate]; [self invalidate];
return nil; return;
} }
} }
return self;
}
- (BOOL)connectToProxy - (BOOL)connectToProxy
{ {

View File

@ -899,7 +899,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
@implementation RCTBatchedBridge @implementation RCTBatchedBridge
{ {
BOOL _loading; BOOL _loading;
id<RCTJavaScriptExecutor> _javaScriptExecutor; __weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
RCTSparseArray *_modulesByID; RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID; RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue; dispatch_queue_t _methodQueue;
@ -936,13 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [_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 * Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues * so we don't have threading issues
@ -980,7 +973,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
- (Class)executorClass - (Class)executorClass
{ {
return _parentBridge.executorClass; return _parentBridge.executorClass ?: [RCTContextExecutor class];
} }
- (void)setExecutorClass:(Class)executorClass - (void)setExecutorClass:(Class)executorClass
@ -1045,6 +1038,16 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
// Store modules // Store modules
_modulesByName = [modulesByName copy]; _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 // Set bridge
for (id<RCTBridgeModule> module in _modulesByName.allValues) { for (id<RCTBridgeModule> module in _modulesByName.allValues) {
if ([module respondsToSelector:@selector(setBridge:)]) { if ([module respondsToSelector:@selector(setBridge:)]) {

View File

@ -11,6 +11,7 @@
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error); 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 * 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`. * web view (for debugging purposes), or may be running code in a `JSContext`.
*/ */
@protocol RCTJavaScriptExecutor <RCTInvalidating> @protocol RCTJavaScriptExecutor <RCTInvalidating, RCTBridgeModule>
/**
* 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 * 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 @end
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass) __used static void RCTSetExecutorID(id<RCTJavaScriptExecutor> executor)
{ {
static NSUInteger executorID = 0; static NSUInteger executorID = 0;
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
if (executor) { if (executor) {
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
} }
return executor;
} }
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor) __used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)

View File

@ -68,6 +68,8 @@
NSThread *_javaScriptThread; NSThread *_javaScriptThread;
} }
RCT_EXPORT_MODULE()
/** /**
* The one tiny pure native hook that we implement is a native logging hook. * 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 * You could even argue that this is not necessary - we could plumb logging
@ -233,19 +235,29 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
JSGlobalContextRef ctx; JSGlobalContextRef ctx;
if (context) { if (context) {
ctx = JSGlobalContextRetain(context); ctx = JSGlobalContextRetain(context);
} else { strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
JSContextGroupRef group = JSContextGroupCreate(); }
ctx = JSGlobalContextCreateInGroup(group, NULL); }];
#if FB_JSC_HACK
JSContextGroupBindToCurrentThread(group);
#endif
JSContextGroupRelease(group);
} }
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->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
}
[strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[strongSelf _addNativeHook:RCTNoop withName:"noop"]; [strongSelf _addNativeHook:RCTNoop withName:"noop"];
#if RCT_DEV #if RCT_DEV
[strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"];
[strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"];
@ -257,13 +269,9 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
object:nil]; object:nil];
} }
#endif #endif
}]; }];
} }
return self;
}
- (void)toggleProfilingFlag:(NSNotification *)notification - (void)toggleProfilingFlag:(NSNotification *)notification
{ {
JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);

View File

@ -46,16 +46,14 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
NSRegularExpression *_scriptTagsRegex; NSRegularExpression *_scriptTagsRegex;
} }
RCT_EXPORT_MODULE()
@synthesize valid = _valid; @synthesize valid = _valid;
- (instancetype)initWithWebView:(UIWebView *)webView - (instancetype)initWithWebView:(UIWebView *)webView
{ {
if ((self = [super init])) { if ((self = [super init])) {
_objectsToInject = [[NSMutableDictionary alloc] init]; _webView = webView;
_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;
} }
return self; return self;
} }
@ -65,6 +63,18 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
return [self initWithWebView:nil]; 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 - (void)invalidate
{ {
_valid = NO; _valid = NO;