mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-29 12:45:37 +08:00
Fix edge cases and add tests for +[RCTConvert NSURL:]
This commit is contained in:
@@ -49,11 +49,11 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||||||
});
|
});
|
||||||
NSNumber *number = [formatter numberFromString:json];
|
NSNumber *number = [formatter numberFromString:json];
|
||||||
if (!number) {
|
if (!number) {
|
||||||
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
|
RCTLogConvertError(json, "a number");
|
||||||
}
|
}
|
||||||
return number;
|
return number;
|
||||||
} else if (json && json != [NSNull null]) {
|
} else if (json && json != [NSNull null]) {
|
||||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
|
RCTLogConvertError(json, "a number");
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -66,30 +66,38 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||||||
|
|
||||||
+ (NSURL *)NSURL:(id)json
|
+ (NSURL *)NSURL:(id)json
|
||||||
{
|
{
|
||||||
if (!json || json == (id)kCFNull) {
|
NSString *path = [self NSString:json];
|
||||||
|
if (!path.length) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![json isKindOfClass:[NSString class]]) {
|
@try { // NSURL has a history of crashing with bad input, so let's be safe
|
||||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *path = json;
|
NSURL *URL = [NSURL URLWithString:path];
|
||||||
if ([path isAbsolutePath])
|
if (URL.scheme) { // Was a well-formed absolute URL
|
||||||
{
|
return URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it has a scheme
|
||||||
|
if ([path rangeOfString:@"[a-zA-Z][a-zA-Z._-]+:" options:NSRegularExpressionSearch].location == 0) {
|
||||||
|
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
URL = [NSURL URLWithString:path];
|
||||||
|
if (URL) {
|
||||||
|
return URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that it's a local path
|
||||||
|
path = [path stringByRemovingPercentEncoding];
|
||||||
|
if (![path isAbsolutePath]) {
|
||||||
|
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path];
|
||||||
|
}
|
||||||
return [NSURL fileURLWithPath:path];
|
return [NSURL fileURLWithPath:path];
|
||||||
}
|
}
|
||||||
else if ([path length])
|
@catch (__unused NSException *e) {
|
||||||
{
|
RCTLogConvertError(json, "a valid URL");
|
||||||
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
return nil;
|
||||||
if ([URL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) {
|
|
||||||
RCTLogWarn(@"The file '%@' does not exist", URL);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return URL;
|
|
||||||
}
|
}
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||||
@@ -112,11 +120,12 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||||||
});
|
});
|
||||||
NSDate *date = [formatter dateFromString:json];
|
NSDate *date = [formatter dateFromString:json];
|
||||||
if (!date) {
|
if (!date) {
|
||||||
RCTLogError(@"JSON String '%@' could not be interpreted as a date. Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
RCTLogError(@"JSON String '%@' could not be interpreted as a date. "
|
||||||
|
"Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
||||||
}
|
}
|
||||||
return date;
|
return date;
|
||||||
} else if (json && json != [NSNull null]) {
|
} else if (json && json != [NSNull null]) {
|
||||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json classForCoder]);
|
RCTLogConvertError(json, "a date");
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,47 +10,15 @@
|
|||||||
#import "RCTJavaScriptLoader.h"
|
#import "RCTJavaScriptLoader.h"
|
||||||
|
|
||||||
#import "RCTBridge.h"
|
#import "RCTBridge.h"
|
||||||
#import "RCTInvalidating.h"
|
#import "RCTConvert.h"
|
||||||
#import "RCTLog.h"
|
|
||||||
#import "RCTRedBox.h"
|
|
||||||
#import "RCTSourceCode.h"
|
#import "RCTSourceCode.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
|
|
||||||
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
|
|
||||||
|
|
||||||
#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 RCTJavaScriptLoader
|
@implementation RCTJavaScriptLoader
|
||||||
{
|
{
|
||||||
__weak RCTBridge *_bridge;
|
__weak RCTBridge *_bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* `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
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||||
{
|
{
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
@@ -61,92 +29,86 @@
|
|||||||
|
|
||||||
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
|
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
|
||||||
{
|
{
|
||||||
if (scriptURL == nil) {
|
// Sanitize the script URL
|
||||||
|
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
|
||||||
|
|
||||||
|
if (!scriptURL ||
|
||||||
|
([scriptURL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:scriptURL.path])) {
|
||||||
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
|
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
|
||||||
NSLocalizedDescriptionKey: @"No script URL provided"
|
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
|
||||||
}];
|
}];
|
||||||
onComplete(error);
|
onComplete(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([scriptURL isFileURL]) {
|
|
||||||
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
|
|
||||||
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
|
|
||||||
|
|
||||||
if (![localPath hasPrefix:bundlePath]) {
|
|
||||||
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
|
|
||||||
scriptURL = [NSURL fileURLWithPath:absolutePath];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
||||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
|
||||||
// Handle general request errors
|
// Handle general request errors
|
||||||
if (error) {
|
if (error) {
|
||||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||||
NSDictionary *userInfo = @{
|
NSDictionary *userInfo = @{
|
||||||
NSLocalizedDescriptionKey: desc,
|
NSLocalizedDescriptionKey: desc,
|
||||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||||
NSUnderlyingErrorKey: error,
|
NSUnderlyingErrorKey: error,
|
||||||
};
|
};
|
||||||
error = [NSError errorWithDomain:@"JSServer"
|
error = [NSError errorWithDomain:@"JSServer"
|
||||||
code:error.code
|
code:error.code
|
||||||
userInfo:userInfo];
|
userInfo:userInfo];
|
||||||
}
|
}
|
||||||
onComplete(error);
|
onComplete(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse response as text
|
// Parse response as text
|
||||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||||
if (response.textEncodingName != nil) {
|
if (response.textEncodingName != nil) {
|
||||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||||
|
|
||||||
// Handle HTTP errors
|
// Handle HTTP errors
|
||||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||||
NSDictionary *userInfo;
|
NSDictionary *userInfo;
|
||||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||||
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
|
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
|
||||||
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
||||||
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
||||||
for (NSDictionary *err in errorDetails[@"errors"]) {
|
for (NSDictionary *err in errorDetails[@"errors"]) {
|
||||||
[fakeStack addObject: @{
|
[fakeStack addObject: @{
|
||||||
@"methodName": err[@"description"] ?: @"",
|
@"methodName": err[@"description"] ?: @"",
|
||||||
@"file": err[@"filename"] ?: @"",
|
@"file": err[@"filename"] ?: @"",
|
||||||
@"lineNumber": err[@"lineNumber"] ?: @0
|
@"lineNumber": err[@"lineNumber"] ?: @0
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
userInfo = @{
|
userInfo = @{
|
||||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||||
@"stack": fakeStack,
|
@"stack": fakeStack,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||||
}
|
}
|
||||||
error = [NSError errorWithDomain:@"JSServer"
|
error = [NSError errorWithDomain:@"JSServer"
|
||||||
code:[(NSHTTPURLResponse *)response statusCode]
|
code:[(NSHTTPURLResponse *)response statusCode]
|
||||||
userInfo:userInfo];
|
userInfo:userInfo];
|
||||||
|
|
||||||
onComplete(error);
|
onComplete(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||||
sourceCodeModule.scriptURL = scriptURL;
|
sourceCodeModule.scriptURL = scriptURL;
|
||||||
sourceCodeModule.scriptText = rawText;
|
sourceCodeModule.scriptText = rawText;
|
||||||
|
|
||||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
onComplete(scriptError);
|
onComplete(scriptError);
|
||||||
});
|
});
|
||||||
}];
|
}];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[task resume];
|
[task resume];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,6 @@
|
|||||||
#import "RCTWebViewExecutor.h"
|
#import "RCTWebViewExecutor.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* HACK(t6568049) This should be removed soon, hiding to prevent people from
|
|
||||||
* relying on it
|
|
||||||
*/
|
|
||||||
@interface RCTBridge (RCTRootView)
|
|
||||||
|
|
||||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface RCTUIManager (RCTRootView)
|
@interface RCTUIManager (RCTRootView)
|
||||||
|
|
||||||
- (NSNumber *)allocateRootTag;
|
- (NSNumber *)allocateRootTag;
|
||||||
|
|||||||
Reference in New Issue
Block a user