Refine RCTJSCExecutor's APIs for using a thread/context that already exists

Summary:
The `initWithJSContextProvider:` API created a `RCTJSCExecutor` with a thread/context that already exists, but it did not solve the problem of loading an actual application script; the `executeApplicationScript:` API is also asynchronous.

Create a new merged API that allows you to pass in a pre-created thread/context pair and immediately receive an `RCTJSCExector` that has already executed a specified application script.

This also removes the `underlyingJSContext` API entirely, in favor of passing it back in a byref variable in the new API. This minimizes the surface area for API abuse.

Reviewed By: bnham

Differential Revision: D3545349

fbshipit-source-id: 1c564f44d2a5379b5e6f75640079a28fd7169f67
This commit is contained in:
Adam Ernst 2016-07-12 05:13:30 -07:00 committed by Facebook Github Bot 6
parent 38a6eec0db
commit 80c71e5cae
2 changed files with 99 additions and 44 deletions

View File

@ -54,11 +54,6 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
*/
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary;
/**
* Pass a RCTJSContextProvider object to use an NSThread/JSContext pair that have already been created.
*/
- (instancetype)initWithJSContextProvider:(RCTJSContextProvider *)JSContextProvider;
/**
* Create a NSError from a JSError object.
*
@ -68,8 +63,16 @@ RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
- (NSError *)convertJSErrorToNSError:(JSValueRef)jsError context:(JSContextRef)context;
/**
* Returns the underlying JSContext.
* @experimental
* Pass a RCTJSContextProvider object to use an NSThread/JSContext pair that have already been created.
* The returned executor has already executed the supplied application script synchronously.
* The underlying JSContext will be returned in the JSContext pointer if it is non-NULL and there was no error.
* If an error occurs, this method will return nil and specify the error in the error pointer if it is non-NULL.
*/
- (JSContext *)underlyingJSContext;
+ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider
applicationScript:(NSData *)applicationScript
sourceURL:(NSURL *)sourceURL
JSContext:(JSContext **)JSContext
error:(NSError **)error;
@end

View File

