From a10a2185a41a5c6ff423da72cac6120af62b4516 Mon Sep 17 00:00:00 2001 From: Adam Terlson Date: Thu, 24 Sep 2015 00:19:38 +0200 Subject: [PATCH 0001/2702] Add support for repeatInterval to scheduleLocalNotification --- .../PushNotificationIOS.js | 26 +++++++++++++++++++ .../RCTPushNotificationManager.m | 1 + 2 files changed, 27 insertions(+) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index f22beb36501f..06ef2482a6a2 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -99,8 +99,34 @@ class PushNotificationIOS { * - `soundName` : The sound played when the notification is fired (optional). * - `category` : The category of this notification, required for actionable notifications (optional). * - `userInfo` : An optional object containing additional notification data. + * - `repeatInterval` : The interval to repeat as a string. Possible values: `minute`, `hour`, `day`, `week`, `month`, `year`. */ static scheduleLocalNotification(details: Object) { + if (details) { + switch (details.repeatInterval) { + case 'year': + details.repeatInterval = 4; // NSCalendarUnit.CalendarUnitYear + break; + case 'month': + details.repeatInterval = 8; // NSCalendarUnit.CalendarUnitMonth + break; + case 'week': + details.repeatInterval = 8192; // NSCalendarUnit.CalendarUnitWeekOfYear + break; + case 'day': + details.repeatInterval = 16; // NSCalendarUnit.CalendarUnitDay + break; + case 'hour': + details.repeatInterval = 32; // NSCalendarUnit.CalendarUnitHour + break; + case 'minute': + details.repeatInterval = 64; // NSCalendarUnit.CalendarUnitMinute + break; + default: + details.repeatInterval = 0; + } + } + RCTPushNotificationManager.scheduleLocalNotification(details); } diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 5dd5b4774464..7cd9a9c57786 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -40,6 +40,7 @@ + (UILocalNotification *)UILocalNotification:(id)json notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; notification.category = [RCTConvert NSString:details[@"category"]]; + notification.repeatInterval = [RCTConvert NSInteger:details[@"repeatInterval"]]; return notification; } From bce0bd61445c4e9b59bfa6caee11ab3d3dad08f5 Mon Sep 17 00:00:00 2001 From: Adam Terlson Date: Sun, 13 Mar 2016 22:10:08 +0100 Subject: [PATCH 0002/2702] Updates per @grabbou --- .../PushNotificationIOS.js | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 06ef2482a6a2..611db652c7f4 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -23,6 +23,15 @@ var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; var DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived'; +const REPEAT_INTERVAL = { + year: 4, + month: 8, + week: 8192, + day: 16, + hour: 32, + minute: 64 +}; + /** * Handle push notifications for your app, including permission handling and * icon badge number. @@ -103,28 +112,7 @@ class PushNotificationIOS { */ static scheduleLocalNotification(details: Object) { if (details) { - switch (details.repeatInterval) { - case 'year': - details.repeatInterval = 4; // NSCalendarUnit.CalendarUnitYear - break; - case 'month': - details.repeatInterval = 8; // NSCalendarUnit.CalendarUnitMonth - break; - case 'week': - details.repeatInterval = 8192; // NSCalendarUnit.CalendarUnitWeekOfYear - break; - case 'day': - details.repeatInterval = 16; // NSCalendarUnit.CalendarUnitDay - break; - case 'hour': - details.repeatInterval = 32; // NSCalendarUnit.CalendarUnitHour - break; - case 'minute': - details.repeatInterval = 64; // NSCalendarUnit.CalendarUnitMinute - break; - default: - details.repeatInterval = 0; - } + details.repeatInterval = REPEAT_INTERVAL[details.repeatInterval] || 0; } RCTPushNotificationManager.scheduleLocalNotification(details); From a84bf89924ce00b156a8416b0512f87659faa730 Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Sun, 20 Sep 2015 19:28:36 -0400 Subject: [PATCH 0003/2702] Fix typo changed `... for React Native and gis therefore enabled by default.` to `... for React Native and is therefore enabled by default.` --- docs/KnownIssues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index b614485b4764..53a65e4e201e 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -76,7 +76,7 @@ We don't support shadows on Android currently. These are notoriously hard to imp ### Layout-only nodes on Android -An optimization feature of the Android version of React Native is for views which only contribute to the layout to not have a native view, only their layout properties are propagated to their children views. This optimization is to provide stability in deep view hierarchies for React Native and gis therefore enabled by default. Should you depend on a view being present or internal tests incorrectly detect a view is layout only it will be necessary to turn off this behavior. To do this, set `collapsable` to false as in this example: +An optimization feature of the Android version of React Native is for views which only contribute to the layout to not have a native view, only their layout properties are propagated to their children views. This optimization is to provide stability in deep view hierarchies for React Native and is therefore enabled by default. Should you depend on a view being present or internal tests incorrectly detect a view is layout only it will be necessary to turn off this behavior. To do this, set `collapsable` to false as in this example: ``` ... From 2395474b88d193f18cd932f4a012ddbde00e582d Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Sun, 20 Sep 2015 19:30:51 -0400 Subject: [PATCH 0004/2702] Fix Grammar changed `To make it simpler for to access your new functionality ...` to `To make it simpler to access your new functionality ...` --- docs/NativeModulesAndroid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index 64a913c3a0b2..e6fabbb82441 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -119,7 +119,7 @@ mReactInstanceManager = ReactInstanceManager.builder() .build(); ``` -To make it simpler for to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of `NativeModules` each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality. +To make it simpler to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of `NativeModules` each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality. ```java /** From ef1f2f18e936e466c591cbac43a81a19c0ab03b3 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Tue, 22 Sep 2015 10:00:19 -0700 Subject: [PATCH 0005/2702] Add note about PNG files to Image static resources --- docs/Image.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Image.md b/docs/Image.md index 86101eb332cb..6d438e81b90d 100644 --- a/docs/Image.md +++ b/docs/Image.md @@ -27,6 +27,10 @@ When your entire codebase respects this convention, you're able to do interestin *This process is currently being improved, a much better workflow will be available shortly.* +> **NOTE**: PNG images are required when loading with `require('image!my-icon')` +> +> At this time, only PNG images are supported in iOS. There is an [issue](https://github.com/facebook/react-native/issues/646) that is currently addressing this bug. In the meantime a quick fix is to rename your files to image.png or to use the `isStatic` flag like: `source={{ uri: 'image', isStatic: true }}`. + ### Adding Static Resources to your Android app Add your images as [bitmap drawables](http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap) to the android project (`/android/app/src/main/res`). To provide different resolutions of your assets, check out [using configuration qualifiers](http://developer.android.com/guide/practices/screens_support.html#qualifiers). Normally, you will want to put your assets in the following directories (create them under `res` if they don't exist): From f5ddf7bcf81f8504adf7f35e07c7b0196ab40f43 Mon Sep 17 00:00:00 2001 From: Eric Jing Cai Date: Tue, 22 Sep 2015 22:22:03 -0400 Subject: [PATCH 0006/2702] updated ReactiveAndroid installation info --- ReactAndroid/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/README.md b/ReactAndroid/README.md index ab8416bea780..60ce2001d49e 100644 --- a/ReactAndroid/README.md +++ b/ReactAndroid/README.md @@ -12,9 +12,9 @@ Assuming you have the [Android SDK](https://developer.android.com/sdk/installing Make sure you have the following installed: -- Android SDK version 22 (compileSdkVersion in [`build.gradle`](build.gradle)) -- SDK build tools version 22.0.1 (buildToolsVersion in [`build.gradle`](build.gradle)) -- Android Support Repository 17 (for Android Support Library) +- Android SDK version 23 (compileSdkVersion in [`build.gradle`](build.gradle)) +- SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](build.gradle)) +- Android Support Repository 20 (for Android Support Library) - Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html)) Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK` defined, or create a `local.properties` file in the root of your `react-native` checkout with the following contents: From f9809a8d9fc915d7ec1a5a1768ecc5a4f71466c9 Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Wed, 23 Sep 2015 17:47:31 +0100 Subject: [PATCH 0007/2702] change native-modules link to work on gh-pages --- docs/NativeComponentsAndroid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md index b7d41551c6dc..81686e3703f9 100644 --- a/docs/NativeComponentsAndroid.md +++ b/docs/NativeComponentsAndroid.md @@ -96,7 +96,7 @@ Setting properties on a view is not handled by automatically calling setter meth ## 5. Register the `ViewManager` -The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](NativeModulesAndroid.md), via the applications package member function `createViewManagers.` +The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](native-modules-android.html), via the applications package member function `createViewManagers.` ```java @Override From 677da35bde7e049fbdf51cb8ec5af9f7501b80a6 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 23 Sep 2015 11:59:44 -0700 Subject: [PATCH 0008/2702] Add gluecode for systrace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Add gluecode to allow use to systrace as the backend of RCTProfile Reviewed By: @jspahrsummers Differential Revision: D2439181 --- React/Base/RCTProfile.h | 28 +++++++ React/Base/RCTProfile.m | 172 ++++++++++++++++++++++++++++++---------- 2 files changed, 159 insertions(+), 41 deletions(-) diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index eb8045fb6d02..c184cae184c8 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -125,6 +125,34 @@ RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *); */ RCT_EXTERN void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *profielData); +/** + * Systrace gluecode + * + * allow to use systrace to back RCTProfile + */ + +typedef struct { + const char *key; + int key_len; + const char *value; + int value_len; +} systrace_arg_t; + +typedef struct { + void (*start)(uint64_t enabledTags, char *buffer, size_t bufferSize); + void (*stop)(void); + + void (*begin_section)(uint64_t tag, const char *name, size_t numArgs, systrace_arg_t *args); + void (*end_section)(uint64_t tag, size_t numArgs, systrace_arg_t *args); + + void (*begin_async_section)(uint64_t tag, const char *name, int cookie, size_t numArgs, systrace_arg_t *args); + void (*end_async_section)(uint64_t tag, const char *name, int cookie, size_t numArgs, systrace_arg_t *args); + + void (*instant_section)(uint64_t tag, const char *name, char scope); +} RCTProfileCallbacks; + +RCT_EXTERN void RCTProfileRegisterCallbacks(RCTProfileCallbacks *); + #else #define RCTProfileBeginFlowEvent() diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index f905c0c8cb89..fc4d85d046c3 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -35,11 +35,11 @@ #pragma mark - Variables -NSDictionary *RCTProfileInfo; -NSUInteger RCTProfileEventID = 0; -NSMutableDictionary *RCTProfileOngoingEvents; -NSTimeInterval RCTProfileStartTime; -NSRecursiveLock *_RCTProfileLock; +static BOOL RCTProfileProfiling; +static NSDictionary *RCTProfileInfo; +static NSMutableDictionary *RCTProfileOngoingEvents; +static NSTimeInterval RCTProfileStartTime; +static NSUInteger RCTProfileEventID = 0; #pragma mark - Macros @@ -56,12 +56,55 @@ } #define RCTProfileLock(...) \ -[_RCTProfileLock lock]; \ +[_RCTProfileLock() lock]; \ __VA_ARGS__ \ -[_RCTProfileLock unlock] +[_RCTProfileLock() unlock] + +#pragma mark - systrace glue code + +static RCTProfileCallbacks *callbacks; +static char *systrace_buffer; + +static systrace_arg_t *RCTProfileSystraceArgsFromNSDictionary(NSDictionary *args) +{ + if (args.count == 0) { + return NULL; + } + + systrace_arg_t *systrace_args = malloc(sizeof(systrace_arg_t) * args.count); + __block size_t i = 0; + [args enumerateKeysAndObjectsUsingBlock:^(id key, id value, __unused BOOL *stop) { + const char *keyc = [key description].UTF8String; + systrace_args[i].key = keyc; + systrace_args[i].key_len = (int)strlen(keyc); + + const char *valuec = RCTJSONStringify(value, nil).UTF8String; + systrace_args[i].value = valuec; + systrace_args[i].value_len = (int)strlen(valuec); + i++; + }]; + return systrace_args; +} + +void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb) +{ + callbacks = cb; +} #pragma mark - Private Helpers +static NSLock *_RCTProfileLock() +{ + static dispatch_once_t token; + static NSLock *lock; + dispatch_once(&token, ^{ + lock = [NSLock new]; + lock.name = @"RCTProfileLock"; + }); + + return lock; +} + static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) { return @((timestamp - RCTProfileStartTime) * 1e6); @@ -215,28 +258,28 @@ void RCTProfileUnhookModules(RCTBridge *bridge) BOOL RCTProfileIsProfiling(void) { - RCTProfileLock( - BOOL profiling = RCTProfileInfo != nil; - ); - return profiling; + return RCTProfileProfiling; } void RCTProfileInit(RCTBridge *bridge) { RCTProfileHookModules(bridge); + RCTProfileProfiling = YES; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _RCTProfileLock = [NSRecursiveLock new]; - }); - RCTProfileLock( - RCTProfileStartTime = CACurrentMediaTime(); - RCTProfileOngoingEvents = [NSMutableDictionary new]; - RCTProfileInfo = @{ - RCTProfileTraceEvents: [NSMutableArray new], - RCTProfileSamples: [NSMutableArray new], - }; - ); + if (callbacks != NULL) { + size_t buffer_size = 1 << 22; + systrace_buffer = calloc(1, buffer_size); + callbacks->start(~((uint64_t)0), systrace_buffer, buffer_size); + } else { + RCTProfileLock( + RCTProfileStartTime = CACurrentMediaTime(); + RCTProfileOngoingEvents = [NSMutableDictionary new]; + RCTProfileInfo = @{ + RCTProfileTraceEvents: [NSMutableArray new], + RCTProfileSamples: [NSMutableArray new], + }; + ); + } [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling object:nil]; @@ -247,16 +290,26 @@ void RCTProfileInit(RCTBridge *bridge) [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:nil]; + RCTProfileProfiling = NO; + RCTProfileLock( - NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); - RCTProfileEventID = 0; - RCTProfileInfo = nil; - RCTProfileOngoingEvents = nil; + RCTProfileUnhookModules(bridge); ); - RCTProfileUnhookModules(bridge); + if (callbacks != NULL) { + callbacks->stop(); + + return @(systrace_buffer); + } else { + RCTProfileLock( + NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); + RCTProfileEventID = 0; + RCTProfileInfo = nil; + RCTProfileOngoingEvents = nil; + ); - return log; + return log; + } } static NSMutableArray *RCTProfileGetThreadEvents(void) @@ -273,6 +326,12 @@ void RCTProfileInit(RCTBridge *bridge) void RCTProfileBeginEvent(uint64_t tag, NSString *name, NSDictionary *args) { CHECK(); + + if (callbacks != NULL) { + callbacks->begin_section(tag, name.UTF8String, args.count, RCTProfileSystraceArgsFromNSDictionary(args)); + return; + } + NSMutableArray *events = RCTProfileGetThreadEvents(); [events addObject:@[ RCTProfileTimestamp(CACurrentMediaTime()), @@ -283,12 +342,17 @@ void RCTProfileBeginEvent(uint64_t tag, NSString *name, NSDictionary *args) } void RCTProfileEndEvent( - __unused uint64_t tag, + uint64_t tag, NSString *category, NSDictionary *args ) { CHECK(); + if (callbacks != NULL) { + callbacks->end_section(tag, args.count, RCTProfileSystraceArgsFromNSDictionary(args)); + return; + } + NSMutableArray *events = RCTProfileGetThreadEvents(); NSArray *event = events.lastObject; [events removeLastObject]; @@ -312,7 +376,7 @@ void RCTProfileEndEvent( } int RCTProfileBeginAsyncEvent( - __unused uint64_t tag, + uint64_t tag, NSString *name, NSDictionary *args ) { @@ -320,25 +384,35 @@ int RCTProfileBeginAsyncEvent( static int eventID = 0; - RCTProfileLock( - RCTProfileOngoingEvents[@(eventID)] = @[ - RCTProfileTimestamp(CACurrentMediaTime()), - name, - RCTNullIfNil(args), - ]; - ); + if (callbacks != NULL) { + callbacks->begin_async_section(tag, name.UTF8String, eventID, args.count, RCTProfileSystraceArgsFromNSDictionary(args)); + } else { + RCTProfileLock( + RCTProfileOngoingEvents[@(eventID)] = @[ + RCTProfileTimestamp(CACurrentMediaTime()), + name, + RCTNullIfNil(args), + ]; + ); + } return eventID++; } void RCTProfileEndAsyncEvent( - __unused uint64_t tag, + uint64_t tag, NSString *category, int cookie, - __unused NSString *name, + NSString *name, NSDictionary *args ) { CHECK(); + + if (callbacks != NULL) { + callbacks->end_async_section(tag, name.UTF8String, cookie, args.count, RCTProfileSystraceArgsFromNSDictionary(args)); + return; + } + RCTProfileLock( NSArray *event = RCTProfileOngoingEvents[@(cookie)]; if (event) { @@ -358,12 +432,17 @@ void RCTProfileEndAsyncEvent( } void RCTProfileImmediateEvent( - __unused uint64_t tag, + uint64_t tag, NSString *name, char scope ) { CHECK(); + if (callbacks != NULL) { + callbacks->instant_section(tag, name.UTF8String, scope); + return; + } + RCTProfileLock( RCTProfileAddEvent(RCTProfileTraceEvents, @"name": name, @@ -380,6 +459,12 @@ void RCTProfileImmediateEvent( static NSUInteger flowID = 0; CHECK(@0); + + if (callbacks != NULL) { + // flow events not supported yet + return @0; + } + RCTProfileAddEvent(RCTProfileTraceEvents, @"name": @"flow", @"id": @(++flowID), @@ -394,6 +479,11 @@ void RCTProfileImmediateEvent( void _RCTProfileEndFlowEvent(NSNumber *flowID) { CHECK(); + + if (callbacks != NULL) { + return; + } + RCTProfileAddEvent(RCTProfileTraceEvents, @"name": @"flow", @"id": flowID, From d077410f87fd3cedd25e0edc8590d0281feb665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Sagnes?= Date: Wed, 23 Sep 2015 12:00:05 -0700 Subject: [PATCH 0009/2702] Implement cancellation for RCTAssetsLibraryImageLoader Reviewed By: @tadeuzagallo Differential Revision: D2471253 --- .../CameraRoll/RCTAssetsLibraryImageLoader.m | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m index bc6908fed78b..0e84ba503732 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m @@ -11,6 +11,7 @@ #import #import +#import #import #import "RCTBridge.h" @@ -45,17 +46,26 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { + __block volatile uint32_t cancelled = 0; + [[self assetsLibrary] assetForURL:imageURL resultBlock:^(ALAsset *asset) { + if (cancelled) { + return; + } + if (asset) { // ALAssetLibrary API is async and will be multi-threaded. Loading a few full // resolution images at once will spike the memory up to store the image data, // and might trigger memory warnings and/or OOM crashes. // To improve this, process the loaded asset in a serial queue. dispatch_async(RCTAssetsLibraryImageLoaderQueue(), ^{ + if (cancelled) { + return; + } + // Also make sure the image is released immediately after it's used so it // doesn't spike the memory up during the process. @autoreleasepool { - BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero); ALAssetRepresentation *representation = [asset defaultRepresentation]; @@ -78,12 +88,18 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSiz completionHandler(error, nil); } } failureBlock:^(NSError *loadError) { + if (cancelled) { + return; + } + NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError]; NSError *error = RCTErrorWithMessage(errorText); completionHandler(error, nil); }]; - return ^{}; + return ^{ + OSAtomicOr32Barrier(1, &cancelled); + }; } @end From 6d4fce488dbe76e395f072839eeda7d54d3c0af9 Mon Sep 17 00:00:00 2001 From: Martin Bigio Date: Wed, 23 Sep 2015 12:38:55 -0700 Subject: [PATCH 0010/2702] Don't swallow client errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: @​swarr Differential Revision: D2472285 --- packager/react-packager/src/SocketInterface/SocketClient.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/SocketInterface/SocketClient.js b/packager/react-packager/src/SocketInterface/SocketClient.js index ca34150e2fc7..0ff974421dca 100644 --- a/packager/react-packager/src/SocketInterface/SocketClient.js +++ b/packager/react-packager/src/SocketInterface/SocketClient.js @@ -29,7 +29,10 @@ class SocketClient { this._sock = net.connect(sockPath); this._ready = new Promise((resolve, reject) => { - this._sock.on('connect', () => resolve(this)); + this._sock.on('connect', () => { + this._sock.removeAllListeners('error'); + resolve(this); + }); this._sock.on('error', (e) => { e.message = `Error connecting to server on ${sockPath} ` + `with error: ${e.message}`; From ab6e59cf8d2f2d1087f28cfa128f7bc05ad66eea Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Wed, 23 Sep 2015 12:59:50 -0700 Subject: [PATCH 0011/2702] merge .flowconfig files part 1 Differential Revision: D2472462 committer: Service User --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 441cc7048470..fcd8aad3f264 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "LICENSE", "PATENTS", "README.md", - "jestSupport" + "jestSupport", + ".flowconfig" ], "scripts": { "test": "NODE_ENV=test jest", From dbee3eea0e8377e8337b2f449428e656de4c516a Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Wed, 23 Sep 2015 19:30:13 +0100 Subject: [PATCH 0012/2702] merge the root .flowconfig and local-cli/generator/templates/_flowconfig Test Plan: flow check && scripts/e2e-tests.sh --- local-cli/generator/index.js | 9 +++-- local-cli/generator/templates/_flowconfig | 48 ----------------------- 2 files changed, 6 insertions(+), 51 deletions(-) delete mode 100644 local-cli/generator/templates/_flowconfig diff --git a/local-cli/generator/index.js b/local-cli/generator/index.js index 1879fa35f6f2..5e11dec0b9f2 100644 --- a/local-cli/generator/index.js +++ b/local-cli/generator/index.js @@ -2,6 +2,7 @@ var path = require('path'); var yeoman = require('yeoman-generator'); +var utils = require('../generator-utils'); module.exports = yeoman.generators.NamedBase.extend({ constructor: function() { @@ -33,10 +34,12 @@ module.exports = yeoman.generators.NamedBase.extend({ }, configuring: function() { - this.fs.copy( - this.templatePath('_flowconfig'), - this.destinationPath('.flowconfig') + utils.copyAndReplace( + this.templatePath('../../../.flowconfig'), + this.destinationPath('.flowconfig'), + { 'Libraries\/react-native\/react-native-interface.js' : 'node_modules/react-native/Libraries/react-native/react-native-interface.js' } ); + this.fs.copy( this.templatePath('_gitignore'), this.destinationPath('.gitignore') diff --git a/local-cli/generator/templates/_flowconfig b/local-cli/generator/templates/_flowconfig deleted file mode 100644 index 48f0ba0d411d..000000000000 --- a/local-cli/generator/templates/_flowconfig +++ /dev/null @@ -1,48 +0,0 @@ -[ignore] - -# We fork some components by platform. -.*/*.web.js -.*/*.android.js - -# Some modules have their own node_modules with overlap -.*/node_modules/node-haste/.* - -# Ignore react-tools where there are overlaps, but don't ignore anything that -# react-native relies on -.*/node_modules/react-tools/src/React.js -.*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js -.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js -.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js - -# Ignore commoner tests -.*/node_modules/commoner/test/.* - -# See https://github.com/facebook/flow/issues/442 -.*/react-tools/node_modules/commoner/lib/reader.js - -# Ignore jest -.*/react-native/node_modules/jest-cli/.* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js - -[options] -module.system=haste - -munge_underscores=true - -module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FixMe - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy - -[version] -0.16.0 From f5299c02bf3121de7680f131c3da1361affee977 Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Wed, 23 Sep 2015 22:01:14 +0100 Subject: [PATCH 0013/2702] [ReactNative] remove unless files in SampleApp --- Examples/SampleApp/_flowconfig | 49 ------------------------------ Examples/SampleApp/_gitignore | 28 ----------------- Examples/SampleApp/_watchmanconfig | 1 - 3 files changed, 78 deletions(-) delete mode 100644 Examples/SampleApp/_flowconfig delete mode 100644 Examples/SampleApp/_gitignore delete mode 100644 Examples/SampleApp/_watchmanconfig diff --git a/Examples/SampleApp/_flowconfig b/Examples/SampleApp/_flowconfig deleted file mode 100644 index 3c2ec66c16a9..000000000000 --- a/Examples/SampleApp/_flowconfig +++ /dev/null @@ -1,49 +0,0 @@ -[ignore] - -# We fork some components by platform. -.*/*.web.js -.*/*.android.js - -# Some modules have their own node_modules with overlap -.*/node_modules/node-haste/.* - -# Ignore react-tools where there are overlaps, but don't ignore anything that -# react-native relies on -.*/node_modules/react-tools/src/React.js -.*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js -.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js -.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js - - -# Ignore commoner tests -.*/node_modules/commoner/test/.* - -# See https://github.com/facebook/flow/issues/442 -.*/react-tools/node_modules/commoner/lib/reader.js - -# Ignore jest -.*/react-native/node_modules/jest-cli/.* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js - -[options] -module.system=haste - -munge_underscores=true - -module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FixMe - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy - -[version] -0.16.0 diff --git a/Examples/SampleApp/_gitignore b/Examples/SampleApp/_gitignore deleted file mode 100644 index b927355df441..000000000000 --- a/Examples/SampleApp/_gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace - -# node.js -# -node_modules/ -npm-debug.log diff --git a/Examples/SampleApp/_watchmanconfig b/Examples/SampleApp/_watchmanconfig deleted file mode 100644 index 0967ef424bce..000000000000 --- a/Examples/SampleApp/_watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} From 776686768f8c7646f7b3ea9cdf3b6de22e063594 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 23 Sep 2015 14:01:21 -0700 Subject: [PATCH 0014/2702] [Tests] Move the packager and cli tests to separate matrix entries The packager and cli tests now run independently of the RN JS tests. When the packager moves to its own repo we can just remove the packager entry from .travis.yml. The cli tests are also marked as allowed to fail for now. --- .travis.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 27a58332c998..32ad8b32da5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,17 @@ script: elif [ "$TEST_TYPE" = js ] then - flow check && npm test + flow check && npm test -- '\/Libraries\/' + + elif [ "$TEST_TYPE" = packager ] + then + + npm test -- '\/packager\/' + + elif [ "$TEST_TYPE" = cli ] + then + + npm test -- '\/(local|private|react-native)-cli\/' elif [ "$TEST_TYPE" = build_website ] then @@ -57,8 +67,12 @@ env: matrix: - TEST_TYPE=objc - TEST_TYPE=js + - TEST_TYPE=packager + - TEST_TYPE=cli - TEST_TYPE=build_website - TEST_TYPE=e2e + allow_failures: + - TEST_TYPE=cli global: - secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU=" From c5d3fa4a3627bd45aa85a8304efd16f709921d47 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Wed, 23 Sep 2015 15:24:50 -0700 Subject: [PATCH 0015/2702] Update cacheVersion in bundle.js As requested by @vjeux, this moves the change to this file to a separate PR. For more info see #2905. --- local-cli/bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-cli/bundle.js b/local-cli/bundle.js index 0f80b06bf154..b060cbe6e935 100644 --- a/local-cli/bundle.js +++ b/local-cli/bundle.js @@ -35,7 +35,7 @@ function getBundle(flags) { projectRoots: projectRoots, transformModulePath: require.resolve('../packager/transformer.js'), assetRoots: assetRoots, - cacheVersion: '2', + cacheVersion: '3', blacklistRE: blacklist(platform), }; From 34e03a6e24f601ba9410fc67a2fa6fd0e7a400e7 Mon Sep 17 00:00:00 2001 From: Thomas Aylott Date: Wed, 23 Sep 2015 16:59:44 -0700 Subject: [PATCH 0016/2702] TextMate support for launchEditor Reviewed By: @frantic Differential Revision: D2467263 --- packager/launchEditor.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packager/launchEditor.js b/packager/launchEditor.js index f98faaf8b1d7..d346f14a1ef4 100644 --- a/packager/launchEditor.js +++ b/packager/launchEditor.js @@ -34,6 +34,9 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber) { case 'joe': case 'emacs': return ['+' + lineNumber, fileName]; + case 'rmate': + case 'mate': + return ['--line', lineNumber, fileName]; } // For all others, drop the lineNumber until we have From b84b01e9e92ee59ad5b770e246e7f04dcfeed6fb Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 23 Sep 2015 17:01:08 -0700 Subject: [PATCH 0017/2702] Hierarchical event bubbling - 1 Reviewed By: @fkgozali Differential Revision: D2469495 --- .../Navigation/NavigationTreeNode.js | 91 ++++++++++++++ .../__tests__/NavigationTreeNode-test.js | 111 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js create mode 100644 Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js new file mode 100644 index 000000000000..b100ab5e43e3 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * @providesModule NavigationTreeNode + * @flow + * @typechecks + */ + +'use strict'; + +var invariant = require('invariant'); +var immutable = require('immutable'); + +var {List} = immutable; + +/** + * Utility to build a tree of nodes. + * Note that this tree does not perform cyclic redundancy check + * while appending child node. + */ +class NavigationTreeNode { + __parent: ?NavigationTreeNode; + + _children: List; + + _value: any; + + constructor(value: any) { + this.__parent = null; + this._children = new List(); + this._value = value; + } + + getValue(): any { + return this._value; + } + + getParent(): ?NavigationTreeNode { + return this.__parent; + } + + getChildrenCount(): number { + return this._children.size; + } + + getChildAt(index: number): ?NavigationTreeNode { + return index > -1 && index < this._children.size ? + this._children.get(index) : + null; + } + + appendChild(child: NavigationTreeNode): void { + if (child.__parent) { + child.__parent.removeChild(child); + } + child.__parent = this; + this._children = this._children.push(child); + } + + removeChild(child: NavigationTreeNode): void { + var index = this._children.indexOf(child); + + invariant( + index > -1, + 'The node to be removed is not a child of this node.' + ); + + child.__parent = null; + + this._children = this._children.splice(index, 1); + } + + indexOf(child: NavigationTreeNode): number { + return this._children.indexOf(child); + } + + forEach(callback: Function, context: any): void { + this._children.forEach(callback, context); + } + + map(callback: Function, context: any): Array { + return this._children.map(callback, context).toJS(); + } + + some(callback: Function, context: any): boolean { + return this._children.some(callback, context); + } +} + + +module.exports = NavigationTreeNode; diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js new file mode 100644 index 000000000000..e89da6a80d29 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + */ + +'use strict'; + +jest + .dontMock('Map') + .dontMock('NavigationTreeNode') + .dontMock('invariant') + .dontMock('immutable'); + +var NavigationTreeNode = require('NavigationTreeNode'); + +describe('NavigationTreeNode-test', () => { + it('should be empty', () => { + var node = new NavigationTreeNode(); + expect(node.getValue()).toEqual(undefined); + expect(node.getParent()).toEqual(null); + expect(node.getChildrenCount()).toEqual(0); + expect(node.getChildAt(0)).toEqual(null); + }); + + + it('should contain value', () => { + var node = new NavigationTreeNode(123); + expect(node.getValue()).toEqual(123); + }); + + it('should appendChild', () => { + var papa = new NavigationTreeNode('hedger'); + var baby = new NavigationTreeNode('hedger jr'); + papa.appendChild(baby); + expect(papa.getChildAt(0)).toEqual(baby); + expect(papa.getChildrenCount()).toEqual(1); + expect(baby.getParent()).toEqual(papa); + }); + + it('should removeChild', () => { + var papa = new NavigationTreeNode('Eddard Stark'); + var baby = new NavigationTreeNode('Robb Stark'); + papa.appendChild(baby); + + papa.removeChild(baby); + expect(papa.getChildAt(0)).toEqual(null); + expect(papa.getChildrenCount()).toEqual(0); + expect(baby.getParent()).toEqual(null); + }); + + it('should not remove non-child', () => { + var papa = new NavigationTreeNode('dog'); + var baby = new NavigationTreeNode('cat'); + expect(papa.removeChild.bind(papa, baby)).toThrow(); + }); + + it('should find child', () => { + var papa = new NavigationTreeNode('Eddard Stark'); + var baby = new NavigationTreeNode('Robb Stark'); + + papa.appendChild(baby); + expect(papa.indexOf(baby)).toEqual(0); + + papa.removeChild(baby); + expect(papa.indexOf(baby)).toEqual(-1); + }); + + + it('should traverse each child', () => { + var parent = new NavigationTreeNode(); + parent.appendChild(new NavigationTreeNode('a')); + parent.appendChild(new NavigationTreeNode('b')); + parent.appendChild(new NavigationTreeNode('c')); + var result = []; + parent.forEach((child, index) => { + result[index] = child.getValue(); + }); + + expect(result).toEqual(['a', 'b', 'c']); + }); + + it('should map children', () => { + var parent = new NavigationTreeNode(); + parent.appendChild(new NavigationTreeNode('a')); + parent.appendChild(new NavigationTreeNode('b')); + parent.appendChild(new NavigationTreeNode('c')); + var result = parent.map((child, index) => { + return child.getValue(); + }); + + expect(result).toEqual(['a', 'b', 'c']); + }); + + it('should traverse some children', () => { + var parent = new NavigationTreeNode(); + parent.appendChild(new NavigationTreeNode('a')); + parent.appendChild(new NavigationTreeNode('b')); + parent.appendChild(new NavigationTreeNode('c')); + + var result = []; + var value = parent.some((child, index) => { + if (index > 1) { + return true; + } else { + result[index] = child.getValue(); + } + }); + + expect(value).toEqual(true); + expect(result).toEqual(['a', 'b']); + }); +}); From f5dfa4e926b36931313ce51c7f26b8d9b3f1112b Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Wed, 23 Sep 2015 17:03:06 -0700 Subject: [PATCH 0018/2702] Remove text input warnings Reviewed By: @mkonicek Differential Revision: D2471396 --- Libraries/Components/TextInput/TextInput.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index d9034941200e..f96c1852fca6 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -40,9 +40,12 @@ var notMultiline = { onSubmitEditing: true, }; -var AndroidTextInput = requireNativeComponent('AndroidTextInput', null); -var RCTTextView = requireNativeComponent('RCTTextView', null); -var RCTTextField = requireNativeComponent('RCTTextField', null); +if (Platform.OS === 'android') { + var AndroidTextInput = requireNativeComponent('AndroidTextInput', null); +} else if (Platform.OS === 'ios') { + var RCTTextView = requireNativeComponent('RCTTextView', null); + var RCTTextField = requireNativeComponent('RCTTextField', null); +} type Event = Object; From 9534be045c88a085e2cf84e43961073e733cde0b Mon Sep 17 00:00:00 2001 From: Elia Grady Date: Wed, 23 Sep 2015 17:09:03 -0700 Subject: [PATCH 0019/2702] Fixed typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: _setRenderSceneToHarwareTextureAndroid was changed to _setRenderSceneToHardwareTextureAndroid. Closes https://github.com/facebook/react-native/pull/2869 Reviewed By: @​svcscm Differential Revision: D2472438 Pulled By: @vjeux --- Libraries/CustomComponents/Navigator/Navigator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 368e95150cdc..95d87e8cedfa 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -558,8 +558,8 @@ var Navigator = React.createClass({ } else if (this.state.activeGesture) { toIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture); } - this._setRenderSceneToHarwareTextureAndroid(fromIndex, true); - this._setRenderSceneToHarwareTextureAndroid(toIndex, true); + this._setRenderSceneToHardwareTextureAndroid(fromIndex, true); + this._setRenderSceneToHardwareTextureAndroid(toIndex, true); var navBar = this._navBar; if (navBar && navBar.onAnimationStart) { navBar.onAnimationStart(fromIndex, toIndex); @@ -569,7 +569,7 @@ var Navigator = React.createClass({ _onAnimationEnd: function() { var max = this.state.routeStack.length - 1; for (var index = 0; index <= max; index++) { - this._setRenderSceneToHarwareTextureAndroid(index, false); + this._setRenderSceneToHardwareTextureAndroid(index, false); } var navBar = this._navBar; @@ -578,7 +578,7 @@ var Navigator = React.createClass({ } }, - _setRenderSceneToHarwareTextureAndroid: function(sceneIndex, shouldRenderToHardwareTexture) { + _setRenderSceneToHardwareTextureAndroid: function(sceneIndex, shouldRenderToHardwareTexture) { var viewAtIndex = this.refs['scene_' + sceneIndex]; if (viewAtIndex === null || viewAtIndex === undefined) { return; From 23c433c5ebea77896ffde7980451fb8b86d5b58e Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 23 Sep 2015 17:11:35 -0700 Subject: [PATCH 0020/2702] Handle errors that are not `Error` instances Reviewed By: @sahrens Differential Revision: D1799587 --- .../Initialization/ExceptionsManager.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index b10f641fd69e..a1335722bc75 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -19,15 +19,9 @@ var stringifySafe = require('stringifySafe'); var sourceMapPromise; -type Exception = { - sourceURL: string; - line: number; - message: string; -} - var exceptionID = 0; -function reportException(e: Exception, isFatal: bool, stack?: any) { +function reportException(e: Error, isFatal: bool, stack?: any) { var currentExceptionID = ++exceptionID; if (RCTExceptionsManager) { if (!stack) { @@ -53,13 +47,20 @@ function reportException(e: Exception, isFatal: bool, stack?: any) { } } -function handleException(e: Exception, isFatal: boolean) { +function handleException(e: Error, isFatal: boolean) { + // Workaround for reporting errors caused by `throw 'some string'` + // Unfortunately there is no way to figure out the stacktrace in this + // case, so if you ended up here trying to trace an error, look for + // `throw ''` somewhere in your codebase. + if (!e.message) { + e = new Error(e); + } var stack = parseErrorStack(e); var msg = 'Error: ' + e.message + '\n stack: \n' + stackToString(stack) + - '\n URL: ' + e.sourceURL + - '\n line: ' + e.line + + '\n URL: ' + (e: any).sourceURL + + '\n line: ' + (e: any).line + '\n message: ' + e.message; if (console.errorOriginal) { console.errorOriginal(msg); From e9903a332125a159a383a7df0013eee36fdf99b4 Mon Sep 17 00:00:00 2001 From: Charlie Cheever Date: Wed, 23 Sep 2015 17:19:47 -0700 Subject: [PATCH 0021/2702] Also call the original `console` methods if they exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Ex. When `console.log` or similar is called, this will call the original method, which can be quite useful. For example, under iOS, this will log to the Safari console debugger, which has an expandable UI for inspecting objects, etc., and is also just useful if you are using that as a REPL. I don't believe this incurs a meaningful performance penalty unless the console is open, but it would be easy to stick behind a flag if that is a problem. Closes https://github.com/facebook/react-native/pull/2486 Reviewed By: @​svcscm Differential Revision: D2472470 Pulled By: @vjeux --- .../src/DependencyResolver/polyfills/console.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packager/react-packager/src/DependencyResolver/polyfills/console.js b/packager/react-packager/src/DependencyResolver/polyfills/console.js index e04597402c98..e841aebb8471 100644 --- a/packager/react-packager/src/DependencyResolver/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/polyfills/console.js @@ -367,6 +367,9 @@ }; function setupConsole(global) { + + var originalConsole = global.console; + if (!global.nativeLoggingHook) { return; } @@ -462,6 +465,16 @@ table: consoleTablePolyfill }; + // If available, also call the original `console` method since that is + // sometimes useful. Ex: on OS X, this will let you see rich output in + // the Safari Web Inspector console. + Object.keys(global.console).forEach(methodName => { + var reactNativeMethod = global.console[methodName]; + global.console[methodName] = function() { + originalConsole[methodName](...arguments); + reactNativeMethod.apply(global.console, arguments); + }; + }); } if (typeof module !== 'undefined') { From 59b6bc93f17e4c4f7651d51148da9e8b1d9121a4 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 23 Sep 2015 17:25:41 -0700 Subject: [PATCH 0022/2702] Support tintColor for managed image assets Reviewed By: @nicklockwood Differential Revision: D2443089 --- Libraries/Image/Image.ios.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index f641167c7127..48fcb278baac 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -170,6 +170,12 @@ var Image = React.createClass({ var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108 var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108 + // This is a workaround for #8243665. RCTNetworkImageView does not support tintColor + // TODO: Remove this hack once we have one image implementation #8389274 + if (isNetwork && tintColor) { + RawImage = RCTImageView; + } + return ( Date: Wed, 23 Sep 2015 18:09:08 -0700 Subject: [PATCH 0023/2702] Remove duplicate PanResponderExample and update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: While looking at the [pan responder docs](https://facebook.github.io/react-native/docs/panresponder.html#working-example) I noticed they linked to `ResponderExample` rather than `PanResponderExample` and that `ResponderExample ` defined `NavigatorIOSExample` which was odd. This PR just kills `ResponderExample` and updates the link in the docs. :bowtie: Closes https://github.com/facebook/react-native/pull/1743 Reviewed By: @​svcscm Differential Revision: D2468010 Pulled By: @vjeux --- Libraries/vendor/react/browser/eventPlugins/PanResponder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js index bf3ed46c1d73..9526ec423901 100644 --- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js +++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js @@ -92,7 +92,7 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY; * ### Working Example * * To see it in action, try the - * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/ResponderExample.js) + * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js) */ var PanResponder = { From e59163da35a0f56cb56794f750ecd3cfbf01209b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 23 Sep 2015 18:48:19 -0700 Subject: [PATCH 0024/2702] Add portfinder dev dependency Reviewed By: @vjeux Differential Revision: D2468120 --- npm-shrinkwrap.json | 24 ++++++++++++++++++++++++ package.json | 1 + 2 files changed, 25 insertions(+) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 49be7d2d3a4b..6d93f511cc78 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3726,6 +3726,30 @@ } } }, + "portfinder": { + "version": "0.4.0", + "from": "portfinder@0.4.0", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz", + "dependencies": { + "async": { + "version": "0.9.0", + "from": "async@0.9.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + } + } + }, "progress": { "version": "1.1.8", "from": "progress@>=1.1.8 <2.0.0", diff --git a/package.json b/package.json index fcd8aad3f264..3aae018a1149 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "babel-eslint": "4.1.1", "eslint": "1.3.1", "eslint-plugin-react": "3.3.1", + "portfinder": "0.4.0", "temp": "0.8.3" } } From 118e67a49a02a70fb0e2385feedcd5b5ff02aead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Thu, 24 Sep 2015 10:13:12 +0100 Subject: [PATCH 0025/2702] [cli] use different names in generator tests --- .travis.yml | 2 - local-cli/__tests__/generator-android-test.js | 19 ++++-- local-cli/__tests__/generator-ios-test.js | 60 +++++++++---------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32ad8b32da5b..92c3ec5dfe6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,8 +71,6 @@ env: - TEST_TYPE=cli - TEST_TYPE=build_website - TEST_TYPE=e2e - allow_failures: - - TEST_TYPE=cli global: - secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU=" diff --git a/local-cli/__tests__/generator-android-test.js b/local-cli/__tests__/generator-android-test.js index 756099f5aebc..748f163ccf1b 100644 --- a/local-cli/__tests__/generator-android-test.js +++ b/local-cli/__tests__/generator-android-test.js @@ -5,15 +5,26 @@ jest.autoMockOff(); var path = require('path'); describe('react:android', function () { - var assert = require('yeoman-generator').assert; + var assert; beforeEach(function (done) { + // A deep dependency of yeoman spams console.log with giant json objects. + // yeoman-generator/node_modules/ + // download/node_modules/ + // caw/node_modules/ + // get-proxy/node_modules/ + // rc/index.js + var log = console.log; + console.log = function() {}; + assert = require('yeoman-generator').assert; var helpers = require('yeoman-generator').test; + console.log = log; + var generated = false; runs(function() { helpers.run(path.resolve(__dirname, '..', 'generator-android')) - .withArguments(['TestApp']) + .withArguments(['TestAppAndroid']) .withOptions({ 'package': 'com.reactnative.test', }) @@ -67,11 +78,11 @@ describe('react:android', function () { ); assert.fileContent( path.join('android', 'app', 'src', 'main', 'java', 'com', 'reactnative', 'test', 'MainActivity.java'), - 'mReactRootView.startReactApplication(mReactInstanceManager, "TestApp", null);' + 'mReactRootView.startReactApplication(mReactInstanceManager, "TestAppAndroid", null);' ); assert.fileContent( path.join('android', 'app', 'src', 'main', 'res', 'values', 'strings.xml'), - 'TestApp' + 'TestAppAndroid' ); }); }); diff --git a/local-cli/__tests__/generator-ios-test.js b/local-cli/__tests__/generator-ios-test.js index 43bd6ca7c59e..be46409fb720 100644 --- a/local-cli/__tests__/generator-ios-test.js +++ b/local-cli/__tests__/generator-ios-test.js @@ -24,7 +24,7 @@ describe('react:ios', function() { runs(function() { helpers.run(path.resolve(__dirname, '../generator-ios')) - .withArguments(['TestApp']) + .withArguments(['TestAppIOS']) .on('end', function() { generated = true; }); @@ -40,58 +40,58 @@ describe('react:ios', function() { it('creates files', function() { assert.file([ 'ios/main.jsbundle', - 'ios/TestApp/AppDelegate.h', - 'ios/TestApp/AppDelegate.m', - 'ios/TestApp/Base.lproj/LaunchScreen.xib', - 'ios/TestApp/Images.xcassets/AppIcon.appiconset/Contents.json', - 'ios/TestApp/Info.plist', - 'ios/TestApp/main.m', - 'ios/TestApp.xcodeproj/project.pbxproj', - 'ios/TestApp.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme', - 'ios/TestAppTests/TestAppTests.m', - 'ios/TestAppTests/Info.plist' + 'ios/TestAppIOS/AppDelegate.h', + 'ios/TestAppIOS/AppDelegate.m', + 'ios/TestAppIOS/Base.lproj/LaunchScreen.xib', + 'ios/TestAppIOS/Images.xcassets/AppIcon.appiconset/Contents.json', + 'ios/TestAppIOS/Info.plist', + 'ios/TestAppIOS/main.m', + 'ios/TestAppIOS.xcodeproj/project.pbxproj', + 'ios/TestAppIOS.xcodeproj/xcshareddata/xcschemes/TestAppIOS.xcscheme', + 'ios/TestAppIOSTests/TestAppIOSTests.m', + 'ios/TestAppIOSTests/Info.plist' ]); }); it('replaces vars in AppDelegate.m', function() { - var appDelegate = 'ios/TestApp/AppDelegate.m'; + var appDelegate = 'ios/TestAppIOS/AppDelegate.m'; - assert.fileContent(appDelegate, 'moduleName:@"TestApp"'); + assert.fileContent(appDelegate, 'moduleName:@"TestAppIOS"'); assert.noFileContent(appDelegate, 'SampleApp'); }); it('replaces vars in LaunchScreen.xib', function() { - var launchScreen = 'ios/TestApp/Base.lproj/LaunchScreen.xib'; + var launchScreen = 'ios/TestAppIOS/Base.lproj/LaunchScreen.xib'; - assert.fileContent(launchScreen, 'text="TestApp"'); + assert.fileContent(launchScreen, 'text="TestAppIOS"'); assert.noFileContent(launchScreen, 'SampleApp'); }); - it('replaces vars in TestAppTests.m', function() { - var tests = 'ios/TestAppTests/TestAppTests.m'; + it('replaces vars in TestAppIOSTests.m', function() { + var tests = 'ios/TestAppIOSTests/TestAppIOSTests.m'; - assert.fileContent(tests, '@interface TestAppTests : XCTestCase'); - assert.fileContent(tests, '@implementation TestAppTests'); + assert.fileContent(tests, '@interface TestAppIOSTests : XCTestCase'); + assert.fileContent(tests, '@implementation TestAppIOSTests'); assert.noFileContent(tests, 'SampleApp'); }); it('replaces vars in project.pbxproj', function() { - var pbxproj = 'ios/TestApp.xcodeproj/project.pbxproj'; - assert.fileContent(pbxproj, '"TestApp"'); - assert.fileContent(pbxproj, '"TestAppTests"'); - assert.fileContent(pbxproj, 'TestApp.app'); - assert.fileContent(pbxproj, 'TestAppTests.xctest'); + var pbxproj = 'ios/TestAppIOS.xcodeproj/project.pbxproj'; + assert.fileContent(pbxproj, '"TestAppIOS"'); + assert.fileContent(pbxproj, '"TestAppIOSTests"'); + assert.fileContent(pbxproj, 'TestAppIOS.app'); + assert.fileContent(pbxproj, 'TestAppIOSTests.xctest'); assert.noFileContent(pbxproj, 'SampleApp'); }); it('replaces vars in xcscheme', function() { - var xcscheme = 'ios/TestApp.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme'; - assert.fileContent(xcscheme, '"TestApp"'); - assert.fileContent(xcscheme, '"TestApp.app"'); - assert.fileContent(xcscheme, 'TestApp.xcodeproj'); - assert.fileContent(xcscheme, '"TestAppTests.xctest"'); - assert.fileContent(xcscheme, '"TestAppTests"'); + var xcscheme = 'ios/TestAppIOS.xcodeproj/xcshareddata/xcschemes/TestAppIOS.xcscheme'; + assert.fileContent(xcscheme, '"TestAppIOS"'); + assert.fileContent(xcscheme, '"TestAppIOS.app"'); + assert.fileContent(xcscheme, 'TestAppIOS.xcodeproj'); + assert.fileContent(xcscheme, '"TestAppIOSTests.xctest"'); + assert.fileContent(xcscheme, '"TestAppIOSTests"'); assert.noFileContent(xcscheme, 'SampleApp'); }); From c4f6f41412d8ed596d20f273eb55bb77d0704717 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 24 Sep 2015 15:18:42 +0100 Subject: [PATCH 0026/2702] Update GettingStarted.md --- docs/GettingStarted.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 11aca0c13964..ed8d9c0bbd99 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -31,20 +31,21 @@ To write React Native apps for Android, you will need to install the Android SDK $ npm install -g react-native-cli $ react-native init AwesomeProject - $ cd AwesomeProject/ **To run the iOS app:** +- `$ cd AwesomeProject` - Open `ios/AwesomeProject.xcodeproj` and hit run in Xcode. - Open `index.ios.js` in your text editor of choice and edit some lines. - Hit ⌘-R in your iOS simulator to reload the app and see your change! **To run the Android app:** -* `$ react-native run-android` -* Open `index.android.js` in your text editor of choice and edit some lines. -* Press the menu button (F2 by default, or ⌘-M in Genymotion) and select *Reload JS* to see your change! -* Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your app's logs +- `$ cd AwesomeProject` +- `$ react-native run-android` +- Open `index.android.js` in your text editor of choice and edit some lines. +- Press the menu button (F2 by default, or ⌘-M in Genymotion) and select *Reload JS* to see your change! +- Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your app's logs _Note: If you are using a device, see the [Running on Device page](http://facebook.github.io/react-native/docs/running-on-device-android.html#content)._ From 6d19f88ae14cf137ffa40a6e5139aabed611db1d Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 23 Sep 2015 19:51:08 -0700 Subject: [PATCH 0027/2702] Add method `stopPropagation` and `stop` to NavigationEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Add method `stopPropagation` and `stop` to NavigationEvent so we can stop event easily once event bubbling and capturing is supported. Reviewed By: @fkgozali Differential Revision: D2471903 --- .../Navigator/Navigation/NavigationEvent.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js index b1d6268dadd1..474336b65124 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js @@ -57,6 +57,7 @@ var _navigationEventPool = new NavigationEventPool(); class NavigationEvent { _data: any; _defaultPrevented: boolean; + _propagationStopped: boolean; _disposed: boolean; _target: ?Object; _type: ?string; @@ -71,6 +72,7 @@ class NavigationEvent { this._data = data; this._defaultPrevented = false; this._disposed = false; + this._propagationStopped = false; } /* $FlowFixMe - get/set properties not yet supported */ @@ -97,6 +99,19 @@ class NavigationEvent { this._defaultPrevented = true; } + stopPropagation(): void { + this._propagationStopped = true; + } + + stop(): void { + this.preventDefault(); + this.stopPropagation(); + } + + isPropagationStopped(): boolean { + return this._propagationStopped; + } + /** * Dispose the event. * NavigationEvent shall be disposed after being emitted by From 9445fca90483a5297f7c37b95659541804b0db45 Mon Sep 17 00:00:00 2001 From: oveddan Date: Wed, 23 Sep 2015 20:02:22 -0700 Subject: [PATCH 0028/2702] Have the chrome debugger run javascript within a web worker, to remove the global document. Summary: To make the chrome debugger environment consisten with the JSC executer environment, where there is no `window.document`, make the chrome debugger run the javascript inside a web worker. This fixes #1473 Closes https://github.com/facebook/react-native/pull/1632 Reviewed By: @martinbigio Differential Revision: D2471710 Pulled By: @vjeux --- packager/debugger.html | 45 ++++++++++++++----------------------- packager/debuggerWorker.js | 46 ++++++++++++++++++++++++++++++++++++++ packager/packager.js | 6 +++++ 3 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 packager/debuggerWorker.js diff --git a/packager/debugger.html b/packager/debugger.html index 3b905efe366e..a69424ea98dd 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -42,32 +42,28 @@ document.getElementById('status').innerHTML = status; } +// This worker will run the application javascript code, +// making sure that it's run in an environment without a global +// document, to make it consistent with the JSC executor environment. +var worker = new Worker('debuggerWorker.js'); + var messageHandlers = { // This method is a bit hacky. Catalyst asks for a new clean JS runtime. // The easiest way to do this is to reload this page. That also means that // web socket connection will be lost. To send reply back we need to remember - // message id + // message id. + // This message also needs to be handled outside of the worker, since the worker + // doesn't have access to local storage. 'prepareJSRuntime': function(message) { window.onbeforeunload = undefined; window.localStorage.setItem('sessionID', message.id); window.location.reload(); }, - 'executeApplicationScript': function(message, sendReply) { - for (var key in message.inject) { - window[key] = JSON.parse(message.inject[key]); - } - loadScript(message.url, sendReply.bind(null, null)); + 'executeApplicationScript': function(message) { + worker.postMessage(message); }, - 'executeJSCall': function(message, sendReply) { - var returnValue = null; - try { - if (window && window.require) { - var module = window.require(message.moduleName); - returnValue = module[message.moduleMethod].apply(module, message.arguments); - } - } finally { - sendReply(JSON.stringify(returnValue)); - } + 'executeJSCall': function(message) { + worker.postMessage(message); } }; @@ -89,12 +85,9 @@ return; } - var sendReply = function(result) { - ws.send(JSON.stringify({replyID: object.id, result: result})); - }; var handler = messageHandlers[object.method]; if (handler) { - handler(object, sendReply); + handler(object); } else { console.warn('Unknown method: ' + object.method); } @@ -107,18 +100,14 @@ window.localStorage.removeItem('sessionID'); debuggerSetTimeout(connectToDebuggerProxy, 100); }; + + worker.onmessage = function(message) { + ws.send(JSON.stringify(message.data)); + } } connectToDebuggerProxy(); -function loadScript(src, callback) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = src; - script.onload = callback; - document.head.appendChild(script); -} - })(); - - diff --git a/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj deleted file mode 100644 index 7123b2d3b2cd..000000000000 --- a/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,755 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; }; - 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; - 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; - 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; - 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; - 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; - 00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; }; - 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; - 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; - 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTActionSheet; - }; - 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTGeolocation; - }; - 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5115D1A9E6B3D00147676; - remoteInfo = RCTImage; - }; - 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B511DB1A9E6C8500147676; - remoteInfo = RCTNetwork; - }; - 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; - remoteInfo = RCTVibration; - }; - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = SampleApp; - }; - 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTSettings; - }; - 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3C86DF461ADF2C930047B81A; - remoteInfo = RCTWebSocket; - }; - 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; - remoteInfo = React; - }; - 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTLinking; - }; - 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5119B1A9E6C1200147676; - remoteInfo = RCTText; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; - 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = ""; }; - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../../React/React.xcodeproj; sourceTree = ""; }; - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 146834051AC3E58100842450 /* libReact.a in Frameworks */, - 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, - 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, - 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, - 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, - 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, - 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, - 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, - 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, - 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 00C302A81ABCB8CE00DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302B61ABCB90400DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302BC1ABCB91800DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302D41ABCB9D200DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302E01ABCB9EE00DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, - ); - name = Products; - sourceTree = ""; - }; - 00E356EF1AD99517003FC87E /* SampleAppTests */ = { - isa = PBXGroup; - children = ( - 00E356F21AD99517003FC87E /* SampleAppTests.m */, - 00E356F01AD99517003FC87E /* Supporting Files */, - ); - path = SampleAppTests; - sourceTree = ""; - }; - 00E356F01AD99517003FC87E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 00E356F11AD99517003FC87E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 139105B71AF99BAD00B5F7CC /* Products */ = { - isa = PBXGroup; - children = ( - 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, - ); - name = Products; - sourceTree = ""; - }; - 139FDEE71B06529A00C62182 /* Products */ = { - isa = PBXGroup; - children = ( - 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, - ); - name = Products; - sourceTree = ""; - }; - 13B07FAE1A68108700A75B9A /* SampleApp */ = { - isa = PBXGroup; - children = ( - 13B07FAF1A68108700A75B9A /* AppDelegate.h */, - 13B07FB01A68108700A75B9A /* AppDelegate.m */, - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, - 008F07F21AC5B25A0029DE68 /* main.jsbundle */, - 13B07FB71A68108700A75B9A /* main.m */, - ); - path = SampleApp; - sourceTree = ""; - }; - 146834001AC3E56700842450 /* Products */ = { - isa = PBXGroup; - children = ( - 146834041AC3E56700842450 /* libReact.a */, - ); - name = Products; - sourceTree = ""; - }; - 78C398B11ACF4ADC00677621 /* Products */ = { - isa = PBXGroup; - children = ( - 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, - ); - name = Products; - sourceTree = ""; - }; - 832341AE1AAA6A7D00B99B32 /* Libraries */ = { - isa = PBXGroup; - children = ( - 146833FF1AC3E56700842450 /* React.xcodeproj */, - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, - ); - name = Libraries; - sourceTree = ""; - }; - 832341B11AAA6A8300B99B32 /* Products */ = { - isa = PBXGroup; - children = ( - 832341B51AAA6A8300B99B32 /* libRCTText.a */, - ); - name = Products; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* SampleApp */, - 00E356EF1AD99517003FC87E /* SampleAppTests */, - 832341AE1AAA6A7D00B99B32 /* Libraries */, - 83CBBA001A601CBA00E9B192 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* SampleApp.app */, - 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* SampleAppTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "SampleAppTests" */; - buildPhases = ( - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = SampleAppTests; - productName = SampleAppTests; - productReference = 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 13B07F861A680F5B00A75B9A /* SampleApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */; - buildPhases = ( - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SampleApp; - productName = "Hello World"; - productReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; - ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; - }, - { - ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; - ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; - }, - { - ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; - ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; - }, - { - ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; - ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; - }, - { - ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; - ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; - }, - { - ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; - ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; - }, - { - ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; - ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; - }, - { - ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; - ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; - }, - { - ProductGroup = 139FDEE71B06529A00C62182 /* Products */; - ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; - }, - { - ProductGroup = 146834001AC3E56700842450 /* Products */; - ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* SampleApp */, - 00E356ED1AD99517003FC87E /* SampleAppTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTActionSheet.a; - remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTGeolocation.a; - remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTImage.a; - remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTNetwork.a; - remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTVibration.a; - remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTSettings.a; - remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocket.a; - remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 146834041AC3E56700842450 /* libReact.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libReact.a; - remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTLinking.a; - remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTText.a; - remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 008F07F31AC5B25A0029DE68 /* main.jsbundle in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* SampleApp */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 13B07FB21A68108700A75B9A /* Base */, - ); - name = LaunchScreen.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - ); - INFOPLIST_FILE = SampleAppTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - ); - INFOPLIST_FILE = SampleAppTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; - }; - name = Release; - }; - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEAD_CODE_STRIPPING = NO; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - ); - INFOPLIST_FILE = "$(SRCROOT)/SampleApp/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = SampleApp; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - ); - INFOPLIST_FILE = "$(SRCROOT)/SampleApp/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = SampleApp; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "SampleAppTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme b/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme deleted file mode 100644 index 6a3c2997a8bb..000000000000 --- a/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/SampleApp/iOS/SampleApp/AppDelegate.h b/Examples/SampleApp/iOS/SampleApp/AppDelegate.h deleted file mode 100644 index a9654d5e01b1..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/AppDelegate.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 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. - */ - -#import - -@interface AppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; - -@end diff --git a/Examples/SampleApp/iOS/SampleApp/AppDelegate.m b/Examples/SampleApp/iOS/SampleApp/AppDelegate.m deleted file mode 100644 index d49b891a21a0..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/AppDelegate.m +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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. - */ - -#import "AppDelegate.h" - -#import "RCTRootView.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - NSURL *jsCodeLocation; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle - * from the root of your project directory, run - * - * $ react-native bundle --minify - * - * see http://facebook.github.io/react-native/docs/runningondevice.html - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"SampleApp" - initialProperties:nil - launchOptions:launchOptions]; - - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [UIViewController new]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - return YES; -} - -@end diff --git a/Examples/SampleApp/iOS/SampleApp/Base.lproj/LaunchScreen.xib b/Examples/SampleApp/iOS/SampleApp/Base.lproj/LaunchScreen.xib deleted file mode 100644 index 73cc9d07c74a..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/SampleApp/iOS/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json b/Examples/SampleApp/iOS/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 118c98f7461b..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/SampleApp/iOS/SampleApp/Info.plist b/Examples/SampleApp/iOS/SampleApp/Info.plist deleted file mode 100644 index cddf0766c980..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/Info.plist +++ /dev/null @@ -1,48 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - NSLocationWhenInUseUsageDescription - - NSAppTransportSecurity - - - NSAllowsArbitraryLoads - - - - diff --git a/Examples/SampleApp/iOS/SampleApp/main.jsbundle b/Examples/SampleApp/iOS/SampleApp/main.jsbundle deleted file mode 100644 index b702b30c66dc..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/main.jsbundle +++ /dev/null @@ -1,8 +0,0 @@ -// Offline JS -// To re-generate the offline bundle, run this from the root of your project: -// -// $ react-native bundle --minify -// -// See http://facebook.github.io/react-native/docs/runningondevice.html for more details. - -throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions'); diff --git a/Examples/SampleApp/iOS/SampleApp/main.m b/Examples/SampleApp/iOS/SampleApp/main.m deleted file mode 100644 index 3d767fcbb9fc..000000000000 --- a/Examples/SampleApp/iOS/SampleApp/main.m +++ /dev/null @@ -1,18 +0,0 @@ -/** - * 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. - */ - -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/Examples/SampleApp/iOS/SampleAppTests/Info.plist b/Examples/SampleApp/iOS/SampleAppTests/Info.plist deleted file mode 100644 index 886825ccc9bf..000000000000 --- a/Examples/SampleApp/iOS/SampleAppTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/Examples/SampleApp/iOS/SampleAppTests/SampleAppTests.m b/Examples/SampleApp/iOS/SampleAppTests/SampleAppTests.m deleted file mode 100644 index 14b9f0fbf0de..000000000000 --- a/Examples/SampleApp/iOS/SampleAppTests/SampleAppTests.m +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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. - */ - -#import -#import - -#import "RCTLog.h" -#import "RCTRootView.h" - -#define TIMEOUT_SECONDS 240 -#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" - -@interface SampleAppTests : XCTestCase - -@end - -@implementation SampleAppTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - - -@end diff --git a/Examples/SampleApp/index.android.js b/Examples/SampleApp/index.android.js deleted file mode 100644 index 47371fa5937e..000000000000 --- a/Examples/SampleApp/index.android.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - */ -'use strict'; - -var React = require('react-native'); -var { - AppRegistry, - StyleSheet, - Text, - View, -} = React; - -var SampleApp = React.createClass({ - render: function() { - return ( - - - Welcome to React Native! - - - To get started, edit index.android.js - - - Shake or press menu button for dev menu - - - ); - } -}); - -var styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - }, -}); - -AppRegistry.registerComponent('SampleApp', () => SampleApp); diff --git a/Examples/SampleApp/index.ios.js b/Examples/SampleApp/index.ios.js deleted file mode 100644 index 46c0712bd9b2..000000000000 --- a/Examples/SampleApp/index.ios.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - */ -'use strict'; - -var React = require('react-native'); -var { - AppRegistry, - StyleSheet, - Text, - View, -} = React; - -var SampleApp = React.createClass({ - render: function() { - return ( - - - Welcome to React Native! - - - To get started, edit index.ios.js - - - Press Cmd+R to reload,{'\n'} - Cmd+D or shake for dev menu - - - ); - } -}); - -var styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - }, -}); - -AppRegistry.registerComponent('SampleApp', () => SampleApp); diff --git a/package.json b/package.json index 3aae018a1149..dd748d9d574a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "files": [ "React", "React.podspec", - "Examples/SampleApp", "Libraries", "packager", "cli.js", diff --git a/settings.gradle b/settings.gradle index 01e8049c9a08..c952f9077a4c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ // Copyright 2004-present Facebook. All Rights Reserved. -include ':ReactAndroid', ':Examples:SampleApp:android:app', ':Examples:UIExplorer:android:app', ':Examples:Movies:android:app' \ No newline at end of file +include ':ReactAndroid', ':Examples:UIExplorer:android:app', ':Examples:Movies:android:app' From 96d204d1b42c450a239ecbb4f2ce19d8f8b7ca88 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 28 Sep 2015 12:22:26 -0700 Subject: [PATCH 0074/2702] Managed assets support for RCTConvert Reviewed By: @nicklockwood Differential Revision: D2443130 --- Examples/UIExplorer/TabBarIOSExample.js | 3 ++- Examples/UIExplorer/flux@3x.png | Bin 0 -> 1924 bytes .../Components/TabBarIOS/TabBarItemIOS.ios.js | 4 +++- .../Image/__tests__/resolveAssetSource-test.js | 6 ++++++ Libraries/Image/resolveAssetSource.js | 3 +++ React/Base/RCTConvert.m | 4 ++++ 6 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Examples/UIExplorer/flux@3x.png diff --git a/Examples/UIExplorer/TabBarIOSExample.js b/Examples/UIExplorer/TabBarIOSExample.js index 5ba3f0099d74..eccc84cbed4d 100644 --- a/Examples/UIExplorer/TabBarIOSExample.js +++ b/Examples/UIExplorer/TabBarIOSExample.js @@ -79,7 +79,8 @@ var TabBarExample = React.createClass({ {this._renderContent('#783E33', 'Red Tab', this.state.notifCount)} { this.setState({ diff --git a/Examples/UIExplorer/flux@3x.png b/Examples/UIExplorer/flux@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7bdef3b1012f9887e1e5b0b41cc8d9a8bce147b0 GIT binary patch literal 1924 zcmah~`#%$k1LcaMnJc7A9+%;IG>?hG*Eh{D%d43?W+E(WBpV^DDD%kj&Y0_23qu)M z7k_%dzdR!G zQ?K)K-G7>FI1C+*48?^<`Vj*~u3x+2PmCZ1or{34i-;Ts!)Dw-EE`GlI5gZVY#Ox2>_^XQThd?K_;6 zX=^yNusokg!S!@!Gh*Y5qIHqhSq+^6JQSpTs*ttY2tv}w8vlIT-;<-YGHS3|f$Lom zbmw3TO1T9_cZ2luqaeBnA4JlDYzPtZKMW<>>=9rRp=ch*OW4HHSC2JxO{s!LPzAcy z6>bb_L8ajkU}&hVB+k}=ew;Ef$x%Wccy&-(i@s$u$Y&+%ph)!JEDkovg*~*6am{xy zZr6NreXiQGzA-(R4~LMXTKD>XRh%VTYmcZYF9d?Br{NK~y;*8@8glt_fx_1|>D0Zy zOuOj9d}TaT@hWb<-`3{;nw}Y+n{45h`J(5OQ3GhZ&I9_yFiWNBv2u`%n5s$U;lWFw zn)V{Tc<+sy7KQ^F+l;du_X?uw*Q^Kqrh_J*-(4-Px`iTKP-h#xZ5j=AV?;rJ9mZe~&pdi3R#%e}aHpF$w9ztKZy zHGc3T*W<9?ex-NhHTI5{)6p+Dbsly?0_k>yo+2~6QVa8=jX_$1?}cBhtzJr)>|anUdadmiEOaiEyorv zs!zmcF#oUwl*yGf0C^c%sh@&$Oll$*mhCHH~CCc*^ z#Y2TJG0hfh=3% z7iIGFQy0H04_acqqR92~QQjfhw`JU$(tckVGDW(5`F{J6WLEorCd_KFIRpPr!@D`d zn#Ot($;2di{~T7ooIkPh;(TDW^Lk}~Xcz){1ovfOZ0a1ZIR)OSU6;}A9Yg;@65YQ% z{tM|cRi(?crH}ue%b!Z|RYF&vkcpLh*VeStOXG8=g_5%-91D)D=a^6M%49wE(Ok#g^+_8Wh91^# zAL*+N#tT%_GYht@9Dc>c&EJ1MdE6K~j2glie!gENmIU&tq*+($C6|Mtdoa(Q3|>`sN!ckBp0d9V_SNMfq?C^8>Vky(aBcF`?7ONXXM|V zW=br4vA|W$&DEJPz4E!3J@X&nyOqS#`!)qa+mj?O%(i8nkpp2Ip5UH8^MxYxi@G}C z{~$71XQl~U!4MCYJ?Yy!)5@S>S@>{O=X*(11^%)@Z8)uCxVY}HMOBojW4pb{s-wq{ z;8kS}&x2UboL4}dc^J{9E4!nVP|EEFr$nE;`H_fX7Eu|r3TiZJlQZxveFQ|u%XBET zXFY8ojy~=m=5Ea-OZDheH3a$0QR0sQb>xy8;3S|!JskwQ$#OQ4eyVZcvSe#7IU&dY z97@;)!U8kxq5Y+)4=HHJ`7=8EK%%j-u4Wu~1x^OHys;oP$GzXn8K;axdb>wfJKWU5 zG&A#(OuA;}Z-}7+c#W$UubqSOiuPU??UZ=>77g+l$6dtN&I|-F1+*Tg=gyyOyLzQQ uJWuyyYX_Yxl#8EeGZz2vM-mY+)jANXzpPi_S@Pq~zj-*!*|zq)Z^A#)$eo-3 literal 0 HcmV?d00001 diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index f44fb0493b91..94285e466ae9 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -15,6 +15,7 @@ var React = require('React'); var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); var View = require('View'); +var resolveAssetSource = require('resolveAssetSource'); var requireNativeComponent = require('requireNativeComponent'); @@ -114,7 +115,8 @@ var TabBarItemIOS = React.createClass({ return ( {tabContents} diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index 9c81dcdc4c35..5d5bdc0e6f37 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -77,10 +77,12 @@ describe('resolveAssetSource', () => { name: 'logo', type: 'png', }, { + __packager_asset: true, isStatic: false, width: 100, height: 200, uri: 'http://10.0.0.1:8081/assets/module/a/logo.png?platform=ios&hash=5b6f00f', + scale: 1, }); }); @@ -96,10 +98,12 @@ describe('resolveAssetSource', () => { name: 'logo', type: 'png', }, { + __packager_asset: true, isStatic: false, width: 100, height: 200, uri: 'http://10.0.0.1:8081/assets/module/a/logo@2x.png?platform=ios&hash=5b6f00f', + scale: 2, }); }); @@ -125,6 +129,7 @@ describe('resolveAssetSource', () => { name: 'logo', type: 'png', }, { + __packager_asset: true, isStatic: true, width: 100, height: 200, @@ -153,6 +158,7 @@ describe('resolveAssetSource', () => { name: '!@Logo#1_€', // Invalid chars shouldn't get passed to native type: 'png', }, { + __packager_asset: true, isStatic: true, width: 100, height: 200, diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 00fb8c84d8ff..29323afa65ae 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -121,13 +121,16 @@ function assetToImageSource(asset) { var devServerURL = getDevServerURL(); if (devServerURL) { return { + __packager_asset: true, width: asset.width, height: asset.height, uri: getPathOnDevserver(devServerURL, asset), isStatic: false, + scale: pickScale(asset.scales, PixelRatio.get()), }; } else { return { + __packager_asset: true, width: asset.width, height: asset.height, uri: getPathInArchive(asset), diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 3a79abced839..aa99b3e44e50 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -413,11 +413,13 @@ + (UIImage *)UIImage:(id)json UIImage *image; NSString *path; CGFloat scale = 0.0; + BOOL isPackagerAsset = NO; if ([json isKindOfClass:[NSString class]]) { path = json; } else if ([json isKindOfClass:[NSDictionary class]]) { path = [self NSString:json[@"uri"]]; scale = [self CGFloat:json[@"scale"]]; + isPackagerAsset = [self BOOL:json[@"__packager_asset"]]; } else { RCTLogConvertError(json, @"an image"); } @@ -459,6 +461,8 @@ + (UIImage *)UIImage:(id)json } else if ([scheme isEqualToString:@"data"]) { image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; + } else if ([scheme isEqualToString:@"http"] && isPackagerAsset) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; } else { RCTLogConvertError(json, @"an image. Only local files or data URIs are supported"); } From 54f899d5ae027b103dd0434c3661e676d81b353f Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 28 Sep 2015 14:58:08 -0700 Subject: [PATCH 0075/2702] Don't call originalConsole methods when they don't exist Reviewed By: @vjeux Differential Revision: D2485562 --- .../DependencyResolver/polyfills/console.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/polyfills/console.js b/packager/react-packager/src/DependencyResolver/polyfills/console.js index e841aebb8471..b6f20c7616bf 100644 --- a/packager/react-packager/src/DependencyResolver/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/polyfills/console.js @@ -468,13 +468,17 @@ // If available, also call the original `console` method since that is // sometimes useful. Ex: on OS X, this will let you see rich output in // the Safari Web Inspector console. - Object.keys(global.console).forEach(methodName => { - var reactNativeMethod = global.console[methodName]; - global.console[methodName] = function() { - originalConsole[methodName](...arguments); - reactNativeMethod.apply(global.console, arguments); - }; - }); + if (__DEV__ && originalConsole) { + Object.keys(global.console).forEach(methodName => { + var reactNativeMethod = global.console[methodName]; + if (originalConsole[methodName]) { + global.console[methodName] = function() { + originalConsole[methodName](...arguments); + reactNativeMethod.apply(global.console, arguments); + }; + } + }); + } } if (typeof module !== 'undefined') { From 47bb46fa8d9f969598f97ff11c251cb763b354e0 Mon Sep 17 00:00:00 2001 From: Yinan Na Date: Mon, 28 Sep 2015 16:30:58 -0700 Subject: [PATCH 0076/2702] RC Fix storyline cannot open bug (iOS 7) Reviewed By: @jingc Differential Revision: D2486832 --- Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m index 331fd5bb91f4..3ccbc34b4873 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m @@ -76,7 +76,14 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSiz sizeBeingLoaded = CGSizeMake(pointSize.width * representation.scale, pointSize.height * representation.scale); } - CGSize screenSize = UIScreen.mainScreen.nativeBounds.size; + CGSize screenSize; + if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) { + screenSize = UIScreen.mainScreen.nativeBounds.size; + } else { + CGSize mainScreenSize = [UIScreen mainScreen].bounds.size; + CGFloat mainScreenScale = [[UIScreen mainScreen] scale]; + screenSize = CGSizeMake(mainScreenSize.width * mainScreenScale, mainScreenSize.height * mainScreenScale); + } CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height); if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) { From de9ab22230468e6516797ddf36073f059d21b0b7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 28 Sep 2015 22:40:56 -0700 Subject: [PATCH 0077/2702] Pass in the platform options when loading dependencies Reviewed By: @jingc Differential Revision: D2488262 --- packager/react-packager/index.js | 4 +-- packager/react-packager/src/Server/index.js | 26 ++++++++++++++++++-- private-cli/src/dependencies/dependencies.js | 13 ++++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index f399eae093f8..f42211cb7671 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -47,9 +47,9 @@ exports.buildBundleFromUrl = function(options, reqUrl) { }); }; -exports.getDependencies = function(options, main) { +exports.getDependencies = function(options, bundleOptions) { var server = createServer(options); - return server.getDependencies(main) + return server.getDependencies(bundleOptions) .then(function(r) { server.end(); return r.dependencies; diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index f8113dbbf444..346f4bcb0d5c 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -96,6 +96,21 @@ const bundleOpts = declareOpts({ } }); +const dependencyOpts = declareOpts({ + platform: { + type: 'string', + required: true, + }, + dev: { + type: 'boolean', + default: true, + }, + entryFile: { + type: 'string', + required: true, + }, +}); + class Server { constructor(options) { const opts = validateOpts(options); @@ -174,8 +189,15 @@ class Server { return this.buildBundle(options); } - getDependencies(main) { - return this._bundler.getDependencies(main); + getDependencies(options) { + return Promise.resolve().then(() => { + const opts = dependencyOpts(options); + return this._bundler.getDependencies( + opts.entryFile, + opts.dev, + opts.platform, + ); + }); } _onFileChange(type, filepath, root) { diff --git a/private-cli/src/dependencies/dependencies.js b/private-cli/src/dependencies/dependencies.js index e4c9ae7859ea..c0bb4a8886d0 100644 --- a/private-cli/src/dependencies/dependencies.js +++ b/private-cli/src/dependencies/dependencies.js @@ -35,7 +35,11 @@ function _dependencies(argv, conf, resolve, reject) { command: 'output', description: 'File name where to store the output, ex. /tmp/dependencies.txt', type: 'string', - } + }, { + command: 'platform', + description: 'The platform extension used for selecting modules', + type: 'string', + }, ], argv); const rootModuleAbsolutePath = args['entry-file']; @@ -57,6 +61,11 @@ function _dependencies(argv, conf, resolve, reject) { ) )[0]; + const options = { + platform: args.platform, + entryFile: relativePath, + }; + const writeToFile = args.output; const outStream = writeToFile ? fs.createWriteStream(args.output) @@ -66,7 +75,7 @@ function _dependencies(argv, conf, resolve, reject) { log('Waiting for the packager.'); resolve(ReactPackager.createClientFor(config).then(client => { log('Packager client was created'); - return client.getDependencies(relativePath) + return client.getDependencies(options) .then(deps => { log('Packager returned dependencies'); client.close(); From 100a35244ffc8c5eb82bc7f7add98e825de45e55 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 29 Sep 2015 03:14:14 -0700 Subject: [PATCH 0078/2702] Move immediates to React batchedUpdates Reviewed By: @jspahrsummers Differential Revision: D2484935 --- Libraries/Utilities/MessageQueue.js | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 007b0dfca571..729d150e800c 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -83,11 +83,19 @@ class MessageQueue { '__callFunction' : '__invokeCallback'; guard(() => this[method].apply(this, call.args)); }); - BridgeProfiling.profile('ReactUpdates.batchedUpdates()'); + + this.__callImmediates(); }); - BridgeProfiling.profileEnd(); + + // batchedUpdates might still trigger setImmediates + while (JSTimersExecution.immediates.length) { + ReactUpdates.batchedUpdates(() => { + this.__callImmediates(); + }); + } }); - return this.flushedQueue(); + + return this.__flushedQueue(); } callFunctionReturnFlushedQueue(module, method, args) { @@ -101,17 +109,25 @@ class MessageQueue { } flushedQueue() { + this.__callImmediates(); + return this.__flushedQueue(); + } + + /** + * "Private" methods + */ + + __callImmediates() { BridgeProfiling.profile('JSTimersExecution.callImmediates()'); guard(() => JSTimersExecution.callImmediates()); BridgeProfiling.profileEnd(); + } + + __flushedQueue() { let queue = this._queue; this._queue = [[],[],[]]; return queue[0].length ? queue : null; } - - /** - * "Private" methods - */ __nativeCall(module, method, params, onFail, onSucc) { if (onFail || onSucc) { // eventually delete old debug info From 557e26ab08c85da1a33af34643499090ce2915e0 Mon Sep 17 00:00:00 2001 From: Alexey Lang Date: Tue, 29 Sep 2015 05:26:01 -0700 Subject: [PATCH 0079/2702] Pause JS DisplayLink if nothing to process. Reviewed By: @jspahrsummers Differential Revision: D2489107 --- React/Base/RCTBatchedBridge.m | 30 +++++++++++++++++++++++++++++- React/Base/RCTEventDispatcher.m | 16 ++++++++++++++-- React/Base/RCTFrameUpdate.h | 10 +++++++--- React/Modules/RCTTiming.m | 15 +++++++++++++-- React/Views/RCTNavigator.m | 15 +++++++++++++-- 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index e4b8e782810a..3559b58c37b1 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -309,6 +309,15 @@ - (NSString *)moduleConfig config[moduleData.name] = moduleData.config; if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { [_frameUpdateObservers addObject:moduleData]; + + id observer = (id)moduleData.instance; + __weak typeof(self) weakSelf = self; + __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; + observer.pauseCallback = ^{ + [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ + [weakSelf updateJSDisplayLinkState]; + }]; + }; } } @@ -317,6 +326,23 @@ - (NSString *)moduleConfig }, NULL); } +- (void)updateJSDisplayLinkState +{ + RCTAssertJSThread(); + + BOOL pauseDisplayLink = ![_scheduledCallbacks count] && ![_scheduledCalls count]; + if (pauseDisplayLink) { + for (RCTModuleData *moduleData in _frameUpdateObservers) { + id observer = (id)moduleData.instance; + if (!observer.paused) { + pauseDisplayLink = NO; + break; + } + } + } + _jsDisplayLink.paused = pauseDisplayLink; +} + - (void)injectJSONConfiguration:(NSString *)configJSON onComplete:(void (^)(NSError *))onComplete { @@ -620,6 +646,7 @@ - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arg } else { [strongSelf->_scheduledCalls addObject:call]; } + [strongSelf updateJSDisplayLinkState]; RCTProfileEndEvent(0, @"objc_call", call); }]; @@ -804,7 +831,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (RCTModuleData *moduleData in _frameUpdateObservers) { id observer = (id)moduleData.instance; - if (![observer respondsToSelector:@selector(isPaused)] || !observer.paused) { + if (!observer.paused) { RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) RCTProfileBeginFlowEvent(); @@ -833,6 +860,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"processBatch" arguments:@[[calls valueForKey:@"js_args"]]]; + [self updateJSDisplayLinkState]; } RCTProfileEndEvent(0, @"objc_call", nil); diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index 484cf2f0a087..48f98e457570 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -93,18 +93,30 @@ @implementation RCTEventDispatcher @synthesize bridge = _bridge; @synthesize paused = _paused; +@synthesize pauseCallback = _pauseCallback; RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { + _paused = YES; _eventQueue = [NSMutableDictionary new]; _eventQueueLock = [NSLock new]; } return self; } +- (void)setPaused:(BOOL)paused +{ + if (_paused != paused) { + _paused = paused; + if (_pauseCallback) { + _pauseCallback(); + } + } +} + - (void)sendAppEventWithName:(NSString *)name body:(id)body { [_bridge enqueueJSCall:@"RCTNativeAppEventEmitter.emit" @@ -169,7 +181,7 @@ - (void)sendEvent:(id)event } _eventQueue[eventID] = event; - _paused = NO; + self.paused = NO; [_eventQueueLock unlock]; } @@ -202,7 +214,7 @@ - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update [_eventQueueLock lock]; NSDictionary *eventQueue = _eventQueue; _eventQueue = [NSMutableDictionary new]; - _paused = YES; + self.paused = YES; [_eventQueueLock unlock]; for (id event in eventQueue.allValues) { diff --git a/React/Base/RCTFrameUpdate.h b/React/Base/RCTFrameUpdate.h index f14bd5b86d16..bbf4bb6c9be9 100644 --- a/React/Base/RCTFrameUpdate.h +++ b/React/Base/RCTFrameUpdate.h @@ -40,11 +40,15 @@ */ - (void)didUpdateFrame:(RCTFrameUpdate *)update; -@optional - /** * Synthesize and set to true to pause the calls to -[didUpdateFrame:] */ -@property (nonatomic, assign, getter=isPaused) BOOL paused; +@property (nonatomic, readonly, getter=isPaused) BOOL paused; + +/** + * Callback for pause/resume observer. + * Observer should call it when paused property is changed. + */ +@property (nonatomic, copy) dispatch_block_t pauseCallback; @end diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 2754925e2656..9da28d9df74a 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -71,6 +71,7 @@ @implementation RCTTiming @synthesize bridge = _bridge; @synthesize paused = _paused; +@synthesize pauseCallback = _pauseCallback; RCT_EXPORT_MODULE() @@ -120,7 +121,7 @@ - (void)invalidate - (void)stopTimers { - _paused = YES; + self.paused = YES; } - (void)startTimers @@ -129,7 +130,17 @@ - (void)startTimers return; } - _paused = NO; + self.paused = NO; +} + +- (void)setPaused:(BOOL)paused +{ + if (_paused != paused) { + _paused = paused; + if (_pauseCallback) { + _pauseCallback(); + } + } } - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update diff --git a/React/Views/RCTNavigator.m b/React/Views/RCTNavigator.m index a79fe099298e..8b67222aaed6 100644 --- a/React/Views/RCTNavigator.m +++ b/React/Views/RCTNavigator.m @@ -269,6 +269,7 @@ @implementation RCTNavigator } @synthesize paused = _paused; +@synthesize pauseCallback = _pauseCallback; - (instancetype)initWithBridge:(RCTBridge *)bridge { @@ -321,6 +322,16 @@ - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update } } +- (void)setPaused:(BOOL)paused +{ + if (_paused != paused) { + _paused = paused; + if (_pauseCallback) { + _pauseCallback(); + } + } +} + - (void)dealloc { _navigationController.delegate = nil; @@ -355,14 +366,14 @@ - (void)navigationController:(UINavigationController *)navigationController _dummyView.frame = (CGRect){{destination, 0}, CGSizeZero}; _currentlyTransitioningFrom = indexOfFrom; _currentlyTransitioningTo = indexOfTo; - _paused = NO; + self.paused = NO; } completion:^(__unused id context) { [weakSelf freeLock]; _currentlyTransitioningFrom = 0; _currentlyTransitioningTo = 0; _dummyView.frame = CGRectZero; - _paused = YES; + self.paused = YES; // Reset the parallel position tracker }]; } From 253a5bb9df14d2b2c92569fde08cc3cac583686a Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 29 Sep 2015 09:06:23 -0700 Subject: [PATCH 0080/2702] Log column number in RCTRedBox messages Reviewed By: @jspahrsummers Differential Revision: D2489364 --- React/Modules/RCTRedBox.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index ecf3fd921510..733a43713188 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -193,9 +193,8 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictiona } cell.textLabel.text = stackFrame[@"methodName"]; - - NSString *fileAndLine = [stackFrame[@"file"] lastPathComponent]; - cell.detailTextLabel.text = fileAndLine ? [fileAndLine stringByAppendingFormat:@":%@", stackFrame[@"lineNumber"]] : nil; + cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ @ %@:%@", + [stackFrame[@"file"] lastPathComponent], stackFrame[@"lineNumber"], stackFrame[@"column"]]; return cell; } From f8546e075cbb56b02d455ea581bd8a78b7cce35d Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 29 Sep 2015 09:12:19 -0700 Subject: [PATCH 0081/2702] Fix background color issue Differential Revision: D2489578 committer: Service User --- Examples/UIExplorer/TextExample.android.js | 9 +++++++++ Examples/UIExplorer/TextInputExample.android.js | 7 +++++++ .../facebook/react/views/text/ReactTextShadowNode.java | 5 +++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/TextExample.android.js index 4159d0c18f00..42d17ed07a43 100644 --- a/Examples/UIExplorer/TextExample.android.js +++ b/Examples/UIExplorer/TextExample.android.js @@ -310,6 +310,15 @@ var TextExample = React.createClass({ + + Same alpha as background, + + Inherited alpha from background, + + Reapply alpha + + + diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js index 9659e9180fd6..99f28231f788 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/TextInputExample.android.js @@ -221,6 +221,13 @@ exports.examples = [ style={styles.singleLine} underlineColorAndroid="blue" /> + + + Darker backgroundColor + + ); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 8fb0ee4d6b12..21540b59c0c8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -134,7 +134,7 @@ protected static final Spanned fromTextCSSNode(ReactTextShadowNode textCSSNode) // a new spannable will be wiped out List ops = new ArrayList(); buildSpannedFromTextCSSNode(textCSSNode, sb, ops); - if (textCSSNode.mFontSize == -1) { + if (textCSSNode.mFontSize == UNSET) { sb.setSpan( new AbsoluteSizeSpan((int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))), 0, @@ -315,7 +315,8 @@ public void updateProperties(CatalystStylesDiffMap styles) { } markUpdated(); } - if (styles.hasKey(ViewProps.BACKGROUND_COLOR)) { + // Don't apply background color to anchor TextView since it will be applied on the View directly + if (styles.hasKey(ViewProps.BACKGROUND_COLOR) && this.isVirtualAnchor() == false) { if (styles.isNull(ViewProps.BACKGROUND_COLOR)) { mIsBackgroundColorSet = false; } else { From 6a85d3dda45a3808e3ebe56ceac83345dedd5d25 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 29 Sep 2015 10:33:43 -0700 Subject: [PATCH 0082/2702] Use bundleForClass instead of mainBundle to find resources Reviewed By: @jspahrsummers Differential Revision: D2485109 --- .../RCTConvert_NSURLTests.m | 2 +- Libraries/Image/RCTAssetBundleImageLoader.m | 7 ++-- Libraries/Image/RCTImageDownloader.m | 3 +- React/Base/RCTConvert.m | 40 ++++++++----------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m index a42d587b0698..0de322a88a3e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m @@ -36,7 +36,7 @@ - (void)test_##name { \ } \ #define TEST_BUNDLE_PATH(name, _input, _expectedPath) \ -TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:_expectedPath]) +TEST_PATH(name, _input, [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:_expectedPath]) // Basic tests TEST_URL(basic, @"http://example.com", @"http://example.com") diff --git a/Libraries/Image/RCTAssetBundleImageLoader.m b/Libraries/Image/RCTAssetBundleImageLoader.m index fb9aaa057217..3ac517c41bee 100644 --- a/Libraries/Image/RCTAssetBundleImageLoader.m +++ b/Libraries/Image/RCTAssetBundleImageLoader.m @@ -21,7 +21,7 @@ - (NSString *)imageNameForRequestURL:(NSURL *)requestURL return nil; } - NSString *resourcesPath = [NSBundle mainBundle].resourcePath; + NSString *resourcesPath = [NSBundle bundleForClass:[self class]].resourcePath; NSString *requestPath = requestURL.absoluteURL.path; if (requestPath.length < resourcesPath.length + 1) { return nil; @@ -37,8 +37,9 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL return NO; } - if ([[NSBundle mainBundle] URLForResource:imageName withExtension:nil] || - [[NSBundle mainBundle] URLForResource:imageName withExtension:@"png"]) { + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + if ([bundle URLForResource:imageName withExtension:nil] || + [bundle URLForResource:imageName withExtension:@"png"]) { return YES; } diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index c2aa89f73a4a..e299b13282f1 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -39,7 +39,8 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL // Have to exclude 'file://' from the main bundle, otherwise this would conflict with RCTAssetBundleImageLoader return [requestURL.scheme compare:@"http" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 4)] == NSOrderedSame || - ([requestURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame && ![requestURL.path hasPrefix:[NSBundle mainBundle].resourcePath]) || + ([requestURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame && + ![requestURL.path hasPrefix:[NSBundle bundleForClass:[self class]].resourcePath]) || [requestURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame; } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index aa99b3e44e50..79efea631f61 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -101,7 +101,7 @@ + (NSURL *)NSURL:(id)json path = path.stringByExpandingTildeInPath; } else if (!path.absolutePath) { // Assume it's a resource path - path = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:path]; + path = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:path]; } return [NSURL fileURLWithPath:path]; } @@ -426,28 +426,22 @@ + (UIImage *)UIImage:(id)json NSURL *URL = [self NSURL:path]; NSString *scheme = URL.scheme.lowercaseString; - if (path && [scheme isEqualToString:@"file"]) { - if (RCT_DEBUG || [NSThread currentThread] == [NSThread mainThread]) { - if ([URL.path hasPrefix:[NSBundle mainBundle].resourcePath]) { - // Image may reside inside a .car file, in which case we have no choice - // but to use +[UIImage imageNamed] - but this method isn't thread safe - static NSMutableDictionary *XCAssetMap = nil; - if (!XCAssetMap) { - XCAssetMap = [NSMutableDictionary new]; - } - NSNumber *isAsset = XCAssetMap[path]; - if (!isAsset || isAsset.boolValue) { - image = [UIImage imageNamed:path]; - if (RCT_DEBUG && image) { - // If we succeeded in loading the image via imageNamed, and the - // method wasn't called on the main thread, that's a coding error - RCTAssertMainThread(); - } - } - if (!isAsset) { - // Avoid calling `+imageNamed` again in future if it's not needed. - XCAssetMap[path] = @(image != nil); - } + if (URL && [scheme isEqualToString:@"file"]) { + RCTAssertMainThread(); + if ([URL.path hasPrefix:[NSBundle bundleForClass:[self class]].resourcePath]) { + // Image may reside inside a .car file, in which case we have no choice + // but to use +[UIImage imageNamed] - but this method isn't thread safe + static NSMutableDictionary *XCAssetMap = nil; + if (!XCAssetMap) { + XCAssetMap = [NSMutableDictionary new]; + } + NSNumber *isAsset = XCAssetMap[path]; + if (!isAsset || isAsset.boolValue) { + image = [UIImage imageNamed:URL.path]; + } + if (!isAsset) { + // Avoid calling `+imageNamed` again in future if it's not needed. + XCAssetMap[path] = @(image != nil); } } From 6c615721a33ed3774dd96c8c0f1a3e2171621d61 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 29 Sep 2015 12:41:06 -0700 Subject: [PATCH 0083/2702] remove randomness Reviewed By: @javache Differential Revision: D2490001 --- .../src/DependencyResolver/DependencyGraph/HasteMap.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 386651cf2b5f..6d720ea50cd2 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -103,16 +103,14 @@ class HasteMap { chalk.yellow( '\nWARNING: Found multiple haste modules or packages ' + 'with the name `%s`. Please fix this by adding it to ' + - 'the blacklist or deleting the modules keeping only one.\n' + - 'One of the following modules will be selected at random:\n%s\n' + 'the blacklist or deleting the modules keeping only one.\n' ), name, modules.map(m => m.path).join('\n'), ); } - const randomIndex = Math.floor(Math.random() * modules.length); - return modules[randomIndex]; + return modules[0]; } return modules[0]; From fdbb9cd3a830c1566294e6bcf956d1658aa794c0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 29 Sep 2015 16:06:42 -0700 Subject: [PATCH 0084/2702] Fix debug namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Have a top-level debug namespace: `ReactNativePackager` And add a couple of debugs in the transformer. This is ground work for adding a verbose option. Reviewed By: @DmitrySoshnikov Differential Revision: D2489960 --- .../DependencyResolver/DependencyGraph/DeprecatedAssetMap.js | 2 +- .../DependencyResolver/DependencyGraph/ResolutionRequest.js | 2 +- .../react-packager/src/DependencyResolver/crawlers/node.js | 2 +- packager/react-packager/src/JSTransformer/index.js | 5 +++++ packager/react-packager/src/SocketInterface/SocketClient.js | 2 +- packager/react-packager/src/SocketInterface/SocketServer.js | 2 +- packager/react-packager/src/SocketInterface/index.js | 4 ++-- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index d4900b809891..ca537078f773 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -11,7 +11,7 @@ const Activity = require('../../Activity'); const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); -const debug = require('debug')('ReactPackager:DependencyGraph'); +const debug = require('debug')('ReactNativePackager:DependencyGraph'); const path = require('path'); class DeprecatedAssetMap { diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index 2ed0dc9f202d..f3a7553753b3 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -8,7 +8,7 @@ */ 'use strict'; -const debug = require('debug')('ReactPackager:DependencyGraph'); +const debug = require('debug')('ReactNativePackager:DependencyGraph'); const util = require('util'); const path = require('path'); const isAbsolutePath = require('absolute-path'); diff --git a/packager/react-packager/src/DependencyResolver/crawlers/node.js b/packager/react-packager/src/DependencyResolver/crawlers/node.js index 72030905d689..528cd5e7648e 100644 --- a/packager/react-packager/src/DependencyResolver/crawlers/node.js +++ b/packager/react-packager/src/DependencyResolver/crawlers/node.js @@ -1,7 +1,7 @@ 'use strict'; const Promise = require('promise'); -const debug = require('debug')('DependencyGraph'); +const debug = require('debug')('ReactNativePackager:DependencyGraph'); const fs = require('fs'); const path = require('path'); diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 9bf191390ba1..5c819381caf8 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -14,6 +14,7 @@ const declareOpts = require('../lib/declareOpts'); const fs = require('fs'); const util = require('util'); const workerFarm = require('worker-farm'); +const debug = require('debug')('ReactNativePackager:JStransformer'); const readFile = Promise.denodeify(fs.readFile); @@ -86,6 +87,8 @@ class Transformer { return Promise.reject(new Error('No transfrom module')); } + debug('transforming file', filePath); + return this._cache.get( filePath, 'transformedSource', @@ -107,6 +110,8 @@ class Transformer { throw formatError(res.error, filePath); } + debug('done transforming file', filePath); + return new ModuleTransport({ code: res.code, map: res.map, diff --git a/packager/react-packager/src/SocketInterface/SocketClient.js b/packager/react-packager/src/SocketInterface/SocketClient.js index 2a353f276505..b89f5cca4ee5 100644 --- a/packager/react-packager/src/SocketInterface/SocketClient.js +++ b/packager/react-packager/src/SocketInterface/SocketClient.js @@ -11,7 +11,7 @@ const Bundle = require('../Bundler/Bundle'); const Promise = require('promise'); const bser = require('bser'); -const debug = require('debug')('ReactPackager:SocketClient'); +const debug = require('debug')('ReactNativePackager:SocketClient'); const fs = require('fs'); const net = require('net'); const path = require('path'); diff --git a/packager/react-packager/src/SocketInterface/SocketServer.js b/packager/react-packager/src/SocketInterface/SocketServer.js index 3d8e3a337d09..5098ea8706c4 100644 --- a/packager/react-packager/src/SocketInterface/SocketServer.js +++ b/packager/react-packager/src/SocketInterface/SocketServer.js @@ -11,7 +11,7 @@ const Promise = require('promise'); const Server = require('../Server'); const bser = require('bser'); -const debug = require('debug')('ReactPackager:SocketServer'); +const debug = require('debug')('ReactNativePackager:SocketServer'); const fs = require('fs'); const net = require('net'); diff --git a/packager/react-packager/src/SocketInterface/index.js b/packager/react-packager/src/SocketInterface/index.js index eab83fa4f3d1..2a885b1cb5c8 100644 --- a/packager/react-packager/src/SocketInterface/index.js +++ b/packager/react-packager/src/SocketInterface/index.js @@ -13,7 +13,7 @@ const SocketClient = require('./SocketClient'); const SocketServer = require('./SocketServer'); const _ = require('underscore'); const crypto = require('crypto'); -const debug = require('debug')('ReactPackager:SocketInterface'); +const debug = require('debug')('ReactNativePackager:SocketInterface'); const fs = require('fs'); const net = require('net'); const path = require('path'); @@ -90,7 +90,7 @@ function createServer(resolve, reject, options, sockPath) { // Enable server debugging by default since it's going to a log file. const env = _.clone(process.env); - env.DEBUG = 'ReactPackager:SocketServer'; + env.DEBUG = 'ReactNativePackager:SocketServer'; // We have to go through the main entry point to make sure // we go through the babel require hook. From 76fb9df095a4d9e1932169b75342fcf393e318d7 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 29 Sep 2015 18:32:12 -0700 Subject: [PATCH 0085/2702] Introduce `getOrderedDependencyPaths` that gets all concrete dependecy paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Since we added packager-managed assets -- internally we still think of asset dependency as a single "module". In reality there are multiple files that represent this module. This becomes important with the `getDependencies` API which is used by Buck to inform it on what to rebuild. Since `getDependencies` deals with modules, and is more of an internal API, I've introduced a new one and would go on to deprecate this. Reviewed By: @frantic Differential Revision: D2487207 --- .../src/Bundler/__tests__/Bundler-test.js | 118 ++++++++++++++---- packager/react-packager/src/Bundler/index.js | 32 +++++ packager/react-packager/src/Server/index.js | 7 ++ .../src/SocketInterface/SocketClient.js | 7 ++ .../src/SocketInterface/SocketServer.js | 8 ++ private-cli/src/dependencies/dependencies.js | 10 +- 6 files changed, 153 insertions(+), 29 deletions(-) diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js index b397f24e33df..74a246b80709 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -24,6 +24,27 @@ var sizeOf = require('image-size'); var fs = require('fs'); describe('Bundler', function() { + + function createModule({ + path, + id, + dependencies, + isAsset, + isAsset_DEPRECATED, + isJSON, + resolution, + }) { + return { + path, + resolution, + getDependencies() { return Promise.resolve(dependencies); }, + getName() { return Promise.resolve(id); }, + isJSON() { return isJSON; }, + isAsset() { return isAsset; }, + isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, + }; + } + var getDependencies; var wrapModule; var bundler; @@ -59,27 +80,6 @@ describe('Bundler', function() { assetServer: assetServer, }); - - function createModule({ - path, - id, - dependencies, - isAsset, - isAsset_DEPRECATED, - isJSON, - resolution, - }) { - return { - path, - resolution, - getDependencies() { return Promise.resolve(dependencies); }, - getName() { return Promise.resolve(id); }, - isJSON() { return isJSON; }, - isAsset() { return isAsset; }, - isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, - }; - } - modules = [ createModule({id: 'foo', path: '/root/foo.js', dependencies: []}), createModule({id: 'bar', path: '/root/bar.js', dependencies: []}), @@ -129,18 +129,23 @@ describe('Bundler', function() { sizeOf.mockImpl(function(path, cb) { cb(null, { width: 50, height: 100 }); }); + }); - assetServer.getAssetData.mockImpl(function() { + pit('create a bundle', function() { + assetServer.getAssetData.mockImpl(() => { return { scales: [1,2,3], + files: [ + '/root/img/img.png', + '/root/img/img@2x.png', + '/root/img/img@3x.png', + ], hash: 'i am a hash', name: 'img', type: 'png', }; }); - }); - pit('create a bundle', function() { return bundler.bundle('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ @@ -186,6 +191,11 @@ describe('Bundler', function() { width: 25, height: 50, scales: [1, 2, 3], + files: [ + '/root/img/img.png', + '/root/img/img@2x.png', + '/root/img/img@3x.png', + ], hash: 'i am a hash', name: 'img', type: 'png', @@ -235,4 +245,64 @@ describe('Bundler', function() { .toBeCalledWith('/root/foo.js', { dev: true }) ); }); + + describe('getOrderedDependencyPaths', () => { + beforeEach(() => { + assetServer.getAssetData.mockImpl(function(relPath) { + if (relPath === 'img/new_image.png') { + return { + scales: [1,2,3], + files: [ + '/root/img/new_image.png', + '/root/img/new_image@2x.png', + '/root/img/new_image@3x.png', + ], + hash: 'i am a hash', + name: 'img', + type: 'png', + }; + } else if (relPath === 'img/new_image2.png') { + return { + scales: [1,2,3], + files: [ + '/root/img/new_image2.png', + '/root/img/new_image2@2x.png', + '/root/img/new_image2@3x.png', + ], + hash: 'i am a hash', + name: 'img', + type: 'png', + }; + } + + throw new Error('unknown image ' + relPath); + }); + }); + + pit('should get the concrete list of all dependency files', () => { + modules.push( + createModule({ + id: 'new_image2.png', + path: '/root/img/new_image2.png', + isAsset: true, + resolution: 2, + dependencies: [] + }), + ); + + return bundler.getOrderedDependencyPaths('/root/foo.js', true) + .then((paths) => expect(paths).toEqual([ + '/root/foo.js', + '/root/bar.js', + '/root/img/img.png', + '/root/img/new_image.png', + '/root/img/new_image@2x.png', + '/root/img/new_image@3x.png', + '/root/file.json', + '/root/img/new_image2.png', + '/root/img/new_image2@2x.png', + '/root/img/new_image2@3x.png', + ])); + }); + }); }); diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index d2d8ae7f6543..289911d4c838 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -187,6 +187,38 @@ class Bundler { return this._resolver.getDependencies(main, { dev: isDev, platform }); } + getOrderedDependencyPaths({ entryFile, dev, platform }) { + return this.getDependencies(entryFile, dev, platform).then( + ({ dependencies }) => { + const ret = []; + const promises = []; + const placeHolder = {}; + dependencies.forEach(dep => { + if (dep.isAsset()) { + const relPath = getPathRelativeToRoot( + this._projectRoots, + dep.path + ); + promises.push( + this._assetServer.getAssetData(relPath, platform) + ); + ret.push(placeHolder); + } else { + ret.push(dep.path); + } + }); + + return Promise.all(promises).then(assetsData => { + assetsData.forEach(({ files }) => { + const index = ret.indexOf(placeHolder); + ret.splice(index, 1, ...files); + }); + return ret; + }); + } + ); + } + _transformModule(bundle, response, module, platform = null) { let transform; diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 346f4bcb0d5c..74e839272c1a 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -200,6 +200,13 @@ class Server { }); } + getOrderedDependencyPaths(options) { + return Promise.resolve().then(() => { + const opts = dependencyOpts(options); + return this._bundler.getOrderedDependencyPaths(opts); + }); + } + _onFileChange(type, filepath, root) { const absPath = path.join(root, filepath); this._bundler.invalidateFile(absPath); diff --git a/packager/react-packager/src/SocketInterface/SocketClient.js b/packager/react-packager/src/SocketInterface/SocketClient.js index b89f5cca4ee5..41ce0df14ee2 100644 --- a/packager/react-packager/src/SocketInterface/SocketClient.js +++ b/packager/react-packager/src/SocketInterface/SocketClient.js @@ -86,6 +86,13 @@ class SocketClient { }); } + getOrderedDependencyPaths(main) { + return this._send({ + type: 'getOrderedDependencyPaths', + data: main, + }); + } + buildBundle(options) { return this._send({ type: 'buildBundle', diff --git a/packager/react-packager/src/SocketInterface/SocketServer.js b/packager/react-packager/src/SocketInterface/SocketServer.js index 5098ea8706c4..8258873a4bd5 100644 --- a/packager/react-packager/src/SocketInterface/SocketServer.js +++ b/packager/react-packager/src/SocketInterface/SocketServer.js @@ -121,6 +121,14 @@ class SocketServer { ); break; + case 'getOrderedDependencyPaths': + this._jobs++; + this._packagerServer.getOrderedDependencyPaths(m.data).then( + (dependencies) => this._reply(sock, m.id, 'result', dependencies), + handleError, + ); + break; + default: this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type); } diff --git a/private-cli/src/dependencies/dependencies.js b/private-cli/src/dependencies/dependencies.js index c0bb4a8886d0..01bc79de96e0 100644 --- a/private-cli/src/dependencies/dependencies.js +++ b/private-cli/src/dependencies/dependencies.js @@ -39,7 +39,7 @@ function _dependencies(argv, conf, resolve, reject) { command: 'platform', description: 'The platform extension used for selecting modules', type: 'string', - }, + } ], argv); const rootModuleAbsolutePath = args['entry-file']; @@ -75,22 +75,22 @@ function _dependencies(argv, conf, resolve, reject) { log('Waiting for the packager.'); resolve(ReactPackager.createClientFor(config).then(client => { log('Packager client was created'); - return client.getDependencies(options) + return client.getOrderedDependencyPaths(options) .then(deps => { log('Packager returned dependencies'); client.close(); - deps.forEach(module => { + deps.forEach(modulePath => { // Temporary hack to disable listing dependencies not under this directory. // Long term, we need either // (a) JS code to not depend on anything outside this directory, or // (b) Come up with a way to declare this dependency in Buck. const isInsideProjectRoots = config.projectRoots.filter(root => - module.path.startsWith(root) + modulePath.startsWith(root) ).length > 0; if (isInsideProjectRoots) { - outStream.write(module.path + '\n'); + outStream.write(modulePath + '\n'); } }); writeToFile && outStream.end(); From 4d503e0702e29ec30326c26630ebf3de1c683f0d Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 29 Sep 2015 21:57:46 -0700 Subject: [PATCH 0086/2702] Fix more name collisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Add more duplicate files to the blacklist. Reviewed By: @vjeux Differential Revision: D2493066 --- packager/blacklist.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packager/blacklist.js b/packager/blacklist.js index ce5246353f47..dbe9fdfeefd4 100644 --- a/packager/blacklist.js +++ b/packager/blacklist.js @@ -36,6 +36,12 @@ var sharedBlacklist = [ 'downstream/core/createArrayFromMixed.js', 'downstream/core/toArray.js', 'downstream/core/dom/getActiveElement.js', + 'downstream/core/dom/focusNode.js', + 'downstream/core/dom/getUnboundedScrollPosition.js', + 'downstream/core/createNodesFromMarkup.js', + 'downstream/core/CSSCore.js', + 'downstream/core/getMarkupWrap.js', + 'downstream/core/hyphenateStyleName.js', ]; // Raw unescaped patterns in case you need to use wildcards From 09b64961ac7e8ded024d04633b06797d49d82595 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Tue, 29 Sep 2015 22:22:10 -0700 Subject: [PATCH 0087/2702] added QPL for main interactions Reviewed By: @amirrosenfeld Differential Revision: D2488420 --- .../QuickPerformanceLogger/QuickPerformanceLogger.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js b/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js index 8533c5aa9537..fa6b257dd3e6 100644 --- a/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js +++ b/Libraries/QuickPerformanceLogger/QuickPerformanceLogger.js @@ -28,7 +28,7 @@ var QuickPerformanceLogger = { MarkerId: {}, markerStart(markerId, opts) { - if (markerId === undefined) { + if (typeof markerId !== 'number') { return; } if (global.nativeQPLMarkerStart) { @@ -38,7 +38,7 @@ var QuickPerformanceLogger = { }, markerEnd(markerId, actionId, opts) { - if (markerId === undefined || actionId === undefined) { + if (typeof markerId !== 'number' || typeof actionId !== 'number') { return; } if (global.nativeQPLMarkerEnd) { @@ -48,7 +48,7 @@ var QuickPerformanceLogger = { }, markerNote(markerId, actionId, opts) { - if (markerId === undefined || actionId === undefined) { + if (typeof markerId !== 'number' || typeof actionId !== 'number') { return; } if (global.nativeQPLMarkerNote) { @@ -58,7 +58,7 @@ var QuickPerformanceLogger = { }, markerCancel(markerId, opts) { - if (markerId === undefined) { + if (typeof markerId !== 'number') { return; } if (global.nativeQPLMarkerCancel) { From 12828d42434a139e8682e0b92357b9072f2b8080 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Wed, 30 Sep 2015 09:42:56 -0400 Subject: [PATCH 0088/2702] [Docs] First pass at performance guide --- docs/KnownIssues.md | 2 +- docs/Performance.md | 309 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 docs/Performance.md diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 53a65e4e201e..766f4b7ea038 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -4,7 +4,7 @@ title: Known Issues layout: docs category: Guides permalink: docs/known-issues.html -next: native-modules-ios +next: performance --- ### Missing Modules and Native Views diff --git a/docs/Performance.md b/docs/Performance.md new file mode 100644 index 000000000000..7fe2b67e13f5 --- /dev/null +++ b/docs/Performance.md @@ -0,0 +1,309 @@ +--- +id: performance +title: Performance +layout: docs +category: Guides +permalink: docs/performance.html +next: native-modules-ios +--- + +A compelling reason for using React Native instead of WebView-based +tools is to achieve 60 FPS and a native look & feel to your apps. Where +possible, we would like for React Native to do the right thing and help +you to focus on your app instead of performance optimization, but there +are areas where we're not quite there yet, and others where React Native +(similar to writing native code directly) cannot possibly determine the +best way to optimize for you and so manual intervention will be +necessary. + +This guide is intended to teach you some basics to help you +to troubleshoot performance issues, as well as discuss common sources of +problems and their suggested solutions. + +### What you need to know about frames + +Your grandparents' generation called movies ["moving +pictures"](https://www.youtube.com/watch?v=F1i40rnpOsA) for a reason: +realistic motion in video is an illusion created by quickly changing +static images at a consistent speed. We refer to each of these images as +frames. The number of frames that is displayed each second has a direct +impact on how smooth and ultimately life-like a video (or user +interface) seems to be. iOS devices display 60 frames per second, which +gives you and the UI system about 16.67ms to do all of the work needed to +generate the static image (frame) that the user will see on the screen +for that interval. If you are unable to do the work necessary to +generate that frame within the allotted 16.67ms, then you will "drop a +frame" and the UI will appear unresponsive. + +Now to confuse the matter a little bit, open up the developer menu in +your app and toggle `Show FPS Monitor`. You will notice that there are +two different frame rates. + +#### JavaScript frame rate + +For most React Native applications, your business logic will run on the +JavaScript thread. This is where your React application lives, API calls +are made, touch events are processed, etc... Updates to native-backed +views are batched and sent over to the native side at the end of each iteration of the event loop, before the frame deadline (if +all goes well). If the JavaScript thread is unresponsive for a frame, it +will be considered a dropped frame. For example, if you were to call +`this.setState` on the root component of a complex application and it +resulted in re-rendering computationally expensive component subtrees, +it's conceivable that this might take 200ms and result in 12 frames +being dropped. Any animations controlled by JavaScript would appear to freeze during that time. If anything takes longer than 100ms, the user will feel it. + +This often happens during Navigator transitions: when you push a new +route, the JavaScript thread needs to render all of the components +necessary for the scene in order to send over the proper commands to the +native side to create the backing views. It's common for the work being +done here to take a few frames and cause jank because the transition is +controlled by the JavaScript thread. Sometimes components will do +additional work on `componentDidMount`, which might result in a second +stutter in the transition. + +Another example is responding to touches: if you are doing work across +multiple frames on the JavaScript thread, you might notice a delay in +responding to TouchableOpacity, for example. This is because the JavaScript thread is busy and cannot process the raw touch events sent over from the main thread. As a result, TouchableOpacity cannot react to the touch events and command the native view to adjust its opacity. + +#### Main thread (aka UI thread) frame rate + +Many people have noticed that performance of `NavigatorIOS` is better +out of the box than `Navigator`. The reason for this is that the +animations for the transitions are done entirely on the main thread, and +so they are not interrupted by frame drops on the JavaScript thread. +([Read about why you should probably use Navigator +anyways.](/docs/navigator-comparison.html)) + +Similarly, you can happily scroll up and down through a ScrollView when +the JavaScript thread is locked up because the ScrollView lives on the +main thread (the scroll events are dispatched to the JS thread though, +but their receipt is not necessary for the scroll to occur). + +### Common sources of performance problems + +#### Development mode (dev=true) + +JavaScript thread performance suffers greatly when running in dev mode. +This is unavoidable: a lot more work needs to be done at runtime to +provide you with good warnings and error messages, such as validating +propTypes and various other assertions. + +#### Slow navigator transitions + +As mentioned above, `Navigator` animations are controlled by the +JavaScript thread. Imagine the "push from right" scene transition: each +frame, the new scene is moved from the right to left, starting offscreen +(let's say at an x-offset of 320) and ultimately settling when the scene sits +at an x-offset of 0. Each frame during this transition, the +JavaScript thread needs to send a new x-offset to the main thread. +If the JavaScript thread is locked up, it cannot do this and so no +update occurs on that frame and the animation stutters. + +Part of the long-term solution to this is to allow for JavaScript-based +animations to be offloaded to the main thread. If we were to do the same +thing as in the above example with this approach, we might calculate a +list of all x-offsets for the new scene when we are starting the +transition and send them to the main thread to execute in an +optimized way. Now that the JavaScript thread is freed of this +responsibility, it's not a big deal if it drops a few frames while +rendering the scene -- you probably won't even notice because you will be +too distracted by the pretty transition. + +Unfortunately this solution is not yet implemented, and so in the +meantime we should use the InteractionManager to selectively render the +minimal amount of content necessary for the new scene as long as the +animation is in progress. `InteractionManager.runAfterInteractions` takes +a callback as its only argument, and that callback is fired when the +navigator transition is complete (each animation from the `Animated` API +also notifies the InteractionManager, but that's beyond the scope of +this discussion). + +Your scene component might look something like this: + +```js +class ExpensiveScene extends React.Component { + constructor(props, context) { + super(props, context); + this.state = {renderPlaceholderOnly: true}; + } + + componentDidMount() { + InteractionManager.runAfterInteractions(() => { + this.setState({renderPlaceholderOnly: false}); + }); + } + + render() { + if (this.state.renderPlaceholderOnly) { + return this._renderPlaceholderView(); + } + + return ( + + Your full view goes here + + ); + } + + + _renderPlaceholderView() { + return ( + + Loading... + + ); + } +}; +``` + +You don't need to be limited to rendering some loading indicator, you +could alternatively render part of your content -- for example, when you +load the Facebook app you see a placeholder news feed item with grey +rectangles where text will be. If you are rendering a Map in your new +scene, you might want to display a grey placeholder view or a spinner +until the transition is complete as this can actually cause frames to be +dropped on the main thread. + +#### ListView initial rendering is too slow or scroll performance is bad for large lists + +This is an issue that comes up frequently because iOS ships with +UITableView which gives you very good performance by re-using underlying +UIViews. Work is in progress to do something similar with React Native, +but until then we have some tools at our disposal to help us tweak the +performance to suit our needs. It may not be possible to get all the way +there, but a little bit of creativity and experimentation with these +options can go a long way. + +##### initialListSize + +This prop specifies how many rows we want to render on our first render +pass. If we are concerned with getting *something* on screen as quickly +as possible, we could set the `initialListSize` to 1, and we'll quickly +see other rows fill in on subsequent frames. The number of rows per +frame is determined by the `pageSize`. + +##### pageSize + +After the initial render where `initialListSize` is used, ListView looks +at the `pageSize` to determine how many rows to render per frame. The +default here is 1 -- but if your views are very small and inexpensive to +render, you might want to bump this up. Tweak it and find what works for +your use case. + +##### scrollRenderAheadDistance + +"How early to start rendering rows before they come on screen, in pixels." + +If we had a list with 2000 items and rendered them all immediately that +would be a poor use of both memory and computational resources. It would +also probably cause some pretty awful jank. So the scrollRenderAhead +distance allows us to specify for far beyond the current viewport we +should continue to render rows. + +##### removeClippedSubviews + +"When true, offscreen child views (whose `overflow` value is `hidden`) +are removed from their native backing superview when offscreen. This +can improve scrolling performance on long lists. The default value is +false." + +This is an extremely important optimization to apply on large ListViews. +On Android the `overflow` value is always `hidden` so you don't need to +worry about setting it, but on iOS you need to be sure to set `overflow: +hidden` on row containers. + +#### My component renders too slowly and I don't need it all immediately + +It's common at first to overlook ListView, but using it properly is +often key to achieving solid performance. As discussed above, it +provides you with a set of tools that lets you split rendering of your +view across various frames and tweak that behavior to fit your specific +needs. Remember that ListView can be horizontal too. + +#### JS FPS plunges when re-rendering a view that hardly changes + +If you are using a ListView, you must provide a `rowHasChanged` function +that can reduce a lot of work by quickly determining whether or not a +row needs to be re-rendered. If you are using immutable data structures, +this would be as simple as a reference equality check. + +Similarly, you can implement `shouldComponentUpdate` and indicate the +exact conditions under which you would like the component to re-render. +If you write pure components (where the return value of the render +function is entirely dependent on props and state), you can leverage +PureRenderMixin to do this for you. Once again, immutable data +structures are useful to keep this fast -- if you have to do a deep +comparison of a large list of objects, it may be that re-rendering your +entire component would be quicker, and it would certainly require less +code. + +#### Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time + +"Slow Navigator transitions" is the most common manifestation of this, +but there are other times this can happen. Using InteractionManager can +be a good approach, but if the user experience cost is too high to delay +work during an animation, then you might want to consider +LayoutAnimation. + +The Animated api currently calculates each keyframe on-demand on the +JavaScript thread, while LayoutAnimation leverages Core Animation and is +unaffected by JS thread and main thread frame drops. + +One case where I have used this is for animating in a modal (sliding +down from top and fading in a translucent overlay) while +initializing and perhaps receiving responses for several network +requests, rendering the contents of the modal, and updating the view +where the modal was opened from. See the Animations guide for more +information about how to use LayoutAnimation. + +Caveats: +- LayoutAnimation only exists on iOS. +- LayoutAnimation only works for fire-and-forget animations ("static" + animations) -- if it must be be interruptible, you will need to use +Animated. + +#### Moving a view on the screen (scrolling, translating, rotating) drops UI thread FPS + +This is especially true when you have text with a transparent background +positioned on top of an image, or any other situation where alpha +compositing would be required to re-draw the view on each frame. You +will find that enabling `shouldRasterizeIOS` or `renderToHardwareTextureAndroid` +can help with this significantly. + +Be careful not to overuse this or your memory usage could go through the +roof. Profile your performance and memory usage when using these props. If you don't plan to move a view anymore, turn this property off. + +#### Animating the size of an image drops UI thread FPS + +On iOS, each time you adjust the width or height of an Image component +it is re-cropped and scaled from the original image. This can be very expensive, +especially for large images. Instead, use the `transform: [{scale}]` +style property to animate the size. An example of when you might do this is +when you tap an image and zoom it in to full screen. + +#### My TouchableX view isn't very responsive + +Sometimes, if we do an action in the same frame that we are adjusting +the opacity or highlight of a component that is responding to a touch, +we won't see that effect until after the `onPress` function has returned. +If `onPress` does a `setState` that results in a lot of work and a few +frames dropped, this may occur. A solution to this is to wrap any action +inside of your `onPress` handler in `requestAnimationFrame`: + +```js +handleOnPress() { + // Always use TimerMixin with requestAnimationFrame, setTimeout and + // setInterval + this.requestAnimationFrame(() => { + this.doExpensiveAction(); + }); +} +``` + +### Profiling + +Use the built-in Profiler to get detailed information about work done in +the JavaScript thread and main thread side-by-side. + +For iOS, Instruments are an invaluable tool, and on Android you should +learn to use systrace. From 70d17d91fce02d530e54e7d4f198a1549b6e857e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Wed, 30 Sep 2015 15:15:30 +0100 Subject: [PATCH 0089/2702] [SampleApp] adding unsynced changes from PR #3025 --- ReactAndroid/README.md | 15 +++++++-------- local-cli/__tests__/generators-test.js | 14 +++++++------- scripts/e2e-test.sh | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/ReactAndroid/README.md b/ReactAndroid/README.md index 56d469926a3e..033bc15b356f 100644 --- a/ReactAndroid/README.md +++ b/ReactAndroid/README.md @@ -16,12 +16,12 @@ Make sure you have the following installed: - SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](build.gradle)) - Android Support Repository 20 (for Android Support Library) - Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html)) - + Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK` defined, or create a `local.properties` file in the root of your `react-native` checkout with the following contents: sdk.dir=absolute_path_to_android_sdk ndk.dir=absolute_path_to_android_ndk - + Example: sdk.dir=/Users/your_unix_name/android-sdk-macosx @@ -53,33 +53,32 @@ To install a snapshot version of the framework code in your local Maven repo: ## Running the examples -To run the Sample app: +To run the UIExplorer app: ```bash cd react-native -./gradlew :Examples:SampleApp:android:app:installDebug +./gradlew :Examples:UIExplorer:android:app:installDebug # Start the packager in a separate shell: # Make sure you ran npm install ./packager/packager.sh -# Open SampleApp in your emulator, Menu button -> Reload JS should work +# Open UIExplorer in your emulator, Menu button -> Reload JS should work ``` You can run any other sample app the same way, e.g.: ```bash ./gradlew :Examples:Movies:android:app:installDebug -./gradlew :Examples:UIExplorer:android:app:installDebug ``` ## Building from Android Studio You'll need to do one additional step until we release the React Native Gradle plugin to Maven central. This is because Android Studio has its own local Maven repo: - + mkdir -p /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react cp -r ~/.m2/repository/com/facebook/react/gradleplugin /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react/ Now, open Android Studio, click _Import Non-Android Studio project_ and find your `react-native` repo. - + In the configurations dropdown, _app_ should be selected. Click _Run_. ## Installing the React Native .aar in your local Maven repo diff --git a/local-cli/__tests__/generators-test.js b/local-cli/__tests__/generators-test.js index 8a982fcd5797..fff3734d7503 100644 --- a/local-cli/__tests__/generators-test.js +++ b/local-cli/__tests__/generators-test.js @@ -56,7 +56,7 @@ describe('React Yeoman Generators', function() { 'AppRegistry.registerComponent(\'TestApp\', () => TestApp);' ); - assert.noFileContent('index.ios.js', 'SampleApp'); + assert.noFileContent('index.ios.js', '<%= name %>'); }); it('replaces vars in index.android.js', function() { @@ -66,7 +66,7 @@ describe('React Yeoman Generators', function() { 'AppRegistry.registerComponent(\'TestApp\', () => TestApp);' ); - assert.noFileContent('index.ios.js', 'SampleApp'); + assert.noFileContent('index.ios.js', '<%= name %>'); }); it('composes with ios generator', function() { @@ -218,14 +218,14 @@ describe('React Yeoman Generators', function() { var appDelegate = 'ios/TestAppIOS/AppDelegate.m'; assert.fileContent(appDelegate, 'moduleName:@"TestAppIOS"'); - assert.noFileContent(appDelegate, 'SampleApp'); + assert.noFileContent(appDelegate, '<%= name %>'); }); it('replaces vars in LaunchScreen.xib', function() { var launchScreen = 'ios/TestAppIOS/Base.lproj/LaunchScreen.xib'; assert.fileContent(launchScreen, 'text="TestAppIOS"'); - assert.noFileContent(launchScreen, 'SampleApp'); + assert.noFileContent(launchScreen, '<%= name %>'); }); it('replaces vars in TestAppIOSTests.m', function() { @@ -233,7 +233,7 @@ describe('React Yeoman Generators', function() { assert.fileContent(tests, '@interface TestAppIOSTests : XCTestCase'); assert.fileContent(tests, '@implementation TestAppIOSTests'); - assert.noFileContent(tests, 'SampleApp'); + assert.noFileContent(tests, '<%= name %>'); }); it('replaces vars in project.pbxproj', function() { @@ -243,7 +243,7 @@ describe('React Yeoman Generators', function() { assert.fileContent(pbxproj, 'TestAppIOS.app'); assert.fileContent(pbxproj, 'TestAppIOSTests.xctest'); - assert.noFileContent(pbxproj, 'SampleApp'); + assert.noFileContent(pbxproj, '<%= name %>'); }); it('replaces vars in xcscheme', function() { @@ -254,7 +254,7 @@ describe('React Yeoman Generators', function() { assert.fileContent(xcscheme, '"TestAppIOSTests.xctest"'); assert.fileContent(xcscheme, '"TestAppIOSTests"'); - assert.noFileContent(xcscheme, 'SampleApp'); + assert.noFileContent(xcscheme, '<%= name %>'); }); }); }); diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh index b105c4017e07..0f48c7e80731 100755 --- a/scripts/e2e-test.sh +++ b/scripts/e2e-test.sh @@ -13,7 +13,7 @@ TEMP=$(mktemp -d /tmp/react-native-XXXXXXXX) export REACT_PACKAGER_LOG="$TEMP/server.log" # To make sure we actually installed the local version -# of react-native, we will create a temp file inside SampleApp +# of react-native, we will create a temp file inside the template # and check that it exists after `react-native init` MARKER=$(mktemp $ROOT/local-cli/generator-ios/templates/main/XXXXXXXX) From 16edb3a0829774c7be4bd42d1015adc378ba1bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Wed, 30 Sep 2015 05:31:30 -0700 Subject: [PATCH 0090/2702] add remote image support to toolbar Differential Revision: D2493627 --- .../ToolbarAndroidExample.android.js | 8 + Examples/UIExplorer/bunny.png | Bin 0 -> 18738 bytes Examples/UIExplorer/hawk.png | Bin 0 -> 62468 bytes .../ToolbarAndroid/ToolbarAndroid.android.js | 26 +- .../toolbar/DrawableWithIntrinsicSize.java | 42 +++ .../react/views/toolbar/ReactToolbar.java | 250 ++++++++++++++++++ .../views/toolbar/ReactToolbarManager.java | 94 +++---- 7 files changed, 344 insertions(+), 76 deletions(-) create mode 100644 Examples/UIExplorer/bunny.png create mode 100644 Examples/UIExplorer/hawk.png create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/toolbar/DrawableWithIntrinsicSize.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java diff --git a/Examples/UIExplorer/ToolbarAndroidExample.android.js b/Examples/UIExplorer/ToolbarAndroidExample.android.js index 359ba087e872..c621296d91ec 100644 --- a/Examples/UIExplorer/ToolbarAndroidExample.android.js +++ b/Examples/UIExplorer/ToolbarAndroidExample.android.js @@ -93,6 +93,14 @@ var ToolbarAndroidExample = React.createClass({ Touch the icon to reset the custom colors to the default (theme-provided) ones. + + + ); }, diff --git a/Examples/UIExplorer/bunny.png b/Examples/UIExplorer/bunny.png new file mode 100644 index 0000000000000000000000000000000000000000..0d94af660bcfc793372a84e0dad82e5641bfe375 GIT binary patch literal 18738 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 IM6N<$f=0}iFaQ7m literal 0 HcmV?d00001 diff --git a/Examples/UIExplorer/hawk.png b/Examples/UIExplorer/hawk.png new file mode 100644 index 0000000000000000000000000000000000000000..7205b1e47d08adf1bb7c2300a8dfcbe492cc0142 GIT binary patch literal 62468 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 mActionsHolder = + new MultiDraweeHolder<>(); + + private final ControllerListener mLogoControllerListener = + new BaseControllerListener() { + @Override + public void onFinalImageSet( + String id, + @Nullable final ImageInfo imageInfo, + @Nullable Animatable animatable) { + if (imageInfo != null) { + final DrawableWithIntrinsicSize logoDrawable = + new DrawableWithIntrinsicSize(mLogoHolder.getTopLevelDrawable(), imageInfo); + setLogo(logoDrawable); + } + } + }; + + private final ControllerListener mNavIconControllerListener = + new BaseControllerListener() { + @Override + public void onFinalImageSet( + String id, + @Nullable final ImageInfo imageInfo, + @Nullable Animatable animatable) { + if (imageInfo != null) { + final DrawableWithIntrinsicSize navIconDrawable = + new DrawableWithIntrinsicSize(mNavIconHolder.getTopLevelDrawable(), imageInfo); + setNavigationIcon(navIconDrawable); + } + } + }; + + private static class ActionIconControllerListener extends BaseControllerListener { + private final MenuItem mItem; + private final DraweeHolder mHolder; + + ActionIconControllerListener(MenuItem item, DraweeHolder holder) { + mItem = item; + mHolder = holder; + } + + @Override + public void onFinalImageSet( + String id, + @Nullable ImageInfo imageInfo, + @Nullable Animatable animatable) { + if (imageInfo != null) { + mItem.setIcon(new DrawableWithIntrinsicSize(mHolder.getTopLevelDrawable(), imageInfo)); + } + } + } + + public ReactToolbar(Context context) { + super(context); + + mLogoHolder = DraweeHolder.create(createDraweeHierarchy(), context); + mNavIconHolder = DraweeHolder.create(createDraweeHierarchy(), context); + } + + private final Runnable mLayoutRunnable = new Runnable() { + @Override + public void run() { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + + @Override + public void requestLayout() { + super.requestLayout(); + + // The toolbar relies on a measure + layout pass happening after it calls requestLayout(). + // Without this, certain calls (e.g. setLogo) only take effect after a second invalidation. + post(mLayoutRunnable); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + detachDraweeHolders(); + } + + @Override + public void onStartTemporaryDetach() { + super.onStartTemporaryDetach(); + detachDraweeHolders(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + attachDraweeHolders(); + } + + @Override + public void onFinishTemporaryDetach() { + super.onFinishTemporaryDetach(); + mLogoHolder.onAttach(); + mNavIconHolder.onAttach(); + } + + private void detachDraweeHolders() { + mLogoHolder.onDetach(); + mNavIconHolder.onDetach(); + mActionsHolder.onDetach(); + } + + private void attachDraweeHolders() { + mLogoHolder.onAttach(); + mNavIconHolder.onAttach(); + mActionsHolder.onAttach(); + } + + /* package */ void setLogoSource(@Nullable ReadableMap source) { + String uri = source != null ? source.getString("uri") : null; + if (uri == null) { + setLogo(null); + } else if (uri.startsWith("http://") || uri.startsWith("https://")) { + DraweeController controller = Fresco.newDraweeControllerBuilder() + .setUri(Uri.parse(uri)) + .setControllerListener(mLogoControllerListener) + .setOldController(mLogoHolder.getController()) + .build(); + mLogoHolder.setController(controller); + } else { + setLogo(getDrawableResourceByName(uri)); + } + } + + /* package */ void setNavIconSource(@Nullable ReadableMap source) { + String uri = source != null ? source.getString("uri") : null; + if (uri == null) { + setNavigationIcon(null); + } else if (uri.startsWith("http://") || uri.startsWith("https://")) { + DraweeController controller = Fresco.newDraweeControllerBuilder() + .setUri(Uri.parse(uri)) + .setControllerListener(mNavIconControllerListener) + .setOldController(mNavIconHolder.getController()) + .build(); + mNavIconHolder.setController(controller); + } else { + setNavigationIcon(getDrawableResourceByName(uri)); + } + } + + /* package */ void setActions(@Nullable ReadableArray actions) { + Menu menu = getMenu(); + menu.clear(); + mActionsHolder.clear(); + if (actions != null) { + for (int i = 0; i < actions.size(); i++) { + ReadableMap action = actions.getMap(i); + MenuItem item = menu.add(Menu.NONE, Menu.NONE, i, action.getString(PROP_ACTION_TITLE)); + ReadableMap icon = action.hasKey(PROP_ACTION_ICON) ? action.getMap(PROP_ACTION_ICON) : null; + if (icon != null) { + String iconSource = icon.getString("uri"); + if (iconSource.startsWith("http://") || iconSource.startsWith("https://")) { + setMenuItemIcon(item, icon); + } else { + item.setIcon(getDrawableResourceByName(iconSource)); + } + } + int showAsAction = action.hasKey(PROP_ACTION_SHOW) + ? action.getInt(PROP_ACTION_SHOW) + : MenuItem.SHOW_AS_ACTION_NEVER; + if (action.hasKey(PROP_ACTION_SHOW_WITH_TEXT) && + action.getBoolean(PROP_ACTION_SHOW_WITH_TEXT)) { + showAsAction = showAsAction | MenuItem.SHOW_AS_ACTION_WITH_TEXT; + } + item.setShowAsAction(showAsAction); + } + } + } + + /** + * This is only used when the icon is remote (http/s). Creates & adds a new {@link DraweeHolder} + * to {@link #mActionsHolder} and attaches a {@link ActionIconControllerListener} that just sets + * the top level drawable when it's loaded. + */ + private void setMenuItemIcon(MenuItem item, ReadableMap icon) { + String iconSource = icon.getString("uri"); + + DraweeHolder holder = + DraweeHolder.create(createDraweeHierarchy(), getContext()); + DraweeController controller = Fresco.newDraweeControllerBuilder() + .setUri(Uri.parse(iconSource)) + .setControllerListener(new ActionIconControllerListener(item, holder)) + .setOldController(holder.getController()) + .build(); + holder.setController(controller); + + mActionsHolder.add(holder); + } + + private GenericDraweeHierarchy createDraweeHierarchy() { + return new GenericDraweeHierarchyBuilder(getResources()) + .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) + .setFadeDuration(0) + .build(); + } + + private int getDrawableResourceByName(String name) { + return getResources().getIdentifier( + name, + "drawable", + getContext().getPackageName()); + } + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java index 6b9a988dd65c..93fe8a61d610 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java @@ -11,19 +11,20 @@ import javax.annotation.Nullable; +import java.util.Map; + import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.os.SystemClock; -import android.support.v7.widget.Toolbar; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.facebook.react.R; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ReactProp; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; @@ -32,52 +33,39 @@ import com.facebook.react.views.toolbar.events.ToolbarClickEvent; /** - * Manages instances of Toolbar. + * Manages instances of ReactToolbar. */ -public class ReactToolbarManager extends ViewGroupManager { +public class ReactToolbarManager extends ViewGroupManager { private static final String REACT_CLASS = "ToolbarAndroid"; - private static final String PROP_ACTION_ICON = "icon"; - private static final String PROP_ACTION_SHOW = "show"; - private static final String PROP_ACTION_SHOW_WITH_TEXT = "showWithText"; - private static final String PROP_ACTION_TITLE = "title"; - @Override public String getName() { return REACT_CLASS; } @Override - protected Toolbar createViewInstance(ThemedReactContext reactContext) { - return new Toolbar(reactContext); + protected ReactToolbar createViewInstance(ThemedReactContext reactContext) { + return new ReactToolbar(reactContext); } @ReactProp(name = "logo") - public void setLogo(Toolbar view, @Nullable String logo) { - if (logo != null) { - view.setLogo(getDrawableResourceByName(view.getContext(), logo)); - } else { - view.setLogo(null); - } + public void setLogo(ReactToolbar view, @Nullable ReadableMap logo) { + view.setLogoSource(logo); } @ReactProp(name = "navIcon") - public void setNavIcon(Toolbar view, @Nullable String navIcon) { - if (navIcon != null) { - view.setNavigationIcon(getDrawableResourceByName(view.getContext(), navIcon)); - } else { - view.setNavigationIcon(null); - } + public void setNavIcon(ReactToolbar view, @Nullable ReadableMap navIcon) { + view.setNavIconSource(navIcon); } @ReactProp(name = "subtitle") - public void setSubtitle(Toolbar view, @Nullable String subtitle) { + public void setSubtitle(ReactToolbar view, @Nullable String subtitle) { view.setSubtitle(subtitle); } @ReactProp(name = "subtitleColor", customType = "Color") - public void setSubtitleColor(Toolbar view, @Nullable Integer subtitleColor) { + public void setSubtitleColor(ReactToolbar view, @Nullable Integer subtitleColor) { int[] defaultColors = getDefaultColors(view.getContext()); if (subtitleColor != null) { view.setSubtitleTextColor(subtitleColor); @@ -87,12 +75,12 @@ public void setSubtitleColor(Toolbar view, @Nullable Integer subtitleColor) { } @ReactProp(name = "title") - public void setTitle(Toolbar view, @Nullable String title) { + public void setTitle(ReactToolbar view, @Nullable String title) { view.setTitle(title); } @ReactProp(name = "titleColor", customType = "Color") - public void setTitleColor(Toolbar view, @Nullable Integer titleColor) { + public void setTitleColor(ReactToolbar view, @Nullable Integer titleColor) { int[] defaultColors = getDefaultColors(view.getContext()); if (titleColor != null) { view.setTitleTextColor(titleColor); @@ -102,37 +90,12 @@ public void setTitleColor(Toolbar view, @Nullable Integer titleColor) { } @ReactProp(name = "actions") - public void setActions(Toolbar view, @Nullable ReadableArray actions) { - Menu menu = view.getMenu(); - menu.clear(); - if (actions != null) { - for (int i = 0; i < actions.size(); i++) { - ReadableMap action = actions.getMap(i); - MenuItem item = menu.add(Menu.NONE, Menu.NONE, i, action.getString(PROP_ACTION_TITLE)); - String icon = action.hasKey(PROP_ACTION_ICON) ? action.getString(PROP_ACTION_ICON) : null; - if (icon != null) { - item.setIcon(getDrawableResourceByName(view.getContext(), icon)); - } - String show = action.hasKey(PROP_ACTION_SHOW) ? action.getString(PROP_ACTION_SHOW) : null; - if (show != null) { - int showAsAction = MenuItem.SHOW_AS_ACTION_NEVER; - if ("always".equals(show)) { - showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS; - } else if ("ifRoom".equals(show)) { - showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; - } - if (action.hasKey(PROP_ACTION_SHOW_WITH_TEXT) && - action.getBoolean(PROP_ACTION_SHOW_WITH_TEXT)) { - showAsAction = showAsAction | MenuItem.SHOW_AS_ACTION_WITH_TEXT; - } - item.setShowAsAction(showAsAction); - } - } - } + public void setActions(ReactToolbar view, @Nullable ReadableArray actions) { + view.setActions(actions); } @Override - protected void addEventEmitters(final ThemedReactContext reactContext, final Toolbar view) { + protected void addEventEmitters(final ThemedReactContext reactContext, final ReactToolbar view) { final EventDispatcher mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class) .getEventDispatcher(); view.setNavigationOnClickListener( @@ -145,7 +108,7 @@ public void onClick(View v) { }); view.setOnMenuItemClickListener( - new Toolbar.OnMenuItemClickListener() { + new ReactToolbar.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { mEventDispatcher.dispatchEvent( @@ -158,6 +121,17 @@ public boolean onMenuItemClick(MenuItem menuItem) { }); } + @Nullable + @Override + public Map getExportedViewConstants() { + return MapBuilder.of( + "ShowAsAction", + MapBuilder.of( + "never", MenuItem.SHOW_AS_ACTION_NEVER, + "always", MenuItem.SHOW_AS_ACTION_ALWAYS, + "ifRoom", MenuItem.SHOW_AS_ACTION_IF_ROOM)); + } + @Override public boolean needsCustomLayoutForChildren() { return true; @@ -205,12 +179,4 @@ private static void recycleQuietly(@Nullable TypedArray style) { } } - private static int getDrawableResourceByName(Context context, String name) { - name = name.toLowerCase().replace("-", "_"); - return context.getResources().getIdentifier( - name, - "drawable", - context.getPackageName()); - } - } From 473679b7414759ac3d741be22bf96306c9bdeeaa Mon Sep 17 00:00:00 2001 From: Stephan Diederich Date: Wed, 30 Sep 2015 06:57:49 -0700 Subject: [PATCH 0091/2702] update some asset catalogs Reviewed By: @nscoding Differential Revision: D2480708 --- .../uie_thumb_big.imageset/Contents.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json index 589a2f493074..cdd15d023904 100644 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json +++ b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json @@ -2,8 +2,16 @@ "images" : [ { "idiom" : "universal", - "scale" : "1x", - "filename" : "uie_thumb_big.png" + "filename" : "uie_thumb_big.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { From 389039268507374f4dd5b577ba2de66825a26b47 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 30 Sep 2015 09:37:54 -0700 Subject: [PATCH 0092/2702] Wait to clear RCTImageView.image until definitively removed from window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: When `RCTImageView` is removed from the view hierarchy, it clears out its `image` to save memory. This makes sense, except that it gets removed from the window (view hierarchy) even when becoming the child of another view. This fixes the logic so that it only clears out the image if the view hasn't been moved somewhere else within one frame. @​public Reviewed By: @javache Differential Revision: D2493849 --- Libraries/Image/RCTImageView.m | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index b37c897be952..10430813142a 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -238,8 +238,22 @@ - (void)didMoveToWindow [super didMoveToWindow]; if (!self.window) { - [self clearImage]; - } else if (self.src) { + // Don't keep self alive through the asynchronous dispatch, if the intention was to remove the view so it would + // deallocate. + __weak typeof(self) weakSelf = self; + + dispatch_async(dispatch_get_main_queue(), ^{ + __strong typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + // If we haven't been re-added to a window by this run loop iteration, clear out the image to save memory. + if (!strongSelf.window) { + [strongSelf clearImage]; + } + }); + } else if (!self.image && self.src) { [self reloadImage]; } } From bb335d022a847ae0b542c19dbfa2b49a2f199716 Mon Sep 17 00:00:00 2001 From: olivier notteghem Date: Wed, 30 Sep 2015 14:07:52 -0700 Subject: [PATCH 0093/2702] Add log output to rule out that multiple react context creation are responsible for increased mem / image not loading in CTScan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: @​krangarajan Differential Revision: D2495435 --- .../src/main/java/com/facebook/react/ReactInstanceManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 688c97294932..4d091a109f38 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -492,6 +492,7 @@ private void tearDownReactContext(ReactContext reactContext) { private ReactApplicationContext createReactContext( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) { + FLog.i(ReactConstants.TAG, "Creating react context."); NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); From 6eb2dcafaec80fb9209e42c044bea5fe44851d2e Mon Sep 17 00:00:00 2001 From: Magnus Bergman Date: Wed, 30 Sep 2015 15:36:09 -0700 Subject: [PATCH 0094/2702] Fix dead link in PushNotificationIOS docs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Closes https://github.com/facebook/react-native/pull/3117 Reviewed By: @​svcscm Differential Revision: D2495539 Pulled By: @frantic --- Libraries/PushNotificationIOS/PushNotificationIOS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index a39058e7dafa..43d6b9496073 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -27,7 +27,7 @@ var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; * Handle push notifications for your app, including permission handling and * icon badge number. * - * To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/ConfiguringPushNotifications/ConfiguringPushNotifications.html) + * To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW6) * and your server-side system. To get an idea, [this is the Parse guide](https://parse.com/tutorials/ios-push-notifications). */ class PushNotificationIOS { From 3b6a27f74c3588606462a413cfd09f38d5a2d518 Mon Sep 17 00:00:00 2001 From: Quentin Valmori Date: Wed, 30 Sep 2015 17:17:43 -0700 Subject: [PATCH 0095/2702] Fix one warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: translucent is a boolean not a stringCloses https://github.com/facebook/react-native/pull/3134 Reviewed By: @​svcscm Differential Revision: D2495517 Pulled By: @frantic --- Examples/UIExplorer/NavigatorIOSColorsExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index 74118690267f..4078cac6db64 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -66,7 +66,7 @@ var NavigatorIOSColors = React.createClass({ tintColor="#FFFFFF" barTintColor="#183E63" titleTextColor="#FFFFFF" - translucent="true" + translucent={true} /> ); }, From 8d4e38002a0fa674c2e67d1ae7175f839afc4560 Mon Sep 17 00:00:00 2001 From: Danny van der Jagt Date: Wed, 30 Sep 2015 18:08:54 -0700 Subject: [PATCH 0096/2702] Fix for IOS 8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: **Problem** Using push notifications in IOS 8 will throw this error: `registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.` The problem is that the check is running on compile instead of runtime. **Solution** If have changed the compile if statement to a runtime if statement. The fix is tested on: IOS 7.1 and 8.* and everything is working now. This solution is also discussed in: https://github.com/facebook/react-native/issues/1613 and it was part of https://github.com/facebook/react-native/pull/1979. (is being separated to keep things moving) Please let me know what you think.Closes https://github.com/facebook/react-native/pull/2332 Reviewed By: @​svcscm Differential Revision: D2490987 Pulled By: @ericvicenti --- .../RCTPushNotificationManager.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 6af5f8e91fa7..3e45add4477a 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -157,13 +157,13 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification } UIApplication *app = RCTSharedApplication(); -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 - id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; - [app registerUserNotificationSettings:notificationSettings]; - [app registerForRemoteNotifications]; -#else - [app registerForRemoteNotificationTypes:types]; -#endif + if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) { + UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil]; + [app registerUserNotificationSettings:notificationSettings]; + [app registerForRemoteNotifications]; + } else { + [app registerForRemoteNotificationTypes:(NSUInteger)types]; + } } RCT_EXPORT_METHOD(abandonPermissions) From f0c20054b4a0ec372e8212b27fca1b9d2f7f17bd Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Wed, 30 Sep 2015 18:38:56 -0700 Subject: [PATCH 0097/2702] update lint packages, fix lint errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​cesarandreu pointed out running eslint on react-native resulted in `t.isReferencedIdentifier is not a function` issues on the babel #linting channel on slack. - update eslint, babel-eslint, eslint-plugin-react - fix eslint errors: `Error - t.isReferencedIdentifier is not a function` - fix lint npm script I see there's also https://github.com/facebook/react-native/pull/1736 from @ide which would fix it tooCloses https://github.com/facebook/react-native/pull/1874 Reviewed By: @vjeux Differential Revision: D2495878 Pulled By: @frantic --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd748d9d574a..155f8af8e68f 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ ], "scripts": { "test": "NODE_ENV=test jest", - "lint": "node linter.js Examples/ Libraries/", + "lint": "eslint Examples/ Libraries/", "start": "./packager/packager.sh || true" }, "bin": { From 754fed4dca8caad11c0c86ef1030c20c7a05bcc5 Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Wed, 30 Sep 2015 18:56:00 -0700 Subject: [PATCH 0098/2702] Format Code in TextInput Comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: * wrap code snippet in TextInput Comment in backticks ``` * unless there is a way to omit portions of comments from reaching the docs this is less confusingCloses https://github.com/facebook/react-native/pull/3085 Reviewed By: @​svcscm Differential Revision: D2495630 Pulled By: @frantic --- Libraries/Components/TextInput/TextInput.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index f96c1852fca6..8ddd13aa99fa 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -68,8 +68,8 @@ type Event = Object; * /> * ``` * - * Note that some props are only available with multiline={true/false}: - * + * Note that some props are only available with `multiline={true/false}`: + * ``` * var onlyMultiline = { * onSelectionChange: true, // not supported in Open Source yet * onTextInput: true, // not supported in Open Source yet @@ -79,6 +79,7 @@ type Event = Object; * var notMultiline = { * onSubmitEditing: true, * }; + * ``` */ var TextInput = React.createClass({ propTypes: { From cc867024422f911db6907a79211c9e704bb7541f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 30 Sep 2015 19:02:52 -0700 Subject: [PATCH 0099/2702] Error on name collisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public This moves us from warnings on name collisions to errors. If the error happens in initialization it will fatal out. However, if the error happens while working (after initialization) then I did my best to make it recoverable. The rational behind this is that if you're working and you're changing names, you may introduce a duplication while moving things around. It will suck if you have to restart the server every time you do that. Reviewed By: @frantic Differential Revision: D2493098 --- .../DependencyGraph/DeprecatedAssetMap.js | 1 + .../DependencyGraph/HasteMap.js | 61 +++----- .../DependencyGraph/ResolutionRequest.js | 1 + .../__tests__/DependencyGraph-test.js | 136 ++---------------- .../DependencyGraph/index.js | 28 +++- 5 files changed, 58 insertions(+), 169 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js index ca537078f773..7292a85ce776 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js @@ -13,6 +13,7 @@ const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); const debug = require('debug')('ReactNativePackager:DependencyGraph'); const path = require('path'); +const Promise = require('promise'); class DeprecatedAssetMap { constructor({ fsCrawl, roots, assetExts, fileWatcher, ignoreFilePath, helpers }) { diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index 6d720ea50cd2..6c8bdc0a4550 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -8,9 +8,9 @@ */ 'use strict'; -const chalk = require('chalk'); const path = require('path'); const getPlatformExtension = require('../../lib/getPlatformExtension'); +const Promise = require('promise'); const GENERIC_PLATFORM = 'generic'; @@ -19,11 +19,11 @@ class HasteMap { this._fastfs = fastfs; this._moduleCache = moduleCache; this._helpers = helpers; - this._map = Object.create(null); - this._warnedAbout = Object.create(null); } build() { + this._map = Object.create(null); + let promises = this._fastfs.findFilesByExt('js', { ignore: (file) => this._helpers.isNodeModulesDir(file) }).map(file => this._processHasteModule(file)); @@ -39,20 +39,15 @@ class HasteMap { processFileChange(type, absPath) { return Promise.resolve().then(() => { - // Rewarn after file changes. - this._warnedAbout = Object.create(null); - /*eslint no-labels: 0 */ if (type === 'delete' || type === 'change') { loop: for (let name in this._map) { const modulesMap = this._map[name]; for (let platform in modulesMap) { - const modules = modulesMap[platform]; - for (var i = 0; i < modules.length; i++) { - if (modules[i].path === absPath) { - modules.splice(i, 1); - break loop; - } + const module = modulesMap[platform]; + if (module.path === absPath) { + delete modulesMap[platform]; + break loop; } } } @@ -82,38 +77,15 @@ class HasteMap { // If no platform is given we choose the generic platform module list. // If a platform is given and no modules exist we fallback // to the generic platform module list. - let modules; if (platform == null) { - modules = modulesMap[GENERIC_PLATFORM]; + return modulesMap[GENERIC_PLATFORM]; } else { - modules = modulesMap[platform]; - if (modules == null) { - modules = modulesMap[GENERIC_PLATFORM]; - } - } - - if (modules == null) { - return null; - } - - if (modules.length > 1) { - if (!this._warnedAbout[name]) { - this._warnedAbout[name] = true; - console.warn( - chalk.yellow( - '\nWARNING: Found multiple haste modules or packages ' + - 'with the name `%s`. Please fix this by adding it to ' + - 'the blacklist or deleting the modules keeping only one.\n' - ), - name, - modules.map(m => m.path).join('\n'), - ); + let module = modulesMap[platform]; + if (module == null) { + module = modulesMap[GENERIC_PLATFORM]; } - - return modules[0]; + return module; } - - return modules[0]; } _processHasteModule(file) { @@ -147,11 +119,14 @@ class HasteMap { const moduleMap = this._map[name]; const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM; - if (!moduleMap[modulePlatform]) { - moduleMap[modulePlatform] = []; + if (moduleMap[modulePlatform]) { + throw new Error( + `Naming collision detected: ${mod.path} ` + + `collides with ${moduleMap[modulePlatform].path}` + ); } - moduleMap[modulePlatform].push(mod); + moduleMap[modulePlatform] = mod; } } diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js index f3a7553753b3..dda09abe8637 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js @@ -13,6 +13,7 @@ const util = require('util'); const path = require('path'); const isAbsolutePath = require('absolute-path'); const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const Promise = require('promise'); class ResolutionRequest { constructor({ diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 017ffe8e74e3..1a9350fa5425 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1070,7 +1070,7 @@ describe('DependencyGraph', function() { }); }); - pit('can have multiple modules with the same name', function() { + pit('should fatal on multiple modules with the same name', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -1078,59 +1078,33 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("b")', ].join('\n'), 'b.js': [ '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - 'c.js': [ - '/**', - ' * @providesModule c', + ' * @providesModule index', ' */', ].join('\n'), - 'somedir': { - 'somefile.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("c")', - ].join('\n') - } } }); + const _exit = process.exit; + const _error = console.error; + + process.exit = jest.genMockFn(); + console.error = jest.genMockFn(); + var dgraph = new DependencyGraph({ roots: [root], fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], cache: cache, }); - return getOrderedDependenciesAsJSON(dgraph, '/root/somedir/somefile.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/somedir/somefile.js', - dependencies: ['c'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'c', - path: '/root/c.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); + + return dgraph.load().catch(() => { + expect(process.exit).toBeCalledWith(1); + expect(console.error).toBeCalled(); + process.exit = _exit; + console.error = _error; }); }); @@ -3629,88 +3603,6 @@ describe('DependencyGraph', function() { }); }); - describe('warnings', () => { - let warn = console.warn; - - beforeEach(() => { - console.warn = jest.genMockFn(); - }); - - afterEach(() => { - console.warn = warn; - }); - - pit('should warn about colliding module names', function() { - fs.__setMockFilesystem({ - 'root': { - 'index.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - 'b.js': ` - /** - * @providesModule a - */ - `, - } - }); - - var dgraph = new DependencyGraph({ - roots: ['/root'], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(console.warn.mock.calls.length).toBe(1); - }); - }); - - - pit('should warn about colliding module names within a platform', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.ios.js': ` - /** - * @providesModule a - */ - `, - 'b.ios.js': ` - /** - * @providesModule a - */ - `, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - cache: cache, - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js', 'ios').then(function(deps) { - expect(console.warn.mock.calls.length).toBe(1); - }); - }); - }); - describe('getAsyncDependencies', () => { pit('should get dependencies', function() { var root = '/root'; diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index a03cd3dbedfc..1baef52f3772 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -73,7 +73,11 @@ class DependencyGraph { this._opts = validateOpts(options); this._cache = this._opts.cache; this._helpers = new Helpers(this._opts); - this.load(); + this.load().catch((err) => { + // This only happens at initialization. Live errors are easier to recover from. + console.error('Error building DepdendencyGraph:\n', err.stack); + process.exit(1); + }); } load() { @@ -198,9 +202,25 @@ class DependencyGraph { return; } - this._loading = this._loading.then( - () => this._hasteMap.processFileChange(type, absPath) - ); + // Ok, this is some tricky promise code. Our requirements are: + // * we need to report back failures + // * failures shouldn't block recovery + // * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild + // After we process a file change we record any errors which will also be + // reported via the next request. On the next file change, we'll see that + // we are in an error state and we should decide to do a full rebuild. + this._loading = this._loading.finally(() => { + if (this._hasteMapError) { + this._hasteMapError = null; + // Rebuild the entire map if last change resulted in an error. + console.warn('Rebuilding haste map to recover from error'); + this._loading = this._hasteMap.build(); + } else { + this._loading = this._hasteMap.processFileChange(type, absPath); + this._loading.catch((e) => this._hasteMapError = e); + } + return this._loading; + }); } } From 0097fc586755bf0fc9ff92cd9326c8431e563fbb Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Wed, 30 Sep 2015 20:09:58 -0700 Subject: [PATCH 0100/2702] enabled border width and border color (sync diff) Differential Revision: D2497518 --- Examples/UIExplorer/ImageExample.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 5e47dcb54f0c..993cde3081e0 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -124,7 +124,6 @@ exports.examples = [ ); }, - platform: 'ios', }, { title: 'Border Width', @@ -142,7 +141,6 @@ exports.examples = [ ); }, - platform: 'ios', }, { title: 'Border Radius', From 3d1cd771a5034d94ea9ad61a2b20c6031ecbb584 Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Wed, 30 Sep 2015 20:56:59 -0700 Subject: [PATCH 0101/2702] enabled border width and border color (sync diff 2) Differential Revision: D2497575 --- .../react/views/image/ReactImageManager.java | 14 ++++++++++++++ .../react/views/image/ReactImageView.java | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index e9d35d8a03bd..429348b79fcb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -58,6 +58,20 @@ public ReactImageView createViewInstance(ThemedReactContext context) { public void setSource(ReactImageView view, @Nullable String source) { view.setSource(source); } + + @ReactProp(name = "borderColor", customType = "Color") + public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { + if (borderColor == null) { + view.setBorderColor(Color.TRANSPARENT); + } else { + view.setBorderColor(borderColor); + } + } + + @ReactProp(name = "borderWidth") + public void setBorderWidth(ReactImageView view, float borderWidth) { + view.setBorderWidth(borderWidth); + } @ReactProp(name = "borderRadius") public void setBorderRadius(ReactImageView view, float borderRadius) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index 6253ea853ba0..14995c3e149b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -22,12 +22,9 @@ import android.graphics.Shader; import android.net.Uri; +import com.facebook.common.util.UriUtil; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.drawee.controller.ControllerListener; -import com.facebook.react.uimanager.PixelUtil; -import com.facebook.common.util.UriUtil; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; @@ -39,6 +36,7 @@ import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.Postprocessor; +import com.facebook.react.uimanager.PixelUtil; /** * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view @@ -99,6 +97,8 @@ public void process(Bitmap output, Bitmap source) { } private @Nullable Uri mUri; + private int mBorderColor; + private float mBorderWidth; private float mBorderRadius; private ScalingUtils.ScaleType mScaleType; private boolean mIsDirty; @@ -127,6 +127,16 @@ public ReactImageView( mCallerContext = callerContext; } + public void setBorderColor(int borderColor) { + mBorderColor = borderColor; + mIsDirty = true; + } + + public void setBorderWidth(float borderWidth) { + mBorderWidth = PixelUtil.toPixelFromDIP(borderWidth); + mIsDirty = true; + } + public void setBorderRadius(float borderRadius) { mBorderRadius = PixelUtil.toPixelFromDIP(borderRadius); mIsDirty = true; @@ -180,6 +190,7 @@ public void maybeUpdateView() { RoundingParams roundingParams = hierarchy.getRoundingParams(); roundingParams.setCornersRadius(hierarchyRadius); + roundingParams.setBorder(mBorderColor, mBorderWidth); hierarchy.setRoundingParams(roundingParams); hierarchy.setFadeDuration(mImageFadeDuration >= 0 ? mImageFadeDuration From 28033742c9a94f8daca1470e98351fb12dd19ee6 Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Thu, 1 Oct 2015 02:28:39 -0700 Subject: [PATCH 0102/2702] fix white space in ReactImageManager.java Differential Revision: D2498072 --- .../com/facebook/react/views/image/ReactImageManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 429348b79fcb..62ef1d7ef5de 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -58,7 +58,7 @@ public ReactImageView createViewInstance(ThemedReactContext context) { public void setSource(ReactImageView view, @Nullable String source) { view.setSource(source); } - + @ReactProp(name = "borderColor", customType = "Color") public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { if (borderColor == null) { @@ -67,7 +67,7 @@ public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { view.setBorderColor(borderColor); } } - + @ReactProp(name = "borderWidth") public void setBorderWidth(ReactImageView view, float borderWidth) { view.setBorderWidth(borderWidth); From e89e344d6138f66dbb3cc31250ac57a4cbd63050 Mon Sep 17 00:00:00 2001 From: jmhdez Date: Tue, 15 Sep 2015 12:47:59 +0200 Subject: [PATCH 0103/2702] Respect projectName on template creation --- local-cli/init.js | 3 +-- react-native-cli/index.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/local-cli/init.js b/local-cli/init.js index fe753d5cb32c..ff2f57d1ae0f 100755 --- a/local-cli/init.js +++ b/local-cli/init.js @@ -3,11 +3,10 @@ var path = require('path'); var yeoman = require('yeoman-environment'); -function init(projectDir, appName) { +function init(projectDir, args) { console.log('Setting up new React Native app in ' + projectDir); var env = yeoman.createEnv(); env.register(require.resolve(path.join(__dirname, 'generator')), 'react:app'); - var args = process.argv.slice(3); var generator = env.create('react:app', {args: args}); generator.destinationRoot(projectDir); generator.run(); diff --git a/react-native-cli/index.js b/react-native-cli/index.js index 1fe259e267ee..b94bcf2672a4 100755 --- a/react-native-cli/index.js +++ b/react-native-cli/index.js @@ -145,8 +145,9 @@ function createProject(name) { process.exit(1); } + var args = [projectName].concat(process.argv.slice(4)); cli = require(CLI_MODULE_PATH()); - cli.init(root, projectName); + cli.init(root, args); }); } From 48ef50209ed03b54457b7855364092d8770fbe45 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 1 Oct 2015 04:19:46 -0700 Subject: [PATCH 0104/2702] Check if test runner is of right type before accessing its internals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: I previously added this check to make the test complete waits until all teardown completed but if you're running tests with any other executor than the JSC one, this would crash. @​public Reviewed By: @jspahrsummers Differential Revision: D2493967 --- Libraries/RCTTest/RCTTestRunner.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 41316360ba12..0da2c838862c 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -15,6 +15,7 @@ #import "RCTRootView.h" #import "RCTTestModule.h" #import "RCTUtils.h" +#import "RCTContextExecutor.h" static const NSTimeInterval kTestTimeoutSeconds = 60; static const NSTimeInterval kTestTeardownTimeoutSeconds = 30; @@ -114,7 +115,10 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName // Take a weak reference to the JS context, so we track its deallocation later // (we can only do this now, since it's been lazily initialized) - weakJSContext = [[[bridge valueForKey:@"batchedBridge"] valueForKey:@"javaScriptExecutor"] valueForKey:@"context"]; + id jsExecutor = [bridge valueForKeyPath:@"batchedBridge.javaScriptExecutor"]; + if ([jsExecutor isKindOfClass:[RCTContextExecutor class]]) { + weakJSContext = [jsExecutor valueForKey:@"context"]; + } [rootView removeFromSuperview]; RCTSetLogFunction(RCTDefaultLogFunction); From 792a0a737deba57f37769e1493c2c5d2a49d5632 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 1 Oct 2015 06:00:35 -0700 Subject: [PATCH 0105/2702] Fix UIExplorer tabbar test Reviewed By: @jspahrsummers Differential Revision: D2498139 --- .../testTabBarExample_1@2x.png | Bin 27129 -> 28731 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png index 62425fde96f040a302fedfbb3b5cbc70a768232f..90874b135922d53e5bc631bf4bf1fce9ef53d96d 100644 GIT binary patch delta 7769 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 N_}%e*#aW+6{{wRZ6#oDK delta 6113 zcmbt%XH-*Z*Y-)Mjs+MCUz9n=lCx4npp` zKqH^l9}}Di5Q2KsSSnv;Dy^6Qs-*gxsgP_+-Q%>3Ww)SRosRE2{{`~nXXE`QF#{F9 zKCVc2IW}lzvfu6a_vKq`a<+5{X44`ZR<1YM)%@i(fC;YK+}h~Zi$}kn$So~OR?Txa z?uTXpjZc17fk40fkypRvsB|Pwc@t<-(OvW z2C3&kem;^1yI=t=*KRaBqz3t)#Chdrk^l4je*!tX$BYjVOqNMdP#{DkcpHX%Ki!vBLY z0IQ>v)COXl$0f^8Z_W?bwBKabaL58bhoAIeY;Kd1^1iUxPt^_!%uBApNF?$@L$!A) zkA|h=AEtj<|LwtO8-H}|!&&da92jEoS{$EL2a;2peDWE)G;yq^KP*>ouEm!g4s7@A z3<{GR$*ePQ_5vlhgDiFM9vP*`I7(uT&M2(P-`FTWd2V?%D9i=0uuGUDV9`s5xUuES zQF!Y1C{nCh;r6Q1GjTFbOW5ADnqy|APYJSspaezm4_OhLyXe9rOB?Wav@Gqxjb1Pa zq4Tbw4AW+Vk0ZwN1Ng;37)4-om3DzUqFAt1`*6T%`<9gP@==M{w4zM9R$}GelCd24j_K}#!Lacl#I@JNdH&i2fiZZ39SRQ(jNYlpB^afw?{sl1`!FC} zelk8 zrLSKLD@rwofTdsm9M{V+%nHX3oh~1Lg9EOdHqsuhthV`N2R$HLAsW^M=Ox#azwyyu zZMphsL?tBgTzQPLy33u{7)+2JmvNo3RR7dhdS)L~b8r6!yqozFR6%A(0$shMie$xC z^Am^qmjf@#mxv-y)5eqmiP{HSd1ygg;d=VggI89sjH}8TU0uO!n3I zj(L=MrpQZ04H^&oQGb)aja?u9Gv`x-&$z->iYz!b>@S&XSfY@0c{sS4HrUg(O%9<>_Q z3@eGTcLCg#GB$V}neBYddcnw3==~S_Yde^DW`pg*7HTVLb$BWITaa44xXA-*<_a;u zzO%l(!c34a6)E zY3w)63aDLs7z+!90O*ic`8*+J=T9w&AdpalvR)!@jnSYA>__r%{%r#L(4T2+L@VQx z@A>u3nEUO3#T<|o`S9;y!X>i{}U|3`EgIWz}{M$|kT#h&Q?%Dd9AXZ*}e;x9D z!^=t;%sc2r2;r1+RUq~3%$oDoq^TZze*K zgTq*g6WX=(R`fj^2lfV#?M}axC32Fl2i=BR}b1-_Tc5*}o zE4rEgt!Faw>E^oA*xQaW)+lirru$R(;mJ=aue>^=(IdR?pQNYlU&P64OFY&zD>g2o)R z){|%)?_IGU_<0Qbb1T2KZI_;c z%&j(4oX__hO6-!IVwj%zwWgGSo(@D%-Z2>DiUo5*+VkxHQ&yp2Z$lSJ>I6*!8tvd! zwqvH$J~;Q+2m{}7k20fI5v;=+h(FJMqH#Bt%%P_v+E83@=2;Hqzdf23`Cu)09X&}y;0x9j8ux?UI8AZT_4bu-^PsNF77X?duiZtRW%b>#pq zLVPMDTh*)JP8I;Gdq7Dddl)vRd<>7I{pR2(x=@OT>k2O3M7iDw_)r^lXW?|jH!o~P zg6iY7Tgt=6V~#7N1f>;Fa>F>db7nXP%ZQUHInGWlRHb|F*Lk8N2SuJpt<2^FC0`a= zYR|U=sk|EY=q&~Gj!@+oqO0JJU4ETc_kd_f%0RuLV+J-%eC@Cel?t=5Og7IFHymc> z?vPE-eO%4EzRPU*$na?MH;vQ;Eep{X0jJlYlRNG9I2AsBo-ohwEp}W^%zBbX6lSsA z9s${jO}zlb=6p2MY6Ymq@5~Nt9@6bZIRr`M*N}T&3{Op6YTn{O7yVVH%Wq0Iyt=mJ zH5au0Fh*6KI{PzRm#GI!s`#D1?)cg}P>hMzu838>_4af~^=-ko0$cMG zeKO{}c94;R9PqJUv+y=OZ2B`|Adp(W!`$@p3GhWshvx*`;Q?K_D}lMNRh=4XdX|8G zNM~=4&|bcf-b_q$jSqMdW@VV}S@4^`1G5QRB;xci`Nco{RN6gC#sSY5&ArJe$#^{S zpAjN?8{Mlr62k#htgFk>;LU;` z{xTvl#^Bcx3lo9sf%MJS_}7JW)6g5QrakMmyEA>V>GVr3oGT9-D@|L;rh{$)znRo_ z*yr!p>COyqC&Kmtk&!2{oiV|Vb9jU9!M23yE&H{uO%uUn%FZL!H3pJre~vS|!+@%3 zt4uM+)~)p2z?72j7{Lqy!GYdA{ewhtv_?f899DL|j+}aAycL!3&y+SB);;1I z{O{3lTiQ%4HkOjS$*60zH$p}g%+O*dbY`;}lf$mmdR6V7+Veyw7dB@+Ss1)L_1E;5 z(2)zy$ydf#Zfq|8?HhimuVgAQBRzU4?Wb=(IGe*@yD@4>W#3E(^}Swj_Se5+cW=@d z9CQANK)(*2$G0>C*Ny>zg1?b(=#8&$T#o8)Z?IaD4YOO4ZUs499i}(n{gOt>0w zs8qbXXgapzoPBO}`)cy5*#~drF}X)`6L{BYVYzxMwhDDU3M{f7JwI}G(T8As7B-w8 zhZBIJ(h}cODwBT~C6}#l&(O%9--Q}UlF!kg_us>DMm*3$x`|9HDNW({KTVmSH6GUP zme4734Q6^I97oR$@6Zp>#6jczltUyRTP6-ZdOlbgwyP$8hBasl899^f*=1)^f-j(fS%OJ_fk@sEXI8J z)R7cTbn4~_>9~7as45x(=OS`R_w_NK>qKzVq!7CHz+joMU`?Sz51pg}(0Se7%?uAG z#&6I{#cdWg_h z6Z6hG?$Bm_FIYZQ3x1x9^5r|CSQ60@UleHXB-g}+>Ermf;gl_Kd%Ly6lTyqF#QCae zJ?Ak{m+v~#LI`kkI8}3t*P?LU(L$BUuPN`~Bh2`7lSS2=Z5#~e{D>Zo*>Yv}qijy+ zt>4mSmq^hT+dp&~DCxKhI=s_@Sm5f+saesw#>GU;4ESPc&1q55DZlo%fZ{%+_{6y4 z#JV6IQw(ldaxy4&{op1$Pbti1zP}9yDn}$T+hwyHewH~s- z8wu3kRYD}_P5g>wxv+KPRg8ylRI?0I12uDM6s6{~WYKr0Yy2lR>&>Z}!C4*mLtUPT zz|xbA4b7>t{lFRUZYNBI7ZDY@dE+kP8u7&r z104d8%^rC9pRC3V4Evo})ok6+w; zS$StS4mlAttnDl&St*QST5UWNSqU=a^EDiqAc3VFp%*uw*1nAbCX;+*k4_>t@DeF6 zH*J(q9G>9h4jODc!>(K22c=b;nPQEvf>a71>1&Y_)M)}tv7IE9FEJ%A`|+E`{hgTT zHHy=pNs6&~Ek_&t9D&=SK^=kZ2Gg#cWP^0>DsSzD-&ji(kpco>SGh|KTEks8a$0@j z9(URztH}J3qF|;ROB1IshH-C-#`C@N{m#lE{(Vq9-3nxntA9lzRP}YdNyZz?2kWPOVS-5V{!is@S?KwKChg&BM_T?yMN5?jJ5x6SR5V3;) zxXyiTZRQTq`E+aLM2(<@$Tvp`E;>}$Wxucm7%Z8nHbyF6$c5zj)#%5nMMod%rGz5l52mSyeXbVeL@f3BfsW-}hXJk)aV5jS=oLLQ)Jpz{=cz;?@z`PwakkDZt+;!s8miz9|9W-H1N_rM;|JGiXyRz>>~NMG0L z3ML6-y8}DSWRODb14&v}hxDIlmZe9yv2~wB?Vyf01{Awk)U79YVjjLszcZImz1KbZ zAN;?2;s$+@whYTfwK4%#3&HyHJML+8(~dnjl-Xh_+}so7`D7LD2q_0x4VTMr|DF)% zX2;Q?Y8s*?8wko0{75zz1mx>w!ANm4Hq8ejkSa1*-b1)A9XT$Fo25 zmysbObB)j7WuQ8>Svh1|oVN3QdshP-D*Hqj(3ReLDfMJ=6W9NBo|F5k7#!oK(CSr zfd#j=>*2J(EBrzmed16;XYEv1p2JIk^)VC?_pc1bY@s%Fi{!2YzX06q3W3Q>e)ZU# zF{_ng4uoC*E8TVeWp};Fqr^=*(1l7x1#j!$WR?(8CxPr2ajF=SADjT;>f-F@=#xhb zGQ7X;o$y%PD|?>{9=QUf$%?>v$Eub~J~B`eA@^4fXgaI-<3dq*`MR7?N6L>)jc>gB zX5^q`0L^~2Fxv_QP(*#jIP<089yvqc*bC7yM0rN6H$U3@6WQ5`_wqRWB@n&t`{^4g zCJWUjSv~r1Air-2VL>u+B zI{HKjbrlvdeNTTHYkmrh=7w=Cp9z&8yn(*@mn!aRA;p?n5B@piy7n^ahpzD9y&9a@ z-MFf_vLKM-Q=@Y+_d$n`TQO?)@rB+&YBUKOeYrSicU(`B1aH#Bu?5!M0ONdFirc_fDfC9uRXYhtA!iV zB~k>!xB)JY;vT?5%q)oCY<9OnxNUn2#BbCLDx#K1!P%k`ds)+jm?B|y)3|cp^|oT4 zMFx50HfP&pQtFZLo_HlQB45NLvDrz-)Nh^vtQRwDW@Ip90LC?T-NsG$v2WX9-j3?s zWx6>aAwz8|_0~57qTk$G$Wg1;Kl7Lofjz8*xBz$^XY#vVOn9Esh@i!}nP)1>Ml(y5 z_M+xKmnemDb6zJz)lBa)e(IW44!FE-G#B`qL2!s3hQFe~ZY z>X6GF7*6X?l=y=-IgT(`57EIStJ-;>%Z<`IEjU-+PV~QToK{hJq;%syfZo3@aq2ER zAoMO_o;(Hi6UE*8H)fu!*9TPoVcB@BjBB!Z?@PBB-ZCP<)(1`8zqu%`0V{Q$XGMl^ zs+s8(zv<$`6P(@8?ZP6u|KldR>)&(m8q9n*ZIb?X(I~DAe&Exhg5UdGm^Ag7x-?-B z>JP0twS54j;#IGPG+fjvtkW)d996tk(*=N@bC1BMt#5>LVz=A4Tj2oZ!qCvSJQV}V m=O#dT9{@l#0UT;W?zt0Da6)tx^9E2{zn*kEQF|=pxBmx2)Q_(K From 88226afbde3bd9836286df92cb02700c4438bb3e Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 15:26:05 +0100 Subject: [PATCH 0106/2702] [cli] Fix 'react-native start' on Windows Based on https://github.com/facebook/react-native/pull/2989 Thanks @BerndWessels! --- local-cli/run-packager.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/local-cli/run-packager.js b/local-cli/run-packager.js index 111c15b0263a..639040134aac 100644 --- a/local-cli/run-packager.js +++ b/local-cli/run-packager.js @@ -16,10 +16,18 @@ module.exports = function(newWindow) { {detached: true}); } } else { - child_process.spawn('sh', [ - path.resolve(__dirname, '..', 'packager', 'packager.sh'), + if (/^win/.test(process.platform)) { + child_process.spawn('node', [ + path.resolve(__dirname, '..', 'packager', 'packager.js'), '--projectRoots', process.cwd(), - ], {stdio: 'inherit'}); + ], {stdio: 'inherit'}); + } else { + child_process.spawn('sh', [ + path.resolve(__dirname, '..', 'packager', 'packager.sh'), + '--projectRoots', + process.cwd(), + ], {stdio: 'inherit'}); + } } }; From e05fdb59770450b7c0d4c1c0a51b4ba5f1ba966e Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 15:33:45 +0100 Subject: [PATCH 0107/2702] [cli] Formatting --- local-cli/run-packager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/local-cli/run-packager.js b/local-cli/run-packager.js index 639040134aac..a4d8f9cab827 100644 --- a/local-cli/run-packager.js +++ b/local-cli/run-packager.js @@ -18,9 +18,9 @@ module.exports = function(newWindow) { } else { if (/^win/.test(process.platform)) { child_process.spawn('node', [ - path.resolve(__dirname, '..', 'packager', 'packager.js'), - '--projectRoots', - process.cwd(), + path.resolve(__dirname, '..', 'packager', 'packager.js'), + '--projectRoots', + process.cwd(), ], {stdio: 'inherit'}); } else { child_process.spawn('sh', [ From a058236e57e759ab956c29b466bd5bd6ebb80aca Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Thu, 1 Oct 2015 10:54:40 -0700 Subject: [PATCH 0108/2702] enable es6 module syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This is an updated copy of #1993, which was approved by @vjeux but hasn't been rebased. It whitelists the es6 module syntax and updates the JS Environment docs to match. cc @ide @​hkjorgensenCloses https://github.com/facebook/react-native/pull/3175 Reviewed By: @​svcscm Differential Revision: D2498360 Pulled By: @vjeux --- packager/react-packager/.babelrc | 1 + packager/transformer.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packager/react-packager/.babelrc b/packager/react-packager/.babelrc index 45e1d6e9f67d..602c9fe61039 100644 --- a/packager/react-packager/.babelrc +++ b/packager/react-packager/.babelrc @@ -9,6 +9,7 @@ "es6.classes", "es6.constants", "es6.destructuring", + "es6.modules", "es6.parameters", "es6.properties.computed", "es6.properties.shorthand", diff --git a/packager/transformer.js b/packager/transformer.js index 59a1ea44f8fd..dc137c620b45 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -37,6 +37,7 @@ function transform(src, filename, options) { 'es6.classes', 'es6.constants', 'es6.destructuring', + 'es6.modules', 'es6.parameters', 'es6.properties.computed', 'es6.properties.shorthand', From 4287a5c61663fb52226b3848540b54e0285e17f4 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 19:46:09 +0100 Subject: [PATCH 0109/2702] Add a note about verbose output for RN packager --- packager/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packager/README.md b/packager/README.md index c3fae48064e8..2c16fec7dfdd 100644 --- a/packager/README.md +++ b/packager/README.md @@ -124,6 +124,14 @@ Given an entry point module. Recursively collect all the dependent modules and return it as an array. `options` is the same options that is passed to `ReactPackager.middleware` +## Debugging + +To get verbose output when running the packager, define an environment variable: + + export DEBUG=ReactNativePackager + +The `/debug` endpoint discussed above is also useful. + ## FAQ ### Can I use this in my own non-React Native project? From 8b39f63a09994bbfb521de344656bf0799980600 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 19:53:05 +0100 Subject: [PATCH 0110/2702] Another note about verbose output for RN packager --- packager/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packager/README.md b/packager/README.md index 2c16fec7dfdd..39dd5bdee696 100644 --- a/packager/README.md +++ b/packager/README.md @@ -130,6 +130,8 @@ To get verbose output when running the packager, define an environment variable: export DEBUG=ReactNativePackager +You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager`. + The `/debug` endpoint discussed above is also useful. ## FAQ From 7be3b18898de7d8a1e16b1c9b8f4ad42c491a1f1 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 19:59:41 +0100 Subject: [PATCH 0111/2702] Mention debug.js in packager docs --- packager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/README.md b/packager/README.md index 39dd5bdee696..2090a0fd6fde 100644 --- a/packager/README.md +++ b/packager/README.md @@ -130,7 +130,7 @@ To get verbose output when running the packager, define an environment variable: export DEBUG=ReactNativePackager -You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager`. +You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager`. Under the hood this uses the [`debug`](https://www.npmjs.com/package/debug) package, see its documentation for all the available options. The `/debug` endpoint discussed above is also useful. From 14ce0a4c5abadbc5a2b99f5323227f33f01e856e Mon Sep 17 00:00:00 2001 From: Atticus White Date: Sat, 26 Sep 2015 23:04:05 -0400 Subject: [PATCH 0112/2702] add android .gradle and build directories to sample app gitignore --- local-cli/generator/templates/_gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/local-cli/generator/templates/_gitignore b/local-cli/generator/templates/_gitignore index b927355df441..94fc86711d18 100644 --- a/local-cli/generator/templates/_gitignore +++ b/local-cli/generator/templates/_gitignore @@ -22,6 +22,12 @@ DerivedData *.xcuserstate project.xcworkspace +# Android/IJ +# +.idea +.gradle +local.properties + # node.js # node_modules/ From 0781171d84cebb6bfb8ad8392d2f7798f61091f3 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 1 Oct 2015 20:10:13 +0100 Subject: [PATCH 0113/2702] Fix packager docs --- packager/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/README.md b/packager/README.md index 2090a0fd6fde..67bab1d9bb21 100644 --- a/packager/README.md +++ b/packager/README.md @@ -128,9 +128,9 @@ is passed to `ReactPackager.middleware` To get verbose output when running the packager, define an environment variable: - export DEBUG=ReactNativePackager + export DEBUG=ReactNativePackager:* -You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager`. Under the hood this uses the [`debug`](https://www.npmjs.com/package/debug) package, see its documentation for all the available options. +You can combine this with other values, e.g. `DEBUG=babel,ReactNativePackager:*`. Under the hood this uses the [`debug`](https://www.npmjs.com/package/debug) package, see its documentation for all the available options. The `/debug` endpoint discussed above is also useful. From dac539435de7fa0dcabf42733615cd0adf728249 Mon Sep 17 00:00:00 2001 From: shlomiatar Date: Thu, 1 Oct 2015 12:15:24 -0700 Subject: [PATCH 0114/2702] Fix DependencyResolver to support ES6 multiline imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: *This is a PR to fix #1939 (DependencyResolver fails to handle ES6 modules import statements with new lines)* **This PR includes:** - A fix to the problematic regular expression - Updated tests that support the new line style. **Summary:** We found out that while the packager does its module wrapping thing for lines like this: ```js import theDefault, { named1, named2 } from 'src/mylib'; ``` It fails to do the same for multi line imports: ```js import theDefault, { named1, named2 } from 'src/mylib'; ``` We've tracked done the issue to a [faulty regular expression in replacePatterns.js](https://github.com/facebook/react-native/blob/master/packager/react-packager/src/DependencyResolver/replacePatterns.js#L12) You can see various import statements with the problematic regular expression [here](http://regexr.com/3bc8m) We've figure out a better regular expression (you can play around with it [here](http://regexr.com/3bd3s))Closes https://github.com/facebook/react-native/pull/1940 Reviewed By: @​svcscm Differential Revision: D2498519 Pulled By: @vjeux --- .../__tests__/HasteDependencyResolver-test.js | 116 ++++++++++++++++++ .../src/DependencyResolver/replacePatterns.js | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 5231dc4a5305..bb04dd4385e2 100644 --- a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -218,6 +218,7 @@ describe('HasteDependencyResolver', function() { /*eslint-disable */ var code = [ + // single line import "import'x';", "import 'x';", "import 'x' ;", @@ -350,6 +351,63 @@ describe('HasteDependencyResolver', function() { 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', 'import Default from "y";', 'import * as All from \'z\';', + // import with support for new lines + "import { Foo,\n Bar }\n from 'x';", + "import { \nFoo,\nBar,\n }\n from 'x';", + "import { Foo as Bar,\n Baz\n }\n from 'x';", + "import { \nFoo as Bar,\n Baz\n, }\n from 'x';", + "import { Foo,\n Bar as Baz\n }\n from 'x';", + "import { Foo,\n Bar as Baz,\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", + "import { Foo,\n Bar,\n Baz }\n from 'x';", + "import { Foo,\n Bar,\n Baz,\n }\n from 'x';", + "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", + "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", + "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", + "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", + "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", + "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", + "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';", + "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';", + "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';", + "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';", + "import Default,\n * as All from 'x';", + "import Default,\n { } from 'x';", + "import Default,\n { Foo\n }\n from 'x';", + "import Default,\n { Foo,\n }\n from 'x';", + "import Default,\n { Foo as Bar\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n }\n from 'x';", + "import Default,\n { Foo,\n Bar\n } from\n 'x';", + "import Default,\n { Foo,\n Bar,\n } from\n 'x';", + "import Default,\n { Foo as Bar,\n Baz\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';", + "import Default,\n { Foo,\n Bar as Baz\n }\n from 'x';", + "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", + "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';", + "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';", + "import Default\n , { } from 'x';", + // require 'require("x")', 'require("y")', 'require( \'z\' )', @@ -381,6 +439,7 @@ describe('HasteDependencyResolver', function() { expect(processedCode).toEqual([ '__d(\'test module\',["changed","Y"],function(global, require,' + ' module, exports) { ' + + // single line import "import'x';", "import 'changed';", "import 'changed' ;", @@ -513,6 +572,63 @@ describe('HasteDependencyResolver', function() { 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', 'import Default from "Y";', 'import * as All from \'z\';', + // import with support for new lines + "import { Foo,\n Bar }\n from 'changed';", + "import { \nFoo,\nBar,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz\n }\n from 'changed';", + "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';", + "import { Foo,\n Bar as Baz\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz }\n from 'changed';", + "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", + "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", + "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", + "import Default,\n * as All from 'changed';", + "import Default,\n { } from 'changed';", + "import Default,\n { Foo\n }\n from 'changed';", + "import Default,\n { Foo,\n }\n from 'changed';", + "import Default,\n { Foo as Bar\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar\n } from\n 'changed';", + "import Default,\n { Foo,\n Bar,\n } from\n 'changed';", + "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", + "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", + "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", + "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", + "import Default\n , { } from 'changed';", + // require 'require("changed")', 'require("Y")', 'require( \'z\' )', diff --git a/packager/react-packager/src/DependencyResolver/replacePatterns.js b/packager/react-packager/src/DependencyResolver/replacePatterns.js index c27d7c7703f2..ef5c42f2fcf4 100644 --- a/packager/react-packager/src/DependencyResolver/replacePatterns.js +++ b/packager/react-packager/src/DependencyResolver/replacePatterns.js @@ -9,7 +9,7 @@ 'use strict'; -exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g; +exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; From 38df6f8132c9ee13eeb4d8239bcd110a29044f2a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 1 Oct 2015 12:25:28 -0700 Subject: [PATCH 0115/2702] Import event-target-shim npm module Reviewed By: @foghina Differential Revision: D2498747 --- npm-shrinkwrap.json | 5 +++++ package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6d93f511cc78..238d10cb1b1c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2071,6 +2071,11 @@ "from": "eslint-plugin-react@3.3.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.3.1.tgz" }, + "event-target-shim": { + "version": "1.0.5", + "from": "event-target-shim@*", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.0.5.tgz" + }, "fbjs-scripts": { "version": "0.2.1", "from": "fbjs-scripts@>=0.2.1 <0.3.0", diff --git a/package.json b/package.json index 155f8af8e68f..fa7105730401 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "chalk": "^1.1.1", "connect": "^2.8.3", "debug": "^2.2.0", + "event-target-shim": "^1.0.5", "fbjs-scripts": "^0.2.1", "graceful-fs": "^4.1.2", "image-size": "^0.3.5", From 45992e7ff207c8b008bb4baf862066dbdd482107 Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Thu, 1 Oct 2015 14:05:44 +0100 Subject: [PATCH 0116/2702] documentation for es6 module syntax --- docs/JavaScriptEnvironment.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/JavaScriptEnvironment.md b/docs/JavaScriptEnvironment.md index 4091cced9a68..a3035fe778cc 100644 --- a/docs/JavaScriptEnvironment.md +++ b/docs/JavaScriptEnvironment.md @@ -34,6 +34,7 @@ ES6 * [Call spread](http://babeljs.io/docs/learn-es2015/#default-rest-spread): `Math.max(...array);` * [Classes](http://babeljs.io/docs/learn-es2015/#classes): `class C extends React.Component { render() { return ; } }` * [Destructuring](http://babeljs.io/docs/learn-es2015/#destructuring): `var {isActive, style} = this.props;` +* [Modules](http://babeljs.io/docs/learn-es2015/#modules): `import React, { Component } from 'react-native';` * [Computed Properties](http://babeljs.io/docs/learn-es2015/#enhanced-object-literals): `var key = 'abc'; var obj = {[key]: 10};` * Object Consise Method: `var obj = { method() { return 10; } };` * [Object Short Notation](http://babeljs.io/docs/learn-es2015/#enhanced-object-literals): `var name = 'vjeux'; var obj = { name };` From 7637f95d10fe688b476246964366e7bfb7c55b74 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 1 Oct 2015 12:48:37 -0700 Subject: [PATCH 0117/2702] Fix profiler setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Update packager entry and profiler pre-built dylib + Update makefile to make it easier to use different versions of Xcode and shortcircuit when using the wrong version. Reviewed By: @jspahrsummers Differential Revision: D2498157 --- JSCLegacyProfiler/Makefile | 10 ++++++---- packager/packager.js | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/JSCLegacyProfiler/Makefile b/JSCLegacyProfiler/Makefile index 01503073526a..921ea75beb37 100644 --- a/JSCLegacyProfiler/Makefile +++ b/JSCLegacyProfiler/Makefile @@ -1,6 +1,7 @@ HEADER_PATHS := `find download/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"` -SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$1.platform/Developer/SDKs/$1.sdk +XCODE_PATH ?= $(shell xcode-select -p) +SDK_PATH = $(XCODE_PATH)/Platforms/$1.platform/Developer/SDKs/$1.sdk SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk) @@ -19,13 +20,14 @@ SYSROOT = -isysroot $(call SDK_PATH,$${PLATFORM}) IOS8_LIBS = download/WebCore/WebCore-7600.1.25 download/WTF/WTF-7600.1.24 download/JavaScriptCore/JavaScriptCore-7600.1.17 download/JavaScriptCore/JavaScriptCore-7600.1.17/Bytecodes.h -ios8: RCTJSCProfiler.ios8.dylib /tmp/RCTJSCProfiler ifneq ($(SDK_VERSION), 8) +all: $(error "Expected to be compiled with iOS SDK version 8, found $(SDK_VERSION)") -else - cp $^ endif +ios8: RCTJSCProfiler.ios8.dylib /tmp/RCTJSCProfiler + cp $^ + /tmp/RCTJSCProfiler: mkdir -p $@ diff --git a/packager/packager.js b/packager/packager.js index 9f0fd6c14d1c..fd040595d091 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -233,14 +233,14 @@ function systraceProfileMiddleware(req, res, next) { childProcess.exec(cmd, function(error) { if (error) { if (error.code === 127) { - res.end( - '\n** Failed executing `' + cmd + '` **\n\n' + + var response = '\n** Failed executing `' + cmd + '` **\n\n' + 'Google trace-viewer is required to visualize the data, You can install it with `brew install trace2html`\n\n' + 'NOTE: Your profile data was kept at:\n' + dumpName - ); + console.log(response); + res.end(response); } else { console.error(error); - res.end('Unknown error %s', error.message); + res.end('Unknown error: ' + error.message); } return; } else { @@ -267,16 +267,16 @@ function cpuProfileMiddleware(req, res, next) { var dumpName = '/tmp/cpu-profile_' + Date.now(); fs.writeFileSync(dumpName + '.json', req.rawBody); - var cmd = path.join(__dirname, '..', 'JSCLegacyProfiler', 'json2trace') + ' -cpuprofiler ' + dumpName + '.cpuprofile ' + dumpName + '.json'; + var cmd = path.join(__dirname, '..', 'react-native-github', 'JSCLegacyProfiler', 'json2trace') + ' -cpuprofiler ' + dumpName + '.cpuprofile ' + dumpName + '.json'; childProcess.exec(cmd, function(error) { if (error) { console.error(error); - res.end('Unknown error: %s', error.message); + res.end('Unknown error: ' + error.message); } else { - res.end( - 'Your profile was generated at\n\n' + dumpName + '.cpuprofile\n\n' + - 'Open `Chrome Dev Tools > Profiles > Load` and select the profile to visualize it.' - ); + var response = 'Your profile was generated at\n\n' + dumpName + '.cpuprofile\n\n' + + 'Open `Chrome Dev Tools > Profiles > Load` and select the profile to visualize it.'; + console.log(response); + res.end(response); } }); } From 44aeece3dec4c62c40ac5ecb4766517a5f5c672d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 1 Oct 2015 14:08:13 -0700 Subject: [PATCH 0118/2702] Fix ReactPerf markers in Systrace Reviewed By: @spicyj Differential Revision: D2468107 --- .../InitializeJavaScriptAppEngine.js | 4 +- Libraries/Utilities/BridgeProfiling.js | 50 ++++++++++++------- React/Executors/RCTContextExecutor.m | 18 +++---- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index ce1e56cfabfa..fde1f06c1892 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -133,7 +133,9 @@ function setUpWebSockets() { function setupProfile() { console.profile = console.profile || GLOBAL.nativeTraceBeginSection || function () {}; console.profileEnd = console.profileEnd || GLOBAL.nativeTraceEndSection || function () {}; - require('BridgeProfiling').swizzleReactPerf(); + if (__DEV__) { + require('BridgeProfiling').swizzleReactPerf(); + } } function setUpProcessEnv() { diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 94ff3b3c6d39..8ff7583cd30d 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -14,9 +14,24 @@ var GLOBAL = GLOBAL || this; var TRACE_TAG_REACT_APPS = 1 << 17; +var _enabled = false; +var _ReactPerf = null; +function ReactPerf() { + if (!_ReactPerf) { + _ReactPerf = require('ReactPerf'); + } + return _ReactPerf; +} + var BridgeProfiling = { + setEnabled(enabled: boolean) { + _enabled = enabled; + + ReactPerf().enableMeasure = enabled; + }, + profile(profileName?: any) { - if (GLOBAL.__BridgeProfilingIsProfiling) { + if (_enabled) { profileName = typeof profileName === 'function' ? profileName() : profileName; console.profile(TRACE_TAG_REACT_APPS, profileName); @@ -24,29 +39,28 @@ var BridgeProfiling = { }, profileEnd() { - if (GLOBAL.__BridgeProfilingIsProfiling) { + if (_enabled) { console.profileEnd(TRACE_TAG_REACT_APPS); } }, - swizzleReactPerf() { - var ReactPerf = require('ReactPerf'); - var originalMeasure = ReactPerf.measure; - ReactPerf.measure = function (objName, fnName, func) { - func = originalMeasure.apply(ReactPerf, arguments); - return function (component) { - if (GLOBAL.__BridgeProfilingIsProfiling) { - var name = this._instance && this._instance.constructor && - (this._instance.constructor.displayName || - this._instance.constructor.name); - BridgeProfiling.profile(`${objName}.${fnName}(${name})`); - } - var ret = func.apply(this, arguments); - BridgeProfiling.profileEnd(); - return ret; - }; + reactPerfMeasure(objName: string, fnName: string, func: any): any { + return function (component) { + if (!_enabled) { + return func.apply(this, arguments); + } + + var name = objName === 'ReactCompositeComponent' && this.getName() || ''; + BridgeProfiling.profile(`${objName}.${fnName}(${name})`); + var ret = func.apply(this, arguments); + BridgeProfiling.profileEnd(); + return ret; }; }, + + swizzleReactPerf() { + ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure); + }, }; module.exports = BridgeProfiling; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 971d10111e38..4781e72deaf8 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -336,17 +336,13 @@ - (void)setUp - (void)toggleProfilingFlag:(NSNotification *)notification { - JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); - - bool enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; - JSStringRef JSName = JSStringCreateWithUTF8CString("__BridgeProfilingIsProfiling"); - JSObjectSetProperty(_context.ctx, - globalObject, - JSName, - JSValueMakeBoolean(_context.ctx, enabled), - kJSPropertyAttributeNone, - NULL); - JSStringRelease(JSName); + [self executeBlockOnJavaScriptQueue:^{ + BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; + NSString *script = [NSString stringWithFormat:@"var p = require('BridgeProfiling') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"]; + JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String); + JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL); + JSStringRelease(scriptJSRef); + }]; } - (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name From c2ff0f8a5027e6a1084dca32671ecd27f2de3423 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 1 Oct 2015 14:13:24 -0700 Subject: [PATCH 0119/2702] Polyfill Number.EPSILON and Number.MIN/MAX_SAFE_INTEGER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Those three properties have been ratified as ES6 but are not yet implementd in JSCore. This polyfills them. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON @​public Reviewed By: @sahrens Differential Revision: D2497528 --- .../InitializeJavaScriptAppEngine.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index fde1f06c1892..fcd695abde81 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -19,7 +19,7 @@ * @providesModule InitializeJavaScriptAppEngine */ -/* eslint global-strict: 0 */ +/* eslint strict: 0 */ /* globals GLOBAL: true, window: true */ // Just to make sure the JS gets packaged up. @@ -59,7 +59,7 @@ function setUpRedBoxConsoleErrorHandler() { } } -function setupFlowChecker() { +function setUpFlowChecker() { if (__DEV__) { var checkFlowAtRuntime = require('checkFlowAtRuntime'); checkFlowAtRuntime(); @@ -130,7 +130,7 @@ function setUpWebSockets() { GLOBAL.WebSocket = require('WebSocket'); } -function setupProfile() { +function setUpProfile() { console.profile = console.profile || GLOBAL.nativeTraceBeginSection || function () {}; console.profileEnd = console.profileEnd || GLOBAL.nativeTraceEndSection || function () {}; if (__DEV__) { @@ -146,6 +146,12 @@ function setUpProcessEnv() { } } +function setUpNumber() { + Number.EPSILON = Number.EPSILON || Math.pow(2, -52); + Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1; + Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1); +} + setUpRedBoxErrorHandler(); setUpTimers(); setUpAlert(); @@ -154,6 +160,7 @@ setUpXHR(); setUpRedBoxConsoleErrorHandler(); setUpGeolocation(); setUpWebSockets(); -setupProfile(); +setUpProfile(); setUpProcessEnv(); -setupFlowChecker(); +setUpFlowChecker(); +setUpNumber(); From c2174d495299a22c1b6d64eb95fd4c78075a3be8 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 1 Oct 2015 14:17:35 -0700 Subject: [PATCH 0120/2702] Add benchmark for attribute diffing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Benchmark for testing the attribute diffing algorithm. @​public Reviewed By: @vjeux Differential Revision: D2498078 --- .../ReactNativeAttributePayload-benchmark.js | 324 ++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js diff --git a/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js b/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js new file mode 100644 index 000000000000..afc2b81f38ad --- /dev/null +++ b/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js @@ -0,0 +1,324 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule ReactNativeAttributePayload-benchmark + */ +'use strict'; + +var StyleSheet = require('StyleSheet'); + +// Various example props + +var small1 = { + accessible: true, + accessibilityLabel: 'Hello', + collapsable: true, +}; + +var small2 = { + accessible: true, + accessibilityLabel: 'Hello 2', + collapsable: false, + needsOffscreenAlphaCompositing: true, +}; + +var small3 = { + accessible: true, + accessibilityLabel: 'Hello 2', +}; + +var medium1 = { + ...small1, + onLayout: function() {}, + onAccessibilityTap: true, + onMagicTap: function() {}, + collapsable: true, + needsOffscreenAlphaCompositing: true, + style: { + backgroundColor: 'rgba(255, 0, 0, 0.5)' + } +}; + +var medium2 = { + ...small2, + onLayout: function() {}, + onAccessibilityTap: true, + onMagicTap: function() {}, + collapsable: true, + needsOffscreenAlphaCompositing: true, + style: { + backgroundColor: 'rgba(128, 0, 0, 1)', + color: [128, 0, 0], + shadowColor: 56, + textDecorationColor: 34, + tintColor: 45, + + transform: [ + {perspective: 5}, + {scale: 5}, + {scaleX: 10}, + {scaleY: 10}, + {translateX: 2}, + {translateY: 5} + ], + } +}; + +var medium3 = { + ...small3, + onAccessibilityTap: true, + onMagicTap: function() {}, + needsOffscreenAlphaCompositing: true, + style: { + backgroundColor: 'rgba(255, 0, 0, 0.5)', + color: [128, 0, 0], + shadowColor: 56, + textDecorationColor: 34, + tintColor: 45, + + transform: [ + {perspective: 5}, + {scale: 5}, + {scaleX: 12}, + {scaleY: 16}, + {translateX: 10}, + {translateY: 5} + ], + } +}; + +var style1 = { + + backgroundColor: 'rgba(10,0,0,1)', + borderColor: 'rgba(10,0,0,1)', + color: [255, 0, 0], + shadowColor: 54, + textDecorationColor: 34, + tintColor: 45, + + transform: [ + {perspective: 5}, + {scale: 5}, + {scaleX: 2}, + {scaleY: 3}, + {translateX: 2}, + {translateY: 3} + ], + +}; + +var style1b = { + + backgroundColor: 'rgba(10,0,0,1)', + borderColor: 'rgba(10,0,0,1)', + color: [128, 0, 0], + shadowColor: 56, + textDecorationColor: 34, + tintColor: 45, + + transform: [ + {perspective: 5}, + {scale: 5}, + {scaleX: 10}, + {scaleY: 10}, + {translateX: 2}, + {translateY: 5} + ], + +}; + +var style2 = { + + shadowOffset: { width: 10, height: 15 }, + + resizeMode: 'contain', // 'cover' + overflow: 'visible', // 'hidden' + + opacity: 0.5, + + width: 123, + height: 43, + top: 13, + left: 43, + right: 12, + bottom: 123, + margin: 13, + padding: 53, + paddingRight: 523, + borderWidth: 63, + borderRadius: 123, +}; + +var style3 = { + position: 'absolute', // 'relative' + flexDirection: 'row', // 'column' + flexWrap: 'wrap', // 'nowrap' + justifyContent: 'flex-start', // 'flex-end' + alignItems: 'center', + alignSelf: 'auto', + flex: 0, +}; + +var style3b = { + position: 'relative', + flexDirection: 'column', + flexWrap: 'nowrap', + justifyContent: 'flex-end', +}; + +var { regStyle2, regStyle3 } = StyleSheet.create({ + regStyle2: style2, + regStyle3: style3 +}); + +var large1 = { + ...medium1, + style: { + ...medium1.style, + ...style1, + ...style2, + ...style3 + } +}; + +var large2 = { + ...medium2, + style: [ + [regStyle2, style1], + regStyle3 + ] +}; + +var large3 = { + ...medium3, + style: [ + [regStyle2, style1b], + style3 + ] +}; + +var large4 = { + ...medium3, + style: [ + [regStyle2, style1b], + style3b + ] +}; + +// Clones, to test + +var clone = function (obj) { + var str = JSON.stringify(obj, function(k, v) { + return typeof v === 'function' ? 'FUNCTION' : v; + }); + return JSON.parse(str, function(k, v) { + return v === 'FUNCTION' ? function() {} : v; + }); +}; + +var small4 = clone(small3); +var medium4 = clone(medium3); +var large5 = clone(large4); + +// Test combinations + +var variants = { + s1: small1, + s2: small2, + s3: small3, + s4: small4, + m1: medium1, + m2: medium2, + m3: medium3, + m4: medium4, + l1: large1, + l2: large2, + l3: large3, + l4: large4, + l5: large5, +}; + +// Differ + +var validAttributes = require('ReactNativeViewAttributes').UIView; + +var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); +ReactNativeBaseComponent.prototype.diff = + ReactNativeBaseComponent.prototype.computeUpdatedProperties; +var Differ = new ReactNativeBaseComponent({ + validAttributes: validAttributes, + uiViewClassName: 'Differ' +}); + +// Runner + +var numberOfBenchmarks = 0; +var totalTimeForAllBenchmarks = 0; +var results = {}; + +function runBenchmarkOnce(value1, value2) { + // Warm up the differ. This is needed if the differ uses state to store the + // previous values. + Differ.diff({}, value1, validAttributes); + var cache = Differ.previousFlattenedStyle; + var start = Date.now(); + for (var i = 0; i < 100; i++) { + Differ.diff(value1, value2, validAttributes); + Differ.previousFlattenedStyle = cache; + } + var end = Date.now(); + return (end - start); +} + +function runBenchmark(key1, key2, value1, value2) { + var totalTime = 0; + var nthRuns = 5; + for (var i = 0; i < nthRuns; i++) { + totalTime += runBenchmarkOnce(value1, value2); + } + results[key1 + key2] = totalTime / nthRuns; + totalTimeForAllBenchmarks += totalTime / nthRuns; + numberOfBenchmarks++; +} + +function runAllCombinations() { + for (var outerKey in variants) { + for (var innerKey in variants) { + if (variants.hasOwnProperty(outerKey) && + variants.hasOwnProperty(innerKey)) { + runBenchmark( + outerKey, + innerKey, + variants[outerKey], + variants[innerKey] + ); + } + } + } +} + +function formatResult() { + var str = + 'Average runtime: ' + + (totalTimeForAllBenchmarks / numberOfBenchmarks) + + ' units\n'; + + var worstCase = 0; + for (var key in results) { + if (results[key] > worstCase) { + worstCase = results[key]; + } + } + + str += 'Worst case: ' + worstCase + ' units\n'; + + str += 'Per combination:\n'; + for (var key in results) { + str += key + ':\u00A0' + results[key] + ', '; + } + return str; +} + +runAllCombinations(); + +module.exports = formatResult(); From 13abb8053e9aec5a44a8c45317cae25588f37224 Mon Sep 17 00:00:00 2001 From: leeyeh Date: Thu, 1 Oct 2015 17:38:40 -0700 Subject: [PATCH 0121/2702] Implement EventTarget interface for WebSocket. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: close #2583Closes https://github.com/facebook/react-native/pull/2599 Reviewed By: @​svcscm Differential Revision: D2498641 Pulled By: @vjeux --- Libraries/WebSocket/WebSocket.ios.js | 30 ++++++++++++++++++++++++---- Libraries/WebSocket/WebSocketBase.js | 5 ++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Libraries/WebSocket/WebSocket.ios.js b/Libraries/WebSocket/WebSocket.ios.js index e48d7882bf9e..c2318b4976ec 100644 --- a/Libraries/WebSocket/WebSocket.ios.js +++ b/Libraries/WebSocket/WebSocket.ios.js @@ -16,6 +16,19 @@ var RCTWebSocketManager = require('NativeModules').WebSocketManager; var WebSocketBase = require('WebSocketBase'); +class Event { + constructor(type) { + this.type = type.toString(); + } +} + +class MessageEvent extends Event { + constructor(type, eventInitDict) { + super(type); + Object.assign(this, eventInitDict); + } +} + var WebSocketId = 0; class WebSocket extends WebSocketBase { @@ -58,9 +71,11 @@ class WebSocket extends WebSocketBase { if (ev.id !== id) { return; } - this.onmessage && this.onmessage({ + var event = new MessageEvent('message', { data: ev.data }); + this.onmessage && this.onmessage(event); + this.dispatchEvent(event); }.bind(this) ), RCTDeviceEventEmitter.addListener( @@ -70,7 +85,9 @@ class WebSocket extends WebSocketBase { return; } this.readyState = this.OPEN; - this.onopen && this.onopen(); + var event = new Event('open'); + this.onopen && this.onopen(event); + this.dispatchEvent(event); }.bind(this) ), RCTDeviceEventEmitter.addListener( @@ -80,7 +97,9 @@ class WebSocket extends WebSocketBase { return; } this.readyState = this.CLOSED; - this.onclose && this.onclose(ev); + var event = new Event('close'); + this.onclose && this.onclose(event); + this.dispatchEvent(event); this._unregisterEvents(); RCTWebSocketManager.close(id); }.bind(this) @@ -91,7 +110,10 @@ class WebSocket extends WebSocketBase { if (ev.id !== id) { return; } - this.onerror && this.onerror(new Error(ev.message)); + var event = new Event('error'); + event.message = ev.message; + this.onerror && this.onerror(event); + this.dispatchEvent(event); this._unregisterEvents(); RCTWebSocketManager.close(id); }.bind(this) diff --git a/Libraries/WebSocket/WebSocketBase.js b/Libraries/WebSocket/WebSocketBase.js index 73cc62cc91d0..a4cca418401e 100644 --- a/Libraries/WebSocket/WebSocketBase.js +++ b/Libraries/WebSocket/WebSocketBase.js @@ -11,10 +11,12 @@ */ 'use strict'; +var EventTarget = require('event-target-shim'); + /** * Shared base for platform-specific WebSocket implementations. */ -class WebSocketBase { +class WebSocketBase extends EventTarget { CONNECTING: number; OPEN: number; CLOSING: number; @@ -33,6 +35,7 @@ class WebSocketBase { url: ?string; constructor(url: string, protocols: ?any) { + super(); this.CONNECTING = 0; this.OPEN = 1; this.CLOSING = 2; From 41cb936d9cb1b43e373a0f2fb9e7fa14e36d228f Mon Sep 17 00:00:00 2001 From: Chace Liang Date: Thu, 1 Oct 2015 18:07:06 -0700 Subject: [PATCH 0122/2702] Add Voice Over related change to AccessibilityManager. Reviewed By: @hedgerwang Differential Revision: D2491520 --- React/Modules/RCTAccessibilityManager.h | 2 ++ React/Modules/RCTAccessibilityManager.m | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/React/Modules/RCTAccessibilityManager.h b/React/Modules/RCTAccessibilityManager.h index a901aa874f56..ded98bcbd8fb 100644 --- a/React/Modules/RCTAccessibilityManager.h +++ b/React/Modules/RCTAccessibilityManager.h @@ -21,6 +21,8 @@ extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; / /// map from UIKit categories to multipliers @property (nonatomic, copy) NSDictionary *multipliers; +@property (nonatomic, assign) BOOL isVoiceOverEnabled; + @end @interface RCTBridge (RCTAccessibilityManager) diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index 12330c8491e2..153368891f2d 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -9,6 +9,8 @@ #import "RCTAccessibilityManager.h" +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" #import "RCTLog.h" NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification = @"RCTAccessibilityManagerDidUpdateMultiplierNotification"; @@ -24,6 +26,7 @@ @implementation RCTAccessibilityManager @synthesize bridge = _bridge; @synthesize multipliers = _multipliers; +@synthesize isVoiceOverEnabled = _isVoiceOverEnabled; RCT_EXPORT_MODULE() @@ -61,7 +64,14 @@ - (instancetype)init selector:@selector(didReceiveNewContentSizeCategory:) name:UIContentSizeCategoryDidChangeNotification object:[UIApplication sharedApplication]]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveNewVoiceOverStatus:) + name:UIAccessibilityVoiceOverStatusChanged + object:nil]; + self.contentSizeCategory = [UIApplication sharedApplication].preferredContentSizeCategory; + _isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); } return self; } @@ -76,6 +86,16 @@ - (void)didReceiveNewContentSizeCategory:(NSNotification *)note self.contentSizeCategory = note.userInfo[UIContentSizeCategoryNewValueKey]; } +- (void)didReceiveNewVoiceOverStatus:(NSNotification *)notification +{ + BOOL newIsVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); + if (_isVoiceOverEnabled != newIsVoiceOverEnabled) { + _isVoiceOverEnabled = newIsVoiceOverEnabled; + [_bridge.eventDispatcher sendDeviceEventWithName:@"voiceOverDidChange" + body:@(_isVoiceOverEnabled)]; + } +} + - (void)setContentSizeCategory:(NSString *)contentSizeCategory { if (_contentSizeCategory != contentSizeCategory) { @@ -145,6 +165,12 @@ - (NSDictionary *)multipliers } } +RCT_EXPORT_METHOD(getCurrentVoiceOverState:(RCTResponseSenderBlock)callback + error:(__unused RCTResponseSenderBlock)error) +{ + callback(@[@(_isVoiceOverEnabled)]); +} + @end @implementation RCTBridge (RCTAccessibilityManager) From 6520532d230cc025725c78a0b943bbe75b09b1a5 Mon Sep 17 00:00:00 2001 From: Benjamin Winterberg Date: Fri, 2 Oct 2015 10:51:45 +0200 Subject: [PATCH 0123/2702] Add --verbose option (fixes #2797) --- react-native-cli/index.js | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/react-native-cli/index.js b/react-native-cli/index.js index b94bcf2672a4..66b215830031 100755 --- a/react-native-cli/index.js +++ b/react-native-cli/index.js @@ -9,6 +9,7 @@ var fs = require('fs'); var path = require('path'); var exec = require('child_process').exec; +var spawn = require('child_process').spawn; var prompt = require('prompt'); var CLI_MODULE_PATH = function() { @@ -42,10 +43,11 @@ if (cli) { switch (args[0]) { case 'init': if (args[1]) { - init(args[1]); + var verbose = process.argv.indexOf('--verbose') >= 0; + init(args[1], verbose); } else { console.error( - 'Usage: react-native init ' + 'Usage: react-native init [--verbose]' ); process.exit(1); } @@ -81,17 +83,17 @@ function validatePackageName(name) { } } -function init(name) { +function init(name, verbose) { validatePackageName(name); if (fs.existsSync(name)) { - createAfterConfirmation(name); + createAfterConfirmation(name, verbose); } else { - createProject(name); + createProject(name, verbose); } } -function createAfterConfirmation(name) { +function createAfterConfirmation(name, verbose) { prompt.start(); var property = { @@ -104,7 +106,7 @@ function createAfterConfirmation(name) { prompt.get(property, function (err, result) { if (result.yesno[0] === 'y') { - createProject(name); + createProject(name, verbose); } else { console.log('Project initialization canceled'); process.exit(); @@ -112,7 +114,7 @@ function createAfterConfirmation(name) { }); } -function createProject(name) { +function createProject(name, verbose) { var root = path.resolve(name); var projectName = path.basename(root); @@ -137,6 +139,15 @@ function createProject(name) { process.chdir(root); console.log('Installing react-native package from npm...'); + + if (verbose) { + runVerbose(root, projectName); + } else { + run(root, projectName); + } +} + +function run(root, projectName) { exec('npm install --save react-native', function(e, stdout, stderr) { if (e) { console.log(stdout); @@ -145,9 +156,21 @@ function createProject(name) { process.exit(1); } - var args = [projectName].concat(process.argv.slice(4)); + var cli = require(CLI_MODULE_PATH()); + cli.init(root, projectName); + }); +} + +function runVerbose(root, projectName) { + var proc = spawn('npm', ['install', '--verbose', '--save', 'react-native'], {stdio: 'inherit'}); + proc.on('close', function (code) { + if (code !== 0) { + console.error('`npm install --save react-native` failed'); + return; + } + cli = require(CLI_MODULE_PATH()); - cli.init(root, args); + cli.init(root, projectName); }); } From 436117822978a3d5c86c5ebc21930faec1f642a5 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 2 Oct 2015 03:30:51 -0700 Subject: [PATCH 0124/2702] Fix RCTConvert analyser error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Fix analyser error on RCTConvert where a key used to subscript an NSMutableDictionary could possibly be nil. Reviewed By: @alexeylang, @jspahrsummers Differential Revision: D2498988 --- React/Base/RCTConvert.m | 1 + 1 file changed, 1 insertion(+) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 79efea631f61..6d37f6244fd7 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -422,6 +422,7 @@ + (UIImage *)UIImage:(id)json isPackagerAsset = [self BOOL:json[@"__packager_asset"]]; } else { RCTLogConvertError(json, @"an image"); + return nil; } NSURL *URL = [self NSURL:path]; From f9d40cb98c7d50bf94900c7e94d53f0cb11f5572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Fri, 2 Oct 2015 03:41:51 -0700 Subject: [PATCH 0125/2702] do not destroy instance when handling exception Differential Revision: D2502513 --- .../java/com/facebook/react/bridge/CatalystInstance.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index be3cd5d0aabc..7314648df928 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -355,13 +355,6 @@ public void handleException(Exception e) { // framework/native code, it was triggered by JS and theoretically since we were able // to set up the bridge, JS could change its logic, reload, and not trigger that crash. mNativeModuleCallExceptionHandler.handleException(e); - mCatalystQueueConfiguration.getUIQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - destroy(); - } - }); } } From 0a13afb33080fdb363214751032b9d7b9c677a63 Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Fri, 2 Oct 2015 04:07:48 -0700 Subject: [PATCH 0126/2702] Add docs for View BorderWidth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Closes https://github.com/facebook/react-native/pull/3133 Reviewed By: @​svcscm Differential Revision: D2502503 Pulled By: @mkonicek --- Libraries/Components/View/ViewStylePropTypes.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index 64ace6d7ba16..e36b143a1e4b 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -34,6 +34,11 @@ var ViewStylePropTypes = { borderBottomLeftRadius: ReactPropTypes.number, borderBottomRightRadius: ReactPropTypes.number, borderStyle: ReactPropTypes.oneOf(['solid', 'dotted', 'dashed']), + borderWidth: ReactPropTypes.number, + borderTopWidth: ReactPropTypes.number, + borderRightWidth: ReactPropTypes.number, + borderBottomWidth: ReactPropTypes.number, + borderLeftWidth: ReactPropTypes.number, opacity: ReactPropTypes.number, overflow: ReactPropTypes.oneOf(['visible', 'hidden']), shadowColor: ReactPropTypes.string, From 4be9cb71270ac0fde55a38edc03959caed109665 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 2 Oct 2015 04:16:04 -0700 Subject: [PATCH 0127/2702] Fix racing conditions on the profiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public The flow event methods weren't properly synchronized and neither was the module unhooking. Reviewed By: @jspahrsummers Differential Revision: D2498719 --- React/Base/RCTProfile.m | 52 ++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index fc4d85d046c3..dba23b5b4ae6 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -166,9 +166,27 @@ static SEL RCTProfileProxySelector(SEL selector) return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]); } + +static dispatch_group_t RCTProfileGetUnhookGroup(void); +static dispatch_group_t RCTProfileGetUnhookGroup(void) +{ + static dispatch_group_t unhookGroup; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + unhookGroup = dispatch_group_create(); + }); + + return unhookGroup; +} + static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *); static void RCTProfileForwardInvocation(NSObject *self, __unused SEL cmd, NSInvocation *invocation) { + /** + * This is still not thread safe, but should reduce reasonably the number of crashes + */ + dispatch_group_wait(RCTProfileGetUnhookGroup(), DISPATCH_TIME_FOREVER); + NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)]; SEL newSel = RCTProfileProxySelector(invocation.selector); @@ -244,13 +262,17 @@ void RCTProfileHookModules(RCTBridge *bridge) void RCTProfileUnhookModules(RCTBridge *bridge) { + dispatch_group_enter(RCTProfileGetUnhookGroup()); + for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { Class proxyClass = object_getClass(moduleData.instance); if (moduleData.moduleClass != proxyClass) { object_setClass(moduleData.instance, moduleData.moduleClass); objc_disposeClassPair(proxyClass); } - }; + } + + dispatch_group_leave(RCTProfileGetUnhookGroup()); } @@ -465,12 +487,14 @@ void RCTProfileImmediateEvent( return @0; } - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": @"flow", - @"id": @(++flowID), - @"cat": @"flow", - @"ph": @"s", - @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + RCTProfileLock( + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": @(++flowID), + @"cat": @"flow", + @"ph": @"s", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); ); return @(flowID); @@ -484,12 +508,14 @@ void _RCTProfileEndFlowEvent(NSNumber *flowID) return; } - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": @"flow", - @"id": flowID, - @"cat": @"flow", - @"ph": @"f", - @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + RCTProfileLock( + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": flowID, + @"cat": @"flow", + @"ph": @"f", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); ); } From c354530a1aa2b85d7788dc6e83650f1a685c7cbc Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 2 Oct 2015 04:50:47 -0700 Subject: [PATCH 0128/2702] Guard against multiple calls from the dev menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public The RCTDevMenu always calls the handler, even with the state hasn't changed. Guard against it. Reviewed By: @javache Differential Revision: D2499034 --- React/Executors/RCTContextExecutor.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 4781e72deaf8..102ac6b65f05 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -226,7 +226,15 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) nativeProfilerEnableByteCode(); } + static BOOL isProfiling = NO; [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { + + if (shouldStart == isProfiling) { + return; + } + + isProfiling = shouldStart; + if (shouldStart) { nativeProfilerStart(context, "profile"); } else { From 3d34b90d2b47a561ea571f5efae694c03ba0d7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Fri, 2 Oct 2015 14:44:20 +0100 Subject: [PATCH 0129/2702] [cli] make init backwards-compatible --- local-cli/init.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/local-cli/init.js b/local-cli/init.js index ff2f57d1ae0f..e08bf48c9f1c 100755 --- a/local-cli/init.js +++ b/local-cli/init.js @@ -3,10 +3,11 @@ var path = require('path'); var yeoman = require('yeoman-environment'); -function init(projectDir, args) { +function init(projectDir, argsOrName) { console.log('Setting up new React Native app in ' + projectDir); var env = yeoman.createEnv(); env.register(require.resolve(path.join(__dirname, 'generator')), 'react:app'); + var args = Array.isArray(argsOrName) ? argsOrName : [argsOrName].concat(process.args.slice(4)); var generator = env.create('react:app', {args: args}); generator.destinationRoot(projectDir); generator.run(); From b934e2b0701da3ba8eecc2374ea33fbc9d9c5e1d Mon Sep 17 00:00:00 2001 From: Mehdi Mulani Date: Fri, 2 Oct 2015 06:53:08 -0700 Subject: [PATCH 0130/2702] Backout ReactNative sync as some tests broke --- .../java/com/facebook/react/bridge/CatalystInstance.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 7314648df928..be3cd5d0aabc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -355,6 +355,13 @@ public void handleException(Exception e) { // framework/native code, it was triggered by JS and theoretically since we were able // to set up the bridge, JS could change its logic, reload, and not trigger that crash. mNativeModuleCallExceptionHandler.handleException(e); + mCatalystQueueConfiguration.getUIQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + destroy(); + } + }); } } From 4c9064b12252b596937a9111cc7bd99fcc6d4b49 Mon Sep 17 00:00:00 2001 From: skv Date: Fri, 2 Oct 2015 17:27:17 +0300 Subject: [PATCH 0131/2702] [Docs] fix link to animation examples --- docs/Animations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Animations.md b/docs/Animations.md index 51a41180141b..552e77026f12 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -242,7 +242,7 @@ vertical panning. The above API gives a powerful tool for expressing all sorts of animations in a concise, robust, and performant way. Check out more example code in -[UIExplorer/AnimationExample](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/AnimationExample). Of course there may still be times where `Animated` +[UIExplorer/AnimationExample](https://github.com/facebook/react-native/tree/master/Examples/UIExplorer/AnimatedGratuitousApp). Of course there may still be times where `Animated` doesn't support what you need, and the following sections cover other animation systems. From 498eeb2aec251ff8ab41a4d09353e33ba82e98d5 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 2 Oct 2015 07:58:32 -0700 Subject: [PATCH 0132/2702] Add link to docs for React addons Reviewed By: @javache Differential Revision: D2502770 --- Libraries/react-native/react-native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 5aaf47ef9e11..5afdc8d9eb3f 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -85,6 +85,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { EdgeInsetsPropType: require('EdgeInsetsPropType'), PointPropType: require('PointPropType'), + // See http://facebook.github.io/react/docs/addons.html addons: { LinkedStateMixin: require('LinkedStateMixin'), Perf: undefined, From 27e057e73a9f72d80ac41358fcaf3ee6e547d221 Mon Sep 17 00:00:00 2001 From: Alexey Lang Date: Fri, 2 Oct 2015 09:53:00 -0700 Subject: [PATCH 0133/2702] log native modules init time and config inject time separately Reviewed By: @tadeuzagallo, @jspahrsummers Differential Revision: D2502620 --- React/Base/RCTBatchedBridge.m | 4 +++- React/Base/RCTPerformanceLogger.h | 1 + React/Base/RCTPerformanceLogger.m | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 3559b58c37b1..05a803f6fd42 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -156,8 +156,9 @@ - (void)start // We're not waiting for this complete to leave the dispatch group, since // injectJSONConfiguration and executeSourceCode will schedule operations on the // same queue anyway. + RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { - RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); + RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf stopLoadingWithError:error]; @@ -295,6 +296,7 @@ - (void)initModules [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules object:self]; + RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } - (void)setupExecutor diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h index 66d3dd863961..8285f061571a 100644 --- a/React/Base/RCTPerformanceLogger.h +++ b/React/Base/RCTPerformanceLogger.h @@ -15,6 +15,7 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLScriptDownload = 0, RCTPLScriptExecution, RCTPLNativeModuleInit, + RCTPLNativeModuleInjectConfig, RCTPLTTI, RCTPLSize }; diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index 87e643355f0f..443976a9ab10 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -33,6 +33,8 @@ void RCTPerformanceLoggerEnd(RCTPLTag tag) @(RCTPLData[RCTPLScriptExecution][1]), @(RCTPLData[RCTPLNativeModuleInit][0]), @(RCTPLData[RCTPLNativeModuleInit][1]), + @(RCTPLData[RCTPLNativeModuleInjectConfig][0]), + @(RCTPLData[RCTPLNativeModuleInjectConfig][1]), @(RCTPLData[RCTPLTTI][0]), @(RCTPLData[RCTPLTTI][1]), ]; @@ -74,6 +76,7 @@ - (void)sendTimespans @"ScriptDownload", @"ScriptExecution", @"NativeModuleInit", + @"NativeModuleInjectConfig", @"TTI", ], ]]; From e5d846499862421cb27dcdbf35a699a78856805c Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Fri, 2 Oct 2015 10:12:40 -0700 Subject: [PATCH 0134/2702] Capture java side sampling at the same time as JavaScript Differential Revision: D2503123 --- .../react/devsupport/DevSupportManager.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index 432b44ef01ff..b434db75d99f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -26,6 +26,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.hardware.SensorManager; +import android.os.Debug; import android.os.Environment; import android.view.WindowManager; import android.widget.Toast; @@ -78,6 +79,9 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler { private static final String EXOPACKAGE_LOCATION_FORMAT = "/data/local/tmp/exopackage/%s//secondary-dex"; + private static final int JAVA_SAMPLING_PROFILE_MEMORY_BYTES = 8 * 1024 * 1024; + private static final int JAVA_SAMPLING_PROFILE_DELTA_US = 100; + private final Context mApplicationContext; private final ShakeDetector mShakeDetector; private final BroadcastReceiver mReloadAppBroadcastReceiver; @@ -280,11 +284,12 @@ public void onOptionSelected() { @Override public void onOptionSelected() { if (mCurrentContext != null && mCurrentContext.hasActiveCatalystInstance()) { + String profileName = (Environment.getExternalStorageDirectory().getPath() + + "/profile_" + mProfileIndex + ".json"); if (mIsCurrentlyProfiling) { mIsCurrentlyProfiling = false; - String profileName = (Environment.getExternalStorageDirectory().getPath() + - "/profile_" + mProfileIndex + ".json"); mProfileIndex++; + Debug.stopMethodTracing(); mCurrentContext.getCatalystInstance() .getBridge() .stopProfiler("profile", profileName); @@ -295,6 +300,10 @@ public void onOptionSelected() { } else { mIsCurrentlyProfiling = true; mCurrentContext.getCatalystInstance().getBridge().startProfiler("profile"); + Debug.startMethodTracingSampling( + profileName, + JAVA_SAMPLING_PROFILE_MEMORY_BYTES, + JAVA_SAMPLING_PROFILE_DELTA_US); } } } @@ -435,6 +444,7 @@ private void resetCurrentContext(@Nullable ReactContext reactContext) { String profileName = (Environment.getExternalStorageDirectory().getPath() + "/profile_" + mProfileIndex + ".json"); mProfileIndex++; + Debug.stopMethodTracing(); mCurrentContext.getCatalystInstance().getBridge().stopProfiler("profile", profileName); } From 075545a32c81af46ce92252b730709b34d192a5f Mon Sep 17 00:00:00 2001 From: Kushal Dave Date: Fri, 2 Oct 2015 10:14:41 -0700 Subject: [PATCH 0135/2702] Set scrollsToTop = NO for UITextViews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Complete the work from 853d5b2221a692110607ef12916263078141e107 by also fixing the placeholder view. Closes https://github.com/facebook/react-native/pull/3129 Reviewed By: @​svcscm Differential Revision: D2499753 Pulled By: @vjeux --- Libraries/Text/RCTTextView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index d21360ed26c1..5e24e3a1c077 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -78,6 +78,7 @@ - (void)updatePlaceholder _placeholderView = [[UITextView alloc] initWithFrame:self.bounds]; _placeholderView.backgroundColor = [UIColor clearColor]; _placeholderView.scrollEnabled = false; + _placeholderView.scrollsToTop = NO; _placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder attributes:@{ NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]), From 7f94475598931f29182651711435880acc62d3ed Mon Sep 17 00:00:00 2001 From: Abhishek Sood Date: Fri, 2 Oct 2015 11:27:28 -0700 Subject: [PATCH 0136/2702] This reverts D2502620 Differential Revision: D2503381 --- React/Base/RCTBatchedBridge.m | 4 +--- React/Base/RCTPerformanceLogger.h | 1 - React/Base/RCTPerformanceLogger.m | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 05a803f6fd42..3559b58c37b1 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -156,9 +156,8 @@ - (void)start // We're not waiting for this complete to leave the dispatch group, since // injectJSONConfiguration and executeSourceCode will schedule operations on the // same queue anyway. - RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { - RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); + RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf stopLoadingWithError:error]; @@ -296,7 +295,6 @@ - (void)initModules [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules object:self]; - RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } - (void)setupExecutor diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h index 8285f061571a..66d3dd863961 100644 --- a/React/Base/RCTPerformanceLogger.h +++ b/React/Base/RCTPerformanceLogger.h @@ -15,7 +15,6 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLScriptDownload = 0, RCTPLScriptExecution, RCTPLNativeModuleInit, - RCTPLNativeModuleInjectConfig, RCTPLTTI, RCTPLSize }; diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index 443976a9ab10..87e643355f0f 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -33,8 +33,6 @@ void RCTPerformanceLoggerEnd(RCTPLTag tag) @(RCTPLData[RCTPLScriptExecution][1]), @(RCTPLData[RCTPLNativeModuleInit][0]), @(RCTPLData[RCTPLNativeModuleInit][1]), - @(RCTPLData[RCTPLNativeModuleInjectConfig][0]), - @(RCTPLData[RCTPLNativeModuleInjectConfig][1]), @(RCTPLData[RCTPLTTI][0]), @(RCTPLData[RCTPLTTI][1]), ]; @@ -76,7 +74,6 @@ - (void)sendTimespans @"ScriptDownload", @"ScriptExecution", @"NativeModuleInit", - @"NativeModuleInjectConfig", @"TTI", ], ]]; From b1d1b83c65bb41a42822f15656190bb1428985d1 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 2 Oct 2015 11:59:02 -0700 Subject: [PATCH 0137/2702] Add packager-managed assets support to NavigatorIOS Reviewed By: @vjeux Differential Revision: D2500520 --- .../Components/Navigation/NavigatorIOS.ios.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 4c8e1b6e7e3b..a0f2199b295d 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -14,15 +14,16 @@ var EventEmitter = require('EventEmitter'); var Image = require('Image'); var NavigationContext = require('NavigationContext'); -var React = require('React'); var RCTNavigatorManager = require('NativeModules').NavigatorManager; -var StyleSheet = require('StyleSheet'); +var React = require('React'); var StaticContainer = require('StaticContainer.react'); +var StyleSheet = require('StyleSheet'); var View = require('View'); -var requireNativeComponent = require('requireNativeComponent'); var invariant = require('invariant'); var logError = require('logError'); +var requireNativeComponent = require('requireNativeComponent'); +var resolveAssetSource = require('resolveAssetSource'); var TRANSITIONER_REF = 'transitionerRef'; @@ -603,12 +604,12 @@ var NavigatorIOS = React.createClass({ this.props.itemWrapperStyle, route.wrapperStyle ]} - backButtonIcon={this._imageNameFromSource(route.backButtonIcon)} + backButtonIcon={resolveAssetSource(route.backButtonIcon)} backButtonTitle={route.backButtonTitle} - leftButtonIcon={this._imageNameFromSource(route.leftButtonIcon)} + leftButtonIcon={resolveAssetSource(route.leftButtonIcon)} leftButtonTitle={route.leftButtonTitle} onNavLeftButtonTap={route.onLeftButtonPress} - rightButtonIcon={this._imageNameFromSource(route.rightButtonIcon)} + rightButtonIcon={resolveAssetSource(route.rightButtonIcon)} rightButtonTitle={route.rightButtonTitle} onNavRightButtonTap={route.onRightButtonPress} navigationBarHidden={this.props.navigationBarHidden} @@ -627,10 +628,6 @@ var NavigatorIOS = React.createClass({ ); }, - _imageNameFromSource: function(source: ?Object) { - return source ? source.uri : undefined; - }, - renderNavigationStackItems: function() { var shouldRecurseToNavigator = this.state.makingNavigatorRequest || From e2cfbcb13acb312ec4acf74612f9ed7e0b0d0d91 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Fri, 2 Oct 2015 12:13:48 -0700 Subject: [PATCH 0138/2702] Introduce SnapshotView which wraps Renderable content and will verify the snapshot Reviewed By: @javache Differential Revision: D2493722 --- Examples/UIExplorer/UIExplorerList.ios.js | 17 +++--- Libraries/RCTTest/RCTSnapshotManager.h | 14 +++++ Libraries/RCTTest/RCTSnapshotManager.m | 47 ++++++++++++++++ .../RCTTest/RCTTest.xcodeproj/project.pbxproj | 6 +++ Libraries/RCTTest/SnapshotView.js | 54 +++++++++++++++++++ Libraries/react-native/react-native.js | 1 + 6 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 Libraries/RCTTest/RCTSnapshotManager.h create mode 100644 Libraries/RCTTest/RCTSnapshotManager.m create mode 100644 Libraries/RCTTest/SnapshotView.js diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 436982dfef34..adc2540d9d5c 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -19,11 +19,10 @@ var React = require('react-native'); var { AppRegistry, Settings, + SnapshotView, StyleSheet, } = React; -var { TestModule } = React.addons; - import type { NavigationContext } from 'NavigationContext'; var UIExplorerListBase = require('./UIExplorerListBase'); @@ -83,17 +82,13 @@ var APIS = [ COMPONENTS.concat(APIS).forEach((Example) => { if (Example.displayName) { var Snapshotter = React.createClass({ - componentDidMount: function() { - // View is still blank after first RAF :\ - global.requestAnimationFrame(() => - global.requestAnimationFrame(() => TestModule.verifySnapshot( - TestModule.markTestPassed - ) - )); - }, render: function() { var Renderable = UIExplorerListBase.makeRenderable(Example); - return ; + return ( + + + + ); }, }); AppRegistry.registerComponent(Example.displayName, () => Snapshotter); diff --git a/Libraries/RCTTest/RCTSnapshotManager.h b/Libraries/RCTTest/RCTSnapshotManager.h new file mode 100644 index 000000000000..6d6517aa57c1 --- /dev/null +++ b/Libraries/RCTTest/RCTSnapshotManager.h @@ -0,0 +1,14 @@ +/** + * 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. + */ + +#import "RCTViewManager.h" + +@interface RCTSnapshotManager : RCTViewManager + +@end diff --git a/Libraries/RCTTest/RCTSnapshotManager.m b/Libraries/RCTTest/RCTSnapshotManager.m new file mode 100644 index 000000000000..637e2db05029 --- /dev/null +++ b/Libraries/RCTTest/RCTSnapshotManager.m @@ -0,0 +1,47 @@ +/** + * 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. + */ + +#import "RCTSnapshotManager.h" + +@interface RCTSnapshotView : UIView + +@property (nonatomic, copy) RCTDirectEventBlock onSnapshotReady; +@property (nonatomic, copy) NSString *testIdentifier; + +@end + +@implementation RCTSnapshotView + +- (void)setTestIdentifier:(NSString *)testIdentifier +{ + if (![_testIdentifier isEqualToString:testIdentifier]) { + _testIdentifier = [testIdentifier copy]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.onSnapshotReady) { + self.onSnapshotReady(@{@"testIdentifier" : self.testIdentifier}); + } + }); + } +} + +@end + + +@implementation RCTSnapshotManager + +RCT_EXPORT_MODULE() + +- (UIView *)view { + return [RCTSnapshotView new]; +} + +RCT_EXPORT_VIEW_PROPERTY(testIdentifier, NSString) +RCT_EXPORT_VIEW_PROPERTY(onSnapshotReady, RCTDirectEventBlock) + +@end diff --git a/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj index eae9a6e6275c..d6aa333e5605 100644 --- a/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj +++ b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 58E64FED1AB964CD007446E2 /* FBSnapshotTestController.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FE71AB964CD007446E2 /* FBSnapshotTestController.m */; }; 58E64FEE1AB964CD007446E2 /* UIImage+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FE91AB964CD007446E2 /* UIImage+Compare.m */; }; 58E64FEF1AB964CD007446E2 /* UIImage+Diff.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FEB1AB964CD007446E2 /* UIImage+Diff.m */; }; + 9913A84B1BBE833400D70E66 /* RCTSnapshotManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9913A84A1BBE833400D70E66 /* RCTSnapshotManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -42,6 +43,8 @@ 58E64FE91AB964CD007446E2 /* UIImage+Compare.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Compare.m"; sourceTree = ""; }; 58E64FEA1AB964CD007446E2 /* UIImage+Diff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Diff.h"; sourceTree = ""; }; 58E64FEB1AB964CD007446E2 /* UIImage+Diff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Diff.m"; sourceTree = ""; }; + 9913A8491BBE833400D70E66 /* RCTSnapshotManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSnapshotManager.h; sourceTree = ""; }; + 9913A84A1BBE833400D70E66 /* RCTSnapshotManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSnapshotManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -59,6 +62,8 @@ isa = PBXGroup; children = ( 58E64FE31AB964CD007446E2 /* FBSnapshotTestCase */, + 9913A8491BBE833400D70E66 /* RCTSnapshotManager.h */, + 9913A84A1BBE833400D70E66 /* RCTSnapshotManager.m */, 585135331AB3C56F00882537 /* RCTTestModule.h */, 585135341AB3C56F00882537 /* RCTTestModule.m */, 585135351AB3C56F00882537 /* RCTTestRunner.h */, @@ -149,6 +154,7 @@ buildActionMask = 2147483647; files = ( 58E64FEE1AB964CD007446E2 /* UIImage+Compare.m in Sources */, + 9913A84B1BBE833400D70E66 /* RCTSnapshotManager.m in Sources */, 585135371AB3C56F00882537 /* RCTTestModule.m in Sources */, 58E64FEF1AB964CD007446E2 /* UIImage+Diff.m in Sources */, 58E64FED1AB964CD007446E2 /* FBSnapshotTestController.m in Sources */, diff --git a/Libraries/RCTTest/SnapshotView.js b/Libraries/RCTTest/SnapshotView.js new file mode 100644 index 000000000000..83bc95296a07 --- /dev/null +++ b/Libraries/RCTTest/SnapshotView.js @@ -0,0 +1,54 @@ +/** + * 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. + * + * @providesModule SnapshotView + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var { TestModule } = require('NativeModules'); + +var requireNativeComponent = require('requireNativeComponent'); + +var SnapshotView = React.createClass({ + onDefaultAction: function(event: Object) { + TestModule.verifySnapshot(TestModule.markTestPassed); + }, + + render: function() { + var testIdentifier = this.props.testIdentifier || 'test'; + var onSnapshotReady = this.props.onSnapshotReady || this.onDefaultAction; + return ( + + ); + }, + + propTypes: { + // A callback when the Snapshot view is ready to be compared + onSnapshotReady : React.PropTypes.func, + // A name to identify the individual instance to the SnapshotView + testIdentifier : React.PropTypes.string, + } +}); + +var style = StyleSheet.create({ + snapshot: { + flex: 1, + }, +}); + +var RCTSnapshot = requireNativeComponent('RCTSnapshot', SnapshotView); + +module.exports = SnapshotView; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 5afdc8d9eb3f..fde908361f67 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -34,6 +34,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { ScrollView: require('ScrollView'), SegmentedControlIOS: require('SegmentedControlIOS'), SliderIOS: require('SliderIOS'), + SnapshotView: require('SnapshotView'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), From 51712ab22f5a61980fdc08f4932344303d3dde72 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Fri, 2 Oct 2015 12:27:10 -0700 Subject: [PATCH 0139/2702] Fix tests related to event-target-shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Jest has an issue where if you export `Symbol` it crashes badly. Since event-target-shim does that, it breaks everything. Mocking that module fixes the issue until @cpojer comes back from vacation and fixes the bug in Jest itself. Reviewed By: @jingc Differential Revision: D2503562 --- Libraries/WebSocket/__mocks__/event-target-shim.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Libraries/WebSocket/__mocks__/event-target-shim.js diff --git a/Libraries/WebSocket/__mocks__/event-target-shim.js b/Libraries/WebSocket/__mocks__/event-target-shim.js new file mode 100644 index 000000000000..3b566fcc94a2 --- /dev/null +++ b/Libraries/WebSocket/__mocks__/event-target-shim.js @@ -0,0 +1,9 @@ +// Jest fatals for the following statement (minimal repro case) +// +// exports.something = Symbol; +// +// Until it is fixed, mocking the entire node module makes the +// problem go away. + +'use strict'; +module.exports = function() {}; From e1928669568fce7bf5d50141426b093343315e8e Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 2 Oct 2015 12:33:49 -0700 Subject: [PATCH 0140/2702] Silence analyzer warning for nil-checking NSNumber Reviewed By: @jspahrsummers Differential Revision: D2503068 --- React/Base/RCTConvert.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 6d37f6244fd7..998a7c4eb812 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -317,7 +317,7 @@ static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDiction for (NSString *alias in aliases) { NSString *key = aliases[alias]; NSNumber *number = json[alias]; - if (number) { + if (number != nil) { RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, type, key); ((NSMutableDictionary *)json)[key] = number; } From ffa0ad9db07f48989bc8e40ba378bad10d5c75e6 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Fri, 2 Oct 2015 13:57:35 -0700 Subject: [PATCH 0141/2702] Fixes #3060 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This pull request fixes a glitch that occurred when scrolling horizontally, as described in the #3060. Closes https://github.com/facebook/react-native/pull/3169 Reviewed By: @​svcscm Differential Revision: D2500186 Pulled By: @mkonicek --- Examples/UIExplorer/ScrollViewExample.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/UIExplorer/ScrollViewExample.js b/Examples/UIExplorer/ScrollViewExample.js index 1ca8baf9a853..53d19148b67f 100644 --- a/Examples/UIExplorer/ScrollViewExample.js +++ b/Examples/UIExplorer/ScrollViewExample.js @@ -33,9 +33,9 @@ exports.examples = [ render: function() { return ( { console.log('onScroll!'); }} scrollEventThrottle={200} - contentInset={{top: -50}} style={styles.scrollView}> {THUMBS.map(createThumbRow)} @@ -47,8 +47,8 @@ exports.examples = [ render: function() { return ( {THUMBS.map(createThumbRow)} From eb8a42f2ef225e40f44b743f783e040e2c73c11c Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Fri, 2 Oct 2015 16:01:18 -0700 Subject: [PATCH 0142/2702] Add --write-sourcemap option to 'bundle' cli command (fixes GH-3202) --- local-cli/bundle.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/local-cli/bundle.js b/local-cli/bundle.js index b060cbe6e935..92329f98baa5 100644 --- a/local-cli/bundle.js +++ b/local-cli/bundle.js @@ -13,10 +13,15 @@ var URL_PATH = { android: '/index.android.bundle?platform=android&dev=', ios: '/index.ios.bundle?platform=ios&dev=' }; +var SOURCEMAP_OUT_PATH = { + android: 'android/index.android.map', + ios: 'ios/index.ios.map' +}; function getBundle(flags) { var platform = flags.platform ? flags.platform : 'ios'; var outPath = flags.out ? flags.out : OUT_PATH[platform]; + var sourceMapOutPath = SOURCEMAP_OUT_PATH[platform]; var projectRoots = [path.resolve(__dirname, '../../..')]; if (flags.root) { @@ -50,12 +55,23 @@ function getBundle(flags) { fs.writeFile(outPath, bundle.getSource({ inlineSourceMap: false, minify: flags.minify - }), function(err) { - if (err) { + }), function(bundleWriteErr) { + if (bundleWriteErr) { console.log(chalk.red('Error saving bundle to disk')); - throw err; - } else { - console.log('Successfully saved bundle to ' + outPath); + throw bundleWriteErr; + } + + console.log('Successfully saved bundle to ' + outPath); + + if (flags.writeSourcemap) { + fs.writeFile(sourceMapOutPath, bundle.getSourceMap(), function(sourceMapWriteErr) { + if (sourceMapWriteErr) { + console.log(chalk.red('Error saving source map to disk')); + throw sourceMapWriteErr; + } else { + console.log('Successfully saved source map to ' + sourceMapOutPath); + } + }); } }); }); @@ -73,6 +89,7 @@ function showHelp() { ' --out\t\tspecify the output file', ' --url\t\tspecify the bundle file url', ' --platform\t\tspecify the platform(android/ios)', + ' --write-sourcemap\t\twrite bundle source map to disk' ].join('\n')); process.exit(1); } @@ -88,6 +105,7 @@ module.exports = { assetRoots: args.indexOf('--assetRoots') !== -1 ? args[args.indexOf('--assetRoots') + 1] : false, out: args.indexOf('--out') !== -1 ? args[args.indexOf('--out') + 1] : false, url: args.indexOf('--url') !== -1 ? args[args.indexOf('--url') + 1] : false, + writeSourcemap: args.indexOf('--write-sourcemap') !== -1, } if (flags.help) { From 24c5b811de1496fcf5402deca25135fd9cd80f04 Mon Sep 17 00:00:00 2001 From: Amir Rosenfeld Date: Fri, 2 Oct 2015 16:51:42 -0700 Subject: [PATCH 0143/2702] robust native in fbobjc Reviewed By: @fkgozali Differential Revision: D2505371 --- .../src/main/jni/react/JSCPerfLogging.cpp | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp b/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp index 39c22e1d018e..85a7696825f1 100644 --- a/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp +++ b/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp @@ -1,6 +1,7 @@ // Copyright 2004-present Facebook. All Rights Reserved. #include "JSCHelpers.h" +#include #include using namespace facebook::jni; @@ -77,6 +78,12 @@ class JObjectWrapper : public JObject { return theQpl; } + static bool check() { + static auto getQPLInstMethod = qplProviderClass()->getStaticMethod("getQPLInstance"); + auto theQpl = getQPLInstMethod(qplProviderClass().get()); + return (theQpl.get() != nullptr); + } + private: static alias_ref qplProviderClass() { @@ -88,6 +95,26 @@ using JQuickPerformanceLoggerProvider = JObjectWrapper; }} +static bool isReady() { + static bool ready = false; + if (!ready) { + try { + findClassStatic("com/facebook/quicklog/QuickPerformanceLoggerProvider"); + } catch(...) { + // Swallow this exception - we don't want to crash the app, an error is enough. + FBLOGE("Calling QPL from JS before class has been loaded in Java. Ignored."); + return false; + } + if (JQuickPerformanceLoggerProvider::check()) { + ready = true; + } else { + FBLOGE("Calling QPL from JS before it has been initialized in Java. Ignored."); + return false; + } + } + return ready; +} + // After having read the implementation of PNaN that is returned from JSValueToNumber, and some // more material on how NaNs are constructed, I think this is the most consistent way to verify // NaN with how we generate it. @@ -125,7 +152,7 @@ static JSValueRef nativeQPLMarkerStart( const JSValueRef arguments[], JSValueRef* exception) { double targets[3]; - if (grabDoubles(3, targets, ctx, argumentCount, arguments, exception)) { + if (isReady() && grabDoubles(3, targets, ctx, argumentCount, arguments, exception)) { int32_t markerId = (int32_t) targets[0]; int32_t instanceKey = (int32_t) targets[1]; int64_t timestamp = (int64_t) targets[2]; @@ -142,7 +169,7 @@ static JSValueRef nativeQPLMarkerEnd( const JSValueRef arguments[], JSValueRef* exception) { double targets[4]; - if (grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) { + if (isReady() && grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) { int32_t markerId = (int32_t) targets[0]; int32_t instanceKey = (int32_t) targets[1]; int16_t actionId = (int16_t) targets[2]; @@ -160,7 +187,7 @@ static JSValueRef nativeQPLMarkerNote( const JSValueRef arguments[], JSValueRef* exception) { double targets[4]; - if (grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) { + if (isReady() && grabDoubles(4, targets, ctx, argumentCount, arguments, exception)) { int32_t markerId = (int32_t) targets[0]; int32_t instanceKey = (int32_t) targets[1]; int16_t actionId = (int16_t) targets[2]; @@ -178,7 +205,7 @@ static JSValueRef nativeQPLMarkerCancel( const JSValueRef arguments[], JSValueRef* exception) { double targets[2]; - if (grabDoubles(2, targets, ctx, argumentCount, arguments, exception)) { + if (isReady() && grabDoubles(2, targets, ctx, argumentCount, arguments, exception)) { int32_t markerId = (int32_t) targets[0]; int32_t instanceKey = (int32_t) targets[1]; JQuickPerformanceLoggerProvider::get()->markerCancel(markerId, instanceKey); @@ -193,6 +220,9 @@ static JSValueRef nativeQPLTimestamp( size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + if (!isReady()) { + return JSValueMakeNumber(ctx, 0); + } int64_t timestamp = JQuickPerformanceLoggerProvider::get()->currentMonotonicTimestamp(); // Since this is monotonic time, I assume the 52 bits of mantissa are enough in the double value. return JSValueMakeNumber(ctx, timestamp); From 8046ab3b2d382bfbb155a17fde42c696c2acedd8 Mon Sep 17 00:00:00 2001 From: Julius Jurgelenas Date: Sat, 3 Oct 2015 11:09:44 +0300 Subject: [PATCH 0144/2702] Add Formations Factory Company name search app to Showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 887aa31c48a6..90a00b7ffd70 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -18,6 +18,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/beetroot/id1016159001?ls=1&mt=8', author: 'Alex Duckmanton', }, + { + name: 'Company name search', + icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/fd/47/53/fd47537c-5861-e208-d1d1-1e26b5e45a36/icon350x350.jpeg', + link: 'https://itunes.apple.com/us/app/company-name-search/id1043824076', + author: 'The Formations Factory Ltd', + }, { name: 'Discord', icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg', From 8c4b5b2374a127aa6e2ed29c8c8ba65e0217d25d Mon Sep 17 00:00:00 2001 From: James Ide Date: Sat, 3 Oct 2015 10:12:51 -0700 Subject: [PATCH 0145/2702] Add unit tests to flattenStyle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: The StyleSheet merging algorithm was modeled after `Object.assign` and the native spread operator. This diff converts `flattenStyle` to actually use `Object.assign`. Closes https://github.com/facebook/react-native/pull/3048 Reviewed By: @​svcscm Differential Revision: D2506387 Pulled By: @vjeux --- .../StyleSheet/__tests__/flattenStyle-test.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Libraries/StyleSheet/__tests__/flattenStyle-test.js diff --git a/Libraries/StyleSheet/__tests__/flattenStyle-test.js b/Libraries/StyleSheet/__tests__/flattenStyle-test.js new file mode 100644 index 000000000000..80bac695c048 --- /dev/null +++ b/Libraries/StyleSheet/__tests__/flattenStyle-test.js @@ -0,0 +1,52 @@ +/** + * 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. + */ +'use strict'; + +jest.autoMockOff(); + +var flattenStyle = require('flattenStyle'); + +describe('flattenStyle', () => { + + it('should merge style objects', () => { + var style1 = {width: 10}; + var style2 = {height: 20}; + var flatStyle = flattenStyle([style1, style2]); + expect(flatStyle.width).toBe(10); + expect(flatStyle.height).toBe(20); + }); + + it('should override style properties', () => { + var style1 = {backgroundColor: '#000', width: 10}; + var style2 = {backgroundColor: '#023c69', width: null}; + var flatStyle = flattenStyle([style1, style2]); + expect(flatStyle.backgroundColor).toBe('#023c69'); + expect(flatStyle.width).toBe(null); + }); + + it('should overwrite properties with `undefined`', () => { + var style1 = {backgroundColor: '#000'}; + var style2 = {backgroundColor: undefined}; + var flatStyle = flattenStyle([style1, style2]); + expect(flatStyle.backgroundColor).toBe(undefined); + }); + + it('should not fail on falsy values', () => { + expect(() => flattenStyle([null, false, undefined])).not.toThrow(); + }); + + it('should recursively flatten arrays', () => { + var style1 = {width: 10}; + var style2 = {height: 20}; + var style3 = {width: 30}; + var flatStyle = flattenStyle([null, [], [style1, style2], style3]); + expect(flatStyle.width).toBe(30); + expect(flatStyle.height).toBe(20); + }); +}); From ecd070bf164bc510cd59cfa453e5514af4325316 Mon Sep 17 00:00:00 2001 From: James Ide Date: Sat, 3 Oct 2015 11:45:08 -0700 Subject: [PATCH 0146/2702] Update the packager's version check message for Node 4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Users should use Node 4.0 instead of io.js. This isn't a hard requirement since io.js still works but it's simpler if everyone is on the latest version. Closes https://github.com/facebook/react-native/pull/2547 Reviewed By: @​svcscm Differential Revision: D2506388 Pulled By: @vjeux --- packager/checkNodeVersion.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packager/checkNodeVersion.js b/packager/checkNodeVersion.js index f1a07c030e86..388a0e2faa64 100644 --- a/packager/checkNodeVersion.js +++ b/packager/checkNodeVersion.js @@ -14,20 +14,20 @@ var semver = require('semver'); var formatBanner = require('./formatBanner'); function checkNodeVersion() { - if (!semver.satisfies(process.version, '>=2.0.0')) { - var engine = semver.lt(process.version, '1.0.0') ? 'Node' : 'io.js'; + if (!semver.satisfies(process.version, '>=4')) { + var engine = semver.satisfies(process.version, '<1 >=4') ? 'Node' : 'io.js'; var message = 'You are currently running ' + engine + ' ' + process.version + '.\n' + '\n' + - 'React Native is moving to io.js 2.x. There are several ways to upgrade' + - 'to io.js depending on your preference.\n' + + 'React Native runs on Node 4.0 or newer. There are several ways to ' + + 'upgrade Node.js depending on your preference.\n' + '\n' + - 'nvm: nvm install iojs && nvm alias default iojs\n' + - 'Homebrew: brew unlink node; brew install iojs && brew ln iojs --force\n' + - 'Installer: download the Mac .pkg from https://iojs.org/\n' + + 'nvm: nvm install node && nvm alias default node\n' + + 'Homebrew: brew unlink iojs; brew install node\n' + + 'Installer: download the Mac .pkg from https://nodejs.org/\n' + '\n' + - 'About io.js: https://iojs.org\n' + - 'Follow along at: https://github.com/facebook/react-native/issues/1737'; + 'About Node.js: https://nodejs.org\n' + + 'Follow along at: https://github.com/facebook/react-native/issues/2545'; console.log(formatBanner(message, { chalkFunction: chalk.green, marginLeft: 1, From 0979a15e2c678e0bf79de3f6fcff7a57b4055edf Mon Sep 17 00:00:00 2001 From: Dral Date: Sat, 3 Oct 2015 11:45:28 -0700 Subject: [PATCH 0147/2702] Clear timeouts on unmount in TouchableMixin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fixes #1152 Closes https://github.com/facebook/react-native/pull/3176 Reviewed By: @​svcscm Differential Revision: D2506385 Pulled By: @vjeux --- .../react_contrib/interactions/Touchable/Touchable.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js index 93c4a7b59b3b..3ff08f736524 100644 --- a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js +++ b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js @@ -303,6 +303,15 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * @lends Touchable.prototype */ var TouchableMixin = { + /** + * Clear all timeouts on unmount + */ + componentWillUnmount: function() { + this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout); + this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout); + this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout); + }, + /** * It's prefer that mixins determine state in this way, having the class * explicitly mix the state in the one and only `getInitialState` method. From 53e0226c97640d392fabc7a85542bd604d5c431e Mon Sep 17 00:00:00 2001 From: James Ide Date: Sat, 3 Oct 2015 11:46:56 -0700 Subject: [PATCH 0148/2702] Include ReactAndroid with the npm distribution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Enables building RN Android from source while keeping it in sync with the iOS distribution. Closes https://github.com/facebook/react-native/pull/3019 Reviewed By: @​svcscm Differential Revision: D2506390 Pulled By: @vjeux --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fa7105730401..b78c90be7f42 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "files": [ "React", "React.podspec", + "ReactAndroid", "Libraries", "packager", "cli.js", From 4afbcab9f38d51fac664f8c0ef202fb66827531c Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sun, 4 Oct 2015 12:31:07 -0700 Subject: [PATCH 0149/2702] Use different cache keys for Package objects (fixes #2949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Dead-lock trying to read package.json because it's both a "module" and a "package". in `Module.getName` it uses the cache key "name" and tries to call `Package.getName` which uses the same cache key and we end up returning the same promise that we're trying to resolve resulting in a dead-lock. This changes the cache keys for the packages that adds a prefix "package-". Reviewed By: @vjeux Differential Revision: D2506979 --- .../__tests__/DependencyGraph-test.js | 132 ++++++++++++++++++ .../src/DependencyResolver/Package.js | 4 +- 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 1a9350fa5425..1794a206dc5d 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -227,6 +227,55 @@ describe('DependencyGraph', function() { }); }); + pit('should get package json as a dep', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./package.json")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + cache: cache, + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./package.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/package.json', + isJSON: true, + path: '/root/package.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -2619,6 +2668,89 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should require package.json', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/package.json");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'require("./package.json")', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + cache: cache, + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo/package.json', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/package.json', + path: '/root/node_modules/foo/package.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: true, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: ['./package.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/package.json', + path: '/root/node_modules/bar/package.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: true, + isPolyfill: false, + resolution: undefined, + }, + + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js index 5354e588c150..d45a8923e35e 100644 --- a/packager/react-packager/src/DependencyResolver/Package.js +++ b/packager/react-packager/src/DependencyResolver/Package.js @@ -34,13 +34,13 @@ class Package { } isHaste() { - return this._cache.get(this.path, 'haste', () => + return this._cache.get(this.path, 'package-haste', () => this._read().then(json => !!json.name) ); } getName() { - return this._cache.get(this.path, 'name', () => + return this._cache.get(this.path, 'package-name', () => this._read().then(json => json.name) ); } From 985990f9b6dc043d3ccf9befdef66d7721797c00 Mon Sep 17 00:00:00 2001 From: Dral Date: Sun, 4 Oct 2015 14:35:48 -0700 Subject: [PATCH 0150/2702] Support hex values in outputRange MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: 1. Should I add an example of this to the UIExplorer Animation example? 2. Should I support mixing hex and rgba values in outputRange? It is possible with this implementation, but may cause confusion Closes https://github.com/facebook/react-native/pull/3177 Reviewed By: @​svcscm Differential Revision: D2506947 Pulled By: @vjeux --- Libraries/Animated/src/Interpolation.js | 15 ++++++ .../src/__tests__/Interpolation-test.js | 53 ++++++++++++++++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js index fecbf0d4a8dc..99c36834cf73 100644 --- a/Libraries/Animated/src/Interpolation.js +++ b/Libraries/Animated/src/Interpolation.js @@ -11,6 +11,8 @@ */ 'use strict'; +var tinycolor = require('tinycolor2'); + // TODO(#7644673): fix this hack once github jest actually checks invariants var invariant = function(condition, message) { if (!condition) { @@ -163,6 +165,18 @@ function interpolate( return result; } +function colorToRgba( + input: string +): string { + var color = tinycolor(input); + if (color.isValid()) { + var {r, g, b, a} = color.toRgb(); + return `rgba(${r}, ${g}, ${b}, ${a === undefined ? 1 : a})`; + } else { + return input; + } +} + var stringShapeRegex = /[0-9\.-]+/g; /** @@ -178,6 +192,7 @@ function createInterpolationFromStringOutputRange( ): (input: number) => string { var outputRange: Array = (config.outputRange: any); invariant(outputRange.length >= 2, 'Bad output range'); + outputRange = outputRange.map(colorToRgba); checkPattern(outputRange); // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] diff --git a/Libraries/Animated/src/__tests__/Interpolation-test.js b/Libraries/Animated/src/__tests__/Interpolation-test.js index aec20ba2b173..4dca8ab61b47 100644 --- a/Libraries/Animated/src/__tests__/Interpolation-test.js +++ b/Libraries/Animated/src/__tests__/Interpolation-test.js @@ -10,7 +10,8 @@ jest .dontMock('Interpolation') - .dontMock('Easing'); + .dontMock('Easing') + .dontMock('tinycolor2'); var Interpolation = require('Interpolation'); var Easing = require('Easing'); @@ -223,6 +224,39 @@ describe('Interpolation', () => { expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.5)'); }); + it('should work with output ranges as short hex string', () => { + var interpolation = Interpolation.create({ + inputRange: [0, 1], + outputRange: ['#024', '#9BF'], + }); + + expect(interpolation(0)).toBe('rgba(0, 34, 68, 1)'); + expect(interpolation(0.5)).toBe('rgba(76.5, 110.5, 161.5, 1)'); + expect(interpolation(1)).toBe('rgba(153, 187, 255, 1)'); + }); + + it('should work with output ranges as long hex string', () => { + var interpolation = Interpolation.create({ + inputRange: [0, 1], + outputRange: ['#FF9500', '#87FC70'], + }); + + expect(interpolation(0)).toBe('rgba(255, 149, 0, 1)'); + expect(interpolation(0.5)).toBe('rgba(195, 200.5, 56, 1)'); + expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)'); + }); + + it('should work with output ranges with mixed hex and rgba strings', () => { + var interpolation = Interpolation.create({ + inputRange: [0, 1], + outputRange: ['rgba(100, 120, 140, .5)', '#87FC70'], + }); + + expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.5)'); + expect(interpolation(0.5)).toBe('rgba(117.5, 186, 126, 0.75)'); + expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)'); + }); + it('should work with negative and decimal values in string ranges', () => { var interpolation = Interpolation.create({ inputRange: [0, 1], @@ -242,12 +276,19 @@ describe('Interpolation', () => { expect(() => { interpolation('45rad'); }).toThrow(); }); - it('should crash when defining output range with different pattern', () => { - expect(() => Interpolation.create({ - inputRange: [0, 1], - outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)'], - })).toThrow(); + it('should support a mix of color patterns', () => { + var interpolation = Interpolation.create({ + inputRange: [0, 1, 2], + outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'], + }); + expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)'); + expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.5)'); + expect(interpolation(1.5)).toBe('rgba(152.5, 75, 125, 1)'); + expect(interpolation(2)).toBe('rgba(255, 0, 0, 1)'); + }); + + it('should crash when defining output range with different pattern', () => { expect(() => Interpolation.create({ inputRange: [0, 1], outputRange: ['20deg', '30rad'], From 58251cd14c92235dfb0d797411464d5c3016048c Mon Sep 17 00:00:00 2001 From: Dral Date: Mon, 5 Oct 2015 03:59:02 -0700 Subject: [PATCH 0151/2702] Silently (warning) fail when source has empty uri MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: fixes #3127 Closes https://github.com/facebook/react-native/pull/3185 Reviewed By: @​svcscm Differential Revision: D2507588 Pulled By: @nicklockwood --- Libraries/Image/Image.android.js | 8 ++++++++ Libraries/Image/Image.ios.js | 6 ++++++ Libraries/Image/RCTImageLoader.m | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 7b055780f0f4..75c722fa4d75 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -113,6 +113,14 @@ var Image = React.createClass({ render: function() { var source = resolveAssetSource(this.props.source); + + // As opposed to the ios version, here it render `null` + // when no source or source.uri... so let's not break that. + + if (source && source.uri === '') { + console.warn('source.uri should not be an empty string'); + } + if (source && source.uri) { var isNetwork = source.uri.match(/^https?:/); invariant( diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 48fcb278baac..59084721e400 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -19,6 +19,7 @@ var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +var View = require('View'); var StyleSheet = require('StyleSheet'); var StyleSheetPropType = require('StyleSheetPropType'); @@ -165,6 +166,11 @@ var Image = React.createClass({ var {width, height} = source; var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {}; + if (source.uri === '') { + console.warn('source.uri should not be an empty string'); + return ; + } + var isNetwork = source.uri && source.uri.match(/^https?:/); var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView; var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108 diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 50040c95fb9b..7a3c235a26d8 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -95,6 +95,10 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag progressBlock:(RCTImageLoaderProgressBlock)progressBlock completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { + if ([imageTag isEqualToString:@""]) { + RCTLogWarn(@"source.uri should not be an empty string "); + return nil; + } NSURL *requestURL = [RCTConvert NSURL:imageTag]; id loadHandler = [self imageURLLoaderForRequest:requestURL]; if (!loadHandler) { From a6523910b22610c38f39caf6f621a847ba5dcd0b Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 5 Oct 2015 04:29:59 -0700 Subject: [PATCH 0152/2702] Vendor tinycolor; strip unnecessary components Reviewed By: @vjeux Differential Revision: D2503048 --- Libraries/Animated/src/Interpolation.js | 2 +- .../src/__tests__/Interpolation-test.js | 2 +- Libraries/StyleSheet/processColor.js | 2 +- Libraries/vendor/tinycolor/tinycolor.js | 506 ++++++++++++++++++ npm-shrinkwrap.json | 5 - package.json | 1 - 6 files changed, 509 insertions(+), 9 deletions(-) create mode 100755 Libraries/vendor/tinycolor/tinycolor.js diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js index 99c36834cf73..925484a1ca01 100644 --- a/Libraries/Animated/src/Interpolation.js +++ b/Libraries/Animated/src/Interpolation.js @@ -11,7 +11,7 @@ */ 'use strict'; -var tinycolor = require('tinycolor2'); +var tinycolor = require('tinycolor'); // TODO(#7644673): fix this hack once github jest actually checks invariants var invariant = function(condition, message) { diff --git a/Libraries/Animated/src/__tests__/Interpolation-test.js b/Libraries/Animated/src/__tests__/Interpolation-test.js index 4dca8ab61b47..d6625f25074c 100644 --- a/Libraries/Animated/src/__tests__/Interpolation-test.js +++ b/Libraries/Animated/src/__tests__/Interpolation-test.js @@ -11,7 +11,7 @@ jest .dontMock('Interpolation') .dontMock('Easing') - .dontMock('tinycolor2'); + .dontMock('tinycolor'); var Interpolation = require('Interpolation'); var Easing = require('Easing'); diff --git a/Libraries/StyleSheet/processColor.js b/Libraries/StyleSheet/processColor.js index 34f4df5fe575..cdec109042be 100644 --- a/Libraries/StyleSheet/processColor.js +++ b/Libraries/StyleSheet/processColor.js @@ -10,7 +10,7 @@ */ 'use strict'; -var tinycolor = require('tinycolor2'); +var tinycolor = require('tinycolor'); var Platform = require('Platform'); function processColor(color) { diff --git a/Libraries/vendor/tinycolor/tinycolor.js b/Libraries/vendor/tinycolor/tinycolor.js new file mode 100755 index 000000000000..12fe005f34a0 --- /dev/null +++ b/Libraries/vendor/tinycolor/tinycolor.js @@ -0,0 +1,506 @@ +/** + * @providesModule tinycolor + * @nolint + */ +'use strict'; + +// TinyColor v1.2.1 +// https://github.com/bgrins/TinyColor +// Brian Grinstead, MIT License + +var trimLeft = /^[\s,#]+/, + trimRight = /\s+$/, + tinyCounter = 0, + mathRound = Math.round, + mathMin = Math.min, + mathMax = Math.max; + +function tinycolor (color, opts) { + // If we are called as a function, call using new instead + if (!(this instanceof tinycolor)) { + return new tinycolor(color, opts); + } + + color = (color) ? color : ''; + opts = opts || { }; + + var rgb = inputToRGB(color); + this._r = rgb.r, + this._g = rgb.g, + this._b = rgb.b, + this._a = rgb.a, + + this._ok = rgb.ok; +} + +tinycolor.prototype = { + toHex8: function() { + return rgbaToHex(this._r, this._g, this._b, this._a); + }, + toRgb: function() { + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; + }, + isValid: function() { + return this._ok; + }, +}; + +// Given a string or object, convert that input to RGB +// Possible string inputs: +// +// "red" +// "#f00" or "f00" +// "#ff0000" or "ff0000" +// "#ff000000" or "ff000000" +// "rgb 255 0 0" or "rgb (255, 0, 0)" +// "rgb 1.0 0 0" or "rgb (1, 0, 0)" +// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" +// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" +// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" +// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" +// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" +// +function inputToRGB(color) { + var rgb = { r: 0, g: 0, b: 0 }; + var a = 1; + var ok = false; + var format = false; + + if (typeof color == "string") { + color = stringInputToObject(color); + } + + if (typeof color == "object") { + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { + rgb = rgbToRgb(color.r, color.g, color.b); + ok = true; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { + color.s = convertToPercentage(color.s); + color.v = convertToPercentage(color.v); + rgb = hsvToRgb(color.h, color.s, color.v); + ok = true; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { + color.s = convertToPercentage(color.s); + color.l = convertToPercentage(color.l); + rgb = hslToRgb(color.h, color.s, color.l); + ok = true; + } + + if (color.hasOwnProperty("a")) { + a = color.a; + } + } + + a = boundAlpha(a); + + return { + ok: ok, + r: mathMin(255, mathMax(rgb.r, 0)), + g: mathMin(255, mathMax(rgb.g, 0)), + b: mathMin(255, mathMax(rgb.b, 0)), + a: a + }; +} + + +// Conversion Functions +// -------------------- + +// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: +// + +// `rgbToRgb` +// Handle bounds / percentage checking to conform to CSS color spec +// +// *Assumes:* r, g, b in [0, 255] or [0, 1] +// *Returns:* { r, g, b } in [0, 255] +function rgbToRgb(r, g, b){ + return { + r: bound01(r, 255) * 255, + g: bound01(g, 255) * 255, + b: bound01(b, 255) * 255 + }; +} + +// `hslToRgb` +// Converts an HSL color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] +function hslToRgb(h, s, l) { + var r, g, b; + + h = bound01(h, 360); + s = bound01(s, 100); + l = bound01(l, 100); + + function hue2rgb(p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + if(s === 0) { + r = g = b = l; // achromatic + } + else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `hsvToRgb` +// Converts an HSV color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] + function hsvToRgb(h, s, v) { + + h = bound01(h, 360) * 6; + s = bound01(s, 100); + v = bound01(v, 100); + + var i = math.floor(h), + f = h - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + mod = i % 6, + r = [v, q, p, p, t, v][mod], + g = [t, v, v, q, p, p][mod], + b = [p, p, t, v, v, q][mod]; + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbaToHex` +// Converts an RGBA color plus alpha transparency to hex +// Assumes r, g, b and a are contained in the set [0, 255] +// Returns an 8 character hex +function rgbaToHex(r, g, b, a) { + var hex = [ + pad2(convertDecimalToHex(a)), + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + return hex.join(""); +} + +// Big List of Colors +// ------------------ +// +var names = tinycolor.names = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + burntsienna: "ea7e5d", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + grey: "808080", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + rebeccapurple: "663399", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32" +}; + +// Return a valid alpha value [0,1] with all invalid values being set to 1 +function boundAlpha(a) { + a = parseFloat(a); + + if (isNaN(a) || a < 0 || a > 1) { + a = 1; + } + + return a; +} + +// Take input from [0, n] and return it as [0, 1] +function bound01(n, max) { + if (isOnePointZero(n)) { n = "100%"; } + + var processPercent = isPercentage(n); + n = mathMin(max, mathMax(0, parseFloat(n))); + + // Automatically convert percentage into number + if (processPercent) { + n = parseInt(n * max, 10) / 100; + } + + // Handle floating point rounding errors + if ((Math.abs(n - max) < 0.000001)) { + return 1; + } + + // Convert into [0, 1] range if it isn't already + return (n % max) / parseFloat(max); +} + +// Parse a base-16 hex value into a base-10 integer +function parseIntFromHex(val) { + return parseInt(val, 16); +} + +// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 +// +function isOnePointZero(n) { + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; +} + +// Check to see if string passed in is a percentage +function isPercentage(n) { + return typeof n === "string" && n.indexOf('%') != -1; +} + +// Force a hex value to have 2 characters +function pad2(c) { + return c.length == 1 ? '0' + c : '' + c; +} + +// Replace a decimal with it's percentage value +function convertToPercentage(n) { + if (n <= 1) { + n = (n * 100) + "%"; + } + + return n; +} + +// Converts a decimal to a hex value +function convertDecimalToHex(d) { + return Math.round(parseFloat(d) * 255).toString(16); +} + +var matchers = (function() { + // + var CSS_INTEGER = "[-\\+]?\\d+%?"; + + // + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; + + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; + + // Actual matching. + // Parentheses and commas are optional, but not required. + // Whitespace can take the place of commas or opening paren + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + + return { + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), + hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), + hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, + }; +})(); + +// `stringInputToObject` +// Permissive string parsing. Take in a number of formats, and output an object +// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` +function stringInputToObject(color) { + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); + var named = false; + if (names[color]) { + color = names[color]; + named = true; + } + else if (color == 'transparent') { + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; + } + + // Try to match string input using regular expressions. + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] + // Just return an object and let the conversion functions handle that. + // This way the result will be the same whether the tinycolor is initialized with string or object. + var match; + if ((match = matchers.rgb.exec(color))) { + return { r: match[1], g: match[2], b: match[3] }; + } + if ((match = matchers.rgba.exec(color))) { + return { r: match[1], g: match[2], b: match[3], a: match[4] }; + } + if ((match = matchers.hsl.exec(color))) { + return { h: match[1], s: match[2], l: match[3] }; + } + if ((match = matchers.hsla.exec(color))) { + return { h: match[1], s: match[2], l: match[3], a: match[4] }; + } + if ((match = matchers.hsv.exec(color))) { + return { h: match[1], s: match[2], v: match[3] }; + } + if ((match = matchers.hsva.exec(color))) { + return { h: match[1], s: match[2], v: match[3], a: match[4] }; + } + if ((match = matchers.hex6.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + format: named ? "name" : "hex" + }; + } + if ((match = matchers.hex3.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + format: named ? "name" : "hex" + }; + } + + return false; +} + +module.exports = tinycolor; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 238d10cb1b1c..63d0a2606f44 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4212,11 +4212,6 @@ } } }, - "tinycolor2": { - "version": "1.1.2", - "from": "tinycolor2@*", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.1.2.tgz" - }, "uglify-js": { "version": "2.4.24", "from": "uglify-js@2.4.24", diff --git a/package.json b/package.json index b78c90be7f42..5e6118d3f3fc 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "semver": "^5.0.1", "source-map": "^0.4.4", "stacktrace-parser": "^0.1.3", - "tinycolor2": "^1.1.2", "uglify-js": "^2.4.24", "underscore": "^1.8.3", "wordwrap": "^1.0.0", From 97ec98a95929d125d26b3dc7caa7f899726e9339 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 5 Oct 2015 07:57:36 -0700 Subject: [PATCH 0153/2702] Markers for JSC and Bridge Initialization Differential Revision: D2507869 --- .../react/bridge/CatalystInstance.java | 9 +++++++- .../react/bridge/NativeModuleRegistry.java | 22 +++++++++++++++---- .../src/main/jni/react/JSCExecutor.cpp | 4 ++++ .../src/main/jni/react/jni/JSLoader.cpp | 14 ++++++++++++ .../src/main/jni/react/jni/OnLoad.cpp | 16 ++++++++++++++ 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index be3cd5d0aabc..4621914f096b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -111,7 +111,14 @@ private void initializeBridge( mBridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(registry, jsModulesConfig)); - jsBundleLoader.loadScript(mBridge); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "CatalystInstance_initializeBridge"); + try { + jsBundleLoader.loadScript(mBridge); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } } /* package */ void callFunction( diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java index 42a794ecae1e..9e00799a690a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java @@ -70,15 +70,29 @@ private NativeModuleRegistry( /* package */ void notifyCatalystInstanceDestroy() { UiThreadUtil.assertOnUiThread(); - for (NativeModule nativeModule : mModuleInstances.values()) { - nativeModule.onCatalystInstanceDestroy(); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "NativeModuleRegistry_notifyCatalystInstanceDestroy"); + try { + for (NativeModule nativeModule : mModuleInstances.values()) { + nativeModule.onCatalystInstanceDestroy(); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } /* package */ void notifyCatalystInstanceInitialized() { UiThreadUtil.assertOnUiThread(); - for (NativeModule nativeModule : mModuleInstances.values()) { - nativeModule.initialize(); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "NativeModuleRegistry_notifyCatalystInstanceInitialized"); + try { + for (NativeModule nativeModule : mModuleInstances.values()) { + nativeModule.initialize(); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 583e0fe92101..f6360f0a5cd1 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -70,6 +70,10 @@ void JSCExecutor::executeApplicationScript( const std::string& sourceURL) { String jsScript(script.c_str()); String jsSourceURL(sourceURL.c_str()); + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::executeApplicationScript", + "sourceURL", sourceURL); + #endif evaluateScriptWithJSC(m_context, jsScript, jsSourceURL); } diff --git a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp index 4e8345c7421c..416640a371de 100644 --- a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp @@ -10,6 +10,11 @@ #include #include +#ifdef WITH_FBSYSTRACE +#include +using fbsystrace::FbSystraceSection; +#endif + namespace facebook { namespace react { @@ -17,6 +22,11 @@ std::string loadScriptFromAssets( JNIEnv *env, jobject assetManager, std::string assetName) { + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromAssets", + "assetName", assetName); + #endif + auto manager = AAssetManager_fromJava(env, assetManager); if (manager) { auto asset = AAssetManager_open( @@ -41,6 +51,10 @@ std::string loadScriptFromAssets( } std::string loadScriptFromFile(std::string fileName) { + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromFile", + "fileName", fileName); + #endif std::ifstream jsfile(fileName); if (jsfile) { std::string output; diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index c471480ed1b5..fde064b85c36 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -17,6 +17,11 @@ #include "NativeArray.h" #include "ProxyExecutor.h" +#ifdef WITH_FBSYSTRACE +#include +using fbsystrace::FbSystraceSection; +#endif + using namespace facebook::jni; namespace facebook { @@ -583,6 +588,11 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager, auto bridge = extractRefPtr(env, obj); auto assetNameStr = fromJString(env, assetName); auto script = react::loadScriptFromAssets(env, assetManager, assetNameStr); + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_" + "executeApplicationScript", + "assetName", assetNameStr); + #endif bridge->executeApplicationScript(script, assetNameStr); } @@ -593,6 +603,12 @@ static void loadScriptFromNetworkCached(JNIEnv* env, jobject obj, jstring source if (tempFileName != NULL) { script = react::loadScriptFromFile(jni::fromJString(env, tempFileName)); } + #ifdef WITH_FBSYSTRACE + auto sourceURLStr = fromJString(env, sourceURL); + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_" + "executeApplicationScript", + "sourceURL", sourceURLStr); + #endif bridge->executeApplicationScript(script, jni::fromJString(env, sourceURL)); } From cd6b3b3ca1d74e5c878213b2d2a65c221f9c2efb Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 5 Oct 2015 08:02:39 -0700 Subject: [PATCH 0154/2702] systrace of Fresco Differential Revision: D2507874 --- .../react/modules/fresco/FrescoModule.java | 9 + .../fresco/SystraceRequestListener.java | 196 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 20f163fe3648..9f278ab57393 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -9,6 +9,8 @@ package com.facebook.react.modules.fresco; +import java.util.HashSet; + import android.content.Context; import com.facebook.cache.common.CacheKey; @@ -18,6 +20,7 @@ import com.facebook.imagepipeline.backends.okhttp.OkHttpImagePipelineConfigFactory; import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.core.ImagePipelineFactory; +import com.facebook.imagepipeline.listener.RequestListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.modules.common.ModuleDataCleaner; @@ -38,6 +41,7 @@ public FrescoModule(ReactApplicationContext reactContext) { super(reactContext); } + @Override public void initialize() { super.initialize(); @@ -50,11 +54,16 @@ public void loadLibrary(String libraryName) { SoLoader.loadLibrary(libraryName); } }); + + HashSet requestListeners = new HashSet<>(); + requestListeners.add(new SystraceRequestListener()); + Context context = this.getReactApplicationContext().getApplicationContext(); OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); ImagePipelineConfig config = OkHttpImagePipelineConfigFactory .newBuilder(context, okHttpClient) .setDownsampleEnabled(false) + .setRequestListeners(requestListeners) .build(); Fresco.initialize(context, config); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java new file mode 100644 index 000000000000..314a4dffa04d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/SystraceRequestListener.java @@ -0,0 +1,196 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.modules.fresco; + +import java.util.HashMap; +import java.util.Map; + +import android.util.Pair; + +import com.facebook.imagepipeline.listener.RequestListener; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.systrace.Systrace; + +/** + * Logs requests to Systrace + */ +public class SystraceRequestListener implements RequestListener { + + int mCurrentID = 0; + Map> mProducerID = new HashMap<>(); + Map> mRequestsID = new HashMap<>(); + + @Override + public void onProducerStart(String requestId, String producerName) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + StringBuilder entryName = new StringBuilder(); + entryName.append("FRESCO_PRODUCER_"); + entryName.append(producerName.replace(':', '_')); + + Pair requestPair = Pair.create(mCurrentID, entryName.toString()); + Systrace.beginAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + requestPair.second, + mCurrentID); + mProducerID.put(requestId, requestPair); + mCurrentID++; + } + + @Override + public void onProducerFinishWithSuccess( + String requestId, + String producerName, + Map extraMap) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mProducerID.containsKey(requestId)) { + Pair entry = mProducerID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mProducerID.remove(requestId); + } + } + + @Override + public void onProducerFinishWithFailure( + String requestId, + String producerName, + Throwable throwable, + Map extraMap) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mProducerID.containsKey(requestId)) { + Pair entry = mProducerID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mProducerID.remove(requestId); + } + } + + @Override + public void onProducerFinishWithCancellation( + String requestId, String producerName, Map extraMap) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mProducerID.containsKey(requestId)) { + Pair entry = mProducerID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mProducerID.remove(requestId); + } + } + + @Override + public void onProducerEvent(String requestId, String producerName, String producerEventName) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + StringBuilder entryName = new StringBuilder(); + entryName.append("FRESCO_PRODUCER_EVENT_"); + entryName.append(requestId.replace(':', '_')); + entryName.append("_"); + entryName.append(producerName.replace(':', '_')); + entryName.append("_"); + entryName.append(producerEventName.replace(':', '_')); + Systrace.traceInstant( + Systrace.TRACE_TAG_REACT_FRESCO, + entryName.toString(), + Systrace.EventScope.THREAD); + } + + @Override + public void onRequestStart( + ImageRequest request, + Object callerContext, + String requestId, + boolean isPrefetch) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + StringBuilder entryName = new StringBuilder(); + entryName.append("FRESCO_REQUEST_"); + entryName.append(request.getSourceUri().toString().replace(':', '_')); + + Pair requestPair = Pair.create(mCurrentID, entryName.toString()); + Systrace.beginAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + requestPair.second, + mCurrentID); + mRequestsID.put(requestId, requestPair); + mCurrentID++; + } + + @Override + public void onRequestSuccess(ImageRequest request, String requestId, boolean isPrefetch) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mRequestsID.containsKey(requestId)) { + Pair entry = mRequestsID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mRequestsID.remove(requestId); + } + } + + @Override + public void onRequestFailure( + ImageRequest request, + String requestId, + Throwable throwable, + boolean isPrefetch) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mRequestsID.containsKey(requestId)) { + Pair entry = mRequestsID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mRequestsID.remove(requestId); + } + } + + @Override + public void onRequestCancellation(String requestId) { + if (!Systrace.isTracing(Systrace.TRACE_TAG_REACT_FRESCO)) { + return; + } + + if (mRequestsID.containsKey(requestId)) { + Pair entry = mRequestsID.get(requestId); + Systrace.endAsyncSection( + Systrace.TRACE_TAG_REACT_FRESCO, + entry.second, + entry.first); + mRequestsID.remove(requestId); + } + } + + @Override + public boolean requiresExtraMap(String id) { + return false; + } +} From 10da9040288e2c114ac1adeeeff7d825fe58e110 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 5 Oct 2015 08:45:43 -0700 Subject: [PATCH 0155/2702] Fix minified source maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public The source maps generated by uglify are already stringified, and therefore were being stringified twice. Reviewed By: @martinbigio Differential Revision: D2498242 --- packager/react-packager/src/Server/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 74e839272c1a..4fd0be570df6 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -361,9 +361,14 @@ class Server { res.end(bundleSource); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { - var sourceMap = JSON.stringify(p.getSourceMap({ + var sourceMap = p.getSourceMap({ minify: options.minify, - })); + }); + + if (typeof sourceMap !== 'string') { + sourceMap = JSON.stringify(sourceMap); + } + res.setHeader('Content-Type', 'application/json'); res.end(sourceMap); Activity.endEvent(startReqEventId); From c0199b0b3591fe1983c1363c44659660472dad22 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 5 Oct 2015 16:51:28 +0100 Subject: [PATCH 0156/2702] Update KnownIssues.md --- docs/KnownIssues.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 766f4b7ea038..b61cd217a754 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -38,8 +38,15 @@ Alert ``` ### Some props are only supported on one platform + There are properties that work on one platform only, either because they can inherently only be supported on that platform or because they haven't been implemented on the other platforms yet. All of these are annotated with `@platform` in JS docs and have a small badge next to them on the website. See e.g. [Image](https://facebook.github.io/react-native/docs/image.html). +### Platform parity + +There are known cases where the API between iOS and Android could be made more consistent: + +- `` and `` are very similar and should be unified to a single `` component + ### Publishing modules on Android There is currently no easy way of publishing custom native modules on Android. Smooth work flow for contributors is important and this will be looked at very closely after the initial Open Source release. Of course the aim will be to streamline and optimize the process between iOS and Android as much as possible. From 7962a3660d611ab679e479d0ff9bb89b07f34e4b Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 5 Oct 2015 17:20:13 +0100 Subject: [PATCH 0157/2702] Update KnownIssues.md --- docs/KnownIssues.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index b61cd217a754..1aa7c61a0b94 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -46,6 +46,11 @@ There are properties that work on one platform only, either because they can inh There are known cases where the API between iOS and Android could be made more consistent: - `` and `` are very similar and should be unified to a single `` component +- `` (to be open sourced soon) and `` on iOS do a similar thing. We might want to unify them to ``. +- `alert()` needs Android support (once the Dialogs module is open sourced) +- It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together +- `ActivityIndicator` could render a native spinning indicator on both platforms (currenty this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android) +- `ProgressBar` could render a horizontal progress bar on both platforms (currently only supported on iOS via `ProgressViewIOS`) ### Publishing modules on Android From 55c94c06e416c1756627671ef7dda9fbdce87ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 5 Oct 2015 09:15:09 -0700 Subject: [PATCH 0158/2702] Move fbobjc bundle command to cli Reviewed By: @frantic Differential Revision: D2457072 --- packager/react-packager/index.js | 4 +- private-cli/index.js | 7 +- private-cli/src/bundle/__mocks__/sign.js | 15 +++ .../__tests__/getAssetDestPathAndroid-test.js | 59 ++++++++++++ .../__tests__/getAssetDestPathIOS-test.js | 36 +++++++ .../bundle/__tests__/saveBundleAndMap-test.js | 63 ++++++++++++ private-cli/src/bundle/bundle.js | 96 +++++++++++++++++++ .../src/bundle/getAssetDestPathAndroid.js | 43 +++++++++ private-cli/src/bundle/getAssetDestPathIOS.js | 19 ++++ private-cli/src/bundle/processBundle.js | 29 ++++++ private-cli/src/bundle/saveBundleAndMap.js | 96 +++++++++++++++++++ private-cli/src/bundle/sign.js | 21 ++++ private-cli/src/bundle/signedsource.js | 96 +++++++++++++++++++ private-cli/src/cli.js | 2 + private-cli/src/dependencies/dependencies.js | 24 ++--- 15 files changed, 590 insertions(+), 20 deletions(-) create mode 100644 private-cli/src/bundle/__mocks__/sign.js create mode 100644 private-cli/src/bundle/__tests__/getAssetDestPathAndroid-test.js create mode 100644 private-cli/src/bundle/__tests__/getAssetDestPathIOS-test.js create mode 100644 private-cli/src/bundle/__tests__/saveBundleAndMap-test.js create mode 100644 private-cli/src/bundle/bundle.js create mode 100644 private-cli/src/bundle/getAssetDestPathAndroid.js create mode 100644 private-cli/src/bundle/getAssetDestPathIOS.js create mode 100644 private-cli/src/bundle/processBundle.js create mode 100644 private-cli/src/bundle/saveBundleAndMap.js create mode 100644 private-cli/src/bundle/sign.js create mode 100644 private-cli/src/bundle/signedsource.js diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index f42211cb7671..27c4de5b21f6 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -8,9 +8,7 @@ */ 'use strict'; -require('babel-core/register')({ - only: /react-packager\/src/ -}); +require('babel-core/register')(); useGracefulFs(); diff --git a/private-cli/index.js b/private-cli/index.js index 51fba7207622..c01a03ca0627 100644 --- a/private-cli/index.js +++ b/private-cli/index.js @@ -8,11 +8,8 @@ */ 'use strict'; -require('babel-core/register')({ - only: [ - /react-native-github\/private-cli\/src/ - ], -}); +// trigger babel-core/register +require('../packager/react-packager'); var cli = require('./src/cli'); var fs = require('fs'); diff --git a/private-cli/src/bundle/__mocks__/sign.js b/private-cli/src/bundle/__mocks__/sign.js new file mode 100644 index 000000000000..80ff1860e3f7 --- /dev/null +++ b/private-cli/src/bundle/__mocks__/sign.js @@ -0,0 +1,15 @@ +/** + * 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. + */ +'use strict'; + +function sign(source) { + return source; +} + +module.exports = sign; diff --git a/private-cli/src/bundle/__tests__/getAssetDestPathAndroid-test.js b/private-cli/src/bundle/__tests__/getAssetDestPathAndroid-test.js new file mode 100644 index 000000000000..1613b5b3656a --- /dev/null +++ b/private-cli/src/bundle/__tests__/getAssetDestPathAndroid-test.js @@ -0,0 +1,59 @@ +/** + * 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. + */ +'use strict'; + +jest.dontMock('../getAssetDestPathAndroid'); + +const getAssetDestPathAndroid = require('../getAssetDestPathAndroid'); + +describe('getAssetDestPathAndroid', () => { + it('should use the right destination folder', () => { + const asset = { + name: 'icon', + type: 'png', + httpServerLocation: '/assets/test', + }; + + const expectDestPathForScaleToStartWith = (scale, path) => { + if (!getAssetDestPathAndroid(asset, scale).startsWith(path)) { + throw new Error(`asset for scale ${scale} should start with path '${path}'`); + } + }; + + expectDestPathForScaleToStartWith(1, 'drawable-mdpi'); + expectDestPathForScaleToStartWith(1.5, 'drawable-hdpi'); + expectDestPathForScaleToStartWith(2, 'drawable-xhdpi'); + expectDestPathForScaleToStartWith(3, 'drawable-xxhdpi'); + expectDestPathForScaleToStartWith(4, 'drawable-xxxhdpi'); + }); + + it('should lowercase path', () => { + const asset = { + name: 'Icon', + type: 'png', + httpServerLocation: '/assets/App/Test', + }; + + expect(getAssetDestPathAndroid(asset, 1)).toBe( + 'drawable-mdpi/app_test_icon.png' + ); + }); + + it('should remove `assets/` prefix', () => { + const asset = { + name: 'icon', + type: 'png', + httpServerLocation: '/assets/RKJSModules/Apps/AndroidSample/Assets', + }; + + expect( + getAssetDestPathAndroid(asset, 1).startsWith('assets_') + ).toBeFalsy(); + }); +}); diff --git a/private-cli/src/bundle/__tests__/getAssetDestPathIOS-test.js b/private-cli/src/bundle/__tests__/getAssetDestPathIOS-test.js new file mode 100644 index 000000000000..d30441b488fc --- /dev/null +++ b/private-cli/src/bundle/__tests__/getAssetDestPathIOS-test.js @@ -0,0 +1,36 @@ +/** + * 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. + */ +'use strict'; + +jest.dontMock('../getAssetDestPathIOS'); + +const getAssetDestPathIOS = require('../getAssetDestPathIOS'); + +describe('getAssetDestPathIOS', () => { + it('should build correct path', () => { + const asset = { + name: 'icon', + type: 'png', + httpServerLocation: '/assets/test', + }; + + expect(getAssetDestPathIOS(asset, 1)).toBe('assets/test/icon.png'); + }); + + it('should consider scale', () => { + const asset = { + name: 'icon', + type: 'png', + httpServerLocation: '/assets/test', + }; + + expect(getAssetDestPathIOS(asset, 2)).toBe('assets/test/icon@2x.png'); + expect(getAssetDestPathIOS(asset, 3)).toBe('assets/test/icon@3x.png'); + }); +}); diff --git a/private-cli/src/bundle/__tests__/saveBundleAndMap-test.js b/private-cli/src/bundle/__tests__/saveBundleAndMap-test.js new file mode 100644 index 000000000000..f828b7346d75 --- /dev/null +++ b/private-cli/src/bundle/__tests__/saveBundleAndMap-test.js @@ -0,0 +1,63 @@ +/** + * 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. + */ +'use strict'; + +jest + .dontMock('../saveBundleAndMap') + .dontMock('os-tmpdir') + .dontMock('temp'); + +jest.mock('fs'); + +const saveBundleAndMap = require('../saveBundleAndMap'); +const fs = require('fs'); +const temp = require('temp'); + +const code = 'const foo = "bar";'; +const map = JSON.stringify({ + version: 3, + file: 'foo.js.map', + sources: ['foo.js'], + sourceRoot: '/', + names: ['bar'], + mappings: 'AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO' +}); + +describe('saveBundleAndMap', () => { + beforeEach(() => { + fs.writeFileSync = jest.genMockFn(); + }); + + it('should save bundle', () => { + const codeWithMap = {code: code}; + const bundleOutput = temp.path({suffix: '.bundle'}); + + saveBundleAndMap( + codeWithMap, + 'ios', + bundleOutput + ); + + expect(fs.writeFileSync.mock.calls[0]).toEqual([bundleOutput, code]); + }); + + it('should save sourcemaps if required so', () => { + const codeWithMap = {code: code, map: map}; + const bundleOutput = temp.path({suffix: '.bundle'}); + const sourceMapOutput = temp.path({suffix: '.map'}); + saveBundleAndMap( + codeWithMap, + 'ios', + bundleOutput, + sourceMapOutput + ); + + expect(fs.writeFileSync.mock.calls[1]).toEqual([sourceMapOutput, map]); + }); +}); diff --git a/private-cli/src/bundle/bundle.js b/private-cli/src/bundle/bundle.js new file mode 100644 index 000000000000..f66bda2b5adc --- /dev/null +++ b/private-cli/src/bundle/bundle.js @@ -0,0 +1,96 @@ +/** + * 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. + */ +'use strict'; + +const log = require('../util/log').out('bundle'); +const parseCommandLine = require('../../../packager/parseCommandLine'); +const processBundle = require('./processBundle'); +const Promise = require('promise'); +const ReactPackager = require('../../../packager/react-packager'); +const saveBundleAndMap = require('./saveBundleAndMap'); + +/** + * Builds the bundle starting to look for dependencies at the given entry path. + */ +function bundle(argv, config) { + return new Promise((resolve, reject) => { + _bundle(argv, config, resolve, reject); + }); +} + +function _bundle(argv, config, resolve, reject) { + const args = parseCommandLine([ + { + command: 'entry-file', + description: 'Path to the root JS file, either absolute or relative to JS root', + type: 'string', + required: true, + }, { + command: 'platform', + description: 'Either "ios" or "android"', + type: 'string', + required: true, + }, { + command: 'dev', + description: 'If false, warnings are disabled and the bundle is minified', + default: true, + }, { + command: 'bundle-output', + description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle', + type: 'string', + required: true, + }, { + command: 'sourcemap-output', + description: 'File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map', + type: 'string', + }, { + command: 'assets-dest', + description: 'Directory name where to store assets referenced in the bundle', + type: 'string', + } + ], argv); + + // This is used by a bazillion of npm modules we don't control so we don't + // have other choice than defining it as an env variable here. + process.env.NODE_ENV = args.dev ? 'development' : 'production'; + + const options = { + projectRoots: config.getProjectRoots(), + assetRoots: config.getAssetRoots(), + blacklistRE: config.getBlacklistRE(), + transformModulePath: config.getTransformModulePath(), + }; + + const requestOpts = { + entryFile: args['entry-file'], + dev: args.dev, + minify: !args.dev, + platform: args.platform, + }; + + resolve(ReactPackager.createClientFor(options).then(client => { + log('Created ReactPackager'); + return client.buildBundle(requestOpts) + .then(outputBundle => { + log('Closing client'); + client.close(); + return outputBundle; + }) + .then(outputBundle => processBundle(outputBundle, !args.dev)) + .then(outputBundle => saveBundleAndMap( + outputBundle, + args.platform, + args['bundle-output'], + args['sourcemap-output'], + args['assets-dest'] + )); + })); +} + +module.exports = bundle; diff --git a/private-cli/src/bundle/getAssetDestPathAndroid.js b/private-cli/src/bundle/getAssetDestPathAndroid.js new file mode 100644 index 000000000000..3e039f54fd5e --- /dev/null +++ b/private-cli/src/bundle/getAssetDestPathAndroid.js @@ -0,0 +1,43 @@ +/** + * 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. + */ +'use strict'; + +const path = require('path'); + +function getAndroidAssetSuffix(scale) { + switch (scale) { + case 0.75: return 'ldpi'; + case 1: return 'mdpi'; + case 1.5: return 'hdpi'; + case 2: return 'xhdpi'; + case 3: return 'xxhdpi'; + case 4: return 'xxxhdpi'; + } +} + +function getAssetDestPathAndroid(asset, scale) { + var suffix = getAndroidAssetSuffix(scale); + if (!suffix) { + throw new Error( + 'Don\'t know which android drawable suffix to use for asset: ' + + JSON.stringify(asset) + ); + } + const androidFolder = 'drawable-' + suffix; + // TODO: reuse this logic from https://fburl.com/151101135 + const fileName = (asset.httpServerLocation.substr(1) + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^assets_/, ''); // Remove "assets_" prefix + + return path.join(androidFolder, fileName + '.' + asset.type); +} + +module.exports = getAssetDestPathAndroid; diff --git a/private-cli/src/bundle/getAssetDestPathIOS.js b/private-cli/src/bundle/getAssetDestPathIOS.js new file mode 100644 index 000000000000..d7db5185853c --- /dev/null +++ b/private-cli/src/bundle/getAssetDestPathIOS.js @@ -0,0 +1,19 @@ +/** + * 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. + */ +'use strict'; + +const path = require('path'); + +function getAssetDestPathIOS(asset, scale) { + const suffix = scale === 1 ? '' : '@' + scale + 'x'; + const fileName = asset.name + suffix + '.' + asset.type; + return path.join(asset.httpServerLocation.substr(1), fileName); +} + +module.exports = getAssetDestPathIOS; diff --git a/private-cli/src/bundle/processBundle.js b/private-cli/src/bundle/processBundle.js new file mode 100644 index 000000000000..6b54cd834219 --- /dev/null +++ b/private-cli/src/bundle/processBundle.js @@ -0,0 +1,29 @@ +/** + * 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. + */ +'use strict'; + +const log = require('../util/log').out('bundle'); + +function processBundle(input, shouldMinify) { + log('start'); + let bundle; + if (shouldMinify) { + bundle = input.getMinifiedSourceAndMap(); + } else { + bundle = { + code: input.getSource(), + map: JSON.stringify(input.getSourceMap()), + }; + } + bundle.assets = input.getAssets(); + log('finish'); + return bundle; +} + +module.exports = processBundle; diff --git a/private-cli/src/bundle/saveBundleAndMap.js b/private-cli/src/bundle/saveBundleAndMap.js new file mode 100644 index 000000000000..27b987850465 --- /dev/null +++ b/private-cli/src/bundle/saveBundleAndMap.js @@ -0,0 +1,96 @@ +/** + * 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. + */ +'use strict'; + +const execFile = require('child_process').execFile; +const fs = require('fs'); +const getAssetDestPathAndroid = require('./getAssetDestPathAndroid'); +const getAssetDestPathIOS = require('./getAssetDestPathIOS'); +const log = require('../util/log').out('bundle'); +const path = require('path'); +const sign = require('./sign'); + +function saveBundleAndMap( + codeWithMap, + platform, + bundleOutput, + sourcemapOutput, + assetsDest +) { + log('Writing bundle output to:', bundleOutput); + fs.writeFileSync(bundleOutput, sign(codeWithMap.code)); + log('Done writing bundle output'); + + if (sourcemapOutput) { + log('Writing sourcemap output to:', sourcemapOutput); + fs.writeFileSync(sourcemapOutput, codeWithMap.map); + log('Done writing sourcemap output'); + } + + if (!assetsDest) { + console.warn('Assets destination folder is not set, skipping...'); + return Promise.resolve(); + } + + const getAssetDestPath = platform === 'android' + ? getAssetDestPathAndroid + : getAssetDestPathIOS; + + const filesToCopy = Object.create(null); // Map src -> dest + codeWithMap.assets + .filter(asset => !asset.deprecated) + .forEach(asset => + asset.scales.forEach((scale, idx) => { + const src = asset.files[idx]; + const dest = path.join(assetsDest, getAssetDestPath(asset, scale)); + filesToCopy[src] = dest; + }) + ); + + return copyAll(filesToCopy); +} + +function copyAll(filesToCopy) { + const queue = Object.keys(filesToCopy); + if (queue.length === 0) { + return Promise.resolve(); + } + + log('Copying ' + queue.length + ' asset files'); + return new Promise((resolve, reject) => { + const copyNext = (error) => { + if (error) { + return reject(error); + } + if (queue.length === 0) { + log('Done copying assets'); + resolve(); + } else { + const src = queue.shift(); + const dest = filesToCopy[src]; + copy(src, dest, copyNext); + } + }; + copyNext(); + }); +} + +function copy(src, dest, callback) { + const destDir = path.dirname(dest); + execFile('mkdir', ['-p', destDir], err => { + if (err) { + return callback(err); + } + fs.createReadStream(src) + .pipe(fs.createWriteStream(dest)) + .on('finish', callback); + }); +} + +module.exports = saveBundleAndMap; diff --git a/private-cli/src/bundle/sign.js b/private-cli/src/bundle/sign.js new file mode 100644 index 000000000000..609d0497a251 --- /dev/null +++ b/private-cli/src/bundle/sign.js @@ -0,0 +1,21 @@ +/** + * 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. + */ +'use strict'; + +const signedsource = require('./signedsource'); +const util = require('util'); + +function sign(source) { + var ssToken = util.format('<<%sSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>', 'Signed'); + var signedPackageText = + source + util.format('\n__SSTOKENSTRING = "@%s %s";\n', 'generated', ssToken); + return signedsource.sign(signedPackageText).signed_data; +} + +module.exports = sign; diff --git a/private-cli/src/bundle/signedsource.js b/private-cli/src/bundle/signedsource.js new file mode 100644 index 000000000000..db09dfffc62b --- /dev/null +++ b/private-cli/src/bundle/signedsource.js @@ -0,0 +1,96 @@ +/** + * 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. + */ +'use strict'; + +var TOKEN = '<>', + OLDTOKEN = '<>', + TOKENS = [TOKEN, OLDTOKEN], + PATTERN = new RegExp('@'+'generated (?:SignedSource<<([a-f0-9]{32})>>)'); + +exports.SIGN_OK = {message:'ok'}; +exports.SIGN_UNSIGNED = new Error('unsigned'); +exports.SIGN_INVALID = new Error('invalid'); + +// Thrown by sign(). Primarily for unit tests. +exports.TokenNotFoundError = new Error( + 'Code signing placeholder not found (expected to find \''+TOKEN+'\')'); + +var md5_hash_hex; + +// MD5 hash function for Node.js. To port this to other platforms, provide an +// alternate code path for defining the md5_hash_hex function. +var crypto = require('crypto'); +md5_hash_hex = function md5_hash_hex(data, input_encoding) { + var md5sum = crypto.createHash('md5'); + md5sum.update(data, input_encoding); + return md5sum.digest('hex'); +}; + +// Returns the signing token to be embedded, generally in a header comment, +// in the file you wish to be signed. +// +// @return str to be embedded in to-be-signed file +function signing_token() { + return '@'+'generated '+TOKEN; +} +exports.signing_token = signing_token; + +// Determine whether a file is signed. This does NOT verify the signature. +// +// @param str File contents as a string. +// @return bool True if the file has a signature. +function is_signed(file_data) { + return !!PATTERN.exec(file_data); +} +exports.is_signed = is_signed; + +// Sign a source file which you have previously embedded a signing token +// into. Signing modifies only the signing token, so the semantics of the +// file will not change if you've put it in a comment. +// +// @param str File contents as a string (with embedded token). +// @return str Signed data. +function sign(file_data) { + var first_time = file_data.indexOf(TOKEN) !== -1; + if (!first_time) { + if (is_signed(file_data)) + file_data = file_data.replace(PATTERN, signing_token()); + else + throw exports.TokenNotFoundError; + } + var signature = md5_hash_hex(file_data, 'utf8'); + var signed_data = file_data.replace(TOKEN, 'SignedSource<<'+signature+'>>'); + return { first_time: first_time, signed_data: signed_data }; +} +exports.sign = sign; + +// Verify a file's signature. +// +// @param str File contents as a string. +// @return Returns SIGN_OK if the data contains a valid signature, +// SIGN_UNSIGNED if it contains no signature, or SIGN_INVALID if +// it contains an invalid signature. +function verify_signature(file_data) { + var match = PATTERN.exec(file_data); + if (!match) + return exports.SIGN_UNSIGNED; + // Replace the signature with the TOKEN, then hash and see if it matches + // the value in the file. For backwards compatibility, also try with + // OLDTOKEN if that doesn't match. + var k, token, with_token, actual_md5, expected_md5 = match[1]; + for (k in TOKENS) { + token = TOKENS[k]; + with_token = file_data.replace(PATTERN, '@'+'generated '+token); + actual_md5 = md5_hash_hex(with_token, 'utf8'); + if (expected_md5 === actual_md5) + return exports.SIGN_OK; + } + return exports.SIGN_INVALID; +} +exports.verify_signature = verify_signature; diff --git a/private-cli/src/cli.js b/private-cli/src/cli.js index c81fa9a32511..679c46d00c14 100644 --- a/private-cli/src/cli.js +++ b/private-cli/src/cli.js @@ -8,11 +8,13 @@ */ 'use strict'; +const bundle = require('./bundle/bundle'); const Config = require('./util/Config'); const dependencies = require('./dependencies/dependencies'); const Promise = require('promise'); const documentedCommands = { + bundle: bundle, dependencies: dependencies, }; diff --git a/private-cli/src/dependencies/dependencies.js b/private-cli/src/dependencies/dependencies.js index 01bc79de96e0..0a0aae4753df 100644 --- a/private-cli/src/dependencies/dependencies.js +++ b/private-cli/src/dependencies/dependencies.js @@ -18,13 +18,13 @@ const ReactPackager = require('../../../packager/react-packager'); /** * Returns the dependencies an entry path has. */ -function dependencies(argv, conf) { +function dependencies(argv, config) { return new Promise((resolve, reject) => { - _dependencies(argv, conf, resolve, reject); + _dependencies(argv, config, resolve, reject); }); } -function _dependencies(argv, conf, resolve, reject) { +function _dependencies(argv, config, resolve, reject) { const args = parseCommandLine([ { command: 'entry-file', @@ -47,14 +47,14 @@ function _dependencies(argv, conf, resolve, reject) { reject(`File ${rootModuleAbsolutePath} does not exist`); } - const config = { - projectRoots: conf.getProjectRoots(), - assetRoots: conf.getAssetRoots(), - blacklistRE: conf.getBlacklistRE(), - transformModulePath: conf.getTransformModulePath(), + const packageOpts = { + projectRoots: config.getProjectRoots(), + assetRoots: config.getAssetRoots(), + blacklistRE: config.getBlacklistRE(), + transformModulePath: config.getTransformModulePath(), }; - const relativePath = config.projectRoots.map(root => + const relativePath = packageOpts.projectRoots.map(root => path.relative( root, rootModuleAbsolutePath @@ -73,7 +73,7 @@ function _dependencies(argv, conf, resolve, reject) { log('Running ReactPackager'); log('Waiting for the packager.'); - resolve(ReactPackager.createClientFor(config).then(client => { + resolve(ReactPackager.createClientFor(packageOpts).then(client => { log('Packager client was created'); return client.getOrderedDependencyPaths(options) .then(deps => { @@ -85,8 +85,8 @@ function _dependencies(argv, conf, resolve, reject) { // Long term, we need either // (a) JS code to not depend on anything outside this directory, or // (b) Come up with a way to declare this dependency in Buck. - const isInsideProjectRoots = config.projectRoots.filter(root => - modulePath.startsWith(root) + const isInsideProjectRoots = packageOpts.projectRoots.filter( + root => modulePath.startsWith(root) ).length > 0; if (isInsideProjectRoots) { From b4ff76a713648f3dfcbd1d28ac973c512d801b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 5 Oct 2015 09:15:10 -0700 Subject: [PATCH 0159/2702] Introduce internal `server` command Reviewed By: @frantic Differential Revision: D2449459 --- packager/webSocketProxy.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packager/webSocketProxy.js b/packager/webSocketProxy.js index 22151c4ea74d..0c7d36c20041 100644 --- a/packager/webSocketProxy.js +++ b/packager/webSocketProxy.js @@ -57,6 +57,8 @@ function attachToServer(server, path) { }); }); }); + + return wss; } module.exports = { From 6c4963ab9c02e892b682be68afba95d192cc3295 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 5 Oct 2015 17:23:57 +0100 Subject: [PATCH 0160/2702] Update KnownIssues.md --- docs/KnownIssues.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 1aa7c61a0b94..ea59fa14c1e5 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -45,12 +45,12 @@ There are properties that work on one platform only, either because they can inh There are known cases where the API between iOS and Android could be made more consistent: -- `` and `` are very similar and should be unified to a single `` component +- `` and `` are very similar and should be unified to a single `` component. - `` (to be open sourced soon) and `` on iOS do a similar thing. We might want to unify them to ``. - `alert()` needs Android support (once the Dialogs module is open sourced) -- It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together -- `ActivityIndicator` could render a native spinning indicator on both platforms (currenty this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android) -- `ProgressBar` could render a horizontal progress bar on both platforms (currently only supported on iOS via `ProgressViewIOS`) +- It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together. +- `ActivityIndicator` could render a native spinning indicator on both platforms (currenty this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android). +- `ProgressBar` could render a horizontal progress bar on both platforms (currently only supported on iOS via `ProgressViewIOS`). ### Publishing modules on Android From ac2db7bc38dcb95b2eb72c29e2f1e9959d2a4acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Mon, 5 Oct 2015 09:26:40 -0700 Subject: [PATCH 0161/2702] check for destroyed instance before calling native method Differential Revision: D2508050 --- .../java/com/facebook/react/devsupport/DevSupportManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index b434db75d99f..e20d67380fc7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -274,6 +274,7 @@ public void onOptionSelected() { if (mCurrentContext != null && mCurrentContext.getCatalystInstance() != null && + !mCurrentContext.getCatalystInstance().isDestroyed() && mCurrentContext.getCatalystInstance().getBridge() != null && mCurrentContext.getCatalystInstance().getBridge().supportsProfiling()) { options.put( From 3fa12dd92ddd5e9eb2dd1ec75c847a9ea2f8f31a Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 5 Oct 2015 10:08:10 -0700 Subject: [PATCH 0162/2702] Make processColor more efficient Reviewed By: @nicklockwood, @vjeux Differential Revision: D2507684 --- .../StyleSheet/__tests__/processColor-test.js | 4 +-- Libraries/StyleSheet/processColor.js | 25 ++++++++++------ Libraries/vendor/tinycolor/tinycolor.js | 29 ------------------- 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/Libraries/StyleSheet/__tests__/processColor-test.js b/Libraries/StyleSheet/__tests__/processColor-test.js index 9e11931cd321..9416353b79b6 100644 --- a/Libraries/StyleSheet/__tests__/processColor-test.js +++ b/Libraries/StyleSheet/__tests__/processColor-test.js @@ -91,13 +91,13 @@ describe('processColor', () => { describe('HSL strings', () => { - it('should convert hsl(x, y%, z%)', () => { + it('should convert hsla(x, y%, z%, a)', () => { var colorFromString = processColor('hsla(318, 69%, 55%, 0.25)'); var expectedInt = 0x40DB3DAC; expect(colorFromString).toEqual(expectedInt); }); - it('should convert hsl x, y%, z%', () => { + it('should convert hsla x, y%, z%, a', () => { var colorFromString = processColor('hsla 318, 69%, 55%, 0.25'); var expectedInt = 0x40DB3DAC; expect(colorFromString).toEqual(expectedInt); diff --git a/Libraries/StyleSheet/processColor.js b/Libraries/StyleSheet/processColor.js index cdec109042be..e259113e3e80 100644 --- a/Libraries/StyleSheet/processColor.js +++ b/Libraries/StyleSheet/processColor.js @@ -13,22 +13,29 @@ var tinycolor = require('tinycolor'); var Platform = require('Platform'); +/* eslint no-bitwise: 0 */ function processColor(color) { if (!color || typeof color === 'number') { return color; } else if (color instanceof Array) { return color.map(processColor); } else { - var hexString = tinycolor(color).toHex8(); - var colorInt = parseInt(hexString, 16); - if (Platform.OS === 'android') { - // Android use 32 bit *signed* integer to represent the color - // We utilize the fact that bitwise operations in JS also operates on - // signed 32 bit integers, so that we can use those to convert from - // *unsiged* to *signed* 32bit int that way. - colorInt = colorInt | 0x0; + var color = tinycolor(color); + if (color.isValid()) { + var rgb = color.toRgb(); + // All bitwise operations happen on 32-bit numbers, so we shift the 1 first + // then multiply it with the actual value. + var colorInt = Math.round(rgb.a * 255) * (1 << 24) + rgb.r * (1 << 16) + rgb.g * (1 << 8) + rgb.b; + if (Platform.OS === 'android') { + // Android use 32 bit *signed* integer to represent the color + // We utilize the fact that bitwise operations in JS also operates on + // signed 32 bit integers, so that we can use those to convert from + // *unsiged* to *signed* 32bit int that way. + colorInt = colorInt | 0x0; + } + return colorInt; } - return colorInt; + return 0; } } diff --git a/Libraries/vendor/tinycolor/tinycolor.js b/Libraries/vendor/tinycolor/tinycolor.js index 12fe005f34a0..9b3fe0df1410 100755 --- a/Libraries/vendor/tinycolor/tinycolor.js +++ b/Libraries/vendor/tinycolor/tinycolor.js @@ -34,9 +34,6 @@ function tinycolor (color, opts) { } tinycolor.prototype = { - toHex8: function() { - return rgbaToHex(this._r, this._g, this._b, this._a); - }, toRgb: function() { return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; }, @@ -163,7 +160,6 @@ function hslToRgb(h, s, l) { // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] // *Returns:* { r, g, b } in the set [0, 255] function hsvToRgb(h, s, v) { - h = bound01(h, 360) * 6; s = bound01(s, 100); v = bound01(v, 100); @@ -181,21 +177,6 @@ function hslToRgb(h, s, l) { return { r: r * 255, g: g * 255, b: b * 255 }; } -// `rgbaToHex` -// Converts an RGBA color plus alpha transparency to hex -// Assumes r, g, b and a are contained in the set [0, 255] -// Returns an 8 character hex -function rgbaToHex(r, g, b, a) { - var hex = [ - pad2(convertDecimalToHex(a)), - pad2(mathRound(r).toString(16)), - pad2(mathRound(g).toString(16)), - pad2(mathRound(b).toString(16)) - ]; - - return hex.join(""); -} - // Big List of Colors // ------------------ // @@ -399,11 +380,6 @@ function isPercentage(n) { return typeof n === "string" && n.indexOf('%') != -1; } -// Force a hex value to have 2 characters -function pad2(c) { - return c.length == 1 ? '0' + c : '' + c; -} - // Replace a decimal with it's percentage value function convertToPercentage(n) { if (n <= 1) { @@ -413,11 +389,6 @@ function convertToPercentage(n) { return n; } -// Converts a decimal to a hex value -function convertDecimalToHex(d) { - return Math.round(parseFloat(d) * 255).toString(16); -} - var matchers = (function() { // var CSS_INTEGER = "[-\\+]?\\d+%?"; From 33e361b72a5e2f4e9c16c5e722c3eab6dab81c35 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Mon, 5 Oct 2015 10:19:28 -0700 Subject: [PATCH 0163/2702] Fix keyboardShouldPersistTaps default value Reviewed By: @astreet Differential Revision: D2507878 --- Libraries/Components/ScrollResponder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 3dc162dfaedf..7e98cc502d02 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -181,7 +181,7 @@ var ScrollResponderMixin = { scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean { // First see if we want to eat taps while the keyboard is up var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (this.props.keyboardShouldPersistTaps === false && + if (!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput) { return true; @@ -242,7 +242,7 @@ var ScrollResponderMixin = { // By default scroll views will unfocus a textField // if another touch occurs outside of it var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (this.props.keyboardShouldPersistTaps === false && + if (!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput && !this.state.observedScrollSinceBecomingResponder && From b73a033fee5b493dc42d1b2098a137fc711e2bd5 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 5 Oct 2015 18:53:00 +0100 Subject: [PATCH 0164/2702] Update KnownIssues.md --- docs/KnownIssues.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index ea59fa14c1e5..cb4ca83ed69d 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -8,6 +8,7 @@ next: performance --- ### Missing Modules and Native Views + This is an initial release of React Native Android and therefore not all of the views present on iOS are released on Android. We are very much interested in the communities' feedback on the next set of modules and views for Open Source. Not all native views between iOS and Android have a 100% equivalent representation, here it will be necessary to use a counterpart eg using ProgressBar on Android in place of ActivityIndicator on iOS. Our provisional plan for common views and modules includes: @@ -43,7 +44,7 @@ There are properties that work on one platform only, either because they can inh ### Platform parity -There are known cases where the API between iOS and Android could be made more consistent: +There are known cases where the APIs could be made more consistent across iOS and Android: - `` and `` are very similar and should be unified to a single `` component. - `` (to be open sourced soon) and `` on iOS do a similar thing. We might want to unify them to ``. From b8ac13592cad81a25f5a04538b583d27d93e945a Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 5 Oct 2015 10:57:39 -0700 Subject: [PATCH 0165/2702] [cli] Use contributing.md as readme The readme is basically empty but contributing has a ton of useful information. Github's interface show everything in readme inline when you open the folder, so might as well make this information more visible. --- react-native-cli/CONTRIBUTING.md | 101 ----------------------------- react-native-cli/README.md | 105 +++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 107 deletions(-) delete mode 100644 react-native-cli/CONTRIBUTING.md diff --git a/react-native-cli/CONTRIBUTING.md b/react-native-cli/CONTRIBUTING.md deleted file mode 100644 index 4273503e873c..000000000000 --- a/react-native-cli/CONTRIBUTING.md +++ /dev/null @@ -1,101 +0,0 @@ -## Running CLI with local modifications - -React Native is distributed as two npm packages, `react-native-cli` and `react-native`. The first one is a lightweight package that should be installed globally (`npm install -g react-native-cli`), while the second one contains the actual React Native framework code and is installed locally into your project when you run `react-native init`. - -Because `react-native init` calls `npm install react-native`, simply linking your local github clone into npm is not enough to test local changes. - -### Introducing Sinopia - -[Sinopia] is an npm registry that runs on your local machine and allows you to publish packages to it. Everything else is proxied from `npmjs.com`. We'll set up sinopia for React Native CLI development. First, install it with: - - $ npm install -g sinopia - -Then, open `~/.config/sinopia/config.yaml` and configure it like this (note the `max_body_size`): - - storage: ./storage - - auth: - htpasswd: - file: ./htpasswd - - uplinks: - npmjs: - url: https://registry.npmjs.org/ - - packages: - 'react-native': - allow_access: $all - allow_publish: $all - - 'react-native-cli': - allow_access: $all - allow_publish: $all - - '*': - allow_access: $all - proxy: npmjs - - logs: - - {type: stdout, format: pretty, level: http} - - max_body_size: '50mb' - -Now you can run sinopia by simply doing: - - $ sinopia - -### Publishing to sinopia - -Now we need to publish the two React Native packages to our local registry. To do this, we configure npm to use the new registry, unpublish any existing packages and then publish the new ones: - - react-native$ npm set registry http://localhost:4873/ - react-native$ npm adduser --registry http://localhost:4873/ - # Check that it worked: - react-native$ npm config list - react-native$ npm unpublish --force - react-native$ npm publish - react-native$ cd react-native-cli/ - react-native-cli$ npm unpublish --force - react-native-cli$ npm publish - -### Running the local CLI - -Now that the packages are installed in sinopia, you can install the new `react-native-cli` package globally and when you use `react-native init`, it will install the new `react-native` package as well: - - $ npm uninstall -g react-native-cli - $ npm install -g react-native-cli - $ react-native init AwesomeApp - -## Testing changes - -Most of the CLI code is covered by jest tests, which you can run with: - - $ npm test - -Project generation is also covered by e2e tests, which you can run with: - - $ ./scripts/e2e-test.sh - -These tests actually create a very similar setup to what is described above (using sinopia) and they also run iOS-specific tests, so you will need to run this on OSX and have [xctool] installed. - -Both of these types of tests also run on Travis both continuously and on pull requests. - -[sinopia]: https://www.npmjs.com/package/sinopia -[xctool]: https://github.com/facebook/xctool - -## Clean up - -To unset the npm registry, do: - - $ npm set registry https://registry.npmjs.org/ - # Check that it worked: - $ npm config list - -## Troubleshooting - -##### Sinopia crashes with "Module version mismatch" - -This usually happens when you install a package using one version of Node and then change to a different version. This can happen when you update Node, or switch to a different version with nvm. Do: - - $ npm uninstall -g sinopia - $ npm install -g sinopia diff --git a/react-native-cli/README.md b/react-native-cli/README.md index 67f13a0d8d75..4273503e873c 100644 --- a/react-native-cli/README.md +++ b/react-native-cli/README.md @@ -1,8 +1,101 @@ -Use react-native-cli to initialize a working starter React Native app using the latest react-native version in npm. This package should be installed globally. +## Running CLI with local modifications -Usage: +React Native is distributed as two npm packages, `react-native-cli` and `react-native`. The first one is a lightweight package that should be installed globally (`npm install -g react-native-cli`), while the second one contains the actual React Native framework code and is installed locally into your project when you run `react-native init`. -``` -% npm install -g react-native-cli -% react-native init MyApplication -``` +Because `react-native init` calls `npm install react-native`, simply linking your local github clone into npm is not enough to test local changes. + +### Introducing Sinopia + +[Sinopia] is an npm registry that runs on your local machine and allows you to publish packages to it. Everything else is proxied from `npmjs.com`. We'll set up sinopia for React Native CLI development. First, install it with: + + $ npm install -g sinopia + +Then, open `~/.config/sinopia/config.yaml` and configure it like this (note the `max_body_size`): + + storage: ./storage + + auth: + htpasswd: + file: ./htpasswd + + uplinks: + npmjs: + url: https://registry.npmjs.org/ + + packages: + 'react-native': + allow_access: $all + allow_publish: $all + + 'react-native-cli': + allow_access: $all + allow_publish: $all + + '*': + allow_access: $all + proxy: npmjs + + logs: + - {type: stdout, format: pretty, level: http} + + max_body_size: '50mb' + +Now you can run sinopia by simply doing: + + $ sinopia + +### Publishing to sinopia + +Now we need to publish the two React Native packages to our local registry. To do this, we configure npm to use the new registry, unpublish any existing packages and then publish the new ones: + + react-native$ npm set registry http://localhost:4873/ + react-native$ npm adduser --registry http://localhost:4873/ + # Check that it worked: + react-native$ npm config list + react-native$ npm unpublish --force + react-native$ npm publish + react-native$ cd react-native-cli/ + react-native-cli$ npm unpublish --force + react-native-cli$ npm publish + +### Running the local CLI + +Now that the packages are installed in sinopia, you can install the new `react-native-cli` package globally and when you use `react-native init`, it will install the new `react-native` package as well: + + $ npm uninstall -g react-native-cli + $ npm install -g react-native-cli + $ react-native init AwesomeApp + +## Testing changes + +Most of the CLI code is covered by jest tests, which you can run with: + + $ npm test + +Project generation is also covered by e2e tests, which you can run with: + + $ ./scripts/e2e-test.sh + +These tests actually create a very similar setup to what is described above (using sinopia) and they also run iOS-specific tests, so you will need to run this on OSX and have [xctool] installed. + +Both of these types of tests also run on Travis both continuously and on pull requests. + +[sinopia]: https://www.npmjs.com/package/sinopia +[xctool]: https://github.com/facebook/xctool + +## Clean up + +To unset the npm registry, do: + + $ npm set registry https://registry.npmjs.org/ + # Check that it worked: + $ npm config list + +## Troubleshooting + +##### Sinopia crashes with "Module version mismatch" + +This usually happens when you install a package using one version of Node and then change to a different version. This can happen when you update Node, or switch to a different version with nvm. Do: + + $ npm uninstall -g sinopia + $ npm install -g sinopia From 2f51763972d2b7e964fb85bdc893aede532cf442 Mon Sep 17 00:00:00 2001 From: Alexey Lang Date: Mon, 5 Oct 2015 10:53:33 -0700 Subject: [PATCH 0166/2702] Log native modules init time and config inject time separately Reviewed By: @jspahrsummers Differential Revision: D2508010 --- React/Base/RCTBatchedBridge.m | 4 +++- React/Base/RCTPerformanceLogger.h | 1 + React/Base/RCTPerformanceLogger.m | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 3559b58c37b1..05a803f6fd42 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -156,8 +156,9 @@ - (void)start // We're not waiting for this complete to leave the dispatch group, since // injectJSONConfiguration and executeSourceCode will schedule operations on the // same queue anyway. + RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { - RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); + RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); if (error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf stopLoadingWithError:error]; @@ -295,6 +296,7 @@ - (void)initModules [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules object:self]; + RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } - (void)setupExecutor diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h index 66d3dd863961..8285f061571a 100644 --- a/React/Base/RCTPerformanceLogger.h +++ b/React/Base/RCTPerformanceLogger.h @@ -15,6 +15,7 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { RCTPLScriptDownload = 0, RCTPLScriptExecution, RCTPLNativeModuleInit, + RCTPLNativeModuleInjectConfig, RCTPLTTI, RCTPLSize }; diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index 87e643355f0f..443976a9ab10 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -33,6 +33,8 @@ void RCTPerformanceLoggerEnd(RCTPLTag tag) @(RCTPLData[RCTPLScriptExecution][1]), @(RCTPLData[RCTPLNativeModuleInit][0]), @(RCTPLData[RCTPLNativeModuleInit][1]), + @(RCTPLData[RCTPLNativeModuleInjectConfig][0]), + @(RCTPLData[RCTPLNativeModuleInjectConfig][1]), @(RCTPLData[RCTPLTTI][0]), @(RCTPLData[RCTPLTTI][1]), ]; @@ -74,6 +76,7 @@ - (void)sendTimespans @"ScriptDownload", @"ScriptExecution", @"NativeModuleInit", + @"NativeModuleInjectConfig", @"TTI", ], ]]; From a3efb034c615fe2da753af980f05195cd66eac30 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 5 Oct 2015 11:12:00 -0700 Subject: [PATCH 0167/2702] Fix wrong source map test Reviewed By: @javache --- packager/react-packager/src/Server/__tests__/Server-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index 6da2b195f29a..5220c0c15f8e 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -96,7 +96,7 @@ describe('processRequest', () => { requestHandler, 'mybundle.map?runModule=true' ).then(response => - expect(response).toEqual('"this is the source map"') + expect(response).toEqual('this is the source map') ); }); From 4e0e79c73ad8ed969d50bd83cee50186c3dfb6a3 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 5 Oct 2015 11:16:52 -0700 Subject: [PATCH 0168/2702] [cli] Add a big warning to stop people from modifying the global cli --- react-native-cli/index.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/react-native-cli/index.js b/react-native-cli/index.js index 66b215830031..d9d217acc875 100755 --- a/react-native-cli/index.js +++ b/react-native-cli/index.js @@ -1,9 +1,38 @@ #!/usr/bin/env node /** - * Copyright 2004-present Facebook. All Rights Reserved. + * 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. */ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// /!\ DO NOT MODIFY THIS FILE /!\ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// react-native-cli is installed globally on people's computers. This means +// that it is extremely difficult to have them upgrade the version and +// because there's only one global version installed, it is very prone to +// breaking changes. +// +// The only job of react-native-cli is to init the repository and then +// forward all the commands to the local version of react-native. +// +// If you need to add a new command, please add it to local-cli/. +// +// The only reason to modify this file is to add more warnings and +// troubleshooting information for the `react-native init` command. +// +// Do not make breaking changes! We absolutely don't want to have to +// tell people to update their global version of react-native-cli. +// +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// /!\ DO NOT MODIFY THIS FILE /!\ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 'use strict'; var fs = require('fs'); From ececc4781953cb8810f985793f1fb97e268af58d Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 5 Oct 2015 13:42:25 -0700 Subject: [PATCH 0169/2702] Fix ListView bug where onEndReached wouldn't trigger initially MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public Sometimes we want to load a very small number of rows initially and want the onEndReached callback to be called immediately to trigger more data to be loaded without waiting for the user to scroll at all. This diff makes that happen by also checking on mount instead of only when scrolling. Reviewed By: @vjeux Differential Revision: D2507184 fb-gh-sync-id: ea8e47667d00387a935a426dd45afe978fd6d8cd --- .../CustomComponents/ListView/ListView.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 4554eb681257..834de83a0a9f 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -452,10 +452,23 @@ var ListView = React.createClass({ this._updateVisibleRows(childFrames); }, + _maybeCallOnEndReached: function(event) { + if (this.props.onEndReached && + this.scrollProperties.contentLength !== this._sentEndForContentLength && + this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold && + this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { + this._sentEndForContentLength = this.scrollProperties.contentLength; + this.props.onEndReached(event); + return true; + } + return false; + }, + _renderMoreRowsIfNeeded: function() { if (this.scrollProperties.contentLength === null || this.scrollProperties.visibleLength === null || this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { + this._maybeCallOnEndReached(); return; } @@ -570,14 +583,7 @@ var ListView = React.createClass({ isVertical ? 'y' : 'x' ]; this._updateVisibleRows(e.nativeEvent.updatedChildFrames); - var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold; - if (nearEnd && - this.props.onEndReached && - this.scrollProperties.contentLength !== this._sentEndForContentLength && - this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { - this._sentEndForContentLength = this.scrollProperties.contentLength; - this.props.onEndReached(e); - } else { + if (!this._maybeCallOnEndReached(e)) { this._renderMoreRowsIfNeeded(); } From 95fa2bd3d6139194cfa74d7b9784ac810676e8e5 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 5 Oct 2015 14:11:51 -0700 Subject: [PATCH 0170/2702] add allTime vs. perBatch UIManager stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: @​public UIManagerStatTracker now provides allTime and perBatch stats so it's easy to see how bit each batch is. Reviewed By: @vjeux Differential Revision: D2506218 fb-gh-sync-id: 635556185245d2bd6cb149497ea60b503b2523ce --- Libraries/ReactNative/UIManagerStatTracker.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Libraries/ReactNative/UIManagerStatTracker.js b/Libraries/ReactNative/UIManagerStatTracker.js index ef9c64b2bbb5..ed17226e6369 100644 --- a/Libraries/ReactNative/UIManagerStatTracker.js +++ b/Libraries/ReactNative/UIManagerStatTracker.js @@ -13,6 +13,8 @@ var RCTUIManager = require('NativeModules').UIManager; +var performanceNow = require('performanceNow'); + var installed = false; var UIManagerStatTracker = { install: function() { @@ -21,15 +23,24 @@ var UIManagerStatTracker = { } installed = true; var statLogHandle; - var stats = {}; + var startTime = 0; + var allTimeStats = {}; + var perFrameStats = {}; function printStats() { - console.log({UIManagerStatTracker: stats}); + console.log({UIManagerStatTracker: { + allTime: allTimeStats, + lastFrame: perFrameStats, + elapsedMilliseconds: performanceNow() - startTime, + }}); statLogHandle = null; + perFrameStats = {}; } function incStat(key: string, increment: number) { - stats[key] = (stats[key] || 0) + increment; + allTimeStats[key] = (allTimeStats[key] || 0) + increment; + perFrameStats[key] = (perFrameStats[key] || 0) + increment; if (!statLogHandle) { - statLogHandle = setImmediate(printStats); + startTime = performanceNow(); + statLogHandle = window.requestAnimationFrame(printStats); } } var createViewOrig = RCTUIManager.createView; From 467a56beef9476559d2afafff8a83b8c4658a741 Mon Sep 17 00:00:00 2001 From: Martin Bigio Date: Mon, 5 Oct 2015 15:24:13 -0700 Subject: [PATCH 0171/2702] Fix bad require MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: @vjeux, @​swarr Differential Revision: D2509919 fb-gh-sync-id: 8c3555d080a2b49c3a1effbe5c7e1d31a9a04767 --- private-cli/src/dependencies/dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private-cli/src/dependencies/dependencies.js b/private-cli/src/dependencies/dependencies.js index 0a0aae4753df..09410d0e8b5c 100644 --- a/private-cli/src/dependencies/dependencies.js +++ b/private-cli/src/dependencies/dependencies.js @@ -12,7 +12,7 @@ const fs = require('fs'); const log = require('../util/log').out('dependencies'); const parseCommandLine = require('../../../packager/parseCommandLine'); const path = require('path'); -const Promise = require('Promise'); +const Promise = require('promise'); const ReactPackager = require('../../../packager/react-packager'); /** From 645a49dd0bc8cab3f1e08b85aa205a6f32e4b668 Mon Sep 17 00:00:00 2001 From: Madelaine Boyd Date: Mon, 5 Oct 2015 17:20:02 -0700 Subject: [PATCH 0172/2702] Revert g90f63f5c6fbbadd3ce6486e65d6fe6f967f56db6: [RN] add allTime vs. perBatch UIManager stats fb-gh-sync-id: cef98420bcb8e708196bd5e162fa5dc4003b4fa2 --- Libraries/ReactNative/UIManagerStatTracker.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/Libraries/ReactNative/UIManagerStatTracker.js b/Libraries/ReactNative/UIManagerStatTracker.js index ed17226e6369..ef9c64b2bbb5 100644 --- a/Libraries/ReactNative/UIManagerStatTracker.js +++ b/Libraries/ReactNative/UIManagerStatTracker.js @@ -13,8 +13,6 @@ var RCTUIManager = require('NativeModules').UIManager; -var performanceNow = require('performanceNow'); - var installed = false; var UIManagerStatTracker = { install: function() { @@ -23,24 +21,15 @@ var UIManagerStatTracker = { } installed = true; var statLogHandle; - var startTime = 0; - var allTimeStats = {}; - var perFrameStats = {}; + var stats = {}; function printStats() { - console.log({UIManagerStatTracker: { - allTime: allTimeStats, - lastFrame: perFrameStats, - elapsedMilliseconds: performanceNow() - startTime, - }}); + console.log({UIManagerStatTracker: stats}); statLogHandle = null; - perFrameStats = {}; } function incStat(key: string, increment: number) { - allTimeStats[key] = (allTimeStats[key] || 0) + increment; - perFrameStats[key] = (perFrameStats[key] || 0) + increment; + stats[key] = (stats[key] || 0) + increment; if (!statLogHandle) { - startTime = performanceNow(); - statLogHandle = window.requestAnimationFrame(printStats); + statLogHandle = setImmediate(printStats); } } var createViewOrig = RCTUIManager.createView; From bff998f70b1764370d71b7d0966d148276acd390 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 5 Oct 2015 19:19:16 -0700 Subject: [PATCH 0173/2702] Refactor Attribute Processing (Step 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Concolidate the creation of the "update payload" into ReactNativeAttributePayload which now has a create and a diff version. The create version can be used by both mountComponent and setNativeProps. This means that diffRawProperties moves into ReactNativeAttributePayload. Instead of storing previousFlattenedStyle as memoized state in the component tree, I recalculate it every time. This allows better use of the generational GC. However, it is still probably a fairly expensive technique so I will follow it up with a diff that walks both nested array trees to do the diffing in a follow up. This is the first diff of several steps. @​public Reviewed By: @vjeux Differential Revision: D2440644 fb-gh-sync-id: 1d0da4f6e2bf716f33e119df947c044abb918471 --- Libraries/ReactIOS/NativeMethodsMixin.js | 44 +--------- .../ReactNativeAttributePayload.js} | 78 +++++++++++++++- .../ReactNative/ReactNativeBaseComponent.js | 71 +-------------- .../ReactNativeAttributePayload-benchmark.js | 8 +- .../ReactNativeAttributePayload-test.js} | 88 +++++++------------ .../createReactNativeComponentClass.js | 1 - 6 files changed, 118 insertions(+), 172 deletions(-) rename Libraries/{ReactIOS/diffRawProperties.js => ReactNative/ReactNativeAttributePayload.js} (63%) rename Libraries/{ReactIOS/__tests__/diffRawProperties-test.js => ReactNative/__tests__/ReactNativeAttributePayload-test.js} (67%) diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index afc79f777a5e..909327f98abb 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -13,6 +13,7 @@ var NativeModules = require('NativeModules'); var RCTUIManager = NativeModules.UIManager; +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var TextInputState = require('TextInputState'); var findNodeHandle = require('findNodeHandle'); @@ -65,52 +66,15 @@ var NativeMethodsMixin = { * next render, they will remain active. */ setNativeProps: function(nativeProps: Object) { - // nativeProps contains a style attribute that's going to be flattened - // and all the attributes expanded in place. In order to make this - // process do as few allocations and copies as possible, we return - // one if the other is empty. Only if both have values then we create - // a new object and merge. - var hasOnlyStyle = true; - for (var key in nativeProps) { - if (key !== 'style') { - hasOnlyStyle = false; - break; - } - } - - var validAttributes = this.viewConfig.validAttributes; - var hasProcessedProps = false; - var processedProps = {}; - for (var key in nativeProps) { - var process = validAttributes[key] && validAttributes[key].process; - if (process) { - hasProcessedProps = true; - processedProps[key] = process(nativeProps[key]); - } - } - - var style = precomputeStyle( - flattenStyle(processedProps.style || nativeProps.style), + var updatePayload = ReactNativeAttributePayload.create( + nativeProps, this.viewConfig.validAttributes ); - var props = null; - if (hasOnlyStyle) { - props = style; - } else { - props = nativeProps; - if (hasProcessedProps) { - props = mergeFast(props, processedProps); - } - if (style) { - props = mergeFast(props, style); - } - } - RCTUIManager.updateView( findNodeHandle(this), this.viewConfig.uiViewClassName, - props + updatePayload ); }, diff --git a/Libraries/ReactIOS/diffRawProperties.js b/Libraries/ReactNative/ReactNativeAttributePayload.js similarity index 63% rename from Libraries/ReactIOS/diffRawProperties.js rename to Libraries/ReactNative/ReactNativeAttributePayload.js index 96a2ae795e8c..98595208496b 100644 --- a/Libraries/ReactIOS/diffRawProperties.js +++ b/Libraries/ReactNative/ReactNativeAttributePayload.js @@ -6,12 +6,18 @@ * 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. * - * @providesModule diffRawProperties + * @providesModule ReactNativeAttributePayload * @flow */ 'use strict'; +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); + var deepDiffer = require('deepDiffer'); +var styleDiffer = require('styleDiffer'); +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +var flattenStyle = require('flattenStyle'); +var precomputeStyle = require('precomputeStyle'); /** * diffRawProperties takes two sets of props and a set of valid attributes @@ -115,4 +121,72 @@ function diffRawProperties( return updatePayload; } -module.exports = diffRawProperties; +var ReactNativeAttributePayload = { + + create: function( + props : Object, + validAttributes : Object + ) : ?Object { + return ReactNativeAttributePayload.diff({}, props, validAttributes); + }, + + diff: function( + prevProps : Object, + nextProps : Object, + validAttributes : Object + ) : ?Object { + + if (__DEV__) { + for (var key in nextProps) { + if (nextProps.hasOwnProperty(key) && + nextProps[key] && + validAttributes[key]) { + deepFreezeAndThrowOnMutationInDev(nextProps[key]); + } + } + } + + var updatePayload = diffRawProperties( + null, // updatePayload + prevProps, + nextProps, + validAttributes + ); + + for (var key in updatePayload) { + var process = validAttributes[key] && validAttributes[key].process; + if (process) { + updatePayload[key] = process(updatePayload[key]); + } + } + + // The style property is a deeply nested element which includes numbers + // to represent static objects. Most of the time, it doesn't change across + // renders, so it's faster to spend the time checking if it is different + // before actually doing the expensive flattening operation in order to + // compute the diff. + if (styleDiffer(nextProps.style, prevProps.style)) { + // TODO: Use a cached copy of previousFlattenedStyle, or walk both + // props in parallel. + var previousFlattenedStyle = precomputeStyle( + flattenStyle(prevProps.style), + validAttributes + ); + var nextFlattenedStyle = precomputeStyle( + flattenStyle(nextProps.style), + validAttributes + ); + updatePayload = diffRawProperties( + updatePayload, + previousFlattenedStyle, + nextFlattenedStyle, + ReactNativeStyleAttributes + ); + } + + return updatePayload; + } + +}; + +module.exports = ReactNativeAttributePayload; diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 02e327a994c9..21e2c608e36b 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -12,17 +12,13 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); var RCTUIManager = require('NativeModules').UIManager; -var styleDiffer = require('styleDiffer'); -var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -var diffRawProperties = require('diffRawProperties'); -var flattenStyle = require('flattenStyle'); -var precomputeStyle = require('precomputeStyle'); var warning = require('warning'); var registrationNames = ReactNativeEventEmitter.registrationNames; @@ -131,63 +127,6 @@ ReactNativeBaseComponent.Mixin = { } }, - - /** - * Beware, this function has side effect to store this.previousFlattenedStyle! - * - * @param {!object} prevProps Previous properties - * @param {!object} nextProps Next properties - * @param {!object} validAttributes Set of valid attributes and how they - * should be diffed - */ - computeUpdatedProperties: function(prevProps, nextProps, validAttributes) { - if (__DEV__) { - for (var key in nextProps) { - if (nextProps.hasOwnProperty(key) && - nextProps[key] && - validAttributes[key]) { - deepFreezeAndThrowOnMutationInDev(nextProps[key]); - } - } - } - - var updatePayload = diffRawProperties( - null, // updatePayload - prevProps, - nextProps, - validAttributes - ); - - for (var key in updatePayload) { - var process = validAttributes[key] && validAttributes[key].process; - if (process) { - updatePayload[key] = process(updatePayload[key]); - } - } - - // The style property is a deeply nested element which includes numbers - // to represent static objects. Most of the time, it doesn't change across - // renders, so it's faster to spend the time checking if it is different - // before actually doing the expensive flattening operation in order to - // compute the diff. - if (styleDiffer(nextProps.style, prevProps.style)) { - var nextFlattenedStyle = precomputeStyle( - flattenStyle(nextProps.style), - this.viewConfig.validAttributes - ); - updatePayload = diffRawProperties( - updatePayload, - this.previousFlattenedStyle, - nextFlattenedStyle, - ReactNativeStyleAttributes - ); - this.previousFlattenedStyle = nextFlattenedStyle; - } - - return updatePayload; - }, - - /** * Updates the component's currently mounted representation. * @@ -200,7 +139,7 @@ ReactNativeBaseComponent.Mixin = { var prevElement = this._currentElement; this._currentElement = nextElement; - var updatePayload = this.computeUpdatedProperties( + var updatePayload = ReactNativeAttributePayload.diff( prevElement.props, nextElement.props, this.viewConfig.validAttributes @@ -262,10 +201,8 @@ ReactNativeBaseComponent.Mixin = { var tag = ReactNativeTagHandles.allocateTag(); - this.previousFlattenedStyle = {}; - var updatePayload = this.computeUpdatedProperties( - {}, // previous props - this._currentElement.props, // next props + var updatePayload = ReactNativeAttributePayload.create( + this._currentElement.props, this.viewConfig.validAttributes ); diff --git a/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js b/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js index afc2b81f38ad..13dad92ae949 100644 --- a/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js +++ b/Libraries/ReactNative/__benchmarks__/ReactNativeAttributePayload-benchmark.js @@ -242,13 +242,7 @@ var variants = { var validAttributes = require('ReactNativeViewAttributes').UIView; -var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); -ReactNativeBaseComponent.prototype.diff = - ReactNativeBaseComponent.prototype.computeUpdatedProperties; -var Differ = new ReactNativeBaseComponent({ - validAttributes: validAttributes, - uiViewClassName: 'Differ' -}); +var Differ = require('ReactNativeAttributePayload'); // Runner diff --git a/Libraries/ReactIOS/__tests__/diffRawProperties-test.js b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js similarity index 67% rename from Libraries/ReactIOS/__tests__/diffRawProperties-test.js rename to Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js index 3d1fa3478a27..06db449a7a78 100644 --- a/Libraries/ReactIOS/__tests__/diffRawProperties-test.js +++ b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -3,15 +3,19 @@ */ 'use strict'; -jest.dontMock('diffRawProperties'); +jest.dontMock('ReactNativeAttributePayload'); jest.dontMock('deepDiffer'); -var diffRawProperties = require('diffRawProperties'); +jest.dontMock('styleDiffer'); +jest.dontMock('precomputeStyle'); +jest.dontMock('flattenStyle'); +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); -describe('diffRawProperties', function() { +var diff = ReactNativeAttributePayload.diff; + +describe('ReactNativeAttributePayload', function() { it('should work with simple example', () => { - expect(diffRawProperties( - null, + expect(diff( {a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true} @@ -19,8 +23,7 @@ describe('diffRawProperties', function() { }); it('should skip fields that are equal', () => { - expect(diffRawProperties( - null, + expect(diff( {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0}, {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0}, {a: true, b: true, c: true, d: true, e: true, f: true} @@ -28,8 +31,7 @@ describe('diffRawProperties', function() { }); it('should remove fields', () => { - expect(diffRawProperties( - null, + expect(diff( {a: 1}, {}, {a: true} @@ -37,32 +39,17 @@ describe('diffRawProperties', function() { }); it('should ignore invalid fields', () => { - expect(diffRawProperties( - null, + expect(diff( {a: 1}, {b: 2}, {} )).toEqual(null); }); - it('should override the updatePayload argument', () => { - var updatePayload = {a: 1}; - var result = diffRawProperties( - updatePayload, - {}, - {b: 2}, - {b: true} - ); - - expect(result).toBe(updatePayload); - expect(result).toEqual({a: 1, b: 2}); - }); - it('should use the diff attribute', () => { var diffA = jest.genMockFunction().mockImpl((a, b) => true); var diffB = jest.genMockFunction().mockImpl((a, b) => false); - expect(diffRawProperties( - null, + expect(diff( {a: [1], b: [3]}, {a: [2], b: [4]}, {a: {diff: diffA}, b: {diff: diffB}} @@ -74,8 +61,7 @@ describe('diffRawProperties', function() { it('should not use the diff attribute on addition/removal', () => { var diffA = jest.genMockFunction(); var diffB = jest.genMockFunction(); - expect(diffRawProperties( - null, + expect(diff( {a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}} @@ -85,8 +71,7 @@ describe('diffRawProperties', function() { }); it('should do deep diffs of Objects by default', () => { - expect(diffRawProperties( - null, + expect(diff( {a: [1], b: {k: [3,4]}, c: {k: [4,4]} }, {a: [2], b: {k: [3,4]}, c: {k: [4,5]} }, {a: true, b: true, c: true} @@ -94,41 +79,35 @@ describe('diffRawProperties', function() { }); it('should work with undefined styles', () => { - expect(diffRawProperties( - null, - {a: 1, c: 3}, - undefined, - {a: true, b: true} - )).toEqual({a: null}); - expect(diffRawProperties( - null, - undefined, - {a: 1, c: 3}, - {a: true, b: true} - )).toEqual({a: 1}); - expect(diffRawProperties( - null, - undefined, - undefined, - {a: true, b: true} + expect(diff( + { style: { a: '#ffffff', opacity: 1 } }, + { style: undefined }, + { } + )).toEqual({ opacity: null }); + expect(diff( + { style: undefined }, + { style: { a: '#ffffff', opacity: 1 } }, + { } + )).toEqual({ opacity: 1 }); + expect(diff( + { style: undefined }, + { style: undefined }, + { } )).toEqual(null); }); it('should work with empty styles', () => { - expect(diffRawProperties( - null, + expect(diff( {a: 1, c: 3}, {}, {a: true, b: true} )).toEqual({a: null}); - expect(diffRawProperties( - null, + expect(diff( {}, {a: 1, c: 3}, {a: true, b: true} )).toEqual({a: 1}); - expect(diffRawProperties( - null, + expect(diff( {}, {}, {a: true, b: true} @@ -139,8 +118,7 @@ describe('diffRawProperties', function() { it('should convert functions to booleans', () => { // Note that if the property changes from one function to another, we don't // need to send an update. - expect(diffRawProperties( - null, + expect(diff( {a: function() { return 1; }, b: function() { return 2; }, c: 3}, {b: function() { return 9; }, c: function() { return 3; }, }, {a: true, b: true, c: true} diff --git a/Libraries/ReactNative/createReactNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js index 18937f9cf367..61a69a560605 100644 --- a/Libraries/ReactNative/createReactNativeComponentClass.js +++ b/Libraries/ReactNative/createReactNativeComponentClass.js @@ -33,7 +33,6 @@ var createReactNativeComponentClass = function( this._rootNodeID = null; this._renderedChildren = null; - this.previousFlattenedStyle = null; }; Constructor.displayName = viewConfig.uiViewClassName; Constructor.viewConfig = viewConfig; From 0481a63e110a0bc3e49840d6aba8fc7ac26910b2 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 5 Oct 2015 20:21:48 -0700 Subject: [PATCH 0174/2702] Refactor Attribute Processing (Step 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Move the ViewAttributes and StyleAttributes configuration into the Components library since they're coupled and change with the native component configuration. This also decouples StyleAttributes from the reconciler by adding it to the ReactViewAttributes. To do that, I refactored the property diffing to allow for recursive configurations. Now an attribute configuration can be a nested object, a custom configuration (diff/process) or true. The requireNativeComponent path incorrectly gets its attributes set up on the root validAttributes instead of the nested style object. So I also have to add the nested form. Effectively these currently allow these attributes on props or nested. @​public Reviewed By: @vjeux Differential Revision: D2456842 fb-gh-sync-id: cd5405bd8316c2fcb016d06c61244ce7719c26c0 --- .../View}/ReactNativeStyleAttributes.js | 0 .../View}/ReactNativeViewAttributes.js | 9 +- Libraries/ReactIOS/requireNativeComponent.js | 9 + .../ReactNativeAttributePayload.js | 359 +++++++++++------- .../ReactNative/ReactNativeBaseComponent.js | 9 + .../ReactNativeAttributePayload-test.js | 22 +- 6 files changed, 266 insertions(+), 142 deletions(-) rename Libraries/{ReactNative => Components/View}/ReactNativeStyleAttributes.js (100%) rename Libraries/{ReactNative => Components/View}/ReactNativeViewAttributes.js (87%) diff --git a/Libraries/ReactNative/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js similarity index 100% rename from Libraries/ReactNative/ReactNativeStyleAttributes.js rename to Libraries/Components/View/ReactNativeStyleAttributes.js diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/Components/View/ReactNativeViewAttributes.js similarity index 87% rename from Libraries/ReactNative/ReactNativeViewAttributes.js rename to Libraries/Components/View/ReactNativeViewAttributes.js index 0447c78ab562..e5e83a40de95 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/Components/View/ReactNativeViewAttributes.js @@ -11,7 +11,7 @@ */ 'use strict'; -var merge = require('merge'); +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeViewAttributes = {}; @@ -31,10 +31,11 @@ ReactNativeViewAttributes.UIView = { onMagicTap: true, collapsable: true, needsOffscreenAlphaCompositing: true, + style: ReactNativeStyleAttributes, }; -ReactNativeViewAttributes.RCTView = merge( - ReactNativeViewAttributes.UIView, { +ReactNativeViewAttributes.RCTView = { + ...ReactNativeViewAttributes.UIView, // This is a special performance property exposed by RCTView and useful for // scrolling content when there are many subviews, most of which are offscreen. @@ -42,6 +43,6 @@ ReactNativeViewAttributes.RCTView = merge( // many subviews that extend outside its bound. The subviews must also have // overflow: hidden, as should the containing view (or one of its superviews). removeClippedSubviews: true, -}); +}; module.exports = ReactNativeViewAttributes; diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index 0bbb93a4d7ad..11defc0ada13 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -12,6 +12,7 @@ 'use strict'; var RCTUIManager = require('NativeModules').UIManager; +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var UnimplementedView = require('UnimplementedView'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); @@ -75,6 +76,14 @@ function requireNativeComponent( viewConfig.validAttributes[key] = useAttribute ? attribute : true; } + + // Unfortunately, the current set up puts the style properties on the top + // level props object. We also need to add the nested form for API + // compatibility. This allows these props on both the top level and the + // nested style level. TODO: Move these to nested declarations on the + // native side. + viewConfig.validAttributes.style = ReactNativeStyleAttributes; + if (__DEV__) { componentInterface && verifyPropTypes( componentInterface, diff --git a/Libraries/ReactNative/ReactNativeAttributePayload.js b/Libraries/ReactNative/ReactNativeAttributePayload.js index 98595208496b..a0d328f28ead 100644 --- a/Libraries/ReactNative/ReactNativeAttributePayload.js +++ b/Libraries/ReactNative/ReactNativeAttributePayload.js @@ -11,180 +11,277 @@ */ 'use strict'; -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); - var deepDiffer = require('deepDiffer'); var styleDiffer = require('styleDiffer'); -var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var flattenStyle = require('flattenStyle'); var precomputeStyle = require('precomputeStyle'); +type AttributeDiffer = (prevProp : mixed, nextProp : mixed) => boolean; +type AttributePreprocessor = (nextProp: mixed) => mixed; + +type CustomAttributeConfiguration = + { diff : AttributeDiffer, process : AttributePreprocessor } | + { diff : AttributeDiffer } | + { process : AttributePreprocessor }; + +type AttributeConfiguration = + { [key : string]: ( + CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/ + ) }; + +function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean { + if (typeof nextProp !== 'object' || nextProp === null) { + // Scalars have already been checked for equality + return true; + } else { + // For objects and arrays, the default diffing algorithm is a deep compare + return deepDiffer(prevProp, nextProp); + } +} + +function diffNestedProperty( + updatePayload :? Object, + prevProp, // inferred + nextProp, // inferred + validAttributes : AttributeConfiguration +) : ?Object { + // The style property is a deeply nested element which includes numbers + // to represent static objects. Most of the time, it doesn't change across + // renders, so it's faster to spend the time checking if it is different + // before actually doing the expensive flattening operation in order to + // compute the diff. + if (!styleDiffer(prevProp, nextProp)) { + return updatePayload; + } + + // TODO: Walk both props in parallel instead of flattening. + // TODO: Move precomputeStyle to .process for each attribute. + + var previousFlattenedStyle = precomputeStyle( + flattenStyle(prevProp), + validAttributes + ); + + var nextFlattenedStyle = precomputeStyle( + flattenStyle(nextProp), + validAttributes + ); + + if (!previousFlattenedStyle || !nextFlattenedStyle) { + if (nextFlattenedStyle) { + return addProperties( + updatePayload, + nextFlattenedStyle, + validAttributes + ); + } + if (previousFlattenedStyle) { + return clearProperties( + updatePayload, + previousFlattenedStyle, + validAttributes + ); + } + return updatePayload; + } + + // recurse + return diffProperties( + updatePayload, + previousFlattenedStyle, + nextFlattenedStyle, + validAttributes + ); +} + /** - * diffRawProperties takes two sets of props and a set of valid attributes - * and write to updatePayload the values that changed or were deleted - * - * @param {?object} updatePayload Overriden with the props that changed. - * @param {!object} prevProps Previous properties to diff against current - * properties. These properties are as supplied to component construction. - * @param {!object} prevProps Next "current" properties to diff against - * previous. These properties are as supplied to component construction. - * @return {?object} + * addNestedProperty takes a single set of props and valid attribute + * attribute configurations. It processes each prop and adds it to the + * updatePayload. + */ +/* +function addNestedProperty( + updatePayload :? Object, + nextProp : Object, + validAttributes : AttributeConfiguration +) { + // TODO: Fast path + return diffNestedProperty(updatePayload, {}, nextProp, validAttributes); +} +*/ + +/** + * clearNestedProperty takes a single set of props and valid attributes. It + * adds a null sentinel to the updatePayload, for each prop key. */ -function diffRawProperties( - updatePayload: ?Object, - prevProps: ?Object, - nextProps: ?Object, - validAttributes: Object +function clearNestedProperty( + updatePayload :? Object, + prevProp : Object, + validAttributes : AttributeConfiguration +) : ?Object { + // TODO: Fast path + return diffNestedProperty(updatePayload, prevProp, {}, validAttributes); +} + +/** + * diffProperties takes two sets of props and a set of valid attributes + * and write to updatePayload the values that changed or were deleted. + * If no updatePayload is provided, a new one is created and returned if + * anything changed. + */ +function diffProperties( + updatePayload : ?Object, + prevProps : Object, + nextProps : Object, + validAttributes : AttributeConfiguration ): ?Object { - var validAttributeConfig; + var attributeConfig : ?AttributeConfiguration; var nextProp; var prevProp; - var isScalar; - var shouldUpdate; - var differ; - - if (nextProps) { - for (var propKey in nextProps) { - validAttributeConfig = validAttributes[propKey]; - if (!validAttributeConfig) { - continue; // not a valid native prop - } - prevProp = prevProps && prevProps[propKey]; - nextProp = nextProps[propKey]; - // functions are converted to booleans as markers that the associated - // events should be sent from native. + for (var propKey in nextProps) { + attributeConfig = validAttributes[propKey]; + if (!attributeConfig) { + continue; // not a valid native prop + } + if (updatePayload && updatePayload[propKey] !== undefined) { + // If we're in a nested attribute set, we may have set this property + // already. If so, bail out. The previous update is what counts. + continue; + } + prevProp = prevProps[propKey]; + nextProp = nextProps[propKey]; + + // functions are converted to booleans as markers that the associated + // events should be sent from native. + if (typeof nextProp === 'function') { + nextProp = true; + // If nextProp is not a function, then don't bother changing prevProp + // since nextProp will win and go into the updatePayload regardless. if (typeof prevProp === 'function') { prevProp = true; } - if (typeof nextProp === 'function') { - nextProp = true; - } + } + + if (prevProp === nextProp) { + continue; // nothing changed + } - if (prevProp !== nextProp) { - // Scalars and new props are always updated. Objects use deepDiffer by - // default, but can be optimized with custom differs. - differ = validAttributeConfig.diff || deepDiffer; - isScalar = typeof nextProp !== 'object' || nextProp === null; - shouldUpdate = isScalar || !prevProp || differ(prevProp, nextProp); - if (shouldUpdate) { - updatePayload = updatePayload || {}; - updatePayload[propKey] = nextProp; - } + // Pattern match on: attributeConfig + if (typeof attributeConfig !== 'object') { + // case: !Object is the default case + if (defaultDiffer(prevProp, nextProp)) { + // a normal leaf has changed + (updatePayload || (updatePayload = {}))[propKey] = nextProp; + } + } else if (typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration + var shouldUpdate = prevProp === undefined || ( + typeof attributeConfig.diff === 'function' ? + attributeConfig.diff(prevProp, nextProp) : + defaultDiffer(prevProp, nextProp) + ); + if (shouldUpdate) { + var nextValue = typeof attributeConfig.process === 'function' ? + attributeConfig.process(nextProp) : + nextProp; + (updatePayload || (updatePayload = {}))[propKey] = nextValue; } + } else { + // default: fallthrough case when nested properties are defined + updatePayload = diffNestedProperty( + updatePayload, + prevProp, + nextProp, + attributeConfig + ); } } // Also iterate through all the previous props to catch any that have been // removed and make sure native gets the signal so it can reset them to the // default. - if (prevProps) { - for (var propKey in prevProps) { - validAttributeConfig = validAttributes[propKey]; - if (!validAttributeConfig) { - continue; // not a valid native prop - } - if (updatePayload && updatePayload[propKey] !== undefined) { - continue; // Prop already specified - } - prevProp = prevProps[propKey]; - nextProp = nextProps && nextProps[propKey]; - - // functions are converted to booleans as markers that the associated - // events should be sent from native. - if (typeof prevProp === 'function') { - prevProp = true; - } - if (typeof nextProp === 'function') { - nextProp = true; - } - - if (prevProp !== nextProp) { - if (nextProp === undefined) { - nextProp = null; // null is a sentinel we explicitly send to native - } - // Scalars and new props are always updated. Objects use deepDiffer by - // default, but can be optimized with custom differs. - differ = validAttributeConfig.diff || deepDiffer; - isScalar = typeof nextProp !== 'object' || nextProp === null; - shouldUpdate = - isScalar && - prevProp !== nextProp || - differ(prevProp, nextProp); - if (shouldUpdate) { - updatePayload = updatePayload || {}; - updatePayload[propKey] = nextProp; - } - } + for (var propKey in prevProps) { + if (nextProps[propKey] !== undefined) { + continue; // we've already covered this key in the previous pass + } + attributeConfig = validAttributes[propKey]; + if (!attributeConfig) { + continue; // not a valid native prop + } + prevProp = prevProps[propKey]; + if (prevProp === undefined) { + continue; // was already empty anyway + } + // Pattern match on: attributeConfig + if (typeof attributeConfig !== 'object' || + typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration | !Object + // Flag the leaf property for removal by sending a sentinel. + (updatePayload || (updatePayload = {}))[propKey] = null; + } else { + // default: + // This is a nested attribute configuration where all the properties + // were removed so we need to go through and clear out all of them. + updatePayload = clearNestedProperty( + updatePayload, + prevProp, + attributeConfig + ); } } return updatePayload; } +/** + * addProperties adds all the valid props to the payload after being processed. + */ +function addProperties( + updatePayload : ?Object, + props : Object, + validAttributes : AttributeConfiguration +) : ?Object { + return diffProperties(updatePayload, {}, props, validAttributes); +} + +/** + * clearProperties clears all the previous props by adding a null sentinel + * to the payload for each valid key. + */ +function clearProperties( + updatePayload : ?Object, + prevProps : Object, + validAttributes : AttributeConfiguration +) :? Object { + return diffProperties(updatePayload, prevProps, {}, validAttributes); +} + var ReactNativeAttributePayload = { create: function( props : Object, - validAttributes : Object + validAttributes : AttributeConfiguration ) : ?Object { - return ReactNativeAttributePayload.diff({}, props, validAttributes); + return addProperties( + null, // updatePayload + props, + validAttributes + ); }, diff: function( prevProps : Object, nextProps : Object, - validAttributes : Object + validAttributes : AttributeConfiguration ) : ?Object { - - if (__DEV__) { - for (var key in nextProps) { - if (nextProps.hasOwnProperty(key) && - nextProps[key] && - validAttributes[key]) { - deepFreezeAndThrowOnMutationInDev(nextProps[key]); - } - } - } - - var updatePayload = diffRawProperties( + return diffProperties( null, // updatePayload prevProps, nextProps, validAttributes ); - - for (var key in updatePayload) { - var process = validAttributes[key] && validAttributes[key].process; - if (process) { - updatePayload[key] = process(updatePayload[key]); - } - } - - // The style property is a deeply nested element which includes numbers - // to represent static objects. Most of the time, it doesn't change across - // renders, so it's faster to spend the time checking if it is different - // before actually doing the expensive flattening operation in order to - // compute the diff. - if (styleDiffer(nextProps.style, prevProps.style)) { - // TODO: Use a cached copy of previousFlattenedStyle, or walk both - // props in parallel. - var previousFlattenedStyle = precomputeStyle( - flattenStyle(prevProps.style), - validAttributes - ); - var nextFlattenedStyle = precomputeStyle( - flattenStyle(nextProps.style), - validAttributes - ); - updatePayload = diffRawProperties( - updatePayload, - previousFlattenedStyle, - nextFlattenedStyle, - ReactNativeStyleAttributes - ); - } - - return updatePayload; } }; diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 21e2c608e36b..cfd3df3e832d 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -19,6 +19,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); var RCTUIManager = require('NativeModules').UIManager; +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var warning = require('warning'); var registrationNames = ReactNativeEventEmitter.registrationNames; @@ -139,6 +140,10 @@ ReactNativeBaseComponent.Mixin = { var prevElement = this._currentElement; this._currentElement = nextElement; + if (__DEV__) { + deepFreezeAndThrowOnMutationInDev(this._currentElement.props); + } + var updatePayload = ReactNativeAttributePayload.diff( prevElement.props, nextElement.props, @@ -201,6 +206,10 @@ ReactNativeBaseComponent.Mixin = { var tag = ReactNativeTagHandles.allocateTag(); + if (__DEV__) { + deepFreezeAndThrowOnMutationInDev(this._currentElement.props); + } + var updatePayload = ReactNativeAttributePayload.create( this._currentElement.props, this.viewConfig.validAttributes diff --git a/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js index 06db449a7a78..9951a9eb794b 100644 --- a/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js +++ b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -38,6 +38,14 @@ describe('ReactNativeAttributePayload', function() { )).toEqual({a: null}); }); + it('should remove fields that are set to undefined', () => { + expect(diff( + {a: 1}, + {a: undefined}, + {a: true} + )).toEqual({a: null}); + }); + it('should ignore invalid fields', () => { expect(diff( {a: 1}, @@ -80,19 +88,19 @@ describe('ReactNativeAttributePayload', function() { it('should work with undefined styles', () => { expect(diff( - { style: { a: '#ffffff', opacity: 1 } }, + { style: { a: '#ffffff', b: 1 } }, { style: undefined }, - { } - )).toEqual({ opacity: null }); + { style: { b: true } } + )).toEqual({ b: null }); expect(diff( { style: undefined }, - { style: { a: '#ffffff', opacity: 1 } }, - { } - )).toEqual({ opacity: 1 }); + { style: { a: '#ffffff', b: 1 } }, + { style: { b: true } } + )).toEqual({ b: 1 }); expect(diff( { style: undefined }, { style: undefined }, - { } + { style: { b: true } } )).toEqual(null); }); From 12a36bfab01365b3136613b6b160d50f8b722f78 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 5 Oct 2015 20:21:50 -0700 Subject: [PATCH 0175/2702] Refactor Attribute Processing (Step 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Decouple processStyle from the main reconciliation. It is now a process extension to the style attribute `transform`. This effectively decouples a large portion of special cases and helper dependencies from the reconciler. The transform attribute becomes translated into the transformMatrix attribute on the native side so this becomes a little weird in that I have to special case it. I don't think it is worth while having a general solution for this so I intend to rename the native attribute to `transform` and just have it accept the resolved transform. Then I can remove the special cases. The next step is generalizing the flattenStyle function and optimizing it. @​public Reviewed By: @vjeux Differential Revision: D2460465 fb-gh-sync-id: 243e7fd77d282b401bc2c028aec8d57f24522a8e --- .../View/ReactNativeStyleAttributes.js | 2 + Libraries/ReactIOS/NativeMethodsMixin.js | 3 - .../ReactNativeAttributePayload.js | 47 +++++++--- .../ReactNativeAttributePayload-test.js | 94 ++++++++++++++++++- Libraries/StyleSheet/TransformPropTypes.js | 18 +++- ...precomputeStyle.js => processTransform.js} | 75 +-------------- 6 files changed, 148 insertions(+), 91 deletions(-) rename Libraries/StyleSheet/{precomputeStyle.js => processTransform.js} (74%) diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index e2099c9d2a3e..ffeff5420d2e 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -19,6 +19,7 @@ var ViewStylePropTypes = require('ViewStylePropTypes'); var keyMirror = require('keyMirror'); var matricesDiffer = require('matricesDiffer'); var processColor = require('processColor'); +var processTransform = require('processTransform'); var sizesDiffer = require('sizesDiffer'); var ReactNativeStyleAttributes = { @@ -27,6 +28,7 @@ var ReactNativeStyleAttributes = { ...keyMirror(ImageStylePropTypes), }; +ReactNativeStyleAttributes.transform = { process: processTransform }; ReactNativeStyleAttributes.transformMatrix = { diff: matricesDiffer }; ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer }; diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index 909327f98abb..b4037829f3bd 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -17,10 +17,7 @@ var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var TextInputState = require('TextInputState'); var findNodeHandle = require('findNodeHandle'); -var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); -var mergeFast = require('mergeFast'); -var precomputeStyle = require('precomputeStyle'); type MeasureOnSuccessCallback = ( x: number, diff --git a/Libraries/ReactNative/ReactNativeAttributePayload.js b/Libraries/ReactNative/ReactNativeAttributePayload.js index a0d328f28ead..c54c44e861d6 100644 --- a/Libraries/ReactNative/ReactNativeAttributePayload.js +++ b/Libraries/ReactNative/ReactNativeAttributePayload.js @@ -11,10 +11,11 @@ */ 'use strict'; +var Platform = require('Platform'); + var deepDiffer = require('deepDiffer'); var styleDiffer = require('styleDiffer'); var flattenStyle = require('flattenStyle'); -var precomputeStyle = require('precomputeStyle'); type AttributeDiffer = (prevProp : mixed, nextProp : mixed) => boolean; type AttributePreprocessor = (nextProp: mixed) => mixed; @@ -29,6 +30,21 @@ type AttributeConfiguration = CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/ ) }; +function translateKey(propKey : string) : string { + if (propKey === 'transform') { + // We currently special case the key for `transform`. iOS uses the + // transformMatrix name and Android uses the decomposedMatrix name. + // TODO: We could unify these names and just use the name `transform` + // all the time. Just need to update the native side. + if (Platform.OS === 'android') { + return 'decomposedMatrix'; + } else { + return 'transformMatrix'; + } + } + return propKey; +} + function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean { if (typeof nextProp !== 'object' || nextProp === null) { // Scalars have already been checked for equality @@ -55,17 +71,9 @@ function diffNestedProperty( } // TODO: Walk both props in parallel instead of flattening. - // TODO: Move precomputeStyle to .process for each attribute. - - var previousFlattenedStyle = precomputeStyle( - flattenStyle(prevProp), - validAttributes - ); - var nextFlattenedStyle = precomputeStyle( - flattenStyle(nextProp), - validAttributes - ); + var previousFlattenedStyle = flattenStyle(prevProp); + var nextFlattenedStyle = flattenStyle(nextProp); if (!previousFlattenedStyle || !nextFlattenedStyle) { if (nextFlattenedStyle) { @@ -144,7 +152,14 @@ function diffProperties( if (!attributeConfig) { continue; // not a valid native prop } - if (updatePayload && updatePayload[propKey] !== undefined) { + + var altKey = translateKey(propKey); + if (!attributeConfig[altKey]) { + // If there is no config for the alternative, bail out. Helps ART. + altKey = propKey; + } + + if (updatePayload && updatePayload[altKey] !== undefined) { // If we're in a nested attribute set, we may have set this property // already. If so, bail out. The previous update is what counts. continue; @@ -172,7 +187,7 @@ function diffProperties( // case: !Object is the default case if (defaultDiffer(prevProp, nextProp)) { // a normal leaf has changed - (updatePayload || (updatePayload = {}))[propKey] = nextProp; + (updatePayload || (updatePayload = {}))[altKey] = nextProp; } } else if (typeof attributeConfig.diff === 'function' || typeof attributeConfig.process === 'function') { @@ -186,7 +201,7 @@ function diffProperties( var nextValue = typeof attributeConfig.process === 'function' ? attributeConfig.process(nextProp) : nextProp; - (updatePayload || (updatePayload = {}))[propKey] = nextValue; + (updatePayload || (updatePayload = {}))[altKey] = nextValue; } } else { // default: fallthrough case when nested properties are defined @@ -210,6 +225,7 @@ function diffProperties( if (!attributeConfig) { continue; // not a valid native prop } + prevProp = prevProps[propKey]; if (prevProp === undefined) { continue; // was already empty anyway @@ -218,9 +234,10 @@ function diffProperties( if (typeof attributeConfig !== 'object' || typeof attributeConfig.diff === 'function' || typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration | !Object // Flag the leaf property for removal by sending a sentinel. - (updatePayload || (updatePayload = {}))[propKey] = null; + (updatePayload || (updatePayload = {}))[translateKey(propKey)] = null; } else { // default: // This is a nested attribute configuration where all the properties diff --git a/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js index 9951a9eb794b..2f142c5739a3 100644 --- a/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js +++ b/Libraries/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -4,11 +4,13 @@ 'use strict'; jest.dontMock('ReactNativeAttributePayload'); +jest.dontMock('StyleSheetRegistry'); jest.dontMock('deepDiffer'); -jest.dontMock('styleDiffer'); -jest.dontMock('precomputeStyle'); jest.dontMock('flattenStyle'); +jest.dontMock('styleDiffer'); + var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var StyleSheetRegistry = require('StyleSheetRegistry'); var diff = ReactNativeAttributePayload.diff; @@ -122,6 +124,94 @@ describe('ReactNativeAttributePayload', function() { )).toEqual(null); }); + it('should flatten nested styles and predefined styles', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + {}, + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 1, bar: 2 }); + + expect(diff( + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + {}, + validStyleAttribute + )).toEqual({ foo: null, bar: null }); + + var barStyle = StyleSheetRegistry.registerStyle({ + bar: 3, + }); + + expect(diff( + {}, + { someStyle: [[{ foo: 1 }, { foo: 2 }], barStyle]}, + validStyleAttribute + )).toEqual({ foo: 2, bar: 3 }); + }); + + it('should reset a value to a previous if it is removed', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: 3 }]}, + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 1, bar: 2 }); + }); + + it('should not clear removed props if they are still in another slot', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 3 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual(null); + + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 1, bar: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 1 }); + }); + + it('should clear a prop if a later style is explicit null/undefined', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 1 }, { bar: 2, foo: null }]}, + validStyleAttribute + )).toEqual({ foo: null }); + + expect(diff( + { someStyle: [{ foo: 3 }, { foo: null, bar: 2 }]}, + { someStyle: [{ foo: null }, { bar: 2 }]}, + validStyleAttribute + )).toEqual(null); + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: null }]}, + { someStyle: [{ foo: 2 }, { foo: null }]}, + validStyleAttribute + )).toEqual(null); + + // Test the same case with object equality because an early bailout doesn't + // work in this case. + var fooObj = { foo: 3 }; + expect(diff( + { someStyle: [{ foo: 1 }, fooObj]}, + { someStyle: [{ foo: 2 }, fooObj]}, + validStyleAttribute + )).toEqual(null); + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: 3 }]}, + { someStyle: [{ foo: 2 }, { foo: undefined }]}, + validStyleAttribute + )).toEqual({ foo: null }); + }); + // Function properties are just markers to native that events should be sent. it('should convert functions to booleans', () => { // Note that if the property changes from one function to another, we don't diff --git a/Libraries/StyleSheet/TransformPropTypes.js b/Libraries/StyleSheet/TransformPropTypes.js index 2c797ae3242f..ecc44d4aa731 100644 --- a/Libraries/StyleSheet/TransformPropTypes.js +++ b/Libraries/StyleSheet/TransformPropTypes.js @@ -13,6 +13,22 @@ var ReactPropTypes = require('ReactPropTypes'); +var ArrayOfNumberPropType = ReactPropTypes.arrayOf(ReactPropTypes.number); + +var TransformMatrixPropType = function( + props : Object, + propName : string, + componentName : string +) : ?Error { + if (props.transform && props.transformMatrix) { + return new Error( + 'transformMatrix and transform styles cannot be used on the same ' + + 'component' + ); + } + return ArrayOfNumberPropType(props, propName, componentName); +}; + var TransformPropTypes = { transform: ReactPropTypes.arrayOf( ReactPropTypes.oneOfType([ @@ -30,7 +46,7 @@ var TransformPropTypes = { ReactPropTypes.shape({skewY: ReactPropTypes.string}) ]) ), - transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number), + transformMatrix: TransformMatrixPropType, }; module.exports = TransformPropTypes; diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/processTransform.js similarity index 74% rename from Libraries/StyleSheet/precomputeStyle.js rename to Libraries/StyleSheet/processTransform.js index 97bf5a0c1e8f..4666a3055291 100644 --- a/Libraries/StyleSheet/precomputeStyle.js +++ b/Libraries/StyleSheet/processTransform.js @@ -6,74 +6,17 @@ * 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. * - * @providesModule precomputeStyle + * @providesModule processTransform * @flow */ 'use strict'; var MatrixMath = require('MatrixMath'); -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var Platform = require('Platform'); -var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var invariant = require('invariant'); var stringifySafe = require('stringifySafe'); -/** - * This method provides a hook where flattened styles may be precomputed or - * otherwise prepared to become better input data for native code. - */ -function precomputeStyle(style: ?Object, validAttributes: Object): ?Object { - if (!style) { - return style; - } - - var hasPreprocessKeys = false; - for (var i = 0, keys = Object.keys(style); i < keys.length; i++) { - var key = keys[i]; - if (_processor(key, validAttributes)) { - hasPreprocessKeys = true; - break; - } - } - - if (!hasPreprocessKeys && !style.transform) { - return style; - } - - var newStyle = {...style}; - for (var i = 0, keys = Object.keys(style); i < keys.length; i++) { - var key = keys[i]; - var process = _processor(key, validAttributes); - if (process) { - newStyle[key] = process(newStyle[key]); - } - } - - if (style.transform) { - invariant( - !style.transformMatrix, - 'transformMatrix and transform styles cannot be used on the same component' - ); - - newStyle = _precomputeTransforms(newStyle); - } - - deepFreezeAndThrowOnMutationInDev(newStyle); - return newStyle; -} - -function _processor(key: string, validAttributes: Object) { - var process = validAttributes[key] && validAttributes[key].process; - if (!process) { - process = - ReactNativeStyleAttributes[key] && - ReactNativeStyleAttributes[key].process; - } - - return process; -} - /** * Generate a transform matrix based on the provided transforms, and use that * within the style object instead. @@ -82,8 +25,7 @@ function _processor(key: string, validAttributes: Object) { * be applied in an arbitrary order, and yet have a universal, singular * interface to native code. */ -function _precomputeTransforms(style: Object): Object { - var {transform} = style; +function processTransform(transform: Object): Object { var result = MatrixMath.createIdentityMatrix(); transform.forEach(transformation => { @@ -144,16 +86,9 @@ function _precomputeTransforms(style: Object): Object { // get applied in the specific order of (1) translate (2) scale (3) rotate. // Once we can directly apply a matrix, we can remove this decomposition. if (Platform.OS === 'android') { - return { - ...style, - transformMatrix: result, - decomposedMatrix: MatrixMath.decomposeMatrix(result), - }; + return MatrixMath.decomposeMatrix(result); } - return { - ...style, - transformMatrix: result, - }; + return result; } /** @@ -240,4 +175,4 @@ function _validateTransform(key, value, transformation) { } } -module.exports = precomputeStyle; +module.exports = processTransform; From 7df875f252c5cfe020fc6cdce9601c5a3629d22b Mon Sep 17 00:00:00 2001 From: Bret Johnson Date: Tue, 6 Oct 2015 03:52:45 -0700 Subject: [PATCH 0176/2702] Fixed to use ; instead of : as the path delimiter on Windows, for NDK_MODULE_PATH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Also fixed the extra slash before first-party in that path (it was ...//first-party and is now .../first-party). The $dir funct always produces a result that ends with a /, so APP_MK_DIR always ends in a / and no extra / is needed when appending to it. With these two changes all of React Native for Android, including C++, seems to build OK on Windows :smile: Closes https://github.com/facebook/react-native/pull/2961 Reviewed By: @​svcscm Differential Revision: D2512239 Pulled By: @kmagiera fb-gh-sync-id: 4fbc9fc85bab455b8ed1e07a8ac299a48921753e --- ReactAndroid/src/main/jni/Application.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/jni/Application.mk b/ReactAndroid/src/main/jni/Application.mk index d8f9dda846a3..f403e5ba18a2 100644 --- a/ReactAndroid/src/main/jni/Application.mk +++ b/ReactAndroid/src/main/jni/Application.mk @@ -4,7 +4,8 @@ APP_ABI := armeabi-v7a x86 APP_PLATFORM := android-9 APP_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST))) -NDK_MODULE_PATH := $(APP_MK_DIR):$(THIRD_PARTY_NDK_DIR):$(APP_MK_DIR)/first-party + +NDK_MODULE_PATH := $(APP_MK_DIR)$(HOST_DIRSEP)$(THIRD_PARTY_NDK_DIR)$(HOST_DIRSEP)$(APP_MK_DIR)first-party APP_STL := gnustl_shared From 181b184552d53da9a9823336de55fa2aba89d01b Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 6 Oct 2015 06:10:37 -0700 Subject: [PATCH 0177/2702] Protect against SQLiteFullExceptions Reviewed By: @kmagiera Differential Revision: D2512317 fb-gh-sync-id: 93fd65ebd88e42b5afc4e06c0612576101f15c97 --- .../modules/storage/AsyncStorageModule.java | 87 ++++++++++++++----- .../storage/ReactDatabaseSupplier.java | 8 +- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java index fd2fa710583a..abd6ed274b11 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.SetBuilder; import com.facebook.react.modules.common.ModuleDataCleaner; @@ -137,8 +138,9 @@ protected void doInBackgroundGuarded(Void... params) { } while (cursor.moveToNext()); } } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database multiGet ", e); + FLog.w(ReactConstants.TAG, e.getMessage(), e); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null); + return; } finally { cursor.close(); } @@ -179,19 +181,20 @@ protected void doInBackgroundGuarded(Void... params) { String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);"; SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql); - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int idx=0; idx < keyValueArray.size(); idx++) { if (keyValueArray.getArray(idx).size() != 2) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } if (keyValueArray.getArray(idx).getString(0) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); + error = AsyncStorageErrorUtil.getInvalidKeyError(null); return; } if (keyValueArray.getArray(idx).getString(1) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } @@ -202,12 +205,23 @@ protected void doInBackgroundGuarded(Void... params) { } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + FLog.w(ReactConstants.TAG, e.getMessage(), e); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { - mReactDatabaseSupplier.get().endTransaction(); + try { + mReactDatabaseSupplier.get().endTransaction(); + } catch (Exception e) { + FLog.w(ReactConstants.TAG, e.getMessage(), e); + if (error == null) { + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); + } + } + } + if (error != null) { + callback.invoke(error); + } else { + callback.invoke(); } - callback.invoke(); } }.execute(); } @@ -230,8 +244,9 @@ protected void doInBackgroundGuarded(Void... params) { return; } - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) { int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS); mReactDatabaseSupplier.get().delete( @@ -241,12 +256,23 @@ protected void doInBackgroundGuarded(Void... params) { } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database multiRemove ", e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + FLog.w(ReactConstants.TAG, e.getMessage(), e); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { + try { mReactDatabaseSupplier.get().endTransaction(); + } catch (Exception e) { + FLog.w(ReactConstants.TAG, e.getMessage(), e); + if (error == null) { + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); + } + } + } + if (error != null) { + callback.invoke(error); + } else { + callback.invoke(); } - callback.invoke(); } }.execute(); } @@ -264,21 +290,22 @@ protected void doInBackgroundGuarded(Void... params) { callback.invoke(AsyncStorageErrorUtil.getDBError(null)); return; } - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int idx = 0; idx < keyValueArray.size(); idx++) { if (keyValueArray.getArray(idx).size() != 2) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } if (keyValueArray.getArray(idx).getString(0) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); + error = AsyncStorageErrorUtil.getInvalidKeyError(null); return; } if (keyValueArray.getArray(idx).getString(1) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } @@ -286,18 +313,29 @@ protected void doInBackgroundGuarded(Void... params) { mReactDatabaseSupplier.get(), keyValueArray.getArray(idx).getString(0), keyValueArray.getArray(idx).getString(1))) { - callback.invoke(AsyncStorageErrorUtil.getDBError(null)); + error = AsyncStorageErrorUtil.getDBError(null); return; } } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { FLog.w(ReactConstants.TAG, e.getMessage(), e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { - mReactDatabaseSupplier.get().endTransaction(); + try { + mReactDatabaseSupplier.get().endTransaction(); + } catch (Exception e) { + FLog.w(ReactConstants.TAG, e.getMessage(), e); + if (error == null) { + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); + } + } + } + if (error != null) { + callback.invoke(error); + } else { + callback.invoke(); } - callback.invoke(); } }.execute(); } @@ -316,11 +354,11 @@ protected void doInBackgroundGuarded(Void... params) { } try { mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null); + callback.invoke(); } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database clear ", e); + FLog.w(ReactConstants.TAG, e.getMessage(), e); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); } - callback.invoke(); } }.execute(); } @@ -348,8 +386,9 @@ protected void doInBackgroundGuarded(Void... params) { } while (cursor.moveToNext()); } } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database getAllKeys ", e); + FLog.w(ReactConstants.TAG, e.getMessage(), e); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null); + return; } finally { cursor.close(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java index be14cc3aa6ba..2f127c6586ae 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java @@ -21,8 +21,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { // VisibleForTesting public static final String DATABASE_NAME = "RKStorage"; - static final int DATABASE_VERSION = 1; + + private static final int DATABASE_VERSION = 1; private static final int SLEEP_TIME_MS = 30; + private static final long DEFAULT_MAX_DB_SIZE = 6L * 1024L * 1024L; // 6 MB in bytes static final String TABLE_CATALYST = "catalystLocalStorage"; static final String KEY_COLUMN = "key"; @@ -85,6 +87,10 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (mDb == null) { throw lastSQLiteException; } + // This is a sane limit to protect the user from the app storing too much data in the database. + // This also protects the database from filling up the disk cache and becoming malformed + // (endTransaction() calls will throw an exception, not rollback, and leave the db malformed). + mDb.setMaximumSize(DEFAULT_MAX_DB_SIZE); return true; } From 6264d53d69149d17feddc7622b0eb63e73194c15 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 6 Oct 2015 07:34:44 -0700 Subject: [PATCH 0178/2702] Fix UIExplorer integration tests Reviewed By: @vjeux Differential Revision: D2510761 fb-gh-sync-id: 21ec8988305ba9f3526277a8b445676ac4fcf3aa --- .../UIExplorerIntegrationTests.m | 7 ++++--- .../js/IntegrationTestsApp.js | 1 + .../UIExplorerIntegrationTests/js/LayoutEventsTest.js | 11 ++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m index 9bc71ed80218..d649494914b3 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m @@ -58,12 +58,13 @@ - (void)testTheTester_ExpectError expectErrorRegex:@"because shouldThrow"]; } -RCT_TEST(TimersTest) +// This list should be kept in sync with IntegrationTestsApp.js RCT_TEST(IntegrationTestHarnessTest) +RCT_TEST(TimersTest) RCT_TEST(AsyncStorageTest) -// RCT_TEST(LayoutEventsTest) -- Disabled: #8153468 +RCT_TEST(LayoutEventsTest) RCT_TEST(AppEventsTest) +RCT_TEST(SimpleSnapshotTest) RCT_TEST(PromiseTest) -// RCT_TEST(SimpleSnapshotTest) -- Disabled: #8153475 @end diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js index 2c16d9c75e77..ce9704f8053c 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js @@ -23,6 +23,7 @@ var { View, } = React; +/* Keep this list in sync with UIExplorerIntegrationTests.m */ var TESTS = [ require('./IntegrationTestHarnessTest'), require('./TimersTest'), diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js index 9149eab4e36c..6719585fca2c 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/LayoutEventsTest.js @@ -25,7 +25,7 @@ var TestModule = NativeModules.TestModule; var deepDiffer = require('deepDiffer'); function debug() { - //console.log.apply(null, arguments); + // console.log.apply(null, arguments); } type LayoutEvent = { @@ -46,22 +46,25 @@ var LayoutEventsTest = React.createClass({ }; }, animateViewLayout: function() { + debug('animateViewLayout invoked'); LayoutAnimation.configureNext( LayoutAnimation.Presets.spring, () => { - debug('layout animation done.'); + debug('animateViewLayout done'); this.checkLayout(this.addWrapText); } ); this.setState({viewStyle: {margin: 60}}); }, addWrapText: function() { + debug('addWrapText invoked'); this.setState( {extraText: ' And a bunch more text to wrap around a few lines.'}, () => this.checkLayout(this.changeContainer) ); }, changeContainer: function() { + debug('changeContainer invoked'); this.setState( {containerStyle: {width: 280}}, () => this.checkLayout(TestModule.markTestCompleted) @@ -113,6 +116,7 @@ var LayoutEventsTest = React.createClass({ var viewStyle = [styles.view, this.state.viewStyle]; var textLayout = this.state.textLayout || {width: '?', height: '?'}; var imageLayout = this.state.imageLayout || {x: '?', y: '?'}; + debug('viewLayout', this.state.viewLayout); return ( @@ -122,9 +126,6 @@ var LayoutEventsTest = React.createClass({ style={styles.image} source={{uri: 'uie_thumb_big.png'}} /> - - ViewLayout: {JSON.stringify(this.state.viewLayout, null, ' ') + '\n\n'} - A simple piece of text.{this.state.extraText} From 37ca1f1d71805a00c0eae1d853e585147d81f963 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 6 Oct 2015 07:42:59 -0700 Subject: [PATCH 0179/2702] Allow nil tabbar item icon Reviewed By: @nicklockwood Differential Revision: D2508310 fb-gh-sync-id: e4eaf1237cca380b74d154c3cda50379fbafcf00 --- React/Views/RCTTabBarItem.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m index 3695f24a712b..8699ff96d71a 100644 --- a/React/Views/RCTTabBarItem.m +++ b/React/Views/RCTTabBarItem.m @@ -54,8 +54,8 @@ - (void)setIcon:(id)icon UIImage *image = [RCTConvert UIImage:_icon]; UITabBarItem *oldItem = _barItem; if (image) { - - // Recreate barItem if previous item was a system icon + // Recreate barItem if previous item was a system icon. Calling self.barItem + // creates a new instance if it wasn't set yet. if (wasSystemIcon) { _barItem = nil; self.barItem.image = image; @@ -63,9 +63,7 @@ - (void)setIcon:(id)icon self.barItem.image = image; return; } - - } else { - + } else if ([icon isKindOfClass:[NSString class]] && [icon length] > 0) { // Not a custom image, may be a system item? NSNumber *systemIcon = systemIcons[icon]; if (!systemIcon) { @@ -73,6 +71,8 @@ - (void)setIcon:(id)icon return; } _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemIcon.integerValue tag:oldItem.tag]; + } else { + self.barItem.image = nil; } // Reapply previous properties From 8b4bbea95dd17bbca19b17bc82a498f9514cb97b Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Tue, 6 Oct 2015 08:10:08 -0700 Subject: [PATCH 0180/2702] use listview for redbox stacktrace, open file on click Reviewed By: @andreicoman11 Differential Revision: D2512364 fb-gh-sync-id: 5f2c90db7eca010185080f726fd3ef0ee519cdbc --- .../react/devsupport/DevSupportManager.java | 23 ++- .../devsupport/ExceptionFormatterHelper.java | 75 -------- .../react/devsupport/RedBoxDialog.java | 172 ++++++++++++++++-- .../react/devsupport/StackTraceHelper.java | 127 +++++++++++++ .../devsupport/layout/redbox_item_frame.xml | 23 +++ .../devsupport/layout/redbox_item_title.xml | 10 + .../res/devsupport/layout/redbox_view.xml | 60 ++---- 7 files changed, 339 insertions(+), 151 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/ExceptionFormatterHelper.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java create mode 100644 ReactAndroid/src/main/res/devsupport/layout/redbox_item_frame.xml create mode 100644 ReactAndroid/src/main/res/devsupport/layout/redbox_item_title.xml diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index e20d67380fc7..1fb477b13d75 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -43,6 +43,7 @@ import com.facebook.react.bridge.WebsocketJavaScriptExecutor; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ShakeDetector; +import com.facebook.react.devsupport.StackTraceHelper.StackFrame; import com.facebook.react.modules.debug.DeveloperSettings; /** @@ -154,8 +155,7 @@ public void onReceive(Context context, Intent intent) { public void handleException(Exception e) { if (mIsDevSupportEnabled) { FLog.e(ReactConstants.TAG, "Exception in native call from JS", e); - CharSequence details = ExceptionFormatterHelper.javaStackTraceToHtml(e.getStackTrace()); - showNewError(e.getMessage(), details, JAVA_ERROR_COOKIE); + showNewError(e.getMessage(), StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE); } else { if (e instanceof RuntimeException) { // Because we are rethrowing the original exception, the original stacktrace will be @@ -179,7 +179,7 @@ public void addCustomDevOption( } public void showNewJSError(String message, ReadableArray details, int errorCookie) { - showNewError(message, ExceptionFormatterHelper.jsStackTraceToHtml(details), errorCookie); + showNewError(message, StackTraceHelper.convertJsStackTrace(details), errorCookie); } public void updateJSError( @@ -198,8 +198,9 @@ public void run() { errorCookie != mRedBoxDialog.getErrorCookie()) { return; } - mRedBoxDialog.setTitle(message); - mRedBoxDialog.setDetails(ExceptionFormatterHelper.jsStackTraceToHtml(details)); + mRedBoxDialog.setExceptionDetails( + message, + StackTraceHelper.convertJsStackTrace(details)); mRedBoxDialog.show(); } }); @@ -207,7 +208,7 @@ public void run() { private void showNewError( final String message, - final CharSequence details, + final StackFrame[] stack, final int errorCookie) { UiThreadUtil.runOnUiThread( new Runnable() { @@ -222,8 +223,7 @@ public void run() { // show the first and most actionable one. return; } - mRedBoxDialog.setTitle(message); - mRedBoxDialog.setDetails(details); + mRedBoxDialog.setExceptionDetails(message, stack); mRedBoxDialog.setErrorCookie(errorCookie); mRedBoxDialog.show(); } @@ -520,7 +520,7 @@ public void onFailure(final Throwable cause) { public void run() { showNewError( mApplicationContext.getString(R.string.catalyst_remotedbg_error), - ExceptionFormatterHelper.javaStackTraceToHtml(cause.getStackTrace()), + StackTraceHelper.convertJavaStackTrace(cause), JAVA_ERROR_COOKIE); } }); @@ -555,13 +555,12 @@ public void run() { DebugServerException debugServerException = (DebugServerException) cause; showNewError( debugServerException.description, - ExceptionFormatterHelper.debugServerExcStackTraceToHtml( - (DebugServerException) cause), + StackTraceHelper.convertJavaStackTrace(cause), JAVA_ERROR_COOKIE); } else { showNewError( mApplicationContext.getString(R.string.catalyst_jsload_error), - ExceptionFormatterHelper.javaStackTraceToHtml(cause.getStackTrace()), + StackTraceHelper.convertJavaStackTrace(cause), JAVA_ERROR_COOKIE); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ExceptionFormatterHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ExceptionFormatterHelper.java deleted file mode 100644 index 89ae7d9bf4cc..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ExceptionFormatterHelper.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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. - */ - -package com.facebook.react.devsupport; - -import java.io.File; - -import android.text.Html; - -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; - -/** - * Helper class for displaying errors in an eye-catching form (red box). - */ -/* package */ class ExceptionFormatterHelper { - - private static String getStackTraceHtmlComponent( - String methodName, String filename, int lineNumber, int columnNumber) { - StringBuilder stringBuilder = new StringBuilder(); - methodName = methodName.replace("<", "<").replace(">", ">"); - stringBuilder.append("") - .append(methodName) - .append("
") - .append(filename) - .append(":") - .append(lineNumber); - if (columnNumber != -1) { - stringBuilder - .append(":") - .append(columnNumber); - } - stringBuilder.append("

"); - return stringBuilder.toString(); - } - - public static CharSequence jsStackTraceToHtml(ReadableArray stack) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < stack.size(); i++) { - ReadableMap frame = stack.getMap(i); - String methodName = frame.getString("methodName"); - String fileName = new File(frame.getString("file")).getName(); - int lineNumber = frame.getInt("lineNumber"); - int columnNumber = -1; - if (frame.hasKey("column") && !frame.isNull("column")) { - columnNumber = frame.getInt("column"); - } - stringBuilder.append(getStackTraceHtmlComponent( - methodName, fileName, lineNumber, columnNumber)); - } - return Html.fromHtml(stringBuilder.toString()); - } - - public static CharSequence javaStackTraceToHtml(StackTraceElement[] stack) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i< stack.length; i++) { - stringBuilder.append(getStackTraceHtmlComponent( - stack[i].getMethodName(), stack[i].getFileName(), stack[i].getLineNumber(), -1)); - - } - return Html.fromHtml(stringBuilder.toString()); - } - - public static CharSequence debugServerExcStackTraceToHtml(DebugServerException e) { - String s = getStackTraceHtmlComponent("", e.fileName, e.lineNumber, e.column); - return Html.fromHtml(s); - } - -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java index 1a3973a3dd0d..39e48fefe24c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java @@ -11,28 +11,166 @@ import android.app.Dialog; import android.content.Context; -import android.graphics.Typeface; -import android.text.method.ScrollingMovementMethod; +import android.net.Uri; +import android.os.AsyncTask; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.Window; +import android.widget.AdapterView; +import android.widget.BaseAdapter; import android.widget.Button; +import android.widget.ListView; import android.widget.TextView; +import com.facebook.common.logging.FLog; import com.facebook.react.R; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.devsupport.StackTraceHelper.StackFrame; + +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import org.json.JSONObject; /** * Dialog for displaying JS errors in an eye-catching form (red box). */ -/* package */ class RedBoxDialog extends Dialog { +/* package */ class RedBoxDialog extends Dialog implements AdapterView.OnItemClickListener { private final DevSupportManager mDevSupportManager; - private TextView mTitle; - private TextView mDetails; + private ListView mStackView; private Button mReloadJs; private int mCookie = 0; + private static class StackAdapter extends BaseAdapter { + private static final int VIEW_TYPE_COUNT = 2; + private static final int VIEW_TYPE_TITLE = 0; + private static final int VIEW_TYPE_STACKFRAME = 1; + + private final String mTitle; + private final StackFrame[] mStack; + + private static class FrameViewHolder { + private final TextView mMethodView; + private final TextView mFileView; + + private FrameViewHolder(View v) { + mMethodView = (TextView) v.findViewById(R.id.rn_frame_method); + mFileView = (TextView) v.findViewById(R.id.rn_frame_file); + } + } + + public StackAdapter(String title, StackFrame[] stack) { + mTitle = title; + mStack = stack; + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return position > 0; + } + + @Override + public int getCount() { + return mStack.length + 1; + } + + @Override + public Object getItem(int position) { + return position == 0 ? mTitle : mStack[position - 1]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getViewTypeCount() { + return VIEW_TYPE_COUNT; + } + + @Override + public int getItemViewType(int position) { + return position == 0 ? VIEW_TYPE_TITLE : VIEW_TYPE_STACKFRAME; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (position == 0) { + TextView title = convertView != null + ? (TextView) convertView + : (TextView) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.redbox_item_title, parent, false); + title.setText(mTitle); + return title; + } else { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.redbox_item_frame, parent, false); + convertView.setTag(new FrameViewHolder(convertView)); + } + StackFrame frame = mStack[position - 1]; + FrameViewHolder holder = (FrameViewHolder) convertView.getTag(); + holder.mMethodView.setText(frame.getMethod()); + holder.mFileView.setText(frame.getFileName() + ":" + frame.getLine()); + return convertView; + } + } + } + + private static class OpenStackFrameTask extends AsyncTask { + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + private final DevSupportManager mDevSupportManager; + + private OpenStackFrameTask(DevSupportManager devSupportManager) { + mDevSupportManager = devSupportManager; + } + + @Override + protected Void doInBackground(StackFrame... stackFrames) { + try { + String openStackFrameUrl = + Uri.parse(mDevSupportManager.getSourceUrl()).buildUpon() + .path("/open-stack-frame") + .query(null) + .build() + .toString(); + OkHttpClient client = new OkHttpClient(); + for (StackFrame frame: stackFrames) { + String payload = stackFrameToJson(frame).toString(); + RequestBody body = RequestBody.create(JSON, payload); + Request request = new Request.Builder().url(openStackFrameUrl).post(body).build(); + client.newCall(request).execute(); + } + } catch (Exception e) { + FLog.e(ReactConstants.TAG, "Could not open stack frame", e); + } + return null; + } + + private static JSONObject stackFrameToJson(StackFrame frame) { + return new JSONObject( + MapBuilder.of( + "file", frame.getFile(), + "methodName", frame.getMethod(), + "lineNumber", frame.getLine(), + "column", frame.getColumn() + )); + } + } + protected RedBoxDialog(Context context, DevSupportManager devSupportManager) { super(context, R.style.Theme_Catalyst_RedBox); @@ -42,12 +180,9 @@ protected RedBoxDialog(Context context, DevSupportManager devSupportManager) { mDevSupportManager = devSupportManager; - mTitle = (TextView) findViewById(R.id.catalyst_redbox_title); - mDetails = (TextView) findViewById(R.id.catalyst_redbox_details); - mDetails.setTypeface(Typeface.MONOSPACE); - mDetails.setHorizontallyScrolling(true); - mDetails.setMovementMethod(new ScrollingMovementMethod()); - mReloadJs = (Button) findViewById(R.id.catalyst_redbox_reloadjs); + mStackView = (ListView) findViewById(R.id.rn_redbox_stack); + mStackView.setOnItemClickListener(this); + mReloadJs = (Button) findViewById(R.id.rn_redbox_reloadjs); mReloadJs.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -56,12 +191,8 @@ public void onClick(View v) { }); } - public void setTitle(String title) { - mTitle.setText(title); - } - - public void setDetails(CharSequence details) { - mDetails.setText(details); + public void setExceptionDetails(String title, StackFrame[] stack) { + mStackView.setAdapter(new StackAdapter(title, stack)); } public void setErrorCookie(int cookie) { @@ -72,6 +203,13 @@ public int getErrorCookie() { return mCookie; } + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + new OpenStackFrameTask(mDevSupportManager).executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, + (StackFrame) mStackView.getAdapter().getItem(position)); + } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java new file mode 100644 index 000000000000..5618ba78dac6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -0,0 +1,127 @@ +/** + * 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. + */ + +package com.facebook.react.devsupport; + +import java.io.File; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; + +/** + * Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects. + */ +/* package */ class StackTraceHelper { + + /** + * Represents a generic entry in a stack trace, be it originally from JS or Java. + */ + public static class StackFrame { + private final String mFile; + private final String mMethod; + private final int mLine; + private final int mColumn; + private final String mFileName; + + private StackFrame(String file, String method, int line, int column) { + mFile = file; + mMethod = method; + mLine = line; + mColumn = column; + mFileName = new File(file).getName(); + } + + private StackFrame(String file, String fileName, String method, int line, int column) { + mFile = file; + mFileName = fileName; + mMethod = method; + mLine = line; + mColumn = column; + } + + /** + * Get the file this stack frame points to. + * + * JS traces return the full path to the file here, while Java traces only return the file name + * (the path is not known). + */ + public String getFile() { + return mFile; + } + + /** + * Get the name of the method this frame points to. + */ + public String getMethod() { + return mMethod; + } + + /** + * Get the line number this frame points to in the file returned by {@link #getFile()}. + */ + public int getLine() { + return mLine; + } + + /** + * Get the column this frame points to in the file returned by {@link #getFile()}. + */ + public int getColumn() { + return mColumn; + } + + /** + * Get just the name of the file this frame points to. + * + * For JS traces this is different from {@link #getFile()} in that it only returns the file + * name, not the full path. For Java traces there is no difference. + */ + public String getFileName() { + return mFileName; + } + } + + /** + * Convert a JavaScript stack trace (see {@code parseErrorStack} JS module) to an array of + * {@link StackFrame}s. + */ + public static StackFrame[] convertJsStackTrace(ReadableArray stack) { + StackFrame[] result = new StackFrame[stack.size()]; + for (int i = 0; i < stack.size(); i++) { + ReadableMap frame = stack.getMap(i); + String methodName = frame.getString("methodName"); + String fileName = frame.getString("file"); + int lineNumber = frame.getInt("lineNumber"); + int columnNumber = -1; + if (frame.hasKey("column") && !frame.isNull("column")) { + columnNumber = frame.getInt("column"); + } + result[i] = new StackFrame(fileName, methodName, lineNumber, columnNumber); + } + return result; + } + + /** + * Convert a {@link Throwable} to an array of {@link StackFrame}s. + */ + public static StackFrame[] convertJavaStackTrace(Throwable exception) { + StackTraceElement[] stackTrace = exception.getStackTrace(); + StackFrame[] result = new StackFrame[stackTrace.length]; + for (int i = 0; i < stackTrace.length; i++) { + result[i] = new StackFrame( + stackTrace[i].getClassName(), + stackTrace[i].getFileName(), + stackTrace[i].getMethodName(), + stackTrace[i].getLineNumber(), + 0); + } + return result; + } + +} diff --git a/ReactAndroid/src/main/res/devsupport/layout/redbox_item_frame.xml b/ReactAndroid/src/main/res/devsupport/layout/redbox_item_frame.xml new file mode 100644 index 000000000000..45923c1c7e3a --- /dev/null +++ b/ReactAndroid/src/main/res/devsupport/layout/redbox_item_frame.xml @@ -0,0 +1,23 @@ + + + + diff --git a/ReactAndroid/src/main/res/devsupport/layout/redbox_item_title.xml b/ReactAndroid/src/main/res/devsupport/layout/redbox_item_title.xml new file mode 100644 index 000000000000..95ef0c926f25 --- /dev/null +++ b/ReactAndroid/src/main/res/devsupport/layout/redbox_item_title.xml @@ -0,0 +1,10 @@ + diff --git a/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml b/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml index b85d4f83d1b6..faa9a90f0410 100644 --- a/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml +++ b/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml @@ -1,54 +1,20 @@ - - - +