[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:
parent
68bb3a7e71
commit
847dff8d75
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
|
||||
@implementation TestExecutor
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)setUp {}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
|
|
|
@ -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");
|
||||
}];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:)]) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue