Add context executor API for sync hooks

Summary:
public

This exposes a proper API for adding synchronous callbacks to JS, as an optional feature of the executor.

This is based on nicklockwood's work in D2764492, but avoids refactoring bridge/executor interactions for the time being, since we agree on this API and can move the actual callsites around later.

Reviewed By: nicklockwood

Differential Revision: D2799506

fb-gh-sync-id: af209d9a0be927f3404205feb16e59745cc37aec
This commit is contained in:
Justin Spahr-Summers 2016-01-05 07:59:54 -08:00 committed by facebook-github-bot-7
parent 1c1d7006c2
commit a5d82c2823
4 changed files with 103 additions and 83 deletions

View File

@ -187,7 +187,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a
moduleProvider:nil moduleProvider:nil
launchOptions:nil]; launchOptions:nil];
id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"])); RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"_context"]));
XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created"); XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created");
(void)bridge; (void)bridge;
} }

View File

@ -136,7 +136,7 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
// (we can only do this now, since it's been lazily initialized) // (we can only do this now, since it's been lazily initialized)
id jsExecutor = [bridge valueForKeyPath:@"batchedBridge.javaScriptExecutor"]; id jsExecutor = [bridge valueForKeyPath:@"batchedBridge.javaScriptExecutor"];
if ([jsExecutor isKindOfClass:[RCTJSCExecutor class]]) { if ([jsExecutor isKindOfClass:[RCTJSCExecutor class]]) {
weakJSContext = [jsExecutor valueForKey:@"context"]; weakJSContext = [jsExecutor valueForKey:@"_context"];
} }
[rootView removeFromSuperview]; [rootView removeFromSuperview];

View File

@ -85,4 +85,10 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
*/ */
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block; - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
/**
* For executors that support it, this method can be used to add a synchronous
* callback function for communicating with the javascript context.
*/
- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block;
@end @end

View File

@ -215,99 +215,113 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
return [self initWithJavaScriptThread:javaScriptThread context:context]; return [self initWithJavaScriptThread:javaScriptThread context:context];
} }
- (void)setUp - (RCTJavaScriptContext *)context
{
RCTAssertThread(_javaScriptThread, @"Must be called on JS thread.");
if (!self.isValid) {
return nil;
}
if (!_context) {
JSContext *context = [JSContext new];
_context = [[RCTJavaScriptContext alloc] initWithJSContext:context];
}
return _context;
}
- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block
{ {
__weak RCTJSCExecutor *weakSelf = self; __weak RCTJSCExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{ [self executeBlockOnJavaScriptQueue:^{
RCTJSCExecutor *strongSelf = weakSelf; weakSelf.context.context[name] = block;
if (!strongSelf.isValid) { }];
return;
} }
if (!strongSelf->_context) { - (void)setUp
JSContext *context = [JSContext new]; {
strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context]; __weak RCTJSCExecutor *weakSelf = self;
} [self addSynchronousHookWithName:@"noop" usingBlock:^{}];
__weak RCTBridge *weakBridge = strongSelf->_bridge; [self addSynchronousHookWithName:@"nativeLoggingHook" usingBlock:^(NSString *message, NSNumber *logLevel) {
JSContext *context = strongSelf->_context.context;
context[@"noop"] = ^{};
context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) {
RCTLogLevel level = RCTLogLevelInfo; RCTLogLevel level = RCTLogLevelInfo;
if (logLevel) { if (logLevel) {
level = MAX(level, logLevel.integerValue); level = MAX(level, logLevel.integerValue);
} }
_RCTLogJavaScriptInternal(level, message); _RCTLogJavaScriptInternal(level, message);
}; }];
context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { [self addSynchronousHookWithName:@"nativeRequireModuleConfig" usingBlock:^NSString *(NSString *moduleName) {
if (!weakSelf.valid) { RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return nil; return nil;
} }
NSArray *config = [weakBridge configForModuleName:moduleName];
NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
if (config) { if (config) {
return RCTJSONStringify(config, NULL); return RCTJSONStringify(config, NULL);
} }
return nil;
};
context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){ return nil;
if (!weakSelf.valid || !calls) { }];
[self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid || !calls) {
return; return;
} }
[weakBridge handleBuffer:calls batchEnded:NO];
};
context[@"nativePerformanceNow"] = ^{ [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
}];
[self addSynchronousHookWithName:@"nativePerformanceNow" usingBlock:^{
return @(CACurrentMediaTime() * 1000); return @(CACurrentMediaTime() * 1000);
}; }];
#if RCT_DEV #if RCT_DEV
if (RCTProfileIsProfiling()) { if (RCTProfileIsProfiling()) {
context[@"__RCTProfileIsProfiling"] = @YES; // Cheating, since it's not a "hook", but meh
[self addSynchronousHookWithName:@"__RCTProfileIsProfiling" usingBlock:@YES];
} }
CFMutableDictionaryRef cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); CFMutableDictionaryRef cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { [self addSynchronousHookWithName:@"nativeTraceBeginAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) {
NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil); NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil);
CFDictionarySetValue(cookieMap, (const void *)cookie, (const void *)newCookie); CFDictionarySetValue(cookieMap, (const void *)cookie, (const void *)newCookie);
}; }];
context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { [self addSynchronousHookWithName:@"nativeTraceEndAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) {
NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(cookieMap, (const void *)cookie); NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(cookieMap, (const void *)cookie);
RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, nil); RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, nil);
CFDictionaryRemoveValue(cookieMap, (const void *)cookie); CFDictionaryRemoveValue(cookieMap, (const void *)cookie);
}; }];
context[@"nativeTraceBeginSection"] = ^(NSNumber *tag, NSString *profileName){ [self addSynchronousHookWithName:@"nativeTraceBeginSection" usingBlock:^(NSNumber *tag, NSString *profileName){
static int profileCounter = 1; static int profileCounter = 1;
if (!profileName) { if (!profileName) {
profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++]; profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
} }
RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, nil); RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, nil);
}; }];
context[@"nativeTraceEndSection"] = ^(NSNumber *tag) { [self addSynchronousHookWithName:@"nativeTraceEndSection" usingBlock:^(NSNumber *tag) {
RCT_PROFILE_END_EVENT(tag.longLongValue, @"console", nil); RCT_PROFILE_END_EVENT(tag.longLongValue, @"console", nil);
}; }];
RCTInstallJSCProfiler(_bridge, strongSelf->_context.ctx); [self executeBlockOnJavaScriptQueue:^{
RCTInstallJSCProfiler(_bridge, self.context.ctx);
}];
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
[[NSNotificationCenter defaultCenter] addObserver:strongSelf [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(toggleProfilingFlag:) selector:@selector(toggleProfilingFlag:)
name:event name:event
object:nil]; object:nil];
} }
#endif #endif
}];
} }
- (void)toggleProfilingFlag:(NSNotification *)notification - (void)toggleProfilingFlag:(NSNotification *)notification