react-native/ReactKit/Base/RCTJavaScriptAppEngine.m
Christopher Chedeau ccd8f184af 2015-02-02 updates
- Removed special-case treatment of RCTTiming and RCTUIManager modules | Nick Lockwood
2015-02-03 16:12:07 -08:00

159 lines
6.1 KiB
Objective-C
Executable File

// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTJavaScriptAppEngine.h"
#import "RCTBridge.h"
#import "RCTInvalidating.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTUtils.h"
#define JS_SERVER_NOT_AVAILABLE @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root"
#define CACHE_DIR @"RCTJSBundleCache"
#pragma mark - Application Engine
/**
* TODO:
* - Add window resize rotation events matching the DOM API.
* - Device pixel ration hooks.
* - Source maps.
*/
@implementation RCTJavaScriptAppEngine
{
NSDictionary *_loadedResource;
__weak RCTBridge *_bridge;
}
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
/**
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
* engine in its own dedicated thread.
*
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
* additional GCD dispatch per frame and likely makes it so that other UIThread
* operations don't delay the dispatch (so we can begin working in JS much
* faster.) Event handling must still be sent via a GCD dispatch, of course.
*
* We must add the display link to two runloops in order to get setTimeouts to
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
* TODO: We can invent a `requestAnimationFrame` and
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
* an animation.
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
*
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertMainThread();
if ((self = [super init])) {
_bridge = bridge;
}
return self;
}
#pragma mark - Module and script loading
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL
{
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
NSString *filePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
}
/**
* TODO: All loading of script via network or disk should be done in a separate
* thread, not the JS thread, and not the main UI thread (launch blocker).
*/
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
NSString *cachedFilePath;
if (useCache) {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
cachedFilePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachedFilePath]) {
NSError *error;
NSString *rawText = [NSString stringWithContentsOfFile:cachedFilePath encoding:NSUTF8StringEncoding error:&error];
if (rawText.length == 0 || error != nil) {
if (onComplete) onComplete(error);
} else {
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
}
return;
}
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:moduleURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: JS_SERVER_NOT_AVAILABLE,
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
if (onComplete) onComplete(error);
return;
}
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName != nil) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (cfEncoding != kCFStringEncodingInvalidId) {
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
userInfo = @{
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
@"stack": @[@{
@"methodName": errorDetails[@"description"] ?: @"",
@"file": errorDetails[@"filename"] ?: @"",
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
}]
};
} else {
userInfo = @{NSLocalizedDescriptionKey: rawText};
}
NSError *serverError = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
if (onComplete) onComplete(serverError);
return;
}
if (useCache) {
[[NSFileManager defaultManager] createDirectoryAtPath:cachedFilePath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:NULL];
[rawText writeToFile:cachedFilePath atomically:YES encoding:encoding error:NULL];
}
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
}];
[task resume];
}
- (void)_enqueueLoadBundleResource:(NSString *)rawText url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
[_bridge enqueueApplicationScript:rawText url:url onComplete:onComplete];
}
@end