[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;
}
// 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");
}

View File

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

View File

@ -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");
}];

View File

@ -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;

View File

@ -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);

View File

@ -899,7 +899,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
@implementation RCTBatchedBridge
{
BOOL _loading;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
__weak id<RCTJavaScriptExecutor> _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<RCTBridgeModule> module in _modulesByName.allValues) {
if ([module respondsToSelector:@selector(setBridge:)]) {

View File

@ -11,6 +11,7 @@
#import <JavaScriptCore/JavaScriptCore.h>
#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 <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
@ -61,14 +68,12 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
@end
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
__used static void RCTSetExecutorID(id<RCTJavaScriptExecutor> executor)
{
static NSUInteger executorID = 0;
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
if (executor) {
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
}
return executor;
}
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)

View File

@ -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);

View File

@ -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;