@ -320,10 +320,26 @@ static NSThread *newJavaScriptThread(void)
return self;
}
- (instancetype)initWithJSContextProvider:(RCTJSContextProvider *)JSContextProvider
+ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider
applicationScript:(NSData *)applicationScript
sourceURL:(NSURL *)sourceURL
JSContext:(JSContext **)JSContext
error:(NSError **)error
{
const RCTJSContextData data = JSContextProvider.data;
if (JSContext) {
*JSContext = data.context;
}
RCTJSCExecutor *executor = [[RCTJSCExecutor alloc] initWithJSContextData:data];
if (![executor _synchronouslyExecuteApplicationScript:applicationScript sourceURL:sourceURL JSContext:data.context error:error]) {
return nil; // error has been set by _synchronouslyExecuteApplicationScript:
}
return executor;
}
- (instancetype)initWithJSContextData:(const RCTJSContextData &)data
{
if (self = [super init]) {
const RCTJSContextData data = JSContextProvider.data;
_useCustomJSCLibrary = data.useCustomJSCLibrary;
_valid = YES;
_javaScriptThread = data.javaScriptThread;
@ -333,6 +349,30 @@ static NSThread *newJavaScriptThread(void)
return self;
}
- (BOOL)_synchronouslyExecuteApplicationScript:(NSData *)script
sourceURL:(NSURL *)sourceURL
JSContext:(JSContext *)context
error:(NSError **)error
{
BOOL isRAMBundle = NO;
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, error);
if (!script) {
return NO;
}
if (isRAMBundle) {
registerNativeRequire(context, self);
}
NSError *returnedError = executeApplicationScript(script, sourceURL, _jscWrapper, _performanceLogger, _context.context.JSGlobalContextRef);
if (returnedError) {
if (error) {
*error = returnedError;
}
return NO;
} else {
return YES;
}
}
- (RCTJavaScriptContext *)context
{
RCTAssertThread(_javaScriptThread, @"Must be called on JS thread.");
@ -343,11 +383,6 @@ static NSThread *newJavaScriptThread(void)
return _context;
}
- (JSContext *)underlyingJSContext
{
return self.context.context;
}
- (void)setUp
{
#if RCT_PROFILE
@ -662,36 +697,16 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
RCTAssertParam(script);
RCTAssertParam(sourceURL);
// The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`.
uint32_t magicNumber = NSSwapLittleIntToHost(*((uint32_t *)script.bytes));
BOOL isRAMBundle = magicNumber == RCTRAMBundleMagicNumber;
if (isRAMBundle) {
[_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
BOOL isRAMBundle = NO;
{
NSError *error;
script = loadRAMBundle(sourceURL, &error, _randomAccessBundle);
[_performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[_performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize];
// Reset the counters that the native require implementation uses
[_performanceLogger setValue:0 forTag:RCTPLRAMNativeRequires];
[_performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresCount];
[_performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresSize];
if (error) {
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, &error);
if (script == nil) {
if (onComplete) {
onComplete(error);
}
return;
}
} else {
// JSStringCreateWithUTF8CString expects a null terminated C string.
// RAM Bundling already provides a null terminated one.
NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1];
[nullTerminatedScript appendData:script];
[nullTerminatedScript appendBytes:"" length:1];
script = nullTerminatedScript;
}
[self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
@ -699,26 +714,63 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
return;
}
if (isRAMBundle) {
__weak RCTJSCExecutor *weakSelf = self;
self.context.context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakSelf _nativeRequire:moduleID]; };
registerNativeRequire(self.context.context, self);
}
[self->_performanceLogger markStartForTag:RCTPLScriptExecution];
NSError *error = executeApplicationScript(self->_jscWrapper, script, sourceURL, self->_context.context.JSGlobalContextRef);
[self->_performanceLogger markStopForTag:RCTPLScriptExecution];
NSError *error = executeApplicationScript(script, sourceURL, self->_jscWrapper, self->_performanceLogger,
self->_context.context.JSGlobalContextRef);
if (onComplete) {
onComplete(error);
}
}), 0, @"js_call", (@{ @"url": sourceURL.absoluteString }))];
}
static NSError *executeApplicationScript(RCTJSCWrapper *jscWrapper, NSData *script, NSURL *sourceURL, JSGlobalContextRef ctx)
static NSData *loadPossiblyBundledApplicationScript(NSData *script, NSURL *sourceURL,
RCTPerformanceLogger *performanceLogger,
BOOL &isRAMBundle, RandomAccessBundleData &randomAccessBundle,
NSError **error)
{
// The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`.
uint32_t magicNumber = NSSwapLittleIntToHost(*((uint32_t *)script.bytes));
isRAMBundle = magicNumber == RCTRAMBundleMagicNumber;
if (isRAMBundle) {
[performanceLogger markStartForTag:RCTPLRAMBundleLoad];
script = loadRAMBundle(sourceURL, error, randomAccessBundle);
[performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize];
// Reset the counters that the native require implementation uses
[performanceLogger setValue:0 forTag:RCTPLRAMNativeRequires];
[performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresCount];
[performanceLogger setValue:0 forTag:RCTPLRAMNativeRequiresSize];
return script;
} else {
// JSStringCreateWithUTF8CString expects a null terminated C string.
// RAM Bundling already provides a null terminated one.
NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1];
[nullTerminatedScript appendData:script];
[nullTerminatedScript appendBytes:"" length:1];
return nullTerminatedScript;
}
}
static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor)
{
__weak RCTJSCExecutor *weakExecutor = executor;
context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; };
}
static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper,
RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx)
{
[performanceLogger markStartForTag:RCTPLScriptExecution];
JSValueRef jsError = NULL;
JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes);
JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String);
JSValueRef result = jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
jscWrapper->JSStringRelease(bundleURL);
jscWrapper->JSStringRelease(execJSString);
[performanceLogger markStopForTag:RCTPLScriptExecution];
return result ? nil : RCTNSErrorFromJSError(jscWrapper, ctx, jsError);
}