2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-12-16 10:49:27 +00:00
|
|
|
#import "RCTJSCExecutor.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
#import <cinttypes>
|
|
|
|
#import <memory>
|
2015-02-20 04:10:52 +00:00
|
|
|
#import <pthread.h>
|
2016-05-31 19:50:48 +00:00
|
|
|
#import <string>
|
2016-07-07 23:36:52 +00:00
|
|
|
#import <unordered_map>
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-21 02:30:59 +00:00
|
|
|
#import <UIKit/UIDevice.h>
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
#import <cxxreact/JSBundleType.h>
|
2016-11-23 19:33:44 +00:00
|
|
|
#import <jschelpers/JavaScriptCore.h>
|
2016-11-23 14:31:43 +00:00
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
#import "JSCSamplingProfiler.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTAssert.h"
|
2015-12-15 13:39:30 +00:00
|
|
|
#import "RCTBridge+Private.h"
|
2015-04-21 12:26:51 +00:00
|
|
|
#import "RCTDefines.h"
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
#import "RCTDevSettings.h"
|
2016-11-23 15:47:52 +00:00
|
|
|
#import "RCTJSCErrorHandling.h"
|
|
|
|
#import "RCTJSCProfiler.h"
|
2016-03-17 17:34:46 +00:00
|
|
|
#import "RCTJavaScriptLoader.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTLog.h"
|
2015-06-19 21:59:42 +00:00
|
|
|
#import "RCTPerformanceLogger.h"
|
2016-11-23 15:47:52 +00:00
|
|
|
#import "RCTProfile.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTUtils.h"
|
2015-07-20 16:14:53 +00:00
|
|
|
|
2017-02-27 21:25:47 +00:00
|
|
|
#if (RCT_PROFILE || RCT_DEV) && __has_include("RCTDevMenu.h")
|
|
|
|
#import "RCTDevMenu.h"
|
|
|
|
#endif
|
|
|
|
|
2016-04-01 14:01:51 +00:00
|
|
|
NSString *const RCTJSCThreadName = @"com.facebook.react.JavaScript";
|
2016-02-15 20:57:21 +00:00
|
|
|
NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
|
2016-09-27 16:33:10 +00:00
|
|
|
RCT_EXTERN NSString *const RCTFBJSContextClassKey = @"_RCTFBJSContextClassKey";
|
|
|
|
RCT_EXTERN NSString *const RCTFBJSValueClassKey = @"_RCTFBJSValueClassKey";
|
2016-02-15 20:57:21 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
struct __attribute__((packed)) ModuleData {
|
2016-03-13 18:13:39 +00:00
|
|
|
uint32_t offset;
|
2016-05-16 11:40:53 +00:00
|
|
|
uint32_t size;
|
|
|
|
};
|
|
|
|
|
|
|
|
using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
|
|
|
|
using memory_ptr = std::unique_ptr<void, decltype(&free)>;
|
|
|
|
|
|
|
|
struct RandomAccessBundleData {
|
|
|
|
file_ptr bundle;
|
|
|
|
size_t baseOffset;
|
|
|
|
size_t numTableEntries;
|
2016-05-23 14:18:55 +00:00
|
|
|
std::unique_ptr<ModuleData[]> table;
|
|
|
|
RandomAccessBundleData(): bundle(nullptr, fclose) {}
|
2016-05-16 11:40:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct RandomAccessBundleStartupCode {
|
|
|
|
memory_ptr code;
|
|
|
|
size_t size;
|
|
|
|
static RandomAccessBundleStartupCode empty() {
|
|
|
|
return RandomAccessBundleStartupCode{memory_ptr(nullptr, free), 0};
|
|
|
|
};
|
|
|
|
bool isEmpty() {
|
|
|
|
return !code;
|
|
|
|
}
|
|
|
|
};
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
struct TaggedScript {
|
2016-11-23 14:31:43 +00:00
|
|
|
const facebook::react::ScriptTag tag;
|
2016-11-01 17:14:00 +00:00
|
|
|
const NSData *script;
|
|
|
|
};
|
|
|
|
|
2016-07-07 23:36:52 +00:00
|
|
|
#if RCT_PROFILE
|
|
|
|
@interface RCTCookieMap : NSObject
|
|
|
|
{
|
|
|
|
@package
|
|
|
|
std::unordered_map<NSUInteger, NSUInteger> _cookieMap;
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTCookieMap @end
|
|
|
|
#endif
|
|
|
|
|
2016-07-08 19:19:27 +00:00
|
|
|
struct RCTJSContextData {
|
|
|
|
BOOL useCustomJSCLibrary;
|
|
|
|
NSThread *javaScriptThread;
|
|
|
|
JSContext *context;
|
|
|
|
};
|
|
|
|
|
2015-04-10 14:28:10 +00:00
|
|
|
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
|
|
|
|
|
2015-10-19 10:49:43 +00:00
|
|
|
@property (nonatomic, strong, readonly) JSContext *context;
|
2015-04-10 14:28:10 +00:00
|
|
|
|
2016-03-30 15:11:04 +00:00
|
|
|
- (instancetype)initWithJSContext:(JSContext *)context
|
|
|
|
onThread:(NSThread *)javaScriptThread NS_DESIGNATED_INITIALIZER;
|
2015-04-10 14:28:10 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTJavaScriptContext
|
|
|
|
{
|
2015-12-09 12:37:40 +00:00
|
|
|
RCTJavaScriptContext *_selfReference;
|
2016-03-30 15:11:04 +00:00
|
|
|
NSThread *_javaScriptThread;
|
2015-04-10 14:28:10 +00:00
|
|
|
}
|
|
|
|
|
2015-10-19 10:49:43 +00:00
|
|
|
- (instancetype)initWithJSContext:(JSContext *)context
|
2016-03-30 15:11:04 +00:00
|
|
|
onThread:(NSThread *)javaScriptThread
|
2015-04-10 14:28:10 +00:00
|
|
|
{
|
|
|
|
if ((self = [super init])) {
|
2015-10-19 10:49:43 +00:00
|
|
|
_context = context;
|
2016-10-14 18:29:47 +00:00
|
|
|
_context.name = @"RCTJSContext";
|
2016-03-30 15:11:04 +00:00
|
|
|
_javaScriptThread = javaScriptThread;
|
2015-12-09 12:37:40 +00:00
|
|
|
|
|
|
|
/**
|
2015-12-16 10:49:27 +00:00
|
|
|
* Explicitly introduce a retain cycle here - The RCTJSCExecutor might
|
2015-12-09 12:37:40 +00:00
|
|
|
* be deallocated while there's still work enqueued in the JS thread, so
|
|
|
|
* we wouldn't be able kill the JSContext. Instead we create this retain
|
|
|
|
* cycle, and enqueue the -invalidate message in this object, it then
|
|
|
|
* releases the JSContext, breaks the cycle and stops the runloop.
|
|
|
|
*/
|
|
|
|
_selfReference = self;
|
2015-04-10 14:28:10 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-08-24 10:14:33 +00:00
|
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|
|
|
|
2015-04-10 14:28:10 +00:00
|
|
|
- (BOOL)isValid
|
|
|
|
{
|
2015-10-19 10:49:43 +00:00
|
|
|
return _context != nil;
|
2015-04-10 14:28:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)invalidate
|
|
|
|
{
|
2015-04-20 12:40:42 +00:00
|
|
|
if (self.isValid) {
|
2016-03-30 15:11:04 +00:00
|
|
|
RCTAssertThread(_javaScriptThread, @"Must be invalidated on JS thread.");
|
|
|
|
|
2015-10-19 10:49:43 +00:00
|
|
|
_context = nil;
|
2015-12-09 12:37:40 +00:00
|
|
|
_selfReference = nil;
|
2016-03-30 15:11:04 +00:00
|
|
|
_javaScriptThread = nil;
|
2015-04-10 14:28:10 +00:00
|
|
|
|
2016-03-30 15:11:04 +00:00
|
|
|
CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]);
|
|
|
|
}
|
2015-04-27 10:47:48 +00:00
|
|
|
}
|
|
|
|
|
2015-04-10 14:28:10 +00:00
|
|
|
@end
|
|
|
|
|
2015-12-16 10:49:27 +00:00
|
|
|
@implementation RCTJSCExecutor
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2016-07-08 19:19:26 +00:00
|
|
|
// Set at init time:
|
|
|
|
BOOL _useCustomJSCLibrary;
|
2015-02-20 04:10:52 +00:00
|
|
|
NSThread *_javaScriptThread;
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-07-08 19:19:26 +00:00
|
|
|
// Set at setUp time:
|
|
|
|
RCTPerformanceLogger *_performanceLogger;
|
|
|
|
RCTJavaScriptContext *_context;
|
2016-07-07 14:20:03 +00:00
|
|
|
|
2016-07-08 19:19:26 +00:00
|
|
|
// Set as needed:
|
|
|
|
RandomAccessBundleData _randomAccessBundle;
|
|
|
|
JSValueRef _batchedBridgeRef;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 17:43:22 +00:00
|
|
|
@synthesize valid = _valid;
|
2015-08-28 17:11:02 +00:00
|
|
|
@synthesize bridge = _bridge;
|
2015-06-12 17:43:22 +00:00
|
|
|
|
2015-06-09 22:42:10 +00:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2015-08-25 08:31:22 +00:00
|
|
|
#if RCT_DEV
|
2015-09-10 16:03:03 +00:00
|
|
|
static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
|
|
|
|
{
|
2017-02-27 21:25:47 +00:00
|
|
|
#if __has_include("RCTDevMenu.h")
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
__weak RCTBridge *weakBridge = bridge;
|
|
|
|
__weak RCTDevSettings *devSettings = bridge.devSettings;
|
2015-12-15 11:11:30 +00:00
|
|
|
if (RCTJSCProfilerIsSupported()) {
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
[bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
|
|
|
|
return devSettings.isJSCProfilingEnabled ? @"Stop Profiling" : @"Start Profiling";
|
|
|
|
} handler:^{
|
|
|
|
BOOL shouldStart = !devSettings.isJSCProfilingEnabled;
|
|
|
|
devSettings.isJSCProfilingEnabled = shouldStart;
|
2015-12-15 11:11:30 +00:00
|
|
|
if (shouldStart != RCTJSCProfilerIsProfiling(context)) {
|
2015-09-15 09:49:04 +00:00
|
|
|
if (shouldStart) {
|
2015-12-15 11:11:30 +00:00
|
|
|
RCTJSCProfilerStart(context);
|
2015-09-15 09:49:04 +00:00
|
|
|
} else {
|
2015-12-15 11:11:30 +00:00
|
|
|
NSString *outputFile = RCTJSCProfilerStop(context);
|
|
|
|
NSData *profileData = [NSData dataWithContentsOfFile:outputFile options:NSDataReadingMappedIfSafe error:NULL];
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
RCTProfileSendResult(weakBridge, @"cpu-profile", profileData);
|
2015-09-10 16:03:03 +00:00
|
|
|
}
|
2015-12-15 11:11:30 +00:00
|
|
|
}
|
|
|
|
}]];
|
2015-09-10 16:03:03 +00:00
|
|
|
}
|
2017-02-27 21:25:47 +00:00
|
|
|
#endif
|
2015-09-10 16:03:03 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 08:31:22 +00:00
|
|
|
#endif
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
+ (void)runRunLoopThread
|
|
|
|
{
|
|
|
|
@autoreleasepool {
|
|
|
|
// copy thread name to pthread name
|
2015-08-24 10:14:33 +00:00
|
|
|
pthread_setname_np([NSThread currentThread].name.UTF8String);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
// Set up a dummy runloop source to avoid spinning
|
|
|
|
CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
|
|
|
|
CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
|
|
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
|
|
|
|
CFRelease(noSpinSource);
|
|
|
|
|
|
|
|
// run the run loop
|
2015-08-24 10:14:33 +00:00
|
|
|
while (kCFRunLoopRunStopped != CFRunLoopRunInMode(kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
|
2015-03-26 16:44:58 +00:00
|
|
|
RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-08 19:19:27 +00:00
|
|
|
static NSThread *newJavaScriptThread(void)
|
|
|
|
{
|
|
|
|
NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[RCTJSCExecutor class]
|
|
|
|
selector:@selector(runRunLoopThread)
|
|
|
|
object:nil];
|
|
|
|
javaScriptThread.name = RCTJSCThreadName;
|
2017-02-13 20:06:17 +00:00
|
|
|
javaScriptThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
|
2016-07-08 19:19:27 +00:00
|
|
|
[javaScriptThread start];
|
|
|
|
return javaScriptThread;
|
|
|
|
}
|
|
|
|
|
2016-07-07 14:20:03 +00:00
|
|
|
- (void)setBridge:(RCTBridge *)bridge
|
|
|
|
{
|
|
|
|
_bridge = bridge;
|
|
|
|
_performanceLogger = [bridge performanceLogger];
|
|
|
|
}
|
|
|
|
|
2016-06-06 18:10:24 +00:00
|
|
|
- (instancetype)init
|
2016-05-31 19:50:48 +00:00
|
|
|
{
|
2016-06-06 18:10:24 +00:00
|
|
|
return [self initWithUseCustomJSCLibrary:NO];
|
2016-05-31 19:50:48 +00:00
|
|
|
}
|
|
|
|
|
2016-06-06 18:10:24 +00:00
|
|
|
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2016-07-19 14:16:32 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTJSCExecutor init]", nil);
|
|
|
|
|
2016-03-16 17:22:38 +00:00
|
|
|
if (self = [super init]) {
|
2016-06-06 18:10:24 +00:00
|
|
|
_useCustomJSCLibrary = useCustomJSCLibrary;
|
2016-03-16 17:22:38 +00:00
|
|
|
_valid = YES;
|
2016-07-08 19:19:27 +00:00
|
|
|
_javaScriptThread = newJavaScriptThread();
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
2016-07-08 19:19:27 +00:00
|
|
|
return self;
|
|
|
|
}
|
2015-04-27 10:47:48 +00:00
|
|
|
|
2016-07-12 12:13:30 +00:00
|
|
|
- (instancetype)initWithJSContextData:(const RCTJSContextData &)data
|
2016-07-08 19:19:27 +00:00
|
|
|
{
|
|
|
|
if (self = [super init]) {
|
|
|
|
_useCustomJSCLibrary = data.useCustomJSCLibrary;
|
|
|
|
_valid = YES;
|
|
|
|
_javaScriptThread = data.javaScriptThread;
|
|
|
|
_context = [[RCTJavaScriptContext alloc] initWithJSContext:data.context onThread:_javaScriptThread];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2016-11-03 17:45:06 +00:00
|
|
|
- (NSError *)synchronouslyExecuteApplicationScript:(NSData *)script
|
|
|
|
sourceURL:(NSURL *)sourceURL
|
2016-07-12 12:13:30 +00:00
|
|
|
{
|
2016-11-03 17:45:06 +00:00
|
|
|
NSError *loadError;
|
|
|
|
TaggedScript taggedScript = loadTaggedScript(script, sourceURL, _performanceLogger, _randomAccessBundle, &loadError);
|
2016-11-01 17:14:00 +00:00
|
|
|
|
2016-11-03 17:45:06 +00:00
|
|
|
if (loadError) {
|
|
|
|
return loadError;
|
2016-07-12 12:13:30 +00:00
|
|
|
}
|
2016-11-01 17:14:00 +00:00
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
if (taggedScript.tag == facebook::react::ScriptTag::RAMBundle) {
|
2016-11-03 17:45:06 +00:00
|
|
|
registerNativeRequire(_context.context, self);
|
2016-07-12 12:13:30 +00:00
|
|
|
}
|
2016-11-01 17:14:00 +00:00
|
|
|
|
2016-11-08 17:11:29 +00:00
|
|
|
return executeApplicationScript(taggedScript, sourceURL,
|
|
|
|
_performanceLogger,
|
|
|
|
_context.context.JSGlobalContextRef);
|
2016-07-12 12:13:30 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 15:59:54 +00:00
|
|
|
- (RCTJavaScriptContext *)context
|
|
|
|
{
|
|
|
|
RCTAssertThread(_javaScriptThread, @"Must be called on JS thread.");
|
|
|
|
if (!self.isValid) {
|
|
|
|
return nil;
|
|
|
|
}
|
2016-07-08 16:08:36 +00:00
|
|
|
RCTAssert(_context != nil, @"Fetching context while valid, but before it is created");
|
2016-01-05 15:59:54 +00:00
|
|
|
return _context;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setUp
|
|
|
|
{
|
2016-07-08 16:08:34 +00:00
|
|
|
#if RCT_PROFILE
|
2016-07-07 23:36:48 +00:00
|
|
|
#ifndef __clang_analyzer__
|
|
|
|
_bridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
|
|
|
|
#endif
|
|
|
|
_bridge.flowIDMapLock = [NSLock new];
|
|
|
|
|
|
|
|
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(toggleProfilingFlag:)
|
|
|
|
name:event
|
|
|
|
object:nil];
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-04-21 15:58:29 +00:00
|
|
|
[self executeBlockOnJavaScriptQueue:^{
|
2016-07-08 16:08:34 +00:00
|
|
|
if (!self.valid) {
|
2016-04-21 15:58:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
JSGlobalContextRef contextRef = nullptr;
|
2016-07-08 19:19:27 +00:00
|
|
|
JSContext *context = nil;
|
2016-11-23 19:33:44 +00:00
|
|
|
if (self->_context) {
|
2016-07-08 19:19:27 +00:00
|
|
|
context = self->_context.context;
|
2016-11-23 19:33:44 +00:00
|
|
|
contextRef = context.JSGlobalContextRef;
|
2016-07-08 19:19:27 +00:00
|
|
|
} else {
|
2016-11-23 19:33:44 +00:00
|
|
|
if (self->_useCustomJSCLibrary) {
|
2017-01-25 22:28:02 +00:00
|
|
|
JSC_configureJSCForIOS(true, RCTJSONStringify(@{
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
@"StartSamplingProfilerOnInit": @(self->_bridge.devSettings.startSamplingProfilerOnLaunch)
|
2017-01-25 22:28:02 +00:00
|
|
|
}, NULL).UTF8String);
|
2016-11-23 19:33:44 +00:00
|
|
|
}
|
|
|
|
contextRef = JSC_JSGlobalContextCreateInGroup(self->_useCustomJSCLibrary, nullptr, nullptr);
|
|
|
|
context = [JSC_JSContext(contextRef) contextWithJSGlobalContextRef:contextRef];
|
2016-12-12 13:38:50 +00:00
|
|
|
// We release the global context reference here to balance retainCount after JSGlobalContextCreateInGroup.
|
|
|
|
// The global context _is not_ going to be released since the JSContext keeps the strong reference to it.
|
|
|
|
JSC_JSGlobalContextRelease(contextRef);
|
2016-07-08 19:19:27 +00:00
|
|
|
self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
|
|
|
|
object:context];
|
|
|
|
|
|
|
|
installBasicSynchronousHooksOnContext(context);
|
2016-04-21 15:58:29 +00:00
|
|
|
}
|
|
|
|
|
2016-09-27 16:33:10 +00:00
|
|
|
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
|
|
|
|
if (!threadDictionary[RCTFBJSContextClassKey] || !threadDictionary[RCTFBJSValueClassKey]) {
|
2016-11-23 19:33:44 +00:00
|
|
|
threadDictionary[RCTFBJSContextClassKey] = JSC_JSContext(contextRef);
|
|
|
|
threadDictionary[RCTFBJSValueClassKey] = JSC_JSValue(contextRef);
|
2016-09-27 16:33:10 +00:00
|
|
|
}
|
|
|
|
|
2017-02-17 13:47:28 +00:00
|
|
|
RCTFBQuickPerformanceLoggerConfigureHooks(context.JSGlobalContextRef);
|
2017-02-16 19:30:39 +00:00
|
|
|
|
2016-07-08 16:08:34 +00:00
|
|
|
__weak RCTJSCExecutor *weakSelf = self;
|
2016-09-08 10:59:32 +00:00
|
|
|
context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
|
2016-07-08 16:08:34 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.valid) {
|
2016-07-07 23:36:48 +00:00
|
|
|
return nil;
|
|
|
|
}
|
2015-12-15 13:32:24 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", @{ @"moduleName": moduleName });
|
2016-09-08 10:59:32 +00:00
|
|
|
NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
|
2016-09-23 18:12:54 +00:00
|
|
|
return RCTNullIfNil(result);
|
2016-07-07 23:36:48 +00:00
|
|
|
};
|
2015-12-10 12:27:45 +00:00
|
|
|
|
2016-07-07 23:36:48 +00:00
|
|
|
context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
|
2016-07-08 16:08:34 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.valid || !calls) {
|
2016-07-07 23:36:48 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-12-10 12:27:45 +00:00
|
|
|
|
2016-07-07 23:36:48 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
|
2016-07-08 16:08:34 +00:00
|
|
|
[strongSelf->_bridge handleBuffer:calls batchEnded:NO];
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
|
2016-07-07 23:36:48 +00:00
|
|
|
};
|
2015-10-19 15:02:50 +00:00
|
|
|
|
2016-09-05 14:32:20 +00:00
|
|
|
context[@"nativeCallSyncHook"] = ^id(NSUInteger module, NSUInteger method, NSArray *args) {
|
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.valid) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeCallSyncHook", nil);
|
|
|
|
id result = [strongSelf->_bridge callNativeModule:module method:method params:args];
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
|
2016-09-05 14:32:20 +00:00
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2016-06-13 11:16:19 +00:00
|
|
|
#if RCT_PROFILE
|
2016-07-08 16:08:34 +00:00
|
|
|
__weak RCTBridge *weakBridge = self->_bridge;
|
2016-07-07 23:36:48 +00:00
|
|
|
context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
|
|
[weakBridge.flowIDMapLock lock];
|
2016-08-09 10:53:59 +00:00
|
|
|
NSUInteger newCookie = _RCTProfileBeginFlowEvent();
|
2016-07-07 23:36:48 +00:00
|
|
|
CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie);
|
|
|
|
[weakBridge.flowIDMapLock unlock];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
context[@"nativeTraceEndAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
|
|
[weakBridge.flowIDMapLock lock];
|
2016-09-27 13:07:29 +00:00
|
|
|
NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
|
2016-08-09 10:53:59 +00:00
|
|
|
_RCTProfileEndFlowEvent(newCookie);
|
2016-07-07 23:36:48 +00:00
|
|
|
CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
|
|
|
|
[weakBridge.flowIDMapLock unlock];
|
|
|
|
}
|
|
|
|
};
|
2016-11-15 17:28:10 +00:00
|
|
|
|
|
|
|
// Add toggles for JSC's sampling profiler, if the profiler is enabled
|
2016-11-23 19:33:44 +00:00
|
|
|
if (JSC_JSSamplingProfilerEnabled(context.JSGlobalContextRef)) {
|
2017-02-27 21:25:47 +00:00
|
|
|
// Mark this thread as the main JS thread before starting profiling.
|
|
|
|
JSC_JSStartSamplingProfilingOnMainJSCThread(context.JSGlobalContextRef);
|
2016-11-15 17:28:10 +00:00
|
|
|
|
|
|
|
__weak JSContext *weakContext = self->_context.context;
|
2017-02-27 21:25:47 +00:00
|
|
|
|
|
|
|
#if __has_include("RCTDevMenu.h")
|
|
|
|
// Allow to toggle the sampling profiler through RN's dev menu
|
2016-11-15 17:28:10 +00:00
|
|
|
[self->_bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{
|
2016-11-23 19:33:44 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.valid || !weakContext) {
|
|
|
|
return;
|
|
|
|
}
|
Add RCTDevSettings module
Summary:
This decouples non-UI logic from RCTDevMenu into a new module RCTDevSettings.
**Motivation**: This allows developers to change dev settings without depending on the built-in dev menu, e.g. if they want to introduce their own UI, or have other devtools logic that doesn't depend on an action sheet.
It also introduces the RCTDevSettingsDataSource protocol for storing dev tools preferences. This could allow a developer to implement alternative behaviors, e.g. loading the settings from some other config, changing settings based on the user, deciding not to persist some settings, or something else.
The included data source implementation, RCTDevSettingsUserDefaultsDataSource, uses NSUserDefaults and is backwards compatible with the older implementation, so **no workflows or dependent code will break, and old saved settings will persist.**
The RCTDevMenu interface has not changed and is therefore also backwards-compatible, though
some methods are now deprecated.
In order to ensure that RCTDevSettings
Closes https://github.com/facebook/react-native/pull/11613
Reviewed By: mmmulani
Differential Revision: D4571773
Pulled By: javache
fbshipit-source-id: 25555d0a6eaa81f694343e079ed02439e5845fbc
2017-02-24 14:50:29 +00:00
|
|
|
[weakSelf.bridge.devSettings toggleJSCSamplingProfiler];
|
2016-11-15 17:28:10 +00:00
|
|
|
}]];
|
2017-02-27 21:25:47 +00:00
|
|
|
#endif
|
2016-11-15 17:28:10 +00:00
|
|
|
|
|
|
|
// Allow for the profiler to be poked from JS code as well
|
|
|
|
// (see SamplingProfiler.js for an example of how it could be used with the JSCSamplingProfiler module).
|
2016-11-23 19:33:44 +00:00
|
|
|
context[@"pokeSamplingProfiler"] = ^NSDictionary *() {
|
|
|
|
if (!weakContext) {
|
2016-11-15 17:28:10 +00:00
|
|
|
return @{};
|
|
|
|
}
|
2016-11-23 19:33:44 +00:00
|
|
|
JSGlobalContextRef ctx = weakContext.JSGlobalContextRef;
|
|
|
|
JSValueRef result = JSC_JSPokeSamplingProfiler(ctx);
|
|
|
|
return [[JSC_JSValue(ctx) valueWithJSValueRef:result inContext:weakContext] toObject];
|
2016-11-15 17:28:10 +00:00
|
|
|
};
|
|
|
|
}
|
2016-06-21 19:02:31 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if RCT_DEV
|
2016-07-08 16:08:34 +00:00
|
|
|
RCTInstallJSCProfiler(self->_bridge, context.JSGlobalContextRef);
|
2015-12-10 12:27:45 +00:00
|
|
|
|
2016-07-07 23:36:48 +00:00
|
|
|
// Inject handler used by HMR
|
|
|
|
context[@"nativeInjectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
|
2016-07-08 16:08:34 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.valid) {
|
2016-07-07 23:36:48 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-02-12 16:09:43 +00:00
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
JSGlobalContextRef ctx = strongSelf->_context.context.JSGlobalContextRef;
|
|
|
|
JSStringRef execJSString = JSC_JSStringCreateWithUTF8CString(ctx, sourceCode.UTF8String);
|
|
|
|
JSStringRef jsURL = JSC_JSStringCreateWithUTF8CString(ctx, sourceCodeURL.UTF8String);
|
|
|
|
JSC_JSEvaluateScript(ctx, execJSString, NULL, jsURL, 0, NULL);
|
|
|
|
JSC_JSStringRelease(ctx, jsURL);
|
|
|
|
JSC_JSStringRelease(ctx, execJSString);
|
2016-07-07 23:36:48 +00:00
|
|
|
};
|
2015-06-09 22:42:10 +00:00
|
|
|
#endif
|
2016-07-07 23:36:48 +00:00
|
|
|
}];
|
2015-06-09 22:42:10 +00:00
|
|
|
}
|
|
|
|
|
2016-07-08 19:19:27 +00:00
|
|
|
/** Installs synchronous hooks that don't require a weak reference back to the RCTJSCExecutor. */
|
|
|
|
static void installBasicSynchronousHooksOnContext(JSContext *context)
|
2016-07-07 23:36:51 +00:00
|
|
|
{
|
|
|
|
context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) {
|
|
|
|
RCTLogLevel level = RCTLogLevelInfo;
|
|
|
|
if (logLevel) {
|
|
|
|
level = MAX(level, (RCTLogLevel)logLevel.integerValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
_RCTLogJavaScriptInternal(level, message);
|
|
|
|
};
|
|
|
|
context[@"nativePerformanceNow"] = ^{
|
|
|
|
return @(CACurrentMediaTime() * 1000);
|
|
|
|
};
|
|
|
|
#if RCT_PROFILE
|
|
|
|
if (RCTProfileIsProfiling()) {
|
|
|
|
// Cheating, since it's not a "hook", but meh
|
|
|
|
context[@"__RCTProfileIsProfiling"] = @YES;
|
|
|
|
}
|
|
|
|
context[@"nativeTraceBeginSection"] = ^(NSNumber *tag, NSString *profileName, NSDictionary *args) {
|
|
|
|
static int profileCounter = 1;
|
|
|
|
if (!profileName) {
|
|
|
|
profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
|
|
|
|
}
|
|
|
|
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, args);
|
|
|
|
};
|
|
|
|
context[@"nativeTraceEndSection"] = ^(NSNumber *tag) {
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(tag.longLongValue, @"console");
|
2016-07-07 23:36:51 +00:00
|
|
|
};
|
2016-07-07 23:36:52 +00:00
|
|
|
RCTCookieMap *cookieMap = [RCTCookieMap new];
|
|
|
|
context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) {
|
|
|
|
NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil);
|
|
|
|
cookieMap->_cookieMap.insert({cookie, newCookie});
|
|
|
|
};
|
|
|
|
context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) {
|
|
|
|
NSUInteger newCookie = 0;
|
|
|
|
const auto &it = cookieMap->_cookieMap.find(cookie);
|
|
|
|
if (it != cookieMap->_cookieMap.end()) {
|
|
|
|
newCookie = it->second;
|
|
|
|
cookieMap->_cookieMap.erase(it);
|
|
|
|
}
|
2016-09-05 18:11:37 +00:00
|
|
|
RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, @"JS async");
|
2016-07-07 23:36:52 +00:00
|
|
|
};
|
2016-07-07 23:36:51 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-06-02 13:15:53 +00:00
|
|
|
- (void)toggleProfilingFlag:(NSNotification *)notification
|
|
|
|
{
|
2015-10-01 21:08:13 +00:00
|
|
|
[self executeBlockOnJavaScriptQueue:^{
|
|
|
|
BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling];
|
2016-08-02 18:06:19 +00:00
|
|
|
[self->_bridge enqueueJSCall:@"Systrace"
|
|
|
|
method:@"setEnabled"
|
|
|
|
args:@[enabled ? @YES : @NO]
|
|
|
|
completion:NULL];
|
2015-10-01 21:08:13 +00:00
|
|
|
}];
|
2015-06-02 13:15:53 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)invalidate
|
|
|
|
{
|
2015-06-12 17:43:22 +00:00
|
|
|
if (!self.isValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_valid = NO;
|
|
|
|
|
2016-10-14 18:29:47 +00:00
|
|
|
#if RCT_PROFILE
|
2015-06-02 13:15:53 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
#endif
|
2016-03-07 17:45:20 +00:00
|
|
|
}
|
|
|
|
|
2016-11-11 13:21:37 +00:00
|
|
|
- (int32_t)bytecodeFileFormatVersion
|
|
|
|
{
|
2017-02-17 13:55:43 +00:00
|
|
|
return _useCustomJSCLibrary
|
2016-11-23 19:33:44 +00:00
|
|
|
? facebook::react::customJSCWrapper()->JSBytecodeFileFormatVersion
|
2016-11-11 13:21:39 +00:00
|
|
|
: JSNoBytecodeFileFormatVersion;
|
2016-11-11 13:21:37 +00:00
|
|
|
}
|
|
|
|
|
2016-10-14 18:29:47 +00:00
|
|
|
- (NSString *)contextName
|
|
|
|
{
|
|
|
|
return [_context.context name];
|
|
|
|
}
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
|
|
|
|
{
|
|
|
|
[_context.context setName:contextName];
|
|
|
|
}
|
|
|
|
|
2016-03-07 17:45:20 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self invalidate];
|
2015-06-02 13:15:53 +00:00
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[_context performSelector:@selector(invalidate)
|
|
|
|
onThread:_javaScriptThread
|
|
|
|
withObject:nil
|
|
|
|
waitUntilDone:NO];
|
2015-07-20 09:34:11 +00:00
|
|
|
_context = nil;
|
2016-04-08 14:33:14 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
_randomAccessBundle.bundle.reset();
|
|
|
|
_randomAccessBundle.table.reset();
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-12-08 23:57:34 +00:00
|
|
|
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
|
|
|
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
|
2016-07-18 14:12:19 +00:00
|
|
|
[self _executeJSCall:@"flushedQueue" arguments:@[] unwrapResult:YES callback:onComplete];
|
2015-12-08 23:57:34 +00:00
|
|
|
}
|
|
|
|
|
2016-07-18 14:12:19 +00:00
|
|
|
- (void)_callFunctionOnModule:(NSString *)module
|
|
|
|
method:(NSString *)method
|
|
|
|
arguments:(NSArray *)args
|
2016-09-16 13:14:13 +00:00
|
|
|
returnValue:(BOOL)returnValue
|
2016-07-18 14:12:19 +00:00
|
|
|
unwrapResult:(BOOL)unwrapResult
|
|
|
|
callback:(RCTJavaScriptCallback)onComplete
|
2015-12-08 23:57:34 +00:00
|
|
|
{
|
|
|
|
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
|
2016-09-16 13:14:13 +00:00
|
|
|
NSString *bridgeMethod = returnValue ? @"callFunctionReturnFlushedQueue" : @"callFunctionReturnResultAndFlushedQueue";
|
2016-07-18 14:12:19 +00:00
|
|
|
[self _executeJSCall:bridgeMethod arguments:@[module, method, args] unwrapResult:unwrapResult callback:onComplete];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
2016-09-16 13:14:13 +00:00
|
|
|
[self _callFunctionOnModule:module method:method arguments:args returnValue:YES unwrapResult:YES callback:onComplete];
|
2016-07-18 14:12:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args jsValueCallback:(RCTJavaScriptValueCallback)onComplete
|
|
|
|
{
|
2016-09-16 13:14:13 +00:00
|
|
|
[self _callFunctionOnModule:module method:method arguments:args returnValue:NO unwrapResult:NO callback:onComplete];
|
2015-12-08 23:57:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)invokeCallbackID:(NSNumber *)cbID
|
|
|
|
arguments:(NSArray *)args
|
|
|
|
callback:(RCTJavaScriptCallback)onComplete
|
|
|
|
{
|
|
|
|
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
|
2016-07-18 14:12:19 +00:00
|
|
|
[self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] unwrapResult:YES callback:onComplete];
|
2015-12-08 23:57:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_executeJSCall:(NSString *)method
|
|
|
|
arguments:(NSArray *)arguments
|
2016-07-18 14:12:19 +00:00
|
|
|
unwrapResult:(BOOL)unwrapResult
|
2015-12-08 23:57:34 +00:00
|
|
|
callback:(RCTJavaScriptCallback)onComplete
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
|
2015-12-16 10:49:27 +00:00
|
|
|
__weak RCTJSCExecutor *weakSelf = self;
|
2016-07-19 14:16:32 +00:00
|
|
|
[self executeBlockOnJavaScriptQueue:^{
|
2015-12-16 10:49:27 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
2015-07-14 23:16:21 +00:00
|
|
|
if (!strongSelf || !strongSelf.isValid) {
|
2015-04-10 14:28:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"executeJSCall", (@{@"method": method, @"args": arguments}));
|
2016-07-19 14:16:32 +00:00
|
|
|
|
2016-04-27 13:34:53 +00:00
|
|
|
JSContext *context = strongSelf->_context.context;
|
2016-11-23 19:33:44 +00:00
|
|
|
JSGlobalContextRef ctx = context.JSGlobalContextRef;
|
2015-05-23 02:33:21 +00:00
|
|
|
|
2015-12-08 23:57:34 +00:00
|
|
|
// get the BatchedBridge object
|
2016-07-19 14:16:32 +00:00
|
|
|
JSValueRef errorJSRef = NULL;
|
2016-06-14 12:09:23 +00:00
|
|
|
JSValueRef batchedBridgeRef = strongSelf->_batchedBridgeRef;
|
|
|
|
if (!batchedBridgeRef) {
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef moduleNameJSStringRef = JSC_JSStringCreateWithUTF8CString(ctx, "__fbBatchedBridge");
|
|
|
|
JSObjectRef globalObjectJSRef = JSC_JSContextGetGlobalObject(ctx);
|
|
|
|
batchedBridgeRef = JSC_JSObjectGetProperty(ctx, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
|
|
|
|
JSC_JSStringRelease(ctx, moduleNameJSStringRef);
|
2016-06-14 12:09:23 +00:00
|
|
|
strongSelf->_batchedBridgeRef = batchedBridgeRef;
|
|
|
|
}
|
2015-05-23 02:33:21 +00:00
|
|
|
|
2016-07-19 14:16:32 +00:00
|
|
|
NSError *error;
|
|
|
|
JSValueRef resultJSRef = NULL;
|
2016-11-23 19:33:44 +00:00
|
|
|
if (batchedBridgeRef != NULL && errorJSRef == NULL && JSC_JSValueGetType(ctx, batchedBridgeRef) != kJSTypeUndefined) {
|
2015-12-08 23:57:34 +00:00
|
|
|
// get method
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef methodNameJSStringRef = JSC_JSStringCreateWithCFString(ctx, (__bridge CFStringRef)method);
|
|
|
|
JSValueRef methodJSRef = JSC_JSObjectGetProperty(ctx, (JSObjectRef)batchedBridgeRef, methodNameJSStringRef, &errorJSRef);
|
|
|
|
JSC_JSStringRelease(ctx, methodNameJSStringRef);
|
2015-05-23 02:33:21 +00:00
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
if (methodJSRef != NULL && errorJSRef == NULL && JSC_JSValueGetType(ctx, methodJSRef) != kJSTypeUndefined) {
|
2016-04-27 13:34:53 +00:00
|
|
|
JSValueRef jsArgs[arguments.count];
|
|
|
|
for (NSUInteger i = 0; i < arguments.count; i++) {
|
2016-11-23 19:33:44 +00:00
|
|
|
jsArgs[i] = [JSC_JSValue(ctx) valueWithObject:arguments[i] inContext:context].JSValueRef;
|
2015-05-23 02:33:21 +00:00
|
|
|
}
|
2016-11-23 19:33:44 +00:00
|
|
|
resultJSRef = JSC_JSObjectCallAsFunction(ctx, (JSObjectRef)methodJSRef, (JSObjectRef)batchedBridgeRef, arguments.count, jsArgs, &errorJSRef);
|
2015-12-18 01:26:12 +00:00
|
|
|
} else {
|
2016-11-23 19:33:44 +00:00
|
|
|
if (!errorJSRef && JSC_JSValueGetType(ctx, methodJSRef) == kJSTypeUndefined) {
|
2015-12-18 01:26:12 +00:00
|
|
|
error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to execute JS call: method %@ is undefined", method]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-11-23 19:33:44 +00:00
|
|
|
if (!errorJSRef && JSC_JSValueGetType(ctx, batchedBridgeRef) == kJSTypeUndefined) {
|
2015-12-18 01:26:12 +00:00
|
|
|
error = RCTErrorWithMessage(@"Unable to execute JS call: __fbBatchedBridge is undefined");
|
2015-05-23 02:33:21 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-07-19 14:16:32 +00:00
|
|
|
id objcValue;
|
2015-12-18 01:26:12 +00:00
|
|
|
if (errorJSRef || error) {
|
|
|
|
if (!error) {
|
2016-11-23 19:33:44 +00:00
|
|
|
error = RCTNSErrorFromJSError([JSC_JSValue(ctx) valueWithJSValueRef:errorJSRef inContext:context]);
|
2015-12-18 01:26:12 +00:00
|
|
|
}
|
2016-07-18 14:12:19 +00:00
|
|
|
} else {
|
|
|
|
// We often return `null` from JS when there is nothing for native side. [JSValue toValue]
|
|
|
|
// returns [NSNull null] in this case, which we don't want.
|
2016-11-23 19:33:44 +00:00
|
|
|
if (JSC_JSValueGetType(ctx, resultJSRef) != kJSTypeNull) {
|
|
|
|
JSValue *result = [JSC_JSValue(ctx) valueWithJSValueRef:resultJSRef inContext:context];
|
2016-07-18 14:12:19 +00:00
|
|
|
objcValue = unwrapResult ? [result toObject] : result;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(0, @"js_call");
|
2016-07-19 14:16:32 +00:00
|
|
|
|
|
|
|
onComplete(objcValue, error);
|
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-10-16 15:10:25 +00:00
|
|
|
- (void)executeApplicationScript:(NSData *)script
|
2015-04-19 19:55:46 +00:00
|
|
|
sourceURL:(NSURL *)sourceURL
|
2015-02-20 04:10:52 +00:00
|
|
|
onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
|
|
|
{
|
2015-08-07 13:42:34 +00:00
|
|
|
RCTAssertParam(script);
|
|
|
|
RCTAssertParam(sourceURL);
|
2015-04-19 19:55:46 +00:00
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
NSError *loadError;
|
|
|
|
TaggedScript taggedScript = loadTaggedScript(script, sourceURL,
|
|
|
|
_performanceLogger,
|
|
|
|
_randomAccessBundle,
|
|
|
|
&loadError);
|
|
|
|
if (!taggedScript.script) {
|
|
|
|
if (onComplete) {
|
|
|
|
onComplete(loadError);
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
2016-11-01 17:14:00 +00:00
|
|
|
return;
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2016-07-19 14:16:32 +00:00
|
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
[self executeBlockOnJavaScriptQueue:^{
|
|
|
|
RCTProfileEndFlowEvent();
|
2016-07-11 20:14:21 +00:00
|
|
|
if (!self.isValid) {
|
2015-04-10 14:28:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
if (taggedScript.tag == facebook::react::ScriptTag::RAMBundle) {
|
2016-07-12 12:13:30 +00:00
|
|
|
registerNativeRequire(self.context.context, self);
|
2016-07-11 22:43:02 +00:00
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
NSError *error = executeApplicationScript(taggedScript, sourceURL,
|
|
|
|
self->_performanceLogger,
|
2016-07-12 12:13:30 +00:00
|
|
|
self->_context.context.JSGlobalContextRef);
|
2015-04-19 19:55:46 +00:00
|
|
|
if (onComplete) {
|
|
|
|
onComplete(error);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
static TaggedScript loadTaggedScript(NSData *script,
|
|
|
|
NSURL *sourceURL,
|
|
|
|
RCTPerformanceLogger *performanceLogger,
|
|
|
|
RandomAccessBundleData &randomAccessBundle,
|
|
|
|
NSError **error)
|
2016-07-12 12:13:30 +00:00
|
|
|
{
|
2016-07-19 14:16:32 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil);
|
|
|
|
|
Simplifying Struct definition.
Summary:
Since we are reading from a file, we should make sure this struct is packed, just in case we change it down the line and the compiler decides it might want to introduce padding, we're now protected against that.
There was also a discussion about the fact that people might use `ptr += sizeof(BundleHeader)` as an idiom in their code, which would currently be incorrect, if padding was introduced at the end of the file. Actually, it remains incorrect to do that now, because a RAM bundle header is a different size to a BC Bundle header. If people are properly testing their code, they should spot this pretty quickly, because it will always be an incorrect thing to do with a RAM bundle, so this isn't as bad as previously thought: where the code only succeeds when the compiler deigns to not pad the struct at the end.
This diff also cleans up how headers are initialised. `BundleHeader` has a constructor that explicitly zero-initialises it so we can rely on the default initializer to do the right thing now.
Reviewed By: mhorowitz
Differential Revision: D4572032
fbshipit-source-id: 7dc50cfa9438dfdfb9f842dc39d8f15334813c63
2017-02-20 12:28:32 +00:00
|
|
|
facebook::react::BundleHeader header;
|
2016-11-11 13:21:37 +00:00
|
|
|
[script getBytes:&header length:sizeof(header)];
|
2016-11-23 14:31:43 +00:00
|
|
|
facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header);
|
2016-11-01 17:14:00 +00:00
|
|
|
|
|
|
|
NSData *loadedScript = NULL;
|
|
|
|
switch (tag) {
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::RAMBundle:
|
2016-11-01 17:14:00 +00:00
|
|
|
[performanceLogger markStartForTag:RCTPLRAMBundleLoad];
|
|
|
|
|
|
|
|
loadedScript = loadRAMBundle(sourceURL, error, randomAccessBundle);
|
|
|
|
|
|
|
|
[performanceLogger markStopForTag:RCTPLRAMBundleLoad];
|
|
|
|
[performanceLogger setValue:loadedScript.length forTag:RCTPLRAMStartupCodeSize];
|
|
|
|
break;
|
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::BCBundle:
|
2016-11-01 17:14:00 +00:00
|
|
|
loadedScript = script;
|
|
|
|
break;
|
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::String: {
|
2016-11-01 17:14:00 +00:00
|
|
|
NSMutableData *nullTerminatedScript = [NSMutableData dataWithData:script];
|
|
|
|
[nullTerminatedScript appendBytes:"" length:1];
|
|
|
|
loadedScript = nullTerminatedScript;
|
|
|
|
}
|
2016-07-12 12:13:30 +00:00
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
|
2016-11-01 17:14:00 +00:00
|
|
|
return { .tag = tag, .script = loadedScript };
|
2016-07-12 12:13:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor)
|
|
|
|
{
|
|
|
|
__weak RCTJSCExecutor *weakExecutor = executor;
|
|
|
|
context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; };
|
|
|
|
}
|
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
static NSError *executeApplicationScript(TaggedScript taggedScript,
|
|
|
|
NSURL *sourceURL,
|
|
|
|
RCTPerformanceLogger *performanceLogger,
|
|
|
|
JSGlobalContextRef ctx)
|
2016-07-11 20:14:23 +00:00
|
|
|
{
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{
|
2016-11-01 17:14:00 +00:00
|
|
|
@"url": sourceURL.absoluteString, @"size": @(taggedScript.script.length)
|
2016-09-05 18:11:37 +00:00
|
|
|
}));
|
2016-11-01 17:14:00 +00:00
|
|
|
|
2016-07-12 12:13:30 +00:00
|
|
|
[performanceLogger markStartForTag:RCTPLScriptExecution];
|
2016-07-11 20:14:23 +00:00
|
|
|
JSValueRef jsError = NULL;
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef bundleURL = JSC_JSStringCreateWithUTF8CString(ctx, sourceURL.absoluteString.UTF8String);
|
2016-11-01 17:14:00 +00:00
|
|
|
|
|
|
|
switch (taggedScript.tag) {
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::RAMBundle:
|
2016-11-01 17:14:00 +00:00
|
|
|
/* fallthrough */
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::String: {
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef execJSString = JSC_JSStringCreateWithUTF8CString(ctx, (const char *)taggedScript.script.bytes);
|
|
|
|
JSC_JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
|
|
|
|
JSC_JSStringRelease(ctx, execJSString);
|
2016-11-01 17:14:00 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-11-23 14:31:43 +00:00
|
|
|
case facebook::react::ScriptTag::BCBundle: {
|
2016-11-01 17:14:00 +00:00
|
|
|
file_ptr source(fopen(sourceURL.path.UTF8String, "r"), fclose);
|
|
|
|
int sourceFD = fileno(source.get());
|
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
JSC_JSEvaluateBytecodeBundle(ctx, NULL, sourceFD, bundleURL, &jsError);
|
2016-11-01 17:14:00 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
JSC_JSStringRelease(ctx, bundleURL);
|
2016-07-12 12:13:30 +00:00
|
|
|
[performanceLogger markStopForTag:RCTPLScriptExecution];
|
2016-09-16 13:14:14 +00:00
|
|
|
|
2016-11-01 17:14:00 +00:00
|
|
|
NSError *error = jsError
|
2016-11-23 19:33:44 +00:00
|
|
|
? RCTNSErrorFromJSErrorRef(jsError, ctx)
|
2016-11-01 17:14:00 +00:00
|
|
|
: nil;
|
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(0, @"js_call");
|
2016-07-19 14:16:32 +00:00
|
|
|
return error;
|
2016-07-11 20:14:23 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
|
|
|
{
|
2015-05-28 20:16:06 +00:00
|
|
|
if ([NSThread currentThread] != _javaScriptThread) {
|
|
|
|
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
|
|
|
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
|
|
|
|
} else {
|
|
|
|
block();
|
|
|
|
}
|
2015-04-28 15:02:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
|
|
|
{
|
|
|
|
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
|
|
|
onThread:_javaScriptThread
|
|
|
|
withObject:block
|
|
|
|
waitUntilDone:NO];
|
2015-04-26 02:18:39 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)injectJSONText:(NSString *)script
|
|
|
|
asGlobalObjectNamed:(NSString *)objectName
|
|
|
|
callback:(RCTJavaScriptCompleteBlock)onComplete
|
|
|
|
{
|
2015-04-21 12:26:51 +00:00
|
|
|
if (RCT_DEBUG) {
|
|
|
|
RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-12-16 10:49:27 +00:00
|
|
|
__weak RCTJSCExecutor *weakSelf = self;
|
2016-07-19 14:16:32 +00:00
|
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
[self executeBlockOnJavaScriptQueue:^{
|
|
|
|
RCTProfileEndFlowEvent();
|
|
|
|
|
2015-12-16 10:49:27 +00:00
|
|
|
RCTJSCExecutor *strongSelf = weakSelf;
|
2015-04-10 14:28:10 +00:00
|
|
|
if (!strongSelf || !strongSelf.isValid) {
|
|
|
|
return;
|
|
|
|
}
|
2016-05-31 19:50:48 +00:00
|
|
|
|
2016-07-19 14:16:32 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"injectJSONText", @{@"objectName": objectName});
|
2016-07-07 23:36:53 +00:00
|
|
|
JSGlobalContextRef ctx = strongSelf->_context.context.JSGlobalContextRef;
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef execJSString = JSC_JSStringCreateWithCFString(ctx, (__bridge CFStringRef)script);
|
|
|
|
JSValueRef valueToInject = JSC_JSValueMakeFromJSONString(ctx, execJSString);
|
|
|
|
JSC_JSStringRelease(ctx, execJSString);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2016-07-19 14:16:32 +00:00
|
|
|
NSError *error;
|
2015-02-20 04:10:52 +00:00
|
|
|
if (!valueToInject) {
|
2016-07-19 14:16:32 +00:00
|
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script];
|
|
|
|
error = [NSError errorWithDomain:RCTErrorDomain code:2 userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
|
|
|
|
RCTLogError(@"%@", errorMessage);
|
|
|
|
} else {
|
2016-11-23 19:33:44 +00:00
|
|
|
JSObjectRef globalObject = JSC_JSContextGetGlobalObject(ctx);
|
|
|
|
JSStringRef JSName = JSC_JSStringCreateWithCFString(ctx, (__bridge CFStringRef)objectName);
|
2016-07-19 14:16:32 +00:00
|
|
|
JSValueRef jsError = NULL;
|
2016-11-23 19:33:44 +00:00
|
|
|
JSC_JSObjectSetProperty(ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, &jsError);
|
|
|
|
JSC_JSStringRelease(ctx, JSName);
|
2016-07-19 14:16:32 +00:00
|
|
|
|
|
|
|
if (jsError) {
|
2016-11-23 19:33:44 +00:00
|
|
|
error = RCTNSErrorFromJSErrorRef(jsError, ctx);
|
2015-04-19 19:55:46 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(0, @"js_call,json_call");
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
if (onComplete) {
|
2016-07-19 14:16:32 +00:00
|
|
|
onComplete(error);
|
2015-04-19 19:55:46 +00:00
|
|
|
}
|
2016-07-19 14:16:32 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:50:48 +00:00
|
|
|
static bool readRandomAccessModule(const RandomAccessBundleData &bundleData, size_t offset, size_t size, char *data)
|
2016-03-13 18:13:39 +00:00
|
|
|
{
|
2016-05-16 11:40:53 +00:00
|
|
|
return fseek(bundleData.bundle.get(), offset + bundleData.baseOffset, SEEK_SET) == 0 &&
|
|
|
|
fread(data, 1, size, bundleData.bundle.get()) == size;
|
2016-03-13 18:13:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
static void executeRandomAccessModule(RCTJSCExecutor *executor, uint32_t moduleID, size_t offset, size_t size)
|
2016-03-13 18:13:39 +00:00
|
|
|
{
|
2016-05-23 14:18:55 +00:00
|
|
|
auto data = std::make_unique<char[]>(size);
|
2016-05-16 11:40:53 +00:00
|
|
|
if (!readRandomAccessModule(executor->_randomAccessBundle, offset, size, data.get())) {
|
|
|
|
RCTFatal(RCTErrorWithMessage(@"Error loading RAM module"));
|
|
|
|
return;
|
|
|
|
}
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-05-23 14:18:55 +00:00
|
|
|
char url[14]; // 10 = maximum decimal digits in a 32bit unsigned int + ".js" + null byte
|
2016-05-16 11:40:53 +00:00
|
|
|
sprintf(url, "%" PRIu32 ".js", moduleID);
|
2016-03-13 18:13:40 +00:00
|
|
|
|
2016-07-07 23:36:53 +00:00
|
|
|
JSGlobalContextRef ctx = executor->_context.context.JSGlobalContextRef;
|
2016-11-23 19:33:44 +00:00
|
|
|
JSStringRef code = JSC_JSStringCreateWithUTF8CString(ctx, data.get());
|
|
|
|
JSValueRef jsError = NULL;
|
|
|
|
JSStringRef sourceURL = JSC_JSStringCreateWithUTF8CString(ctx, url);
|
|
|
|
JSValueRef result = JSC_JSEvaluateScript(ctx, code, NULL, sourceURL, 0, &jsError);
|
2016-03-17 17:34:46 +00:00
|
|
|
|
2016-11-23 19:33:44 +00:00
|
|
|
JSC_JSStringRelease(ctx, code);
|
|
|
|
JSC_JSStringRelease(ctx, sourceURL);
|
2016-03-17 17:34:46 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
if (!result) {
|
2016-11-23 19:33:44 +00:00
|
|
|
NSError *error = RCTNSErrorFromJSErrorRef(jsError, ctx);
|
2016-05-16 11:40:53 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2016-09-20 13:27:32 +00:00
|
|
|
RCTFatal(error);
|
2016-05-16 11:40:53 +00:00
|
|
|
[executor invalidate];
|
|
|
|
});
|
|
|
|
}
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
- (void)_nativeRequire:(NSNumber *)moduleID
|
2016-03-13 18:13:39 +00:00
|
|
|
{
|
2016-07-11 22:43:02 +00:00
|
|
|
if (!moduleID) {
|
|
|
|
return;
|
|
|
|
}
|
2016-04-01 18:08:19 +00:00
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
[_performanceLogger addValue:1 forTag:RCTPLRAMNativeRequiresCount];
|
|
|
|
[_performanceLogger appendStartForTag:RCTPLRAMNativeRequires];
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, ([@"nativeRequire_" stringByAppendingFormat:@"%@", moduleID]), nil);
|
2016-04-01 18:08:19 +00:00
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
const uint32_t ID = [moduleID unsignedIntValue];
|
2016-04-29 17:15:26 +00:00
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
if (ID < _randomAccessBundle.numTableEntries) {
|
|
|
|
ModuleData *moduleData = &_randomAccessBundle.table[ID];
|
|
|
|
const uint32_t size = NSSwapLittleIntToHost(moduleData->size);
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
// sparse entry in the table -- module does not exist or is contained in the startup section
|
|
|
|
if (size == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2016-04-01 14:24:40 +00:00
|
|
|
|
2016-07-11 22:43:02 +00:00
|
|
|
executeRandomAccessModule(self, ID, NSSwapLittleIntToHost(moduleData->offset), size);
|
|
|
|
}
|
2016-07-07 23:36:48 +00:00
|
|
|
|
2016-09-05 18:11:37 +00:00
|
|
|
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call");
|
2016-07-11 22:43:02 +00:00
|
|
|
[_performanceLogger appendStopForTag:RCTPLRAMNativeRequires];
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:50:48 +00:00
|
|
|
static RandomAccessBundleStartupCode readRAMBundle(file_ptr bundle, RandomAccessBundleData &randomAccessBundle)
|
2016-03-17 17:34:46 +00:00
|
|
|
{
|
2016-05-16 11:40:53 +00:00
|
|
|
// read in magic header, number of entries, and length of the startup section
|
|
|
|
uint32_t header[3];
|
|
|
|
if (fread(&header, 1, sizeof(header), bundle.get()) != sizeof(header)) {
|
|
|
|
return RandomAccessBundleStartupCode::empty();
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
const size_t numTableEntries = NSSwapLittleIntToHost(header[1]);
|
|
|
|
const size_t startupCodeSize = NSSwapLittleIntToHost(header[2]);
|
|
|
|
const size_t tableSize = numTableEntries * sizeof(ModuleData);
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
// allocate memory for meta data and lookup table. malloc instead of new to avoid constructor calls
|
2016-05-23 14:18:55 +00:00
|
|
|
auto table = std::make_unique<ModuleData[]>(numTableEntries);
|
2016-05-16 11:40:53 +00:00
|
|
|
if (!table) {
|
|
|
|
return RandomAccessBundleStartupCode::empty();
|
|
|
|
}
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
// read the lookup table from the file
|
|
|
|
if (fread(table.get(), 1, tableSize, bundle.get()) != tableSize) {
|
|
|
|
return RandomAccessBundleStartupCode::empty();
|
|
|
|
}
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
// read the startup code
|
|
|
|
memory_ptr code(malloc(startupCodeSize), free);
|
|
|
|
if (!code || fread(code.get(), 1, startupCodeSize, bundle.get()) != startupCodeSize) {
|
|
|
|
return RandomAccessBundleStartupCode::empty();
|
2016-03-17 17:34:46 +00:00
|
|
|
}
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
randomAccessBundle.bundle = std::move(bundle);
|
|
|
|
randomAccessBundle.baseOffset = sizeof(header) + tableSize;
|
|
|
|
randomAccessBundle.numTableEntries = numTableEntries;
|
|
|
|
randomAccessBundle.table = std::move(table);
|
2016-03-13 18:13:40 +00:00
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
return {std::move(code), startupCodeSize};
|
|
|
|
}
|
2016-03-13 18:13:39 +00:00
|
|
|
|
2016-07-11 20:14:17 +00:00
|
|
|
static NSData *loadRAMBundle(NSURL *sourceURL, NSError **error, RandomAccessBundleData &randomAccessBundle)
|
2016-05-16 11:40:53 +00:00
|
|
|
{
|
|
|
|
file_ptr bundle(fopen(sourceURL.path.UTF8String, "r"), fclose);
|
|
|
|
if (!bundle) {
|
2016-03-21 10:20:49 +00:00
|
|
|
if (error) {
|
2016-05-16 11:40:53 +00:00
|
|
|
*error = RCTErrorWithMessage([NSString stringWithFormat:@"Bundle %@ cannot be opened: %d", sourceURL.path, errno]);
|
2016-03-21 10:20:49 +00:00
|
|
|
}
|
2016-03-17 17:34:46 +00:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-07-11 20:14:17 +00:00
|
|
|
auto startupCode = readRAMBundle(std::move(bundle), randomAccessBundle);
|
2016-05-16 11:40:53 +00:00
|
|
|
if (startupCode.isEmpty()) {
|
2016-03-21 10:20:49 +00:00
|
|
|
if (error) {
|
|
|
|
*error = RCTErrorWithMessage(@"Error loading RAM Bundle");
|
|
|
|
}
|
2016-03-17 17:34:46 +00:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2016-05-16 11:40:53 +00:00
|
|
|
return [NSData dataWithBytesNoCopy:startupCode.code.release() length:startupCode.size freeWhenDone:YES];
|
2016-03-13 18:13:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-26 10:43:05 +00:00
|
|
|
- (JSContext *)jsContext
|
|
|
|
{
|
|
|
|
return [self context].context;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@end
|
2016-07-08 19:19:27 +00:00
|
|
|
|
|
|
|
@implementation RCTJSContextProvider
|
|
|
|
{
|
|
|
|
dispatch_semaphore_t _semaphore;
|
|
|
|
NSThread *_javaScriptThread;
|
|
|
|
JSContext *_context;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithUseCustomJSCLibrary:(BOOL)useCustomJSCLibrary
|
|
|
|
{
|
|
|
|
if (self = [super init]) {
|
|
|
|
_semaphore = dispatch_semaphore_create(0);
|
|
|
|
_useCustomJSCLibrary = useCustomJSCLibrary;
|
|
|
|
_javaScriptThread = newJavaScriptThread();
|
|
|
|
[self performSelector:@selector(_createContext) onThread:_javaScriptThread withObject:nil waitUntilDone:NO];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_createContext
|
|
|
|
{
|
2016-11-23 19:33:44 +00:00
|
|
|
if (_useCustomJSCLibrary) {
|
2017-01-25 22:28:02 +00:00
|
|
|
JSC_configureJSCForIOS(true, "{}");
|
2016-11-23 19:33:44 +00:00
|
|
|
}
|
|
|
|
JSGlobalContextRef ctx = JSC_JSGlobalContextCreateInGroup(_useCustomJSCLibrary, nullptr, nullptr);
|
|
|
|
_context = [JSC_JSContext(ctx) contextWithJSGlobalContextRef:ctx];
|
2016-07-08 19:19:27 +00:00
|
|
|
installBasicSynchronousHooksOnContext(_context);
|
|
|
|
dispatch_semaphore_signal(_semaphore);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (RCTJSContextData)data
|
|
|
|
{
|
|
|
|
// Be sure this method is only called once, otherwise it will hang here forever:
|
|
|
|
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
|
|
|
|
return {
|
|
|
|
.useCustomJSCLibrary = _useCustomJSCLibrary,
|
|
|
|
.javaScriptThread = _javaScriptThread,
|
|
|
|
.context = _context,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-02-17 10:29:56 +00:00
|
|
|
|
|
|
|
- (RCTJSCExecutor *)createExecutorWithContext:(JSContext **)JSContext
|
|
|
|
{
|
|
|
|
const RCTJSContextData data = self.data;
|
|
|
|
if (JSContext) {
|
|
|
|
*JSContext = data.context;
|
|
|
|
}
|
|
|
|
return [[RCTJSCExecutor alloc] initWithJSContextData:data];
|
|
|
|
}
|
|
|
|
|
2016-07-08 19:19:27 +00:00
|
|
|
@end
|