From 82a774e92bf19b9a135004cca53b6e1e98dde052 Mon Sep 17 00:00:00 2001
From: Nick Lockwood <nicklockwood@fb.com>
Date: Fri, 17 Jul 2015 04:50:42 -0700
Subject: [PATCH 01/33] Added unit tests for gzip functionality

---
 .../UIExplorer.xcodeproj/project.pbxproj      |  6 ++
 .../UIExplorerUnitTests/RCTGzipTests.m        | 81 +++++++++++++++++++
 Libraries/Network/RCTNetworking.m             | 15 ++--
 React/Base/RCTUtils.m                         |  3 +-
 4 files changed, 97 insertions(+), 8 deletions(-)
 create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m

diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index db89c32b8..e6c9afc35 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; };
 		13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; };
 		134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
 		1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
@@ -156,6 +157,7 @@
 
 /* Begin PBXFileReference section */
 		004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; };
 		13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };
 		13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
 		134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
@@ -357,6 +359,7 @@
 				1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
 				1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
 				138D6A151B53CD440074A87E /* RCTCacheTests.m */,
+				1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
 				143BC57E1B21E18100462512 /* Info.plist */,
 				14D6D7101B220EB3001FB087 /* libOCMock.a */,
 				14D6D7011B220AE3001FB087 /* OCMock */,
@@ -783,6 +786,7 @@
 				1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */,
 				144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */,
 				1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */,
+				1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
 				1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
 				1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */,
 				1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
@@ -1040,6 +1044,7 @@
 					"$(inherited)",
 					/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
 					"$(SRCROOT)/../../React/**",
+					"$(SRCROOT)/../../Libraries/**",
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 7.0;
 				MTL_ENABLE_DEBUG_INFO = YES;
@@ -1094,6 +1099,7 @@
 					"$(inherited)",
 					/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
 					"$(SRCROOT)/../../React/**",
+					"$(SRCROOT)/../../Libraries/**",
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 7.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
new file mode 100644
index 000000000..5e74bf7c4
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
@@ -0,0 +1,81 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <XCTest/XCTest.h>
+#import "RCTUtils.h"
+#import "RCTNetworking.h"
+
+extern BOOL RCTIsGzippedData(NSData *data);
+
+@interface RCTNetworking (Private)
+
+- (void)buildRequest:(NSDictionary *)query
+     completionBlock:(void (^)(NSURLRequest *request))block;
+
+@end
+
+@interface RCTGzipTests : XCTestCase
+
+@end
+
+@implementation RCTGzipTests
+
+- (void)testGzip
+{
+  //set up data
+  NSString *inputString = @"Hello World!";
+  NSData *inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
+
+  //compress
+  NSData *outputData = RCTGzipData(inputData, -1);
+  XCTAssertTrue(RCTIsGzippedData(outputData));
+}
+
+- (void)testDontRezipZippedData
+{
+  //set up data
+  NSString *inputString = @"Hello World!";
+  NSData *inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
+
+  //compress
+  NSData *compressedData = RCTGzipData(inputData, -1);
+  inputString = [[NSString alloc] initWithData:compressedData encoding:NSUTF8StringEncoding];
+
+  //compress again
+  NSData *outputData = RCTGzipData(inputData, -1);
+  NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
+  XCTAssertEqualObjects(outputString, inputString);
+}
+
+- (void)testRequestBodyEncoding
+{
+  NSDictionary *query = @{
+                          @"url": @"http://example.com",
+                          @"method": @"POST",
+                          @"data": @{@"string": @"Hello World"},
+                          @"headers": @{@"Content-Encoding": @"gzip"},
+                          };
+
+  RCTNetworking *networker = [[RCTNetworking alloc] init];
+  __block NSURLRequest *request = nil;
+  [networker buildRequest:query completionBlock:^(NSURLRequest *_request) {
+    request = _request;
+  }];
+
+  XCTAssertNotNil(request);
+  XCTAssertNotNil(request.HTTPBody);
+  XCTAssertTrue(RCTIsGzippedData(request.HTTPBody));
+}
+
+@end
diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m
index d86b00950..df102e594 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -230,15 +230,13 @@ RCT_EXPORT_MODULE()
 }
 
 - (void)buildRequest:(NSDictionary *)query
-      responseSender:(RCTResponseSenderBlock)responseSender
+     completionBlock:(void (^)(NSURLRequest *request))block
 {
   NSURL *URL = [RCTConvert NSURL:query[@"url"]];
   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
   request.HTTPMethod = [[RCTConvert NSString:query[@"method"]] uppercaseString] ?: @"GET";
   request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
 
-  BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
-
   NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
   [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
     if (error) {
@@ -258,9 +256,7 @@ RCT_EXPORT_MODULE()
       [request setValue:[@(request.HTTPBody.length) description] forHTTPHeaderField:@"Content-Length"];
     }
 
-    [self sendRequest:request
-   incrementalUpdates:incrementalUpdates
-       responseSender:responseSender];
+    block(request);
   }];
 }
 
@@ -464,7 +460,12 @@ RCT_EXPORT_MODULE()
 RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
                   responseSender:(RCTResponseSenderBlock)responseSender)
 {
-  [self buildRequest:query responseSender:responseSender];
+  [self buildRequest:query completionBlock:^(NSURLRequest *request) {
+
+    BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
+    [self sendRequest:request incrementalUpdates:incrementalUpdates
+       responseSender:responseSender];
+  }];
 }
 
 RCT_EXPORT_METHOD(cancelRequest:(NSNumber *)requestID)
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index d342c3be4..724a9c368 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -308,7 +308,8 @@ NSURL *RCTDataURL(NSString *mimeType, NSData *data)
            [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
 }
 
-static BOOL RCTIsGzippedData(NSData *data)
+BOOL RCTIsGzippedData(NSData *); // exposed for unit testing purposes
+BOOL RCTIsGzippedData(NSData *data)
 {
   UInt8 *bytes = (UInt8 *)data.bytes;
   return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);

From 61c648d5641da595366801d4a9ef31628cd0eee7 Mon Sep 17 00:00:00 2001
From: Nick Lockwood <nicklockwood@fb.com>
Date: Wed, 15 Jul 2015 19:17:13 -0100
Subject: [PATCH 02/33] Merged RCTNetworkImageView functionality into
 RCTStaticImage

Summary:
RCTNetworkImageView and RCTStaticImage had significant overlap in functionality, but each had a different subset of features and bugs.

This diff merges most of the functionality of RCTNetworkImageView into RCTStaticImage, eliminating some bugs in the former, such as constant redrawing when properties were changed.

I've also removed the onLoadAbort event for now (as it wasn't implemented), and renamed the other events to match the web specs for `<img>` and XHMLHttpRequest. The API is essentially what Adobe proposed here: http://blogs.adobe.com/webplatform/2012/01/13/html5-image-progress-events/

The following features have not yet been ported from RCTNetworkImageView:

- Background color compositing. It's not clear that this adds much value and it increases memory consumption, etc.
- Image request cancelling when images are removed from view. Again, it's not clear if this is a huge benefit, but if it is it should be combined with other optimisations, such as unloading offscreen images.

(Note that this only affects the open source fork. For now, internal apps will still use FBNetworkImageView for remote images.)
---
 Examples/UIExplorer/ImageExample.js           |  10 +-
 Libraries/Image/Image.ios.js                  |  91 +++-----
 .../Image/RCTImage.xcodeproj/project.pbxproj  |  36 +--
 Libraries/Image/RCTImageDownloader.h          |   7 -
 Libraries/Image/RCTImageDownloader.m          |  30 +--
 Libraries/Image/RCTImageLoader.h              |  24 +-
 Libraries/Image/RCTImageLoader.m              |  67 +++---
 .../{RCTStaticImage.h => RCTImageView.h}      |   7 +-
 .../{RCTStaticImage.m => RCTImageView.m}      |  77 +++++-
 ...icImageManager.h => RCTImageViewManager.h} |   2 +-
 Libraries/Image/RCTImageViewManager.m         |  57 +++++
 Libraries/Image/RCTNetworkImageView.h         |  46 ----
 Libraries/Image/RCTNetworkImageView.m         | 220 ------------------
 Libraries/Image/RCTNetworkImageViewManager.h  |  15 --
 Libraries/Image/RCTNetworkImageViewManager.m  |  56 -----
 Libraries/Image/RCTStaticImageManager.m       |  41 ----
 React/Modules/RCTUIManager.m                  |   9 +-
 17 files changed, 253 insertions(+), 542 deletions(-)
 rename Libraries/Image/{RCTStaticImage.h => RCTImageView.h} (72%)
 rename Libraries/Image/{RCTStaticImage.m => RCTImageView.m} (59%)
 rename Libraries/Image/{RCTStaticImageManager.h => RCTImageViewManager.h} (87%)
 create mode 100644 Libraries/Image/RCTImageViewManager.m
 delete mode 100644 Libraries/Image/RCTNetworkImageView.h
 delete mode 100644 Libraries/Image/RCTNetworkImageView.m
 delete mode 100644 Libraries/Image/RCTNetworkImageViewManager.h
 delete mode 100644 Libraries/Image/RCTNetworkImageViewManager.m
 delete mode 100644 Libraries/Image/RCTStaticImageManager.m

diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js
index b886bf861..82721993f 100644
--- a/Examples/UIExplorer/ImageExample.js
+++ b/Examples/UIExplorer/ImageExample.js
@@ -32,7 +32,7 @@ var NetworkImageExample = React.createClass({
   getInitialState: function() {
     return {
       error: false,
-      loading: true,
+      loading: false,
       progress: 0
     };
   },
@@ -47,10 +47,10 @@ var NetworkImageExample = React.createClass({
       <Image
         source={this.props.source}
         style={[styles.base, {overflow: 'visible'}]}
-        onLoadError={(e) => this.setState({error: e.nativeEvent.error})}
-        onLoadProgress={(e) => this.setState({progress: Math.max(0, Math.round(100 * e.nativeEvent.written / e.nativeEvent.total))}) }
-        onLoadEnd={() => this.setState({loading: false, error: false})}
-        onLoadAbort={() => this.setState({error: 'Loading has aborted'})} >
+        onLoadStart={(e) => this.setState({loading: true})}
+        onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})}
+        onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})}
+        onLoad={() => this.setState({loading: false, error: false})}>
         {loader}
       </Image>;
   }
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 63534af3e..e1fc6df2f 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -24,7 +24,6 @@ var StyleSheetPropType = require('StyleSheetPropType');
 
 var flattenStyle = require('flattenStyle');
 var invariant = require('invariant');
-var merge = require('merge');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
 var verifyPropTypes = require('verifyPropTypes');
@@ -57,6 +56,7 @@ var warning = require('warning');
 
 var Image = React.createClass({
   propTypes: {
+    style: StyleSheetPropType(ImageStylePropTypes),
     /**
      * `uri` is a string representing the resource identifier for the image, which
      * could be an http address, a local file path, or the name of a static image
@@ -93,7 +93,6 @@ var Image = React.createClass({
      * image dimensions.
      */
     resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
-    style: StyleSheetPropType(ImageStylePropTypes),
     /**
      * A unique identifier for this element to be used in UI Automation
      * testing scripts.
@@ -102,7 +101,7 @@ var Image = React.createClass({
     /**
      * Invoked on mount and layout changes with
      *
-     *   {nativeEvent: { layout: {x, y, width, height}}}.
+     *   {nativeEvent: {layout: {x, y, width, height}}}.
      */
     onLayout: PropTypes.func,
     /**
@@ -112,25 +111,23 @@ var Image = React.createClass({
     /**
      * Invoked on download progress with
      *
-     *   {nativeEvent: { written, total}}.
+     *   {nativeEvent: {loaded, total}}.
      */
-    onLoadProgress: PropTypes.func,
-    /**
-     * Invoked on load abort
-     */
-    onLoadAbort: PropTypes.func,
+    onProgress: PropTypes.func,
     /**
      * Invoked on load error
      *
-     *   {nativeEvent: { error}}.
+     *   {nativeEvent: {error}}.
      */
-    onLoadError: PropTypes.func,
+    onError: PropTypes.func,
     /**
-     * Invoked on load end
-     *
+     * Invoked when load completes successfully
      */
-    onLoaded: PropTypes.func
-
+    onLoad: PropTypes.func,
+    /**
+     * Invoked when load either succeeds or fails
+     */
+    onLoadEnd: PropTypes.func,
   },
 
   statics: {
@@ -149,46 +146,27 @@ var Image = React.createClass({
   },
 
   render: function() {
-    for (var prop in nativeOnlyProps) {
-      if (this.props[prop] !== undefined) {
-        console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
-          'not be set directly on Image.');
-      }
-    }
     var source = resolveAssetSource(this.props.source) || {};
+    var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {};
 
     var {width, height} = source;
-    var style = flattenStyle([{width, height}, styles.base, this.props.style]);
-    invariant(style, 'style must be initialized');
+    var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
 
     var isNetwork = source.uri && source.uri.match(/^https?:/);
-    invariant(
-      !(isNetwork && source.isStatic),
-      'static image uris cannot start with "http": "' + source.uri + '"'
+    var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
+    var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
+    var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
+
+    return (
+      <RawImage
+        {...this.props}
+        style={style}
+        resizeMode={resizeMode}
+        tintColor={tintColor}
+        src={source.uri}
+        defaultSrc={defaultSource.uri}
+      />
     );
-    var isStored = !source.isStatic && !isNetwork;
-    var RawImage = isNetwork ? RCTNetworkImage : RCTStaticImage;
-
-    if (this.props.style && this.props.style.tintColor) {
-      warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
-    }
-    var resizeMode = this.props.resizeMode || style.resizeMode || 'cover';
-
-    var nativeProps = merge(this.props, {
-      style,
-      resizeMode,
-      tintColor: style.tintColor,
-    });
-    if (isStored) {
-      nativeProps.imageTag = source.uri;
-    } else {
-      nativeProps.src = source.uri;
-    }
-    if (this.props.defaultSource) {
-      nativeProps.defaultImageSrc = this.props.defaultSource.uri;
-    }
-    nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress;
-    return <RawImage {...nativeProps} />;
   }
 });
 
@@ -198,18 +176,7 @@ var styles = StyleSheet.create({
   },
 });
 
-var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
-var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
-
-var nativeOnlyProps = {
-  src: true,
-  defaultImageSrc: true,
-  imageTag: true,
-  progressHandlerRegistered: true
-};
-if (__DEV__) {
-  verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
-  verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
-}
+var RCTImageView = requireNativeComponent('RCTImageView', null);
+var RCTNetworkImageView = (NativeModules.NetworkImageViewManager) ? requireNativeComponent('RCTNetworkImageView', null) : RCTImageView;
 
 module.exports = Image;
diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
index 8ecabbafd..3eabd148e 100644
--- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
+++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
@@ -8,8 +8,8 @@
 
 /* Begin PBXBuildFile section */
 		03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */; };
-		1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
-		1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
+		1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */; };
+		1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
 		1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
 		1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
 		134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
@@ -17,8 +17,6 @@
 		143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
 		143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
 		58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
-		58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
-		58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -36,10 +34,10 @@
 /* Begin PBXFileReference section */
 		03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTaskWrapper.h; sourceTree = "<group>"; };
 		03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = "<group>"; };
-		1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = "<group>"; };
-		1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = "<group>"; };
-		1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = "<group>"; };
-		1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
+		1304D5A71AA8C4A30002E2BE /* RCTImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageView.h; sourceTree = "<group>"; };
+		1304D5A81AA8C4A30002E2BE /* RCTImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageView.m; sourceTree = "<group>"; };
+		1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageViewManager.h; sourceTree = "<group>"; };
+		1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageViewManager.m; sourceTree = "<group>"; };
 		1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
 		1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
 		1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
@@ -55,10 +53,6 @@
 		58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
 		58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
-		58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageView.h; sourceTree = "<group>"; };
-		58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageView.m; sourceTree = "<group>"; };
-		58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageViewManager.h; sourceTree = "<group>"; };
-		58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageViewManager.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -89,14 +83,10 @@
 				137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
 				1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
 				1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
-				58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
-				58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */,
-				58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */,
-				58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */,
-				1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */,
-				1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
-				1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
-				1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
+				1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
+				1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
+				1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
+				1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */,
 				134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
 				134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
 				58B5115E1A9E6B3D00147676 /* Products */,
@@ -171,15 +161,13 @@
 			files = (
 				58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
 				137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
-				58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
-				1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
+				1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
 				1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
-				58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
 				1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
 				143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
 				143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
 				03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
-				1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
+				1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
 				134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h
index 43bb9a69d..44ad1cde3 100644
--- a/Libraries/Image/RCTImageDownloader.h
+++ b/Libraries/Image/RCTImageDownloader.h
@@ -43,11 +43,4 @@ typedef void (^RCTImageDownloadCancellationBlock)(void);
                                            progressBlock:(RCTDataProgressBlock)progressBlock
                                                    block:(RCTImageDownloadBlock)block;
 
-/**
- * Cancel an in-flight download. If multiple requets have been made for the
- * same image, only the request that relates to the token passed will be
- * cancelled.
- */
-- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken;
-
 @end
diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m
index f32d895cb..6cec0f478 100644
--- a/Libraries/Image/RCTImageDownloader.m
+++ b/Libraries/Image/RCTImageDownloader.m
@@ -52,7 +52,9 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
   return self;
 }
 
-- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block
+- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url
+                                           progressBlock:progressBlock
+                                                   block:(RCTCachedDataDownloadBlock)block
 {
   NSString *const cacheKey = url.absoluteString;
 
@@ -134,7 +136,9 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
   return [cancel copy];
 }
 
-- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block
+- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url
+                                          progressBlock:(RCTDataProgressBlock)progressBlock
+                                                  block:(RCTDataDownloadBlock)block
 {
   return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) {
     block(data, error);
@@ -150,24 +154,19 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
                                            progressBlock:(RCTDataProgressBlock)progressBlock
                                                    block:(RCTImageDownloadBlock)block
 {
+  scale = scale ?: RCTScreenScale();
+
   return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) {
     if (!data || error) {
       block(nil, error);
       return;
     }
 
-    if (CGSizeEqualToSize(size, CGSizeZero)) {
-      // Target size wasn't available yet, so abort image drawing
-      block(nil, nil);
-      return;
-    }
-
     UIImage *image = [UIImage imageWithData:data scale:scale];
-    if (image) {
+    if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
 
       // Get scale and size
-      CGFloat destScale = scale ?: RCTScreenScale();
-      CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
+      CGRect imageRect = RCTClipRect(image.size, scale, size, scale, resizeMode);
       CGSize destSize = RCTTargetSizeForClipRect(imageRect);
 
       // Opacity optimizations
@@ -183,7 +182,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
       }
 
       // Decompress image at required size
-      UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
+      UIGraphicsBeginImageContextWithOptions(destSize, opaque, scale);
       if (blendColor) {
         [blendColor setFill];
         UIRectFill((CGRect){CGPointZero, destSize});
@@ -201,11 +200,4 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
   }];
 }
 
-- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken
-{
-  if (downloadToken) {
-    downloadToken();
-  }
-}
-
 @end
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index 4337836fd..25fdb1b30 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -11,6 +11,10 @@
 
 @class ALAssetsLibrary;
 
+typedef void (^RCTImageLoaderProgressBlock)(int64_t written, int64_t total);
+typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id /* UIImage or CAAnimation */);
+typedef void (^RCTImageLoaderCancellationBlock)(void);
+
 @interface RCTImageLoader : NSObject
 
 /**
@@ -22,22 +26,28 @@
  * Can be called from any thread.
  * Will always call callback on main thread.
  */
-+ (void)loadImageWithTag:(NSString *)imageTag
-                callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
++ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                           callback:(RCTImageLoaderCompletionBlock)callback;
 
 /**
  * As above, but includes target size, scale and resizeMode, which are used to
  * select the optimal dimensions for the loaded image.
  */
-+ (void)loadImageWithTag:(NSString *)imageTag
-                    size:(CGSize)size
-                   scale:(CGFloat)scale
-              resizeMode:(UIViewContentMode)resizeMode
-                callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
++ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                               size:(CGSize)size
+                                              scale:(CGFloat)scale
+                                         resizeMode:(UIViewContentMode)resizeMode
+                                      progressBlock:(RCTImageLoaderProgressBlock)progress
+                                    completionBlock:(RCTImageLoaderCompletionBlock)completion;
 
 /**
  * Is the specified image tag an asset library image?
  */
 + (BOOL)isAssetLibraryImage:(NSString *)imageTag;
 
+/**
+ * Is the specified image tag a remote image?
+ */
++ (BOOL)isRemoteImage:(NSString *)imageTag;
+
 @end
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index 69d98a60a..405b4907b 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -57,21 +57,23 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
   return assetsLibrary;
 }
 
-+ (void)loadImageWithTag:(NSString *)imageTag
-                callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback
++ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                           callback:(RCTImageLoaderCompletionBlock)callback
 {
   return [self loadImageWithTag:imageTag
                            size:CGSizeZero
                           scale:0
                      resizeMode:UIViewContentModeScaleToFill
-                       callback:callback];
+                  progressBlock:nil
+                completionBlock:callback];
 }
 
-+ (void)loadImageWithTag:(NSString *)imageTag
-                    size:(CGSize)size
-                   scale:(CGFloat)scale
-              resizeMode:(UIViewContentMode)resizeMode
-                callback:(void (^)(NSError *error, id image))callback
++ (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                               size:(CGSize)size
+                                              scale:(CGFloat)scale
+                                         resizeMode:(UIViewContentMode)resizeMode
+                                      progressBlock:(RCTImageLoaderProgressBlock)progress
+                                    completionBlock:(RCTImageLoaderCompletionBlock)completion
 {
   if ([imageTag hasPrefix:@"assets-library://"]) {
     [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
@@ -109,19 +111,20 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
             }
 
             UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:(UIImageOrientation)orientation];
-            RCTDispatchCallbackOnMainQueue(callback, nil, image);
+            RCTDispatchCallbackOnMainQueue(completion, nil, image);
           }
         });
       } else {
         NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
         NSError *error = RCTErrorWithMessage(errorText);
-        RCTDispatchCallbackOnMainQueue(callback, error, nil);
+        RCTDispatchCallbackOnMainQueue(completion, error, nil);
       }
     } failureBlock:^(NSError *loadError) {
       NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
       NSError *error = RCTErrorWithMessage(errorText);
-      RCTDispatchCallbackOnMainQueue(callback, error, nil);
+      RCTDispatchCallbackOnMainQueue(completion, error, nil);
     }];
+    return ^{};
   } else if ([imageTag hasPrefix:@"ph://"]) {
     // Using PhotoKit for iOS 8+
     // The 'ph://' prefix is used by FBMediaKit to differentiate between
@@ -132,8 +135,8 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
     if (results.count == 0) {
       NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
       NSError *error = RCTErrorWithMessage(errorText);
-      RCTDispatchCallbackOnMainQueue(callback, error, nil);
-      return;
+      RCTDispatchCallbackOnMainQueue(completion, error, nil);
+      return ^{};
     }
 
     PHAsset *asset = [results firstObject];
@@ -144,59 +147,67 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
     }
     [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
       if (result) {
-        RCTDispatchCallbackOnMainQueue(callback, nil, result);
+        RCTDispatchCallbackOnMainQueue(completion, nil, result);
       } else {
         NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
         NSError *error = RCTErrorWithMessage(errorText);
-        RCTDispatchCallbackOnMainQueue(callback, error, nil);
+        RCTDispatchCallbackOnMainQueue(completion, error, nil);
         return;
       }
     }];
+    return ^{};
   } else if ([imageTag hasPrefix:@"http"]) {
     NSURL *url = [NSURL URLWithString:imageTag];
     if (!url) {
       NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
-      RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
-      return;
+      RCTDispatchCallbackOnMainQueue(completion, RCTErrorWithMessage(errorMessage), nil);
+      return ^{};
     }
-    if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
-      [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
+    if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
+      return [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:progress block:^(NSData *data, NSError *error) {
         id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
         if (!image && !error) {
           NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
           error = RCTErrorWithMessage(errorMessage);
         }
-        RCTDispatchCallbackOnMainQueue(callback, error, image);
+        RCTDispatchCallbackOnMainQueue(completion, error, image);
       }];
     } else {
-      [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:NULL block:^(UIImage *image, NSError *error) {
-         RCTDispatchCallbackOnMainQueue(callback, error, image);
+      return [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:progress block:^(UIImage *image, NSError *error) {
+         RCTDispatchCallbackOnMainQueue(completion, error, image);
       }];
     }
-  } else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
+  } else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
     id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
     if (image) {
-      RCTDispatchCallbackOnMainQueue(callback, nil, image);
+      RCTDispatchCallbackOnMainQueue(completion, nil, image);
     } else {
       NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
       NSError *error = RCTErrorWithMessage(errorMessage);
-      RCTDispatchCallbackOnMainQueue(callback, error, nil);
+      RCTDispatchCallbackOnMainQueue(completion, error, nil);
     }
+    return ^{};
   } else {
     UIImage *image = [RCTConvert UIImage:imageTag];
     if (image) {
-      RCTDispatchCallbackOnMainQueue(callback, nil, image);
+      RCTDispatchCallbackOnMainQueue(completion, nil, image);
     } else {
       NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
       NSError *error = RCTErrorWithMessage(errorMessage);
-      RCTDispatchCallbackOnMainQueue(callback, error, nil);
+      RCTDispatchCallbackOnMainQueue(completion, error, nil);
     }
+    return ^{};
   }
 }
 
 + (BOOL)isAssetLibraryImage:(NSString *)imageTag
 {
-  return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph:"];
+  return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
+}
+
++ (BOOL)isRemoteImage:(NSString *)imageTag
+{
+  return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
 }
 
 @end
diff --git a/Libraries/Image/RCTStaticImage.h b/Libraries/Image/RCTImageView.h
similarity index 72%
rename from Libraries/Image/RCTStaticImage.h
rename to Libraries/Image/RCTImageView.h
index c8f46a302..fff7c96a0 100644
--- a/Libraries/Image/RCTStaticImage.h
+++ b/Libraries/Image/RCTImageView.h
@@ -9,9 +9,14 @@
 
 #import <UIKit/UIKit.h>
 
-@interface RCTStaticImage : UIImageView
+@class RCTBridge;
+
+@interface RCTImageView : UIImageView
+
+- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
 
 @property (nonatomic, assign) UIEdgeInsets capInsets;
+@property (nonatomic, strong) UIImage *defaultImage;
 @property (nonatomic, assign) UIImageRenderingMode renderingMode;
 @property (nonatomic, copy) NSString *src;
 
diff --git a/Libraries/Image/RCTStaticImage.m b/Libraries/Image/RCTImageView.m
similarity index 59%
rename from Libraries/Image/RCTStaticImage.m
rename to Libraries/Image/RCTImageView.m
index 0e9d4b608..8773aebb7 100644
--- a/Libraries/Image/RCTStaticImage.m
+++ b/Libraries/Image/RCTImageView.m
@@ -7,16 +7,41 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTStaticImage.h"
+#import "RCTImageView.h"
 
+#import "RCTBridge.h"
 #import "RCTConvert.h"
+#import "RCTEventDispatcher.h"
 #import "RCTGIFImage.h"
 #import "RCTImageLoader.h"
 #import "RCTUtils.h"
 
 #import "UIView+React.h"
 
-@implementation RCTStaticImage
+@interface RCTImageView ()
+
+@property (nonatomic, assign) BOOL onLoadStart;
+@property (nonatomic, assign) BOOL onProgress;
+@property (nonatomic, assign) BOOL onError;
+@property (nonatomic, assign) BOOL onLoad;
+@property (nonatomic, assign) BOOL onLoadEnd;
+
+@end
+
+@implementation RCTImageView
+{
+  RCTBridge *_bridge;
+}
+
+- (instancetype)initWithBridge:(RCTBridge *)bridge
+{
+  if ((self = [super init])) {
+    _bridge = bridge;
+  }
+  return self;
+}
+
+RCT_NOT_IMPLEMENTED(-init)
 
 - (void)_updateImage
 {
@@ -45,7 +70,7 @@
 - (void)setImage:(UIImage *)image
 {
   if (image != super.image) {
-    super.image = image;
+    super.image = image ?: _defaultImage;
     [self _updateImage];
   }
 }
@@ -77,19 +102,55 @@
 - (void)reloadImage
 {
   if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
+
+    if (_onLoadStart) {
+      NSDictionary *event = @{ @"target": self.reactTag };
+      [_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
+    }
+
+    RCTImageLoaderProgressBlock progressHandler = nil;
+    if (_onProgress) {
+      progressHandler =  ^(int64_t loaded, int64_t total) {
+        NSDictionary *event = @{
+          @"target": self.reactTag,
+          @"loaded": @(loaded),
+          @"total": @(total),
+        };
+        [_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
+      };
+    }
+
     [RCTImageLoader loadImageWithTag:_src
                                 size:self.bounds.size
                                scale:RCTScreenScale()
-                          resizeMode:self.contentMode callback:^(NSError *error, id image) {
-      if (error) {
-        RCTLogWarn(@"%@", error.localizedDescription);
-      }
+                          resizeMode:self.contentMode
+                       progressBlock:progressHandler
+                     completionBlock:^(NSError *error, id image) {
+
       if ([image isKindOfClass:[CAAnimation class]]) {
         [self.layer addAnimation:image forKey:@"contents"];
       } else {
         [self.layer removeAnimationForKey:@"contents"];
         self.image = image;
       }
+      if (error) {
+        if (_onError) {
+          NSDictionary *event = @{
+            @"target": self.reactTag,
+            @"error": error.localizedDescription,
+          };
+          [_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
+        }
+      } else {
+        if (_onLoad) {
+          NSDictionary *event = @{ @"target": self.reactTag };
+          [_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
+        }
+      }
+      if (_onLoadEnd) {
+        NSDictionary *event = @{ @"target": self.reactTag };
+        [_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
+      }
     }];
   } else {
     [self.layer removeAnimationForKey:@"contents"];
@@ -102,7 +163,7 @@
   [super reactSetFrame:frame];
   if (self.image == nil) {
     [self reloadImage];
-  } else if ([RCTImageLoader isAssetLibraryImage:_src]) {
+  } else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
     CGSize imageSize = {
       self.image.size.width / RCTScreenScale(),
       self.image.size.height / RCTScreenScale()
diff --git a/Libraries/Image/RCTStaticImageManager.h b/Libraries/Image/RCTImageViewManager.h
similarity index 87%
rename from Libraries/Image/RCTStaticImageManager.h
rename to Libraries/Image/RCTImageViewManager.h
index b02f9fe11..4e8d3fac4 100644
--- a/Libraries/Image/RCTStaticImageManager.h
+++ b/Libraries/Image/RCTImageViewManager.h
@@ -9,6 +9,6 @@
 
 #import "RCTViewManager.h"
 
-@interface RCTStaticImageManager : RCTViewManager
+@interface RCTImageViewManager : RCTViewManager
 
 @end
diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m
new file mode 100644
index 000000000..28f93466a
--- /dev/null
+++ b/Libraries/Image/RCTImageViewManager.m
@@ -0,0 +1,57 @@
+/**
+ * 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 "RCTImageViewManager.h"
+
+#import <UIKit/UIKit.h>
+
+#import "RCTConvert.h"
+#import "RCTImageView.h"
+
+@implementation RCTImageViewManager
+
+RCT_EXPORT_MODULE()
+
+- (UIView *)view
+{
+  return [[RCTImageView alloc] initWithBridge:self.bridge];
+}
+
+RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
+RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
+RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
+RCT_EXPORT_VIEW_PROPERTY(src, NSString)
+RCT_EXPORT_VIEW_PROPERTY(onLoadStart, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onError, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, BOOL)
+RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
+{
+  if (json) {
+    view.renderingMode = UIImageRenderingModeAlwaysTemplate;
+    view.tintColor = [RCTConvert UIColor:json];
+  } else {
+    view.renderingMode = defaultView.renderingMode;
+    view.tintColor = defaultView.tintColor;
+  }
+}
+
+- (NSDictionary *)customDirectEventTypes
+{
+  return @{
+    @"loadStart": @{ @"registrationName": @"onLoadStart" },
+    @"progress":  @{ @"registrationName": @"onProgress" },
+    @"error":     @{ @"registrationName": @"onError" },
+    @"load":      @{ @"registrationName": @"onLoad" },
+    @"loadEnd":   @{ @"registrationName": @"onLoadEnd" },
+  };
+}
+
+@end
diff --git a/Libraries/Image/RCTNetworkImageView.h b/Libraries/Image/RCTNetworkImageView.h
deleted file mode 100644
index 6dd73e9aa..000000000
--- a/Libraries/Image/RCTNetworkImageView.h
+++ /dev/null
@@ -1,46 +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 <UIKit/UIKit.h>
-
-@class RCTEventDispatcher;
-@class RCTImageDownloader;
-
-@interface RCTNetworkImageView : UIView
-
-- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
-                        imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
-
-/**
- * An image that will appear while the view is loading the image from the network,
- * or when imageURL is nil. Defaults to nil.
- */
-@property (nonatomic, strong) UIImage *defaultImage;
-
-/**
- * Specify a URL for an image. The image will be asynchronously loaded and displayed.
- */
-@property (nonatomic, strong) NSURL *imageURL;
-
-/**
- * Whether the image should be masked with this view's tint color.
- */
-@property (nonatomic, assign) BOOL tinted;
-
-/**
- * By default, changing imageURL will reset whatever existing image was present
- * and revert to defaultImage while the new image loads. In certain obscure cases you
- * may want to disable this behavior and instead keep displaying the previous image
- * while the new one loads. In this case, pass NO for resetToDefaultImageWhileLoading.
- * (If you set imageURL to nil, however, resetToDefaultImageWhileLoading is ignored;
- * that will always reset to the default image.)
- */
-- (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)reset;
-
-@end
diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m
deleted file mode 100644
index 20d297b46..000000000
--- a/Libraries/Image/RCTNetworkImageView.m
+++ /dev/null
@@ -1,220 +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 "RCTNetworkImageView.h"
-
-#import "RCTAssert.h"
-#import "RCTConvert.h"
-#import "RCTGIFImage.h"
-#import "RCTImageDownloader.h"
-#import "RCTUtils.h"
-#import "RCTBridgeModule.h"
-#import "RCTEventDispatcher.h"
-#import "UIView+React.h"
-
-@implementation RCTNetworkImageView
-{
-  BOOL _deferred;
-  BOOL _progressHandlerRegistered;
-  NSURL *_imageURL;
-  NSURL *_deferredImageURL;
-  NSUInteger _deferSentinel;
-  RCTImageDownloader *_imageDownloader;
-  id _downloadToken;
-  RCTEventDispatcher *_eventDispatcher;
-}
-
-- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher imageDownloader:(RCTImageDownloader *)imageDownloader
-{
-  if ((self = [super initWithFrame:CGRectZero])) {
-    _eventDispatcher = eventDispatcher;
-    _progressHandlerRegistered = NO;
-    _deferSentinel = 0;
-    _imageDownloader = imageDownloader;
-    self.userInteractionEnabled = NO;
-    self.contentMode = UIViewContentModeScaleAspectFill;
-  }
-  return self;
-}
-
-RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame)
-RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
-
-- (NSURL *)imageURL
-{
-  // We clear our imageURL when we are not in a window for a while,
-  // to make sure we don't consume network resources while offscreen.
-  // However we don't want to expose this hackery externally.
-  return _deferred ? _deferredImageURL : _imageURL;
-}
-
-- (void)setBackgroundColor:(UIColor *)backgroundColor
-{
-  super.backgroundColor = backgroundColor;
-  [self _updateImage];
-}
-
-- (void)setTintColor:(UIColor *)tintColor
-{
-  super.tintColor = tintColor;
-  [self _updateImage];
-}
-
-- (void)setProgressHandlerRegistered:(BOOL)progressHandlerRegistered
-{
-  _progressHandlerRegistered = progressHandlerRegistered;
-}
-
-- (void)reactSetFrame:(CGRect)frame
-{
-  [super reactSetFrame:frame];
-  [self _updateImage];
-}
-
-- (void)_updateImage
-{
-  [self setImageURL:_imageURL resetToDefaultImageWhileLoading:NO];
-}
-
-- (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)reset
-{
-  if (![_imageURL isEqual:imageURL] && _downloadToken) {
-    [_imageDownloader cancelDownload:_downloadToken];
-    NSDictionary *event = @{ @"target": self.reactTag };
-    [_eventDispatcher sendInputEventWithName:@"loadAbort" body:event];
-    _downloadToken = nil;
-  }
-
-  _imageURL = imageURL;
-
-  if (_deferred) {
-    _deferredImageURL = imageURL;
-  } else {
-    if (!imageURL) {
-      self.layer.contents = nil;
-      return;
-    }
-    if (reset) {
-      self.layer.contentsScale = _defaultImage.scale;
-      self.layer.contents = (__bridge id)_defaultImage.CGImage;
-      self.layer.minificationFilter = kCAFilterTrilinear;
-      self.layer.magnificationFilter = kCAFilterTrilinear;
-    }
-    [_eventDispatcher sendInputEventWithName:@"loadStart" body:@{ @"target": self.reactTag }];
-
-    RCTDataProgressBlock progressHandler = ^(int64_t written, int64_t total) {
-      if (_progressHandlerRegistered) {
-        NSDictionary *event = @{
-          @"target": self.reactTag,
-          @"written": @(written),
-          @"total": @(total),
-        };
-        [_eventDispatcher sendInputEventWithName:@"loadProgress" body:event];
-      }
-    };
-
-    void (^errorHandler)(NSString *errorDescription) = ^(NSString *errorDescription) {
-      NSDictionary *event = @{
-        @"target": self.reactTag,
-        @"error": errorDescription,
-      };
-      [_eventDispatcher sendInputEventWithName:@"loadError" body:event];
-    };
-
-    void (^loadEndHandler)(void) = ^(void) {
-      NSDictionary *event = @{ @"target": self.reactTag };
-      [_eventDispatcher sendInputEventWithName:@"loaded" body:event];
-    };
-
-    if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
-      _downloadToken = [_imageDownloader downloadDataForURL:imageURL progressBlock:progressHandler block:^(NSData *data, NSError *error) {
-        if (data) {
-          dispatch_async(dispatch_get_main_queue(), ^{
-            if (imageURL != self.imageURL) {
-              // Image has changed
-              return;
-            }
-            CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
-            self.layer.contentsScale = 1.0;
-            self.layer.minificationFilter = kCAFilterLinear;
-            self.layer.magnificationFilter = kCAFilterLinear;
-            [self.layer addAnimation:animation forKey:@"contents"];
-            loadEndHandler();
-          });
-        } else if (error) {
-          errorHandler([error localizedDescription]);
-        }
-      }];
-    } else {
-      _downloadToken = [_imageDownloader downloadImageForURL:imageURL
-                                                        size:self.bounds.size
-                                                       scale:RCTScreenScale()
-                                                  resizeMode:self.contentMode
-                                                   tintColor:_tinted ? self.tintColor : nil
-                                             backgroundColor:self.backgroundColor
-                                                  progressBlock:progressHandler
-                                                          block:^(UIImage *image, NSError *error) {
-        if (image) {
-          dispatch_async(dispatch_get_main_queue(), ^{
-            if (imageURL != self.imageURL) {
-              // Image has changed
-              return;
-            }
-            [self.layer removeAnimationForKey:@"contents"];
-            self.layer.contentsScale = image.scale;
-            self.layer.contents = (__bridge id)image.CGImage;
-            loadEndHandler();
-          });
-        } else if (error) {
-          errorHandler([error localizedDescription]);
-        }
-      }];
-    }
-  }
-}
-
-- (void)setImageURL:(NSURL *)imageURL
-{
-  [self setImageURL:imageURL resetToDefaultImageWhileLoading:YES];
-}
-
-- (void)willMoveToWindow:(UIWindow *)newWindow
-{
-  [super willMoveToWindow:newWindow];
-  if (newWindow != nil && _deferredImageURL) {
-    // Immediately exit deferred mode and restore the imageURL that we saved when we went offscreen.
-    [self setImageURL:_deferredImageURL resetToDefaultImageWhileLoading:YES];
-    _deferredImageURL = nil;
-  }
-}
-
-- (void)_enterDeferredModeIfNeededForSentinel:(NSUInteger)sentinel
-{
-  if (self.window == nil && _deferSentinel == sentinel) {
-    _deferred = YES;
-    [_imageDownloader cancelDownload:_downloadToken];
-    _downloadToken = nil;
-    _deferredImageURL = _imageURL;
-    _imageURL = nil;
-  }
-}
-
-- (void)didMoveToWindow
-{
-  [super didMoveToWindow];
-  if (self.window == nil) {
-    __weak RCTNetworkImageView *weakSelf = self;
-    NSUInteger sentinelAtDispatchTime = ++_deferSentinel;
-    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
-      [weakSelf _enterDeferredModeIfNeededForSentinel:sentinelAtDispatchTime];
-    });
-  }
-}
-
-@end
diff --git a/Libraries/Image/RCTNetworkImageViewManager.h b/Libraries/Image/RCTNetworkImageViewManager.h
deleted file mode 100644
index 3176ce896..000000000
--- a/Libraries/Image/RCTNetworkImageViewManager.h
+++ /dev/null
@@ -1,15 +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 "RCTViewManager.h"
-
-@interface RCTNetworkImageViewManager : RCTViewManager
-
-@end
-
diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m
deleted file mode 100644
index f42ef48f1..000000000
--- a/Libraries/Image/RCTNetworkImageViewManager.m
+++ /dev/null
@@ -1,56 +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 "RCTNetworkImageViewManager.h"
-
-#import "RCTBridge.h"
-#import "RCTConvert.h"
-#import "RCTImageDownloader.h"
-#import "RCTNetworkImageView.h"
-#import "RCTUtils.h"
-
-@implementation RCTNetworkImageViewManager
-
-RCT_EXPORT_MODULE()
-
-@synthesize bridge = _bridge;
-@synthesize methodQueue = _methodQueue;
-
-- (UIView *)view
-{
-  return [[RCTNetworkImageView alloc] initWithEventDispatcher:self.bridge.eventDispatcher imageDownloader:[RCTImageDownloader sharedInstance]];
-}
-
-RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
-RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
-RCT_EXPORT_VIEW_PROPERTY(progressHandlerRegistered, BOOL)
-RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTNetworkImageView)
-{
-  if (json) {
-    view.tinted = YES;
-    view.tintColor = [RCTConvert UIColor:json];
-  } else {
-    view.tinted = defaultView.tinted;
-    view.tintColor = defaultView.tintColor;
-  }
-}
-
-- (NSDictionary *)customDirectEventTypes
-{
-  return @{
-    @"loadStart":    @{ @"registrationName": @"onLoadStart" },
-    @"loadProgress": @{ @"registrationName": @"onLoadProgress" },
-    @"loaded":       @{ @"registrationName": @"onLoadEnd" },
-    @"loadError":    @{ @"registrationName": @"onLoadError" },
-    @"loadAbort":    @{ @"registrationName": @"onLoadAbort" },
-  };
-}
-
-@end
diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m
deleted file mode 100644
index 7b3fb16db..000000000
--- a/Libraries/Image/RCTStaticImageManager.m
+++ /dev/null
@@ -1,41 +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 "RCTStaticImageManager.h"
-
-#import <UIKit/UIKit.h>
-
-#import "RCTConvert.h"
-#import "RCTStaticImage.h"
-
-@implementation RCTStaticImageManager
-
-RCT_EXPORT_MODULE()
-
-- (UIView *)view
-{
-  return [[RCTStaticImage alloc] init];
-}
-
-RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
-RCT_REMAP_VIEW_PROPERTY(imageTag, src, NSString)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
-RCT_EXPORT_VIEW_PROPERTY(src, NSString)
-RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
-{
-  if (json) {
-    view.renderingMode = UIImageRenderingModeAlwaysTemplate;
-    view.tintColor = [RCTConvert UIColor:json];
-  } else {
-    view.renderingMode = defaultView.renderingMode;
-    view.tintColor = defaultView.tintColor;
-  }
-}
-
-@end
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index 724ace6e6..d1af57705 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -1408,8 +1408,13 @@ RCT_EXPORT_METHOD(clearJSResponder)
   for (RCTViewManager *manager in _viewManagers.allValues) {
     if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
       NSDictionary *eventTypes = [manager customDirectEventTypes];
-      for (NSString *eventName in eventTypes) {
-        RCTAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
+      if (RCT_DEV) {
+        for (NSString *eventName in eventTypes) {
+          id eventType = customDirectEventTypes[eventName];
+          RCTAssert(!eventType || [eventType isEqual:eventTypes[eventName]],
+                    @"Event '%@' registered multiple times with different "
+                    "properties.", eventName);
+        }
       }
       [customDirectEventTypes addEntriesFromDictionary:eventTypes];
     }

From 3c78aa6d256f3e603241ff5f2a80fffecebb00c7 Mon Sep 17 00:00:00 2001
From: DengYun <tdzl2003@gmail.com>
Date: Fri, 17 Jul 2015 09:05:26 -0700
Subject: [PATCH 03/33] [Bridge] remove unused semaphore

Summary:
dispatch_semaphore_wait with DISPATCH_TIME_NOW don't wait for the semaphore. It just test whether there's a signal and return the result.

So the line "dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);" does nothing here. It seems like a bug, but each executing in js thread will be queued, so it's safe to remove the unused semaphore instead of wait for it.
Closes https://github.com/facebook/react-native/pull/1915
Github Author: DengYun <tdzl2003@gmail.com>
---
 React/Base/RCTBatchedBridge.m | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index 138e11695..98fdb5115 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -235,18 +235,13 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
   NSString *configJSON = RCTJSONStringify(@{
     @"remoteModuleConfig": config,
   }, NULL);
-  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   [_javaScriptExecutor injectJSONText:configJSON
-                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:
-   ^(NSError *error) {
-     if (error) {
-       [[RCTRedBox sharedInstance] showError:error];
-     }
-
-     dispatch_semaphore_signal(semaphore);
-   }];
-
-  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
+                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
+                             callback:^(NSError *error) {
+    if (error) {
+      [[RCTRedBox sharedInstance] showError:error];
+    }
+  }];
 
   NSURL *bundleURL = _parentBridge.bundleURL;
   if (_javaScriptExecutor == nil) {

From b56717ede9da9cf073152c146529c0803f604b1f Mon Sep 17 00:00:00 2001
From: Bill Fisher <bfisher@fb.com>
Date: Fri, 17 Jul 2015 12:58:39 -0700
Subject: [PATCH 04/33] [Storyline] SliderIOS responds to
 UIControlEventTouchUpOutside

---
 React/Views/RCTSliderManager.m | 1 +
 1 file changed, 1 insertion(+)

diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m
index f57e1f362..3dbae6916 100644
--- a/React/Views/RCTSliderManager.m
+++ b/React/Views/RCTSliderManager.m
@@ -23,6 +23,7 @@ RCT_EXPORT_MODULE()
   RCTSlider *slider = [[RCTSlider alloc] init];
   [slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
   [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside];
+  [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpOutside];
   return slider;
 }
 

From c4389c006f77728e0642e5804fd8bae430f65caa Mon Sep 17 00:00:00 2001
From: Eric Vicenti <evv@fb.com>
Date: Fri, 17 Jul 2015 13:21:25 -0700
Subject: [PATCH 05/33] [ReactNative] Max RedBox windowLevel to always appear
 on top

---
 React/Base/RCTRedBox.m | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m
index a707c6532..12a36deec 100644
--- a/React/Base/RCTRedBox.m
+++ b/React/Base/RCTRedBox.m
@@ -36,7 +36,7 @@
 {
   if ((self = [super initWithFrame:frame])) {
     _redColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
-    self.windowLevel = UIWindowLevelStatusBar + 5;
+    self.windowLevel = CGFLOAT_MAX;
     self.backgroundColor = _redColor;
     self.hidden = YES;
 

From c28d33f3a23202c436cf794de9397050ec50923b Mon Sep 17 00:00:00 2001
From: Eric Vicenti <evv@fb.com>
Date: Fri, 17 Jul 2015 15:20:04 -0700
Subject: [PATCH 06/33] [ReactNative] Remove dependencies on Navigator idStack

Summary:
idStack is going away soon. This removes all references to it. Looking at the internal state of navigator will make you have a bad time.

The biggest change is switching to the new component-freezing techinique in the navigation bars. This way we avoid dependence on the idStack to provide a scalar ID for each route.
---
 .../NavigatorBreadcrumbNavigationBar.js       | 136 +++++++-----------
 .../Navigator/NavigatorNavigationBar.js       |  60 ++++----
 2 files changed, 87 insertions(+), 109 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
index 4b8724cbf..aa9eb64ce 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
@@ -33,6 +33,8 @@ var StaticContainer = require('StaticContainer.react');
 var StyleSheet = require('StyleSheet');
 var View = require('View');
 
+var { Map } = require('immutable');
+
 var invariant = require('invariant');
 
 var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
@@ -86,7 +88,6 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     }),
     navState: React.PropTypes.shape({
       routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
-      idStack: React.PropTypes.arrayOf(React.PropTypes.number),
       presentedIndex: React.PropTypes.number,
     }),
     style: View.propTypes.style,
@@ -173,11 +174,19 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     }
   },
 
+  componentWillMount: function() {
+    this._descriptors = {
+      crumb: new Map(),
+      title: new Map(),
+      right: new Map(),
+    };
+  },
+
   render: function() {
     var navState = this.props.navState;
-    var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb);
-    var titles = navState.routeStack.map(this._renderOrReturnTitle);
-    var buttons = navState.routeStack.map(this._renderOrReturnRightButton);
+    var icons = navState && navState.routeStack.map(this._getBreadcrumb);
+    var titles = navState.routeStack.map(this._getTitle);
+    var buttons = navState.routeStack.map(this._getRightButton);
     return (
       <View style={[styles.breadCrumbContainer, this.props.style]}>
         {titles}
@@ -187,104 +196,69 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     );
   },
 
-  _renderOrReturnBreadcrumb: function(route, index) {
-    var uid = this.props.navState.idStack[index];
-    var navBarRouteMapper = this.props.routeMapper;
-    var navOps = this.props.navigator;
-    var alreadyRendered = this.refs['crumbContainer' + uid];
-    if (alreadyRendered) {
-      // Don't bother re-calculating the children
-      return (
-        <StaticContainer
-          ref={'crumbContainer' + uid}
-          key={'crumbContainer' + uid}
-          shouldUpdate={false}
-        />
-      );
+  _getBreadcrumb: function(route, index) {
+    if (this._descriptors.crumb.has(route)) {
+      return this._descriptors.crumb.get(route);
     }
+
+    var navBarRouteMapper = this.props.routeMapper;
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
-    return (
-      <StaticContainer
-        ref={'crumbContainer' + uid}
-        key={'crumbContainer' + uid}
-        shouldUpdate={false}>
-        <View ref={'crumb_' + index} style={firstStyles.Crumb}>
-          <View ref={'icon_' + index} style={firstStyles.Icon}>
-            {navBarRouteMapper.iconForRoute(route, navOps)}
-          </View>
-          <View ref={'separator_' + index} style={firstStyles.Separator}>
-            {navBarRouteMapper.separatorForRoute(route, navOps)}
-          </View>
+
+    var breadcrumbDescriptor = (
+      <View ref={'crumb_' + index} style={firstStyles.Crumb}>
+        <View ref={'icon_' + index} style={firstStyles.Icon}>
+          {navBarRouteMapper.iconForRoute(route, this.props.navigator)}
         </View>
-      </StaticContainer>
+        <View ref={'separator_' + index} style={firstStyles.Separator}>
+          {navBarRouteMapper.separatorForRoute(route, this.props.navigator)}
+        </View>
+      </View>
     );
+
+    this._descriptors.crumb = this._descriptors.crumb.set(route, breadcrumbDescriptor);
+    return breadcrumbDescriptor;
   },
 
-  _renderOrReturnTitle: function(route, index) {
-    var navState = this.props.navState;
-    var uid = navState.idStack[index];
-    var alreadyRendered = this.refs['titleContainer' + uid];
-    if (alreadyRendered) {
-      // Don't bother re-calculating the children
-      return (
-        <StaticContainer
-          ref={'titleContainer' + uid}
-          key={'titleContainer' + uid}
-          shouldUpdate={false}
-        />
-      );
+  _getTitle: function(route, index) {
+    if (this._descriptors.title.has(route)) {
+      return this._descriptors.title.get(route);
     }
-    var navBarRouteMapper = this.props.routeMapper;
-    var titleContent = navBarRouteMapper.titleContentForRoute(
-      navState.routeStack[index],
+
+    var titleContent = this.props.routeMapper.titleContentForRoute(
+      this.props.navState.routeStack[index],
       this.props.navigator
     );
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
-    return (
-      <StaticContainer
-        ref={'titleContainer' + uid}
-        key={'titleContainer' + uid}
-        shouldUpdate={false}>
-        <View ref={'title_' + index} style={firstStyles.Title}>
-          {titleContent}
-        </View>
-      </StaticContainer>
+
+    var titleDescriptor = (
+      <View ref={'title_' + index} style={firstStyles.Title}>
+        {titleContent}
+      </View>
     );
+    this._descriptors.title = this._descriptors.title.set(route, titleDescriptor);
+    return titleDescriptor;
   },
 
-  _renderOrReturnRightButton: function(route, index) {
-    var navState = this.props.navState;
-    var navBarRouteMapper = this.props.routeMapper;
-    var uid = navState.idStack[index];
-    var alreadyRendered = this.refs['rightContainer' + uid];
-    if (alreadyRendered) {
-      // Don't bother re-calculating the children
-      return (
-        <StaticContainer
-          ref={'rightContainer' + uid}
-          key={'rightContainer' + uid}
-          shouldUpdate={false}
-        />
-      );
+  _getRightButton: function(route, index) {
+    if (this._descriptors.right.has(route)) {
+      return this._descriptors.right.get(route);
     }
-    var rightContent = navBarRouteMapper.rightContentForRoute(
-      navState.routeStack[index],
+    var rightContent = this.props.routeMapper.rightContentForRoute(
+      this.props.navState.routeStack[index],
       this.props.navigator
     );
     if (!rightContent) {
+      this._descriptors.right = this._descriptors.right.set(route, null);
       return null;
     }
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
-    return (
-      <StaticContainer
-        ref={'rightContainer' + uid}
-        key={'rightContainer' + uid}
-        shouldUpdate={false}>
-        <View ref={'right_' + index} style={firstStyles.RightItem}>
-          {rightContent}
-        </View>
-      </StaticContainer>
+    var rightButtonDescriptor = (
+      <View ref={'right_' + index} style={firstStyles.RightItem}>
+        {rightContent}
+      </View>
     );
+    this._descriptors.right = this._descriptors.right.set(route, rightButtonDescriptor);
+    return rightButtonDescriptor;
   },
 });
 
diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
index 172819de2..7b69e2635 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
@@ -32,6 +32,8 @@ var StaticContainer = require('StaticContainer.react');
 var StyleSheet = require('StyleSheet');
 var View = require('View');
 
+var { Map } = require('immutable');
+
 var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
 
 var navStatePresentedIndex = function(navState) {
@@ -53,7 +55,6 @@ var NavigatorNavigationBar = React.createClass({
     }),
     navState: React.PropTypes.shape({
       routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
-      idStack: React.PropTypes.arrayOf(React.PropTypes.number),
       presentedIndex: React.PropTypes.number,
     }),
     style: View.propTypes.style,
@@ -63,6 +64,16 @@ var NavigatorNavigationBar = React.createClass({
     Styles: NavigatorNavigationBarStyles,
   },
 
+  componentWillMount: function() {
+    this._components = {};
+    this._descriptors = {};
+
+    COMPONENT_NAMES.forEach(componentName => {
+      this._components[componentName] = new Map();
+      this._descriptors[componentName] = new Map();
+    });
+  },
+
   _getReusableProps: function(
     /*string*/componentName,
     /*number*/index
@@ -104,7 +115,7 @@ var NavigatorNavigationBar = React.createClass({
     }
 
     COMPONENT_NAMES.forEach(function (componentName) {
-      var component = this.refs[componentName + index];
+      var component = this._components[componentName].get(this.props.navState.routeStack[index]);
       var props = this._getReusableProps(componentName, index);
       if (component && interpolate[componentName](props.style, amount)) {
         component.setNativeProps(props);
@@ -128,7 +139,7 @@ var NavigatorNavigationBar = React.createClass({
     var navState = this.props.navState;
     var components = COMPONENT_NAMES.map(function (componentName) {
       return navState.routeStack.map(
-        this._renderOrReturnComponent.bind(this, componentName)
+        this._getComponent.bind(this, componentName)
       );
     }, this);
 
@@ -139,28 +150,19 @@ var NavigatorNavigationBar = React.createClass({
     );
   },
 
-  _renderOrReturnComponent: function(
+  _getComponent: function(
     /*string*/componentName,
     /*object*/route,
     /*number*/index
-  ) /*object*/ {
-    var navState = this.props.navState;
-    var uid = navState.idStack[index];
-    var containerRef = componentName + 'Container' + uid;
-    var alreadyRendered = this.refs[containerRef];
-    if (alreadyRendered) {
-      // Don't bother re-calculating the children
-      return (
-        <StaticContainer
-          ref={containerRef}
-          key={containerRef}
-          shouldUpdate={false}
-        />
-      );
+  ) /*?Object*/ {
+    if (this._descriptors[componentName].includes(route)) {
+      return this._descriptors[componentName].get(route);
     }
 
+    var rendered = null;
+
     var content = this.props.routeMapper[componentName](
-      navState.routeStack[index],
+      this.props.navState.routeStack[index],
       this.props.navigator,
       index,
       this.props.navState
@@ -171,16 +173,18 @@ var NavigatorNavigationBar = React.createClass({
 
     var initialStage = index === navStatePresentedIndex(this.props.navState) ?
       NavigatorNavigationBarStyles.Stages.Center : NavigatorNavigationBarStyles.Stages.Left;
-    return (
-      <StaticContainer
-        ref={containerRef}
-        key={containerRef}
-        shouldUpdate={false}>
-        <View ref={componentName + index} style={initialStage[componentName]}>
-          {content}
-        </View>
-      </StaticContainer>
+    rendered = (
+      <View
+        ref={(ref) => {
+          this._components[componentName] = this._components[componentName].set(route, ref);
+        }}
+        style={initialStage[componentName]}>
+        {content}
+      </View>
     );
+
+    this._descriptors[componentName] = this._descriptors[componentName].set(route, rendered);
+    return rendered;
   },
 
 });

From 4a262e0f2b0f5e428d0599dc3c6525ebe14a877c Mon Sep 17 00:00:00 2001
From: Elliot Lynde <elynde@fb.com>
Date: Fri, 17 Jul 2015 16:47:36 -0700
Subject: [PATCH 07/33] [React Native][Pokes Dashboard] Fix crash

---
 Libraries/Components/Touchable/TouchableHighlight.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index dcbfbeee1..47d24b811 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -181,6 +181,10 @@ var TouchableHighlight = React.createClass({
   },
 
   _showUnderlay: function() {
+    if (!this.isMounted()) {
+      return;
+    }
+
     this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
     this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
     this.props.onShowUnderlay && this.props.onShowUnderlay();

From f2d65ea85be5aa0d7bdf87a639d869afc18245d1 Mon Sep 17 00:00:00 2001
From: Tadeu Zagallo <tadeuzagallo@fb.com>
Date: Mon, 20 Jul 2015 02:34:11 -0700
Subject: [PATCH 08/33] [ReactNative] Fix RCTJavaScriptContext deallocation
 (attempt #2)

Summary:
The context wasn't being explicitly released before, since it'd be immediately
released. Now that the executors are bridge modules, it was only being deallocated
when the modules were released, what caused the threads to not be released at all.
---
 React/Base/RCTBatchedBridge.m        | 65 ++++++++++------------------
 React/Executors/RCTContextExecutor.m |  1 +
 2 files changed, 25 insertions(+), 41 deletions(-)

diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index 98fdb5115..7975b5e7c 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -344,53 +344,36 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
     RCTLatestExecutor = nil;
   }
 
-  void (^mainThreadInvalidate)(void) = ^{
-    RCTAssertMainThread();
+  [_mainDisplayLink invalidate];
+  _mainDisplayLink = nil;
 
-    [_mainDisplayLink invalidate];
-    _mainDisplayLink = nil;
-
-    // Invalidate modules
-    dispatch_group_t group = dispatch_group_create();
-    for (RCTModuleData *moduleData in _modules) {
-      if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
-        [moduleData dispatchBlock:^{
-          [(id<RCTInvalidating>)moduleData.instance invalidate];
-        } dispatchGroup:group];
-      }
-      moduleData.queue = nil;
+  // Invalidate modules
+  dispatch_group_t group = dispatch_group_create();
+  for (RCTModuleData *moduleData in _modules) {
+    if (moduleData.instance == _javaScriptExecutor) {
+      continue;
     }
-    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
-      _modules = nil;
-      _modulesByName = nil;
-      _frameUpdateObservers = nil;
-    });
-  };
 
-  if (!_javaScriptExecutor) {
-
-    // No JS thread running
-    mainThreadInvalidate();
-    return;
+    if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
+      [moduleData dispatchBlock:^{
+        [(id<RCTInvalidating>)moduleData.instance invalidate];
+      } dispatchGroup:group];
+    }
+    moduleData.queue = nil;
   }
+  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
+    [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+      [_jsDisplayLink invalidate];
+      _jsDisplayLink = nil;
 
-  [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+      [_javaScriptExecutor invalidate];
+      _javaScriptExecutor = nil;
+    }];
 
-    /**
-     * JS Thread deallocations
-     */
-    [_javaScriptExecutor invalidate];
-    _javaScriptExecutor = nil;
-
-    [_jsDisplayLink invalidate];
-    _jsDisplayLink = nil;
-
-    /**
-     * Main Thread deallocations
-     */
-    dispatch_async(dispatch_get_main_queue(), mainThreadInvalidate);
-
-  }];
+    _modules = nil;
+    _modulesByName = nil;
+    _frameUpdateObservers = nil;
+  });
 }
 
 #pragma mark - RCTBridge methods
diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m
index 5fc529645..173179c5a 100644
--- a/React/Executors/RCTContextExecutor.m
+++ b/React/Executors/RCTContextExecutor.m
@@ -320,6 +320,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
                    onThread:_javaScriptThread
                  withObject:nil
               waitUntilDone:NO];
+  _context = nil;
 }
 
 - (void)dealloc

From 5db42643cf9d41fe5c73c47d9e04c502b1b509cb Mon Sep 17 00:00:00 2001
From: Nick Lockwood <nicklockwood@fb.com>
Date: Mon, 20 Jul 2015 09:20:06 -0700
Subject: [PATCH 09/33] Added JSONKit support

Summary:
React Native will now use JSONKit if it's already available in the project, otherwise it will fall back to using NSJSONSerialization as before. This provides a small performance boost to JSON parsing in some cases.
---
 .../UIExplorer.xcodeproj/project.pbxproj      |   8 +-
 .../UIExplorerUnitTests/RCTJSONTests.m        |  81 ++++++++++++++
 React/Base/RCTUtils.h                         |   1 -
 React/Base/RCTUtils.m                         | 101 ++++++++++++++----
 4 files changed, 170 insertions(+), 21 deletions(-)
 create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m

diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index e6c9afc35..988b3f433 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -20,6 +20,7 @@
 		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 */; };
+		13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
 		141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
 		143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
 		144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClippingTests.m */; };
@@ -174,6 +175,7 @@
 		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = "<group>"; };
 		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
 		13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
+		13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
 		141FC1201B222EBB004D5FFB /* IntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTests.m; sourceTree = "<group>"; };
 		143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
@@ -350,16 +352,17 @@
 			children = (
 				1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */,
 				1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */,
+				138D6A151B53CD440074A87E /* RCTCacheTests.m */,
 				144D21231B2204C5006DB32B /* RCTClippingTests.m */,
 				1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
 				1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
 				1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
 				1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
+				1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
+				13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
 				138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
 				1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
 				1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
-				138D6A151B53CD440074A87E /* RCTCacheTests.m */,
-				1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
 				143BC57E1B21E18100462512 /* Info.plist */,
 				14D6D7101B220EB3001FB087 /* libOCMock.a */,
 				14D6D7011B220AE3001FB087 /* OCMock */,
@@ -793,6 +796,7 @@
 				1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
 				1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,
 				138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
+				13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
 				1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
 				138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
 			);
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m
new file mode 100644
index 000000000..b8951ca02
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m
@@ -0,0 +1,81 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <XCTest/XCTest.h>
+#import "RCTUtils.h"
+
+@interface RCTJSONTests : XCTestCase
+
+@end
+
+@implementation RCTJSONTests
+
+- (void)testEncodingObject
+{
+  NSDictionary *obj = @{@"foo": @"bar"};
+  NSString *json = @"{\"foo\":\"bar\"}";
+  XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
+}
+
+- (void)testEncodingArray
+{
+  NSArray *array = @[@"foo", @"bar"];
+  NSString *json = @"[\"foo\",\"bar\"]";
+  XCTAssertEqualObjects(json, RCTJSONStringify(array, NULL));
+}
+
+- (void)testEncodingString
+{
+  NSString *text = @"Hello\nWorld";
+  NSString *json = @"\"Hello\\nWorld\"";
+  XCTAssertEqualObjects(json, RCTJSONStringify(text, NULL));
+}
+
+- (void)testDecodingObject
+{
+  NSDictionary *obj = @{@"foo": @"bar"};
+  NSString *json = @"{\"foo\":\"bar\"}";
+  XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
+}
+
+- (void)testDecodingArray
+{
+  NSArray *array = @[@"foo", @"bar"];
+  NSString *json = @"[\"foo\",\"bar\"]";
+  XCTAssertEqualObjects(array, RCTJSONParse(json, NULL));
+}
+
+- (void)testDecodingString
+{
+  NSString *text = @"Hello\nWorld";
+  NSString *json = @"\"Hello\\nWorld\"";
+  XCTAssertEqualObjects(text, RCTJSONParse(json, NULL));
+}
+
+- (void)testDecodingMutableArray
+{
+  NSString *json = @"[1,2,3]";
+  NSMutableArray *array = RCTJSONParseMutable(json, NULL);
+  XCTAssertNoThrow([array addObject:@4]);
+  XCTAssertEqualObjects(array, (@[@1, @2, @3, @4]));
+}
+
+- (void)testLeadingWhitespace
+{
+  NSDictionary *obj = @{@"foo": @"bar"};
+  NSString *json = @" \r\n\t{\"foo\":\"bar\"}";
+  XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
+}
+
+@end
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 23926aa71..65eb99f8c 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -19,7 +19,6 @@
 RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
 RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
 RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
-RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options);
 
 // Strip non JSON-safe values from an object graph
 RCT_EXTERN id RCTJSONClean(id object);
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index 724a9c368..c577bf00c 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -23,34 +23,99 @@
 
 NSString *RCTJSONStringify(id jsonObject, NSError **error)
 {
-  NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:(NSJSONWritingOptions)NSJSONReadingAllowFragments error:error];
+  static SEL JSONKitSelector = NULL;
+  static NSSet *collectionTypes;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    SEL selector = NSSelectorFromString(@"JSONStringWithOptions:error:");
+    if ([NSDictionary instancesRespondToSelector:selector]) {
+      JSONKitSelector = selector;
+      collectionTypes = [NSSet setWithObjects:
+                         [NSArray class], [NSMutableArray class],
+                         [NSDictionary class], [NSMutableDictionary class], nil];
+    }
+  });
+
+  // Use JSONKit if available and object is not a fragment
+  if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
+    return ((NSString *(*)(id, SEL, int, NSError **))objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
+  }
+
+  // Use Foundation JSON method
+  NSData *jsonData = [NSJSONSerialization
+                      dataWithJSONObject:jsonObject
+                      options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
+                      error:error];
   return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
 }
 
-id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options)
+static id _RCTJSONParse(NSString *jsonString, BOOL mutable, NSError **error)
 {
-  if (!jsonString) {
-    return nil;
-  }
-  NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
-  if (!jsonData) {
-    jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
-    if (jsonData) {
-      RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
-    } else {
-      RCTLogError(@"RCTJSONParse received invalid UTF8 data");
-      return nil;
+  static SEL JSONKitSelector = NULL;
+  static SEL JSONKitMutableSelector = NULL;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    SEL selector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:");
+    if ([NSString instancesRespondToSelector:selector]) {
+      JSONKitSelector = selector;
+      JSONKitMutableSelector = NSSelectorFromString(@"mutableObjectFromJSONStringWithParseOptions:error:");
     }
+  });
+
+  if (jsonString) {
+
+    // Use JSONKit if available and string is not a fragment
+    if (JSONKitSelector) {
+      NSInteger length = jsonString.length;
+      for (NSInteger i = 0; i < length; i++) {
+        unichar c = [jsonString characterAtIndex:i];
+        if (strchr("{[", c)) {
+          static const int options = (1 << 2); // loose unicode
+          SEL selector = mutable ? JSONKitMutableSelector : JSONKitSelector;
+          return ((id (*)(id, SEL, int, NSError **))objc_msgSend)(jsonString, selector, options, error);
+        }
+        if (!strchr(" \r\n\t", c)) {
+          break;
+        }
+      }
+    }
+
+    // Use Foundation JSON method
+    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+    if (!jsonData) {
+      jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+      if (jsonData) {
+        RCTLogWarn(@"RCTJSONParse received the following string, which could "
+                   "not be losslessly converted to UTF8 data: '%@'", jsonString);
+      } else {
+        NSString *errorMessage = @"RCTJSONParse received invalid UTF8 data";
+        if (error) {
+          *error = RCTErrorWithMessage(errorMessage);
+        } else {
+          RCTLogError(@"%@", errorMessage);
+        }
+        return nil;
+      }
+    }
+    NSJSONReadingOptions options = NSJSONReadingAllowFragments;
+    if (mutable) {
+      options |= NSJSONReadingMutableContainers;
+    }
+    return [NSJSONSerialization JSONObjectWithData:jsonData
+                                           options:options
+                                             error:error];
   }
-  return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error];
+  return nil;
 }
 
-id RCTJSONParse(NSString *jsonString, NSError **error) {
-  return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments);
+id RCTJSONParse(NSString *jsonString, NSError **error)
+{
+  return _RCTJSONParse(jsonString, NO, error);
 }
 
-id RCTJSONParseMutable(NSString *jsonString, NSError **error) {
-  return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves);
+id RCTJSONParseMutable(NSString *jsonString, NSError **error)
+{
+  return _RCTJSONParse(jsonString, YES, error);
 }
 
 id RCTJSONClean(id object)

From 72f7a1be6fc8de43cf364bf10c76db20a3a64e53 Mon Sep 17 00:00:00 2001
From: Tadeu Zagallo <tadeuzagallo@fb.com>
Date: Mon, 20 Jul 2015 09:14:53 -0700
Subject: [PATCH 10/33] [ReactNative] Add JavaScriptCore legacy profiler

---
 JSCLegacyProfiler/JSCLegacyProfiler.h  |  25 ++++
 JSCLegacyProfiler/JSCLegacyProfiler.mm | 161 +++++++++++++++++++++++++
 JSCLegacyProfiler/Makefile             | 108 +++++++++++++++++
 React/Executors/RCTContextExecutor.m   |  28 +++++
 React/React.xcodeproj/project.pbxproj  |  15 +++
 5 files changed, 337 insertions(+)
 create mode 100644 JSCLegacyProfiler/JSCLegacyProfiler.h
 create mode 100644 JSCLegacyProfiler/JSCLegacyProfiler.mm
 create mode 100644 JSCLegacyProfiler/Makefile

diff --git a/JSCLegacyProfiler/JSCLegacyProfiler.h b/JSCLegacyProfiler/JSCLegacyProfiler.h
new file mode 100644
index 000000000..826e39f21
--- /dev/null
+++ b/JSCLegacyProfiler/JSCLegacyProfiler.h
@@ -0,0 +1,25 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#import "JSContextRef.h"
+
+extern "C" {
+
+JSValueRef nativeProfilerStart(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception);
+
+JSValueRef nativeProfilerEnd(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception);
+
+}
diff --git a/JSCLegacyProfiler/JSCLegacyProfiler.mm b/JSCLegacyProfiler/JSCLegacyProfiler.mm
new file mode 100644
index 000000000..218c5e55d
--- /dev/null
+++ b/JSCLegacyProfiler/JSCLegacyProfiler.mm
@@ -0,0 +1,161 @@
+//#include "config.h"
+
+#include "JSCLegacyProfiler.h"
+
+#include "APICast.h"
+#include "LegacyProfiler.h"
+#include "OpaqueJSString.h"
+#include "JSProfilerPrivate.h"
+#include "JSStringRef.h"
+
+#include <yajl/yajl_gen.h>
+
+#define GEN_AND_CHECK(expr) \
+  do { \
+    yajl_gen_status GEN_AND_CHECK_status = (expr); \
+    if (GEN_AND_CHECK_status != yajl_gen_status_ok) { \
+      return GEN_AND_CHECK_status; \
+    } \
+  } while (false)
+
+static inline yajl_gen_status yajl_gen_cstring(yajl_gen gen, const char *str) {
+  return yajl_gen_string(gen, (const unsigned char*)str, strlen(str));
+}
+
+static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node);
+static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node);
+
+static yajl_gen_status append_root_json(yajl_gen gen, const JSC::Profile *profile) {
+  GEN_AND_CHECK(yajl_gen_map_open(gen));
+  GEN_AND_CHECK(yajl_gen_cstring(gen, "rootNodes"));
+  GEN_AND_CHECK(append_children_array_json(gen, profile->head()));
+  GEN_AND_CHECK(yajl_gen_map_close(gen));
+
+  return yajl_gen_status_ok;
+}
+
+static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node) {
+  GEN_AND_CHECK(yajl_gen_array_open(gen));
+  for (RefPtr<JSC::ProfileNode> child : node->children()) {
+    GEN_AND_CHECK(append_node_json(gen, child.get()));
+  }
+  GEN_AND_CHECK(yajl_gen_array_close(gen));
+
+  return yajl_gen_status_ok;
+}
+
+static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node) {
+  GEN_AND_CHECK(yajl_gen_map_open(gen));
+  GEN_AND_CHECK(yajl_gen_cstring(gen, "id"));
+  GEN_AND_CHECK(yajl_gen_integer(gen, node->id()));
+
+  if (!node->functionName().isEmpty()) {
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "functionName"));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, node->functionName().utf8().data()));
+  }
+
+  if (!node->url().isEmpty()) {
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "url"));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, node->url().utf8().data()));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "lineNumber"));
+    GEN_AND_CHECK(yajl_gen_integer(gen, node->lineNumber()));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "columnNumber"));
+    GEN_AND_CHECK(yajl_gen_integer(gen, node->columnNumber()));
+  }
+
+  GEN_AND_CHECK(yajl_gen_cstring(gen, "calls"));
+  GEN_AND_CHECK(yajl_gen_array_open(gen));
+  for (const JSC::ProfileNode::Call &call : node->calls()) {
+    GEN_AND_CHECK(yajl_gen_map_open(gen));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "startTime"));
+    GEN_AND_CHECK(yajl_gen_double(gen, call.startTime()));
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "totalTime"));
+    GEN_AND_CHECK(yajl_gen_double(gen, call.totalTime()));
+    GEN_AND_CHECK(yajl_gen_map_close(gen));
+  }
+  GEN_AND_CHECK(yajl_gen_array_close(gen));
+
+  if (!node->children().isEmpty()) {
+    GEN_AND_CHECK(yajl_gen_cstring(gen, "children"));
+    GEN_AND_CHECK(append_children_array_json(gen, node));
+  }
+
+  GEN_AND_CHECK(yajl_gen_map_close(gen));
+
+  return yajl_gen_status_ok;
+}
+
+static char *render_error_code(yajl_gen_status status) {
+  char err[1024];
+  snprintf(err, sizeof(err), "{\"error\": %d}", (int)status);
+  return strdup(err);
+}
+
+static char *convert_to_json(const JSC::Profile *profile) {
+  yajl_gen_status status;
+  yajl_gen gen = yajl_gen_alloc(NULL);
+
+  status = append_root_json(gen, profile);
+  if (status != yajl_gen_status_ok) {
+    yajl_gen_free(gen);
+    return render_error_code(status);
+  }
+
+  const unsigned char *buf;
+  size_t buf_size;
+  status = yajl_gen_get_buf(gen, &buf, &buf_size);
+  if (status != yajl_gen_status_ok) {
+    yajl_gen_free(gen);
+    return render_error_code(status);
+  }
+
+  char *json_copy = strdup((const char*)buf);
+  yajl_gen_free(gen);
+  return json_copy;
+}
+
+static char *JSEndProfilingAndRender(JSContextRef ctx, JSStringRef title)
+{
+    JSC::ExecState *exec = toJS(ctx);
+    JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
+    RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, title->string());
+    return convert_to_json(rawProfile.get());
+}
+
+JSValueRef nativeProfilerStart(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount < 1) {
+    // Could raise an exception here.
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
+  JSStartProfiling(ctx, title);
+  JSStringRelease(title);
+  return JSValueMakeUndefined(ctx);
+}
+
+JSValueRef nativeProfilerEnd(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount < 1) {
+    // Could raise an exception here.
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
+  char *rendered = JSEndProfilingAndRender(ctx, title);
+  JSStringRelease(title);
+  JSStringRef profile = JSStringCreateWithUTF8CString(rendered);
+  free(rendered);
+  return JSValueMakeString(ctx, profile);
+}
diff --git a/JSCLegacyProfiler/Makefile b/JSCLegacyProfiler/Makefile
new file mode 100644
index 000000000..b825f7764
--- /dev/null
+++ b/JSCLegacyProfiler/Makefile
@@ -0,0 +1,108 @@
+HEADER_PATHS := `find ./tmp/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
+CERT ?= "iPhone Developer"
+
+ios8: prepare build generate
+
+prepare: clean create download
+
+build: x86_64 arm64 armv7
+
+generate:	lipo codesign
+
+clean:
+	@rm -rf tmp/ /tmp/RCTJSCProfiler
+
+lipo:
+	lipo -create -output /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib ./tmp/RCTJSCProfiler_x86_64 ./tmp/RCTJSCProfiler_arm64 ./tmp/RCTJSCProfiler_armv7
+
+codesign:
+	codesign -f -s ${CERT} /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib
+
+create:
+	mkdir -p ./tmp /tmp/RCTJSCProfiler/ ./tmp/CoreFoundation ./tmp/Foundation
+	for file in ./tmp/CoreFoundation/CFUserNotification.h ./tmp/CoreFoundation/CFXMLNode.h ./tmp/CoreFoundation/CFXMLParser.h ./tmp/Foundation/Foundation.h; do echo '' > "$$file"; done
+
+download: wtf jsc webcore yajl
+
+wtf:
+	curl -o tmp/WTF.tar.gz http://www.opensource.apple.com/tarballs/WTF/WTF-7600.1.24.tar.gz
+	tar -zxvf tmp/WTF.tar.gz -C tmp
+
+jsc:
+	curl -o tmp/JSC.tar.gz http://www.opensource.apple.com/tarballs/JavaScriptCore/JavaScriptCore-7600.1.17.tar.gz
+	tar -zxvf tmp/JSC.tar.gz -C tmp
+	mv ./tmp/JavaScriptCore-7600.1.17 ./tmp/JavaScriptCore
+	python ./tmp/JavaScriptCore/generate-bytecode-files --bytecodes_h ./tmp/JavaScriptCore/Bytecodes.h ./tmp/JavaScriptCore/bytecode/BytecodeList.json
+
+webcore:
+	curl -o tmp/WebCore.tar.gz http://www.opensource.apple.com/tarballs/WebCore/WebCore-7600.1.25.tar.gz
+	tar -zxvf tmp/WebCore.tar.gz -C tmp
+
+yajl:
+	curl -o tmp/yajl.tar.gz https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0
+	tar -zxvf tmp/yajl.tar.gz -C tmp
+	mkdir -p ./tmp/yajl-2.1.0/build && cd ./tmp/yajl-2.1.0/build && cmake .. && make
+	echo `find . -name '*.c'`
+	cd ./tmp/yajl-2.1.0/src && \
+		clang -arch arm64 -arch armv7 -std=c99 \
+		-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/ \
+		-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \
+		-I ../build/yajl-2.1.0/include \
+		-c `find . -name '*.c'`
+	libtool -static -o ./tmp/yajl.a `find ./tmp/yajl-2.1.0/src/ -name '*.o'`
+
+x86_64:
+	clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_x86_64 -std=c++11 \
+		-install_name RCTJSCProfiler.ios8.dylib \
+		-include ./tmp/JavaScriptCore/config.h \
+		-I ./tmp \
+		-I ./tmp/WebCore-7600.1.25/icu \
+		-I ./tmp/WTF-7600.1.24 \
+		-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
+		-DNDEBUG=1\
+		-miphoneos-version-min=8.0 \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system \
+		${HEADER_PATHS} \
+		-undefined dynamic_lookup \
+		./JSCLegacyProfiler.mm ./tmp/yajl-2.1.0/build/yajl-2.1.0/lib/libyajl_s.a
+
+arm64:
+	echo $(HEADER_PATHS)
+	clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_arm64 -std=c++11 \
+		-install_name RCTJSCProfiler.ios8.dylib \
+		-arch arm64 \
+		-include ./tmp/JavaScriptCore/config.h \
+		-I ./tmp \
+		-I ./tmp/WebCore-7600.1.25/icu \
+		-I ./tmp/WTF-7600.1.24 \
+		-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
+		-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \
+		-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \
+		-DNDEBUG=1\
+		-miphoneos-version-min=8.0 \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \
+		${HEADER_PATHS} \
+		-undefined dynamic_lookup \
+		./JSCLegacyProfiler.mm ./tmp/yajl.a
+
+armv7:
+	clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_armv7 -std=c++11 \
+		-install_name RCTJSCProfiler.ios8.dylib \
+		-arch armv7 \
+		-include ./tmp/JavaScriptCore/config.h \
+		-I ./tmp \
+		-I ./tmp/WebCore-7600.1.25/icu \
+		-I ./tmp/WTF-7600.1.24 \
+		-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
+		-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \
+		-DNDEBUG=1\
+		-miphoneos-version-min=8.0 \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \
+		-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \
+		${HEADER_PATHS} \
+		-undefined dynamic_lookup \
+		./JSCLegacyProfiler.mm ./tmp/yajl.a
+
+.PHONY: ios8
diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m
index 173179c5a..67232489c 100644
--- a/React/Executors/RCTContextExecutor.m
+++ b/React/Executors/RCTContextExecutor.m
@@ -20,6 +20,22 @@
 #import "RCTPerformanceLogger.h"
 #import "RCTUtils.h"
 
+#ifndef RCT_JSC_PROFILER
+#if RCT_DEV && DEBUG
+#define RCT_JSC_PROFILER 1
+#else
+#define RCT_JSC_PROFILER 0
+#endif
+#endif
+
+#if RCT_JSC_PROFILER
+#include <dlfcn.h>
+
+#ifndef RCT_JSC_PROFILER_DYLIB
+#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[NSProcessInfo processInfo] operatingSystemVersion].majorVersion] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String]
+#endif
+#endif
+
 @interface RCTJavaScriptContext : NSObject <RCTInvalidating>
 
 @property (nonatomic, assign, readonly) JSGlobalContextRef ctx;
@@ -269,6 +285,18 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
     [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"];
     [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"];
 
+#if RCT_JSC_PROFILER
+    void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
+    if (JSCProfiler != NULL) {
+      JSObjectCallAsFunctionCallback nativeProfilerStart = dlsym(JSCProfiler, "nativeProfilerStart");
+      JSObjectCallAsFunctionCallback nativeProfilerEnd = dlsym(JSCProfiler, "nativeProfilerEnd");
+      if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
+        [strongSelf _addNativeHook:nativeProfilerStart withName:"nativeProfilerStart"];
+        [strongSelf _addNativeHook:nativeProfilerEnd withName:"nativeProfilerStop"];
+      }
+    }
+#endif
+
     for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
       [[NSNotificationCenter defaultCenter] addObserver:strongSelf
                                                selector:@selector(toggleProfilingFlag:)
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 1e193d9b2..2c551600b 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -472,6 +472,7 @@
 				83CBBA2A1A601D0E00E9B192 /* Sources */,
 				83CBBA2B1A601D0E00E9B192 /* Frameworks */,
 				83CBBA2C1A601D0E00E9B192 /* Copy Files */,
+				142C4F7F1B582EA6001F0B58 /* ShellScript */,
 			);
 			buildRules = (
 			);
@@ -528,6 +529,20 @@
 			shellPath = /bin/sh;
 			shellScript = "if nc -w 5 -z localhost 8081 ; then\n  if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n    echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n    exit 2\n  fi\nelse\n  open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi";
 		};
+		142C4F7F1B582EA6001F0B58 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n   find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'mkdir -p \"$1/Frameworks\" && cp -r /tmp/RCTJSCProfiler/* \"$1/Frameworks\"' -- {}\nfi";
+			showEnvVarsInLog = 0;
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */

From 0d91b93abab4b42b1722ef199796587904b23400 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= <martinb@fb.com>
Date: Mon, 20 Jul 2015 12:32:08 -0700
Subject: [PATCH 11/33] [LAUNCH-BLOCKER] Revert JS callsite of D2201593

---
 Libraries/CustomComponents/ListView/ListView.js | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 93ba01f34..8aaffa16b 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -29,7 +29,6 @@
 var ListViewDataSource = require('ListViewDataSource');
 var React = require('React');
 var RCTUIManager = require('NativeModules').UIManager;
-var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
 var ScrollView = require('ScrollView');
 var ScrollResponder = require('ScrollResponder');
 var StaticRenderer = require('StaticRenderer');
@@ -416,14 +415,6 @@ var ListView = React.createClass({
       logError,
       this._setScrollVisibleLength
     );
-
-    // RCTScrollViewManager.calculateChildFrames is not available on
-    // every platform
-    RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
-      RCTScrollViewManager.calculateChildFrames(
-        React.findNodeHandle(scrollComponent),
-        this._updateChildFrames,
-      );
   },
 
   _setScrollContentLength: function(left, top, width, height) {

From 95dc38929c0c1cda3c9b07721f823c983f2359fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= <martinb@fb.com>
Date: Mon, 20 Jul 2015 12:32:14 -0700
Subject: [PATCH 12/33] [rn] revert D2201593

---
 Libraries/CustomComponents/ListView/ListView.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 8aaffa16b..93ba01f34 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -29,6 +29,7 @@
 var ListViewDataSource = require('ListViewDataSource');
 var React = require('React');
 var RCTUIManager = require('NativeModules').UIManager;
+var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
 var ScrollView = require('ScrollView');
 var ScrollResponder = require('ScrollResponder');
 var StaticRenderer = require('StaticRenderer');
@@ -415,6 +416,14 @@ var ListView = React.createClass({
       logError,
       this._setScrollVisibleLength
     );
+
+    // RCTScrollViewManager.calculateChildFrames is not available on
+    // every platform
+    RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
+      RCTScrollViewManager.calculateChildFrames(
+        React.findNodeHandle(scrollComponent),
+        this._updateChildFrames,
+      );
   },
 
   _setScrollContentLength: function(left, top, width, height) {

From 6213950334b0e61d3cb4324337cf90a5ddd8c978 Mon Sep 17 00:00:00 2001
From: Matheus Santos <matheus.santos@struct.com.br>
Date: Mon, 20 Jul 2015 12:39:58 -0700
Subject: [PATCH 13/33] =?UTF-8?q?Added=20transitions=20'VerticalUpSwipeJum?=
 =?UTF-8?q?p'=20and=20'VerticalDownSwipeJump'=20in=E2=80=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Summary:
… NavigatorSceneConfigs
Closes https://github.com/facebook/react-native/pull/1822
Github Author: Matheus Santos <matheus.santos@struct.com.br>
---
 .../Navigator/NavigatorSceneConfigs.js        | 148 ++++++++++++++++++
 1 file changed, 148 insertions(+)

diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
index 439534ddd..37715e678 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
@@ -163,6 +163,56 @@ var ToTheLeft = {
   },
 };
 
+var ToTheUp = {
+  transformTranslate: {
+    from: {x: 0, y: 0, z: 0},
+    to: {x: 0, y: -Dimensions.get('window').height, z: 0},
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+  opacity: {
+    value: 1.0,
+    type: 'constant',
+  },
+  translateY: {
+    from: 0,
+    to: -Dimensions.get('window').height,
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+};
+
+var ToTheDown = {
+  transformTranslate: {
+    from: {x: 0, y: 0, z: 0},
+    to: {x: 0, y: Dimensions.get('window').height, z: 0},
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+  opacity: {
+    value: 1.0,
+    type: 'constant',
+  },
+  translateY: {
+    from: 0,
+    to: Dimensions.get('window').height,
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+};
+
 var FromTheRight = {
   opacity: {
     value: 1.0,
@@ -221,6 +271,50 @@ var FromTheLeft = {
   },
 };
 
+var FromTheDown = {
+  ...FromTheRight,
+  transformTranslate: {
+    from: {y: SCREEN_HEIGHT, x: 0, z: 0},
+    to: {x: 0, y: 0, z: 0},
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+  translateY: {
+    from: SCREEN_HEIGHT,
+    to: 0,
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+};
+
+var FromTheTop = {
+  ...FromTheRight,
+  transformTranslate: {
+    from: {y: -SCREEN_HEIGHT, x: 0, z: 0},
+    to: {x: 0, y: 0, z: 0},
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+  translateY: {
+    from: -SCREEN_HEIGHT,
+    to: 0,
+    min: 0,
+    max: 1,
+    type: 'linear',
+    extrapolate: true,
+    round: PixelRatio.get(),
+  },
+};
+
 var ToTheBack = {
   // Rotate *requires* you to break out each individual component of
   // rotation (x, y, z, w)
@@ -378,6 +472,18 @@ var BaseRightToLeftGesture = {
   direction: 'right-to-left',
 };
 
+var BaseDownUpGesture = {
+  ...BaseLeftToRightGesture,
+  fullDistance: SCREEN_HEIGHT,
+  direction: 'down-to-up',
+};
+
+var BaseUpDownGesture = {
+  ...BaseLeftToRightGesture,
+  fullDistance: SCREEN_HEIGHT,
+  direction: 'up-to-down',
+};
+
 var BaseConfig = {
   // A list of all gestures that are enabled on this scene
   gestures: {
@@ -468,6 +574,48 @@ var NavigatorSceneConfigs = {
       out: buildStyleInterpolator(ToTheLeft),
     },
   },
+  VerticalUpSwipeJump: {
+    ...BaseConfig,
+    gestures: {
+      jumpBack: {
+        ...BaseDownUpGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+      jumpForward: {
+        ...BaseDownUpGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+    },
+    animationInterpolators: {
+      into: buildStyleInterpolator(FromTheDown),
+      out: buildStyleInterpolator(ToTheUp),
+    },
+  },
+  VerticalDownSwipeJump: {
+    ...BaseConfig,
+    gestures: {
+      jumpBack: {
+        ...BaseUpDownGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+      jumpForward: {
+        ...BaseUpDownGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+    },
+    animationInterpolators: {
+      into: buildStyleInterpolator(FromTheTop),
+      out: buildStyleInterpolator(ToTheDown),
+    },
+  },
 };
 
 module.exports = NavigatorSceneConfigs;

From 725053acfeba4c7f2a21ac47ae8100588a710476 Mon Sep 17 00:00:00 2001
From: Christopher Chedeau <vjeux@fb.com>
Date: Mon, 20 Jul 2015 16:29:40 -0700
Subject: [PATCH 14/33] [Animated][BREAKING_CHANGE] Convert <TouchableOpacity>
 to Animated

Summary:
Because we don't want to integrate Animated inside of the core of React, we can only pass Animated.Value to styles of <Animated.View>. TouchableOpacity unfortunately used cloneElement. This means that we should have asked every single call site to replace their children to Animated.View. This isn't great.

The other solution is to stop using cloneElement and instead wrap the children inside of an <Animated.View>. This has many advantages:
- We no longer use cloneElement so we're no longer messing up with elements that are not our own.
- Refs are now working correctly for children elements
- No longer need to enforce that there's only one child and that this child is a native element

The downside is that we're introducing a <View> into the hierarchy. Sadly with CSS there is no way to have a View that doesn't affect layout. What we need to do is to remove the inner <View> and transfer all the styles to the TouchableOpacity. It is annoying but fortunately a pretty mechanical process.

I think that having a wrapper is the best solution. I will investigate to see if we can make wrappers on TouchableHighliht and TouchableWithoutFeedback as well.

**Upgrade Path:**

If the child is a View, move the style of the View to TouchableOpacity and remove the View itself.

```
<TouchableOpacity onPress={...}>
  <View style={...}>
    ...
  </View>
</TouchableOpacity>

-->

<TouchableOpacity onPress={...} style={...}>
  ...
</TouchableOpacity>
```

If the child is an Image or Text, on all the examples at Facebook it worked without any change. But it is a great idea to double check them anyway.
---
 Examples/UIExplorer/ListViewPagingExample.js  | 40 ++++++------
 .../Navigator/BreadcrumbNavSample.js          | 20 +++---
 .../Navigator/NavigationBarSample.js          | 22 +++----
 .../js/IntegrationTestsApp.js                 | 12 ++--
 Examples/UIExplorer/WebViewExample.js         | 24 +++----
 .../Components/Touchable/TouchableOpacity.js  | 63 ++++++++-----------
 Libraries/ReactIOS/WarningBox.js              | 61 +++++++++---------
 7 files changed, 109 insertions(+), 133 deletions(-)

diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js
index ab67c9160..7167f3eea 100644
--- a/Examples/UIExplorer/ListViewPagingExample.js
+++ b/Examples/UIExplorer/ListViewPagingExample.js
@@ -48,19 +48,19 @@ var Thumb = React.createClass({
   },
   render: function() {
     return (
-      <TouchableOpacity onPress={this._onPressThumb}>
-        <View style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
-          <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
-          <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
-          <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
-          {this.state.dir === 'column' ?
-            <Text>
-              Oooo, look at this new text!  So awesome it may just be crazy.
-              Let me keep typing here so it wraps at least one line.
-            </Text> :
-            <Text />
-          }
-        </View>
+      <TouchableOpacity
+        onPress={this._onPressThumb}
+        style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
+        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
+        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
+        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
+        {this.state.dir === 'column' ?
+          <Text>
+            Oooo, look at this new text!  So awesome it may just be crazy.
+            Let me keep typing here so it wraps at least one line.
+          </Text> :
+          <Text />
+        }
       </TouchableOpacity>
     );
   }
@@ -127,14 +127,12 @@ var ListViewPagingExample = React.createClass({
       <View><Text style={styles.text}>1 Like</Text></View> :
       null;
     return (
-      <TouchableOpacity onPress={this._onPressHeader}>
-        <View style={styles.header}>
-          {headerLikeText}
-          <View>
-            <Text style={styles.text}>
-              Table Header (click me)
-            </Text>
-          </View>
+      <TouchableOpacity onPress={this._onPressHeader} style={styles.header}>
+        {headerLikeText}
+        <View>
+          <Text style={styles.text}>
+            Table Header (click me)
+          </Text>
         </View>
       </TouchableOpacity>
     );
diff --git a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
index 5abdcd11f..08b8571ae 100644
--- a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
+++ b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
@@ -55,26 +55,24 @@ var BreadcrumbNavSample = React.createClass({
         return (
           <TouchableOpacity
             onPress={() => navigator.push(_getRandomRoute())}>
-            <View>
-              <Text style={styles.titleText}>{route.title}</Text>
-            </View>
+            <Text style={styles.titleText}>{route.title}</Text>
           </TouchableOpacity>
         );
       },
       iconForRoute: function(route, navigator) {
         return (
-          <TouchableOpacity onPress={() => {
-            navigator.popToRoute(route);
-          }}>
-            <View style={styles.crumbIconPlaceholder} />
-          </TouchableOpacity>
+          <TouchableOpacity
+            onPress={() => { navigator.popToRoute(route); }}
+            style={styles.crumbIconPlaceholder}
+          />
         );
       },
       separatorForRoute: function(route, navigator) {
         return (
-          <TouchableOpacity onPress={navigator.pop}>
-            <View style={styles.crumbSeparatorPlaceholder} />
-          </TouchableOpacity>
+          <TouchableOpacity
+            onPress={navigator.pop}
+            style={styles.crumbSeparatorPlaceholder}
+          />
         );
       }
     };
diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js
index 545f76b82..3148ad9c1 100644
--- a/Examples/UIExplorer/Navigator/NavigationBarSample.js
+++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js
@@ -51,12 +51,11 @@ var NavigationBarRouteMapper = {
     var previousRoute = navState.routeStack[index - 1];
     return (
       <TouchableOpacity
-        onPress={() => navigator.pop()}>
-        <View style={styles.navBarLeftButton}>
-          <Text style={[styles.navBarText, styles.navBarButtonText]}>
-            {previousRoute.title}
-          </Text>
-        </View>
+        onPress={() => navigator.pop()}
+        style={styles.navBarLeftButton}>
+        <Text style={[styles.navBarText, styles.navBarButtonText]}>
+          {previousRoute.title}
+        </Text>
       </TouchableOpacity>
     );
   },
@@ -64,12 +63,11 @@ var NavigationBarRouteMapper = {
   RightButton: function(route, navigator, index, navState) {
     return (
       <TouchableOpacity
-        onPress={() => navigator.push(newRandomRoute())}>
-        <View style={styles.navBarRightButton}>
-          <Text style={[styles.navBarText, styles.navBarButtonText]}>
-            Next
-          </Text>
-        </View>
+        onPress={() => navigator.push(newRandomRoute())}
+        style={styles.navBarRightButton}>
+        <Text style={[styles.navBarText, styles.navBarButtonText]}>
+          Next
+        </Text>
       </TouchableOpacity>
     );
   },
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js
index 519e333a2..339766b54 100644
--- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js
@@ -59,12 +59,12 @@ var IntegrationTestsApp = React.createClass({
         <View style={styles.separator} />
         <ScrollView>
           {TESTS.map((test) => [
-            <TouchableOpacity onPress={() => this.setState({test})}>
-              <View style={styles.row}>
-                <Text style={styles.testName}>
-                  {test.displayName}
-                </Text>
-              </View>
+            <TouchableOpacity
+              onPress={() => this.setState({test})}
+              style={styles.row}>
+              <Text style={styles.testName}>
+                {test.displayName}
+              </Text>
             </TouchableOpacity>,
             <View style={styles.separator} />
           ])}
diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js
index b9137e87c..478c2a995 100644
--- a/Examples/UIExplorer/WebViewExample.js
+++ b/Examples/UIExplorer/WebViewExample.js
@@ -58,19 +58,19 @@ var WebViewExample = React.createClass({
     return (
       <View style={[styles.container]}>
         <View style={[styles.addressBarRow]}>
-          <TouchableOpacity onPress={this.goBack}>
-            <View style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
-              <Text>
-                 {'<'}
-              </Text>
-            </View>
+          <TouchableOpacity
+            onPress={this.goBack}
+            style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
+            <Text>
+               {'<'}
+            </Text>
           </TouchableOpacity>
-          <TouchableOpacity onPress={this.goForward}>
-            <View style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
-              <Text>
-                {'>'}
-              </Text>
-            </View>
+          <TouchableOpacity
+            onPress={this.goForward}
+            style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
+            <Text>
+              {'>'}
+            </Text>
           </TouchableOpacity>
           <TextInput
             ref={TEXT_INPUT_REF}
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index 1549faea2..4ce870973 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -12,19 +12,16 @@
 
 // Note (avik): add @flow when Flow supports spread properties in propTypes
 
+var Animated = require('Animated');
 var NativeMethodsMixin = require('NativeMethodsMixin');
-var POPAnimationMixin = require('POPAnimationMixin');
 var React = require('React');
 var TimerMixin = require('react-timer-mixin');
 var Touchable = require('Touchable');
 var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
 
-var cloneWithProps = require('cloneWithProps');
-var ensureComponentIsNative = require('ensureComponentIsNative');
 var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 var flattenStyle = require('flattenStyle');
 var keyOf = require('keyOf');
-var onlyChild = require('onlyChild');
 
 /**
  * A wrapper for making views respond properly to touches.
@@ -49,7 +46,7 @@ var onlyChild = require('onlyChild');
  */
 
 var TouchableOpacity = React.createClass({
-  mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
+  mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
 
   propTypes: {
     ...TouchableWithoutFeedback.propTypes,
@@ -67,16 +64,17 @@ var TouchableOpacity = React.createClass({
   },
 
   getInitialState: function() {
-    return this.touchableGetInitialState();
+    return {
+      ...this.touchableGetInitialState(),
+      anim: new Animated.Value(1),
+    };
   },
 
   componentDidMount: function() {
     ensurePositiveDelayProps(this.props);
-    ensureComponentIsNative(this.refs[CHILD_REF]);
   },
 
   componentDidUpdate: function() {
-    ensureComponentIsNative(this.refs[CHILD_REF]);
   },
 
   componentWillReceiveProps: function(nextProps) {
@@ -84,22 +82,10 @@ var TouchableOpacity = React.createClass({
   },
 
   setOpacityTo: function(value) {
-    if (POPAnimationMixin) {
-      // Reset with animation if POP is available
-      this.stopAllAnimations();
-      var anim = {
-        type: this.AnimationTypes.linear,
-        property: this.AnimationProperties.opacity,
-        duration: 0.15,
-        toValue: value,
-      };
-      this.startAnimation(CHILD_REF, anim);
-    } else {
-      // Reset immediately if POP is unavailable
-      this.refs[CHILD_REF].setNativeProps({
-        opacity: value
-      });
-    }
+    Animated.timing(
+      this.state.anim,
+      {toValue: value, duration: 150}
+    ).start();
   },
 
   /**
@@ -158,25 +144,27 @@ var TouchableOpacity = React.createClass({
   _opacityInactive: function() {
     this.clearTimeout(this._hideTimeout);
     this._hideTimeout = null;
-    var child = onlyChild(this.props.children);
-    var childStyle = flattenStyle(child.props.style) || {};
+    var childStyle = flattenStyle(this.props.style) || {};
     this.setOpacityTo(
       childStyle.opacity === undefined ? 1 : childStyle.opacity
     );
   },
 
   render: function() {
-    return cloneWithProps(onlyChild(this.props.children), {
-      ref: CHILD_REF,
-      accessible: true,
-      testID: this.props.testID,
-      onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
-      onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
-      onResponderGrant: this.touchableHandleResponderGrant,
-      onResponderMove: this.touchableHandleResponderMove,
-      onResponderRelease: this.touchableHandleResponderRelease,
-      onResponderTerminate: this.touchableHandleResponderTerminate,
-    });
+    return (
+      <Animated.View
+        accessible={true}
+        style={[this.props.style, {opacity: this.state.anim}]}
+        testID={this.props.testID}
+        onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+        onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
+        onResponderGrant={this.touchableHandleResponderGrant}
+        onResponderMove={this.touchableHandleResponderMove}
+        onResponderRelease={this.touchableHandleResponderRelease}
+        onResponderTerminate={this.touchableHandleResponderTerminate}>
+        {this.props.children}
+      </Animated.View>
+    );
   },
 });
 
@@ -188,6 +176,5 @@ var TouchableOpacity = React.createClass({
  */
 var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
-var CHILD_REF = keyOf({childRef: null});
 
 module.exports = TouchableOpacity;
diff --git a/Libraries/ReactIOS/WarningBox.js b/Libraries/ReactIOS/WarningBox.js
index 37076ef5c..8caaa5e02 100644
--- a/Libraries/ReactIOS/WarningBox.js
+++ b/Libraries/ReactIOS/WarningBox.js
@@ -174,15 +174,13 @@ var WarningRow = React.createClass({
         {...this.panGesture.panHandlers}>
         <TouchableOpacity
           onPress={this.props.onOpened}>
-          <View>
-            <Text
-              style={styles.warningText}
-              numberOfLines={2}
-              ref={text => { this.text = text; }}>
-              {countText}
-              {this.props.warning}
-            </Text>
-          </View>
+          <Text
+            style={styles.warningText}
+            numberOfLines={2}
+            ref={text => { this.text = text; }}>
+            {countText}
+            {this.props.warning}
+          </Text>
         </TouchableOpacity>
         <View
           ref={closeButton => { this.closeButton = closeButton; }}
@@ -212,30 +210,27 @@ var WarningBoxOpened = React.createClass({
     return (
       <TouchableOpacity
         activeOpacity={0.9}
-        onPress={this.props.onClose}>
-        <View style={styles.yellowBox}>
-          <Text style={styles.yellowBoxText}>
-            {countText}
-            {this.props.warning}
-          </Text>
-          <View style={styles.yellowBoxButtons}>
-            <View style={styles.yellowBoxButton}>
-              <TouchableOpacity
-                onPress={this.props.onDismissed}>
-                <Text style={styles.yellowBoxButtonText}>
-                  Dismiss
-                </Text>
-              </TouchableOpacity>
-            </View>
-            <View style={styles.yellowBoxButton}>
-              <TouchableOpacity
-                onPress={this.props.onIgnored}>
-                <Text style={styles.yellowBoxButtonText}>
-                  Ignore
-                </Text>
-              </TouchableOpacity>
-            </View>
-          </View>
+        onPress={this.props.onClose}
+        style={styles.yellowBox}>
+        <Text style={styles.yellowBoxText}>
+          {countText}
+          {this.props.warning}
+        </Text>
+        <View style={styles.yellowBoxButtons}>
+          <TouchableOpacity
+            onPress={this.props.onDismissed}
+            style={styles.yellowBoxButton}>
+            <Text style={styles.yellowBoxButtonText}>
+              Dismiss
+            </Text>
+          </TouchableOpacity>
+          <TouchableOpacity
+            onPress={this.props.onIgnored}
+            style={styles.yellowBoxButton}>
+            <Text style={styles.yellowBoxButtonText}>
+              Ignore
+            </Text>
+          </TouchableOpacity>
         </View>
       </TouchableOpacity>
     );

From 270e09d46edd732764a2f165fcb44dad10928756 Mon Sep 17 00:00:00 2001
From: Tadeu Zagallo <tadeuzagallo@fb.com>
Date: Mon, 20 Jul 2015 19:30:59 -0700
Subject: [PATCH 15/33] [ReactNative][Profiler] Fix NSProcessInfo instacrash on
 iOS7

Summary:
`NSProcessInfo operatingSystemVersion` was being used to check the system version
for the Legacy Profiler on `RCTContextExecutor` but it's only available on iOS8+

Change it to `[UIDevice systemVersion]`
---
 React/Executors/RCTContextExecutor.m | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m
index 67232489c..daa0fa26d 100644
--- a/React/Executors/RCTContextExecutor.m
+++ b/React/Executors/RCTContextExecutor.m
@@ -12,6 +12,7 @@
 #import <pthread.h>
 
 #import <JavaScriptCore/JavaScriptCore.h>
+#import <UIKit/UIDevice.h>
 
 #import "RCTAssert.h"
 #import "RCTDefines.h"
@@ -32,7 +33,7 @@
 #include <dlfcn.h>
 
 #ifndef RCT_JSC_PROFILER_DYLIB
-#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[NSProcessInfo processInfo] operatingSystemVersion].majorVersion] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String]
+#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String]
 #endif
 #endif
 

From bf7497a4185e0bc1e3ed9ebfb60333578a2c40ba Mon Sep 17 00:00:00 2001
From: Alex Kotliarskyi <frantic@fb.com>
Date: Mon, 20 Jul 2015 19:34:27 -0700
Subject: [PATCH 16/33] [RN] Move ws to react-native-github


From e54e4520267c0e0e75c4ce462260b29c46b9d720 Mon Sep 17 00:00:00 2001
From: Alex Kotliarskyi <frantic@fb.com>
Date: Mon, 20 Jul 2015 19:34:46 -0700
Subject: [PATCH 17/33] [RN] Remove extra copy of stacktrace-parser


From 9105b229250d9e426d93593baba82794de6dcc92 Mon Sep 17 00:00:00 2001
From: Alex Kotliarskyi <frantic@fb.com>
Date: Mon, 20 Jul 2015 19:36:09 -0700
Subject: [PATCH 18/33] [RN] Remove 14 more unused node_modules


From 6e2f07fb8101274b3a4a52f3df09f7fdc61f43c9 Mon Sep 17 00:00:00 2001
From: Hedger Wang <hedger@fb.com>
Date: Mon, 20 Jul 2015 22:49:53 -0700
Subject: [PATCH 19/33] [Navigator] Add a callback that is called after
 emitting an event.

Summary:
While adeveloper requests the emitter to emit an event, the emitter
may not emit the event immediately instead of putting the request
into a queue and process it later.

This diff allows the developer to provide a callback which will be called
when the event has been emitted.

For instance:

```
class NavigationContext {
  push(nextRoute) {
    var nextStack = this._stack.push(nextRoute);
    this.emit(
      'change',
      {
        reason: 'push',
        nextStack: nextStack,
        nextRoute: nextRoute,
      },
      this._onPush
    );
  }

  _onPush(event){
    if (event.defaultPrevented) {
      return;
    }
    this._stack = event.nextStack;
    this.emit('change');
  }
}
```
---
 .../Navigator/Navigation/NavigationEvent.js   |   8 +-
 .../Navigation/NavigationEventEmitter.js      |  25 +++-
 .../__tests__/NavigationEventEmitter-test.js  | 110 +++++++++++++++---
 3 files changed, 119 insertions(+), 24 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
index 343e1f3e6..3a7a3d6de 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
@@ -36,7 +36,7 @@ class NavigationEventPool {
     this._list = [];
   }
 
-  get(type: String, target: Object, data: any): NavigationEvent {
+  get(type: string, target: Object, data: any): NavigationEvent {
     var event;
     if (this._list.length > 0) {
       event = this._list.pop();
@@ -59,13 +59,13 @@ class NavigationEvent {
   _defaultPrevented: boolean;
   _disposed: boolean;
   _target: ?Object;
-  _type: ?String;
+  _type: ?string;
 
-  static pool(type: String, target: Object, data: any): NavigationEvent {
+  static pool(type: string, target: Object, data: any): NavigationEvent {
     return _navigationEventPool.get(type, target, data);
   }
 
-  constructor(type: String, target: Object, data: any) {
+  constructor(type: string, target: Object, data: any) {
     this._type = type;
     this._target = target;
     this._data = data;
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
index db9e78554..ef63bd39d 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
@@ -31,8 +31,9 @@ var EventEmitter = require('EventEmitter');
 var NavigationEvent = require('NavigationEvent');
 
 type EventParams = {
-  eventType: String;
   data: any;
+  didEmitCallback: ?Function;
+  eventType: string;
 };
 
 class NavigationEventEmitter extends EventEmitter {
@@ -47,22 +48,36 @@ class NavigationEventEmitter extends EventEmitter {
     this._target = target;
   }
 
-  emit(eventType: String, data: any): void {
+  emit(
+    eventType: string,
+    data: any,
+    didEmitCallback: ?Function
+  ): void {
     if (this._emitting) {
       // An event cycle that was previously created hasn't finished yet.
       // Put this event cycle into the queue and will finish them later.
-      this._emitQueue.push({eventType, data});
+      this._emitQueue.push({eventType, data, didEmitCallback});
       return;
     }
 
     this._emitting = true;
+
     var event = new NavigationEvent(eventType, this._target, data);
-    super.emit(eventType, event);
+
+    // EventEmitter#emit only takes `eventType` as `String`. Casting `eventType`
+    // to `String` to make @flow happy.
+    super.emit(String(eventType), event);
+
+    if (typeof didEmitCallback === 'function') {
+      didEmitCallback.call(this._target, event);
+    }
+    event.dispose();
+
     this._emitting = false;
 
     while (this._emitQueue.length) {
       var arg = this._emitQueue.shift();
-      this.emit(arg.eventType, arg.data);
+      this.emit(arg.eventType, arg.data, arg.didEmitCallback);
     }
   }
 }
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
index 2a8d7d82a..cc2875c81 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
@@ -34,27 +34,48 @@ jest
 var NavigationEventEmitter = require('NavigationEventEmitter');
 
 describe('NavigationEventEmitter', () => {
-  it('emit event', () => {
-    var target = {};
-    var emitter = new NavigationEventEmitter(target);
-    var focusCounter = 0;
-    var focusTarget;
+  it('emits event', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
+    var logs = [];
+
+    emitter.addListener('ping', (event) => {
+      var {type, data, target, defaultPrevented} = event;
+
+      logs.push({
+        data,
+        defaultPrevented,
+        target,
+        type,
+      });
 
-    emitter.addListener('focus', (event) => {
-      focusCounter++;
-      focusTarget = event.target;
     });
 
-    emitter.emit('focus');
-    emitter.emit('blur');
+    emitter.emit('ping', 'hello');
 
-    expect(focusCounter).toBe(1);
-    expect(focusTarget).toBe(target);
+    expect(logs.length).toBe(1);
+    expect(logs[0].target).toBe(context);
+    expect(logs[0].type).toBe('ping');
+    expect(logs[0].data).toBe('hello');
+    expect(logs[0].defaultPrevented).toBe(false);
   });
 
-  it('put nested emit call in queue', () => {
-    var target = {};
-    var emitter = new NavigationEventEmitter(target);
+  it('does not emit event that has no listeners', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
+    var pinged = false;
+
+    emitter.addListener('ping', () => {
+      pinged = true;
+    });
+
+    emitter.emit('yo', 'bo');
+    expect(pinged).toBe(false);
+  });
+
+  it('puts nested emit call in a queue', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
     var logs = [];
 
     emitter.addListener('one', () => {
@@ -77,4 +98,63 @@ describe('NavigationEventEmitter', () => {
 
     expect(logs).toEqual([1, 2, 3, 4, 5]);
   });
+
+  it('calls callback after emitting', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
+    var logs = [];
+
+    emitter.addListener('ping', (event) => {
+      var {type, data, target, defaultPrevented} = event;
+      logs.push({
+        data,
+        defaultPrevented,
+        target,
+        type,
+      });
+      event.preventDefault();
+    });
+
+    emitter.emit('ping', 'hello', (event) => {
+      var {type, data, target, defaultPrevented} = event;
+      logs.push({
+        data,
+        defaultPrevented,
+        target,
+        type,
+      });
+    });
+
+    expect(logs.length).toBe(2);
+    expect(logs[1].target).toBe(context);
+    expect(logs[1].type).toBe('ping');
+    expect(logs[1].data).toBe('hello');
+    expect(logs[1].defaultPrevented).toBe(true);
+  });
+
+  it('calls callback after emitting the current event and before ' +
+       'emitting the next event', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
+    var logs = [];
+
+    emitter.addListener('ping', (event) => {
+      logs.push('ping');
+      emitter.emit('pong');
+    });
+
+    emitter.addListener('pong', (event) => {
+      logs.push('pong');
+    });
+
+    emitter.emit('ping', null, () => {
+      logs.push('did-ping');
+    });
+
+    expect(logs).toEqual([
+      'ping',
+      'did-ping',
+      'pong',
+    ]);
+  });
 });

From 151ddd9e426a3283cdb0f6882803653fdde7689b Mon Sep 17 00:00:00 2001
From: Philipp von Weitershausen <philikon@fb.com>
Date: Mon, 20 Jul 2015 22:44:42 -0700
Subject: [PATCH 20/33] [React Native] open source ImageStoreManager native
 module and plug into RCTImageLoader

---
 Libraries/Image/RCTCameraRollManager.m        |   5 +-
 .../Image/RCTImage.xcodeproj/project.pbxproj  |   6 +
 Libraries/Image/RCTImageLoader.h              |   3 +
 Libraries/Image/RCTImageLoader.m              |  16 ++
 Libraries/Image/RCTImageRequestHandler.m      |   5 +-
 Libraries/Image/RCTImageStoreManager.h        |  29 ++++
 Libraries/Image/RCTImageStoreManager.m        | 149 ++++++++++++++++++
 Libraries/Image/RCTImageView.m                |   1 +
 8 files changed, 212 insertions(+), 2 deletions(-)
 create mode 100644 Libraries/Image/RCTImageStoreManager.h
 create mode 100644 Libraries/Image/RCTImageStoreManager.m

diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m
index 6fac5d49b..3d331a8f1 100644
--- a/Libraries/Image/RCTCameraRollManager.m
+++ b/Libraries/Image/RCTCameraRollManager.m
@@ -14,6 +14,7 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
+#import "RCTBridge.h"
 #import "RCTImageLoader.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
@@ -22,11 +23,13 @@
 
 RCT_EXPORT_MODULE()
 
+@synthesize bridge = _bridge;
+
 RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
                   successCallback:(RCTResponseSenderBlock)successCallback
                   errorCallback:(RCTResponseErrorBlock)errorCallback)
 {
-  [RCTImageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) {
+  [RCTImageLoader loadImageWithTag:imageTag bridge:_bridge callback:^(NSError *loadError, UIImage *loadedImage) {
     if (loadError) {
       errorCallback(loadError);
       return;
diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
index 3eabd148e..6f18a284f 100644
--- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
+++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
@@ -16,6 +16,7 @@
 		137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
 		143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
 		143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
+		35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
 		58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
 /* End PBXBuildFile section */
 
@@ -50,6 +51,8 @@
 		143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
 		143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
 		143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
+		35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
+		35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageStoreManager.m; sourceTree = "<group>"; };
 		58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
 		58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
@@ -87,6 +90,8 @@
 				1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
 				1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
 				1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */,
+				35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */,
+				35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
 				134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
 				134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
 				58B5115E1A9E6B3D00147676 /* Products */,
@@ -159,6 +164,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
 				58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
 				137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
 				1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index 25fdb1b30..5498f23b7 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -10,6 +10,7 @@
 #import <UIKit/UIKit.h>
 
 @class ALAssetsLibrary;
+@class RCTBridge;
 
 typedef void (^RCTImageLoaderProgressBlock)(int64_t written, int64_t total);
 typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id /* UIImage or CAAnimation */);
@@ -27,6 +28,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
  * Will always call callback on main thread.
  */
 + (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                             bridge:(RCTBridge *)bridge
                                            callback:(RCTImageLoaderCompletionBlock)callback;
 
 /**
@@ -37,6 +39,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
                                                size:(CGSize)size
                                               scale:(CGFloat)scale
                                          resizeMode:(UIViewContentMode)resizeMode
+                                             bridge:(RCTBridge *)bridge
                                       progressBlock:(RCTImageLoaderProgressBlock)progress
                                     completionBlock:(RCTImageLoaderCompletionBlock)completion;
 
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index 405b4907b..4300f3b94 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -15,10 +15,12 @@
 #import <Photos/PHImageManager.h>
 #import <UIKit/UIKit.h>
 
+#import "RCTBridge.h"
 #import "RCTConvert.h"
 #import "RCTDefines.h"
 #import "RCTGIFImage.h"
 #import "RCTImageDownloader.h"
+#import "RCTImageStoreManager.h"
 #import "RCTImageUtils.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
@@ -58,12 +60,14 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
 }
 
 + (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                             bridge:(RCTBridge *)bridge
                                            callback:(RCTImageLoaderCompletionBlock)callback
 {
   return [self loadImageWithTag:imageTag
                            size:CGSizeZero
                           scale:0
                      resizeMode:UIViewContentModeScaleToFill
+                         bridge:bridge
                   progressBlock:nil
                 completionBlock:callback];
 }
@@ -72,6 +76,7 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
                                                size:(CGSize)size
                                               scale:(CGFloat)scale
                                          resizeMode:(UIViewContentMode)resizeMode
+                                             bridge:(RCTBridge *)bridge
                                       progressBlock:(RCTImageLoaderProgressBlock)progress
                                     completionBlock:(RCTImageLoaderCompletionBlock)completion
 {
@@ -177,6 +182,17 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
          RCTDispatchCallbackOnMainQueue(completion, error, image);
       }];
     }
+  } else if ([imageTag hasPrefix:@"rct-image-store://"]) {
+    [bridge.imageStoreManager getImageForTag:imageTag withBlock:^(UIImage *image) {
+      if (image) {
+        RCTDispatchCallbackOnMainQueue(completion, nil, image);
+      } else {
+        NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
+        NSError *error = RCTErrorWithMessage(errorMessage);
+        RCTDispatchCallbackOnMainQueue(completion, error, nil);
+      }
+    }];
+    return ^{};
   } else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
     id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
     if (image) {
diff --git a/Libraries/Image/RCTImageRequestHandler.m b/Libraries/Image/RCTImageRequestHandler.m
index e5eb3bfd4..c1f485b33 100644
--- a/Libraries/Image/RCTImageRequestHandler.m
+++ b/Libraries/Image/RCTImageRequestHandler.m
@@ -10,6 +10,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "RCTBridge.h"
 #import "RCTImageLoader.h"
 #import "RCTUtils.h"
 
@@ -20,6 +21,8 @@
 
 RCT_EXPORT_MODULE()
 
+@synthesize bridge = _bridge;
+
 - (BOOL)canHandleRequest:(NSURLRequest *)request
 {
   return [@[@"assets-library", @"ph"] containsObject:[request.URL.scheme lowercaseString]];
@@ -30,7 +33,7 @@ RCT_EXPORT_MODULE()
 {
   NSNumber *requestToken = @(++_currentToken);
   NSString *URLString = [request.URL absoluteString];
-  [RCTImageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
+  [RCTImageLoader loadImageWithTag:URLString bridge:_bridge callback:^(NSError *error, UIImage *image) {
     if (error) {
       [delegate URLRequest:requestToken didCompleteWithError:error];
       return;
diff --git a/Libraries/Image/RCTImageStoreManager.h b/Libraries/Image/RCTImageStoreManager.h
new file mode 100644
index 000000000..afb4f24cb
--- /dev/null
+++ b/Libraries/Image/RCTImageStoreManager.h
@@ -0,0 +1,29 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import <UIKit/UIKit.h>
+
+#import "RCTBridge.h"
+#import "RCTURLRequestHandler.h"
+
+@interface RCTImageStoreManager : NSObject<RCTURLRequestHandler>
+
+/**
+ * Set and get cached images. These must be called from the main thread.
+ */
+- (NSString *)storeImage:(UIImage *)image;
+- (UIImage *)imageForTag:(NSString *)imageTag;
+
+/**
+ * Set and get cached images asynchronously. It is safe to call these from any
+ * thread. The callbacks will be called on the main thread.
+ */
+- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block;
+- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block;
+
+@end
+
+@interface RCTBridge (RCTImageStoreManager)
+
+@property (nonatomic, readonly) RCTImageStoreManager *imageStoreManager;
+
+@end
diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m
new file mode 100644
index 000000000..e751466f9
--- /dev/null
+++ b/Libraries/Image/RCTImageStoreManager.m
@@ -0,0 +1,149 @@
+/**
+ * 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 "RCTImageStoreManager.h"
+
+#import "RCTAssert.h"
+#import "RCTUtils.h"
+
+@implementation RCTImageStoreManager
+{
+  NSMutableDictionary *_store;
+}
+
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+- (id)init
+{
+  if ((self = [super init])) {
+
+    // TODO: need a way to clear this store
+    _store = [[NSMutableDictionary alloc] init];
+  }
+  return self;
+}
+
+- (NSString *)storeImage:(UIImage *)image
+{
+  RCTAssertMainThread();
+  NSString *tag = [NSString stringWithFormat:@"rct-image-store://%tu", [_store count]];
+  _store[tag] = image;
+  return tag;
+}
+
+- (UIImage *)imageForTag:(NSString *)imageTag
+{
+  RCTAssertMainThread();
+  return _store[imageTag];
+}
+
+- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block
+{
+  dispatch_async(dispatch_get_main_queue(), ^{
+    NSString *imageTag = [self storeImage:image];
+    if (block) {
+      block(imageTag);
+    }
+  });
+}
+
+- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block
+{
+  RCTAssert(block != nil, @"block must not be nil");
+  dispatch_async(dispatch_get_main_queue(), ^{
+    block([self imageForTag:imageTag]);
+  });
+}
+
+// TODO (#5906496): Name could be more explicit - something like getBase64EncodedJPEGDataForTag:?
+RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag
+                  successCallback:(RCTResponseSenderBlock)successCallback
+                  errorCallback:(RCTResponseErrorBlock)errorCallback)
+{
+  [self getImageForTag:imageTag withBlock:^(UIImage *image) {
+    if (!image) {
+      errorCallback(RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]));
+      return;
+    }
+    dispatch_async(_methodQueue, ^{
+      NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
+      NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
+      successCallback(@[[base64 stringByReplacingOccurrencesOfString:@"\n" withString:@""]]);
+    });
+  }];
+}
+
+RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
+                  successCallback:(RCTResponseSenderBlock)successCallback
+                  errorCallback:(RCTResponseErrorBlock)errorCallback)
+
+{
+  NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
+  if (imageData) {
+    UIImage *image = [[UIImage alloc] initWithData:imageData];
+    [self storeImage:image withBlock:^(NSString *imageTag) {
+      successCallback(@[imageTag]);
+    }];
+  } else {
+    errorCallback(RCTErrorWithMessage(@"Failed to add image from base64String"));
+  }
+}
+
+#pragma mark - RCTURLRequestHandler
+
+- (BOOL)canHandleRequest:(NSURLRequest *)request
+{
+  return [@[@"rct-image-store"] containsObject:[request.URL.scheme lowercaseString]];
+}
+
+- (id)sendRequest:(NSURLRequest *)request
+     withDelegate:(id<RCTURLRequestDelegate>)delegate
+{
+  NSString *imageTag = [request.URL absoluteString];
+  [self getImageForTag:imageTag withBlock:^(UIImage *image) {
+    if (!image) {
+      NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]);
+      [delegate URLRequest:request didCompleteWithError:error];
+      return;
+    }
+
+    NSString *mimeType = nil;
+    NSData *imageData = nil;
+    if (RCTImageHasAlpha(image.CGImage)) {
+      mimeType = @"image/png";
+      imageData = UIImagePNGRepresentation(image);
+    } else {
+      mimeType = @"image/jpeg";
+      imageData = UIImageJPEGRepresentation(image, 1.0);
+    }
+
+    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
+                                                        MIMEType:mimeType
+                                           expectedContentLength:imageData.length
+                                                textEncodingName:nil];
+
+    [delegate URLRequest:request didReceiveResponse:response];
+    [delegate URLRequest:request didReceiveData:imageData];
+    [delegate URLRequest:request didCompleteWithError:nil];
+  }];
+  return request;
+}
+
+@end
+
+@implementation RCTBridge (RCTImageStoreManager)
+
+- (RCTImageStoreManager *)imageStoreManager
+{
+  return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])];
+}
+
+@end
diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m
index 8773aebb7..5604ec4b8 100644
--- a/Libraries/Image/RCTImageView.m
+++ b/Libraries/Image/RCTImageView.m
@@ -124,6 +124,7 @@ RCT_NOT_IMPLEMENTED(-init)
                                 size:self.bounds.size
                                scale:RCTScreenScale()
                           resizeMode:self.contentMode
+                              bridge:_bridge
                        progressBlock:progressHandler
                      completionBlock:^(NSError *error, id image) {
 

From 9c5fe3612d6aac0021120ed506c59c56a9218a10 Mon Sep 17 00:00:00 2001
From: Philipp von Weitershausen <philikon@fb.com>
Date: Mon, 20 Jul 2015 22:44:51 -0700
Subject: [PATCH 21/33] [React Native][iOS] XHR upload progress events

---
 Examples/UIExplorer/XHRExample.js         | 13 +++++++++++++
 Libraries/Network/RCTHTTPRequestHandler.m | 10 ++++++++++
 Libraries/Network/RCTNetworking.m         | 16 ++++++++++++++++
 Libraries/Network/XMLHttpRequest.ios.js   | 19 +++++++++++++++++++
 React/Base/RCTURLRequestDelegate.h        |  6 ++++++
 5 files changed, 64 insertions(+)

diff --git a/Examples/UIExplorer/XHRExample.js b/Examples/UIExplorer/XHRExample.js
index 2a3b1ebbb..d142222c9 100644
--- a/Examples/UIExplorer/XHRExample.js
+++ b/Examples/UIExplorer/XHRExample.js
@@ -128,6 +128,7 @@ class FormUploader extends React.Component {
     super(props);
     this.state = {
       isUploading: false,
+      uploadProgress: null,
       randomPhoto: null,
       textParams: [],
     };
@@ -217,6 +218,14 @@ class FormUploader extends React.Component {
     this.state.textParams.forEach(
       (param) => formdata.append(param.name, param.value)
     );
+    if (xhr.upload) {
+      xhr.upload.onprogress = (event) => {
+        console.log('upload onprogress', event);
+        if (event.lengthComputable) {
+          this.setState({uploadProgress: event.loaded / event.total});
+        }
+      };
+    }
     xhr.send(formdata);
     this.setState({isUploading: true});
   }
@@ -251,6 +260,10 @@ class FormUploader extends React.Component {
       </View>
     ));
     var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload';
+    var uploadProgress = this.state.uploadProgress;
+    if (uploadProgress !== null) {
+      uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%';
+    }
     var uploadButton = (
       <View style={styles.uploadButtonBox}>
         <Text style={styles.uploadButtonLabel}>{uploadButtonLabel}</Text>
diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m
index 6a6b385af..c89a4fbc9 100644
--- a/Libraries/Network/RCTHTTPRequestHandler.m
+++ b/Libraries/Network/RCTHTTPRequestHandler.m
@@ -75,6 +75,16 @@ RCT_EXPORT_MODULE()
 
 #pragma mark - NSURLSession delegate
 
+- (void)URLSession:(NSURLSession *)session
+              task:(NSURLSessionTask *)task
+   didSendBodyData:(int64_t)bytesSent
+    totalBytesSent:(int64_t)totalBytesSent
+totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
+{
+  [[_delegates objectForKey:task] URLRequest:task didUploadProgress:(double)totalBytesSent total:(double)totalBytesExpectedToSend];
+}
+
+
 - (void)URLSession:(NSURLSession *)session
           dataTask:(NSURLSessionDataTask *)task
 didReceiveResponse:(NSURLResponse *)response
diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m
index df102e594..d73f1fb4d 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -184,6 +184,11 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError
   return [self initWithRequest:nil handler:nil callback:nil];
 }
 
+- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
+{
+  RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
+}
+
 - (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
 {
   RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
@@ -395,6 +400,17 @@ RCT_EXPORT_MODULE()
 
 #pragma mark - RCTURLRequestDelegate
 
+- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
+{
+  dispatch_async(_methodQueue, ^{
+    RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
+    RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
+
+    NSArray *responseJSON = @[request.requestID, @(progress), @(total)];
+    [_bridge.eventDispatcher sendDeviceEventWithName:@"didUploadProgress" body:responseJSON];
+  });
+}
+
 - (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
 {
   dispatch_async(_methodQueue, ^{
diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js
index 6eb586c26..da020c808 100644
--- a/Libraries/Network/XMLHttpRequest.ios.js
+++ b/Libraries/Network/XMLHttpRequest.ios.js
@@ -21,15 +21,23 @@ class XMLHttpRequest extends XMLHttpRequestBase {
 
   _requestId: ?number;
   _subscriptions: [any];
+  upload: {
+    onprogress?: (event: Object) => void;
+  };
 
   constructor() {
     super();
     this._requestId = null;
     this._subscriptions = [];
+    this.upload = {};
   }
 
   _didCreateRequest(requestId: number): void {
     this._requestId = requestId;
+    this._subscriptions.push(RCTDeviceEventEmitter.addListener(
+      'didUploadProgress',
+      (args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
+    ));
     this._subscriptions.push(RCTDeviceEventEmitter.addListener(
       'didReceiveNetworkResponse',
       (args) => this._didReceiveResponse.call(this, args[0], args[1], args[2])
@@ -44,6 +52,17 @@ class XMLHttpRequest extends XMLHttpRequestBase {
     ));
   }
 
+  _didUploadProgress(requestId: number, progress: number, total: number): void {
+    if (requestId === this._requestId && this.upload.onprogress) {
+      var event = {
+        lengthComputable: true,
+        loaded: progress,
+        total,
+      };
+      this.upload.onprogress(event);
+    }
+  }
+
   _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
     if (requestId === this._requestId) {
       this.status = status;
diff --git a/React/Base/RCTURLRequestDelegate.h b/React/Base/RCTURLRequestDelegate.h
index 3ca5b0e01..48473b84b 100644
--- a/React/Base/RCTURLRequestDelegate.h
+++ b/React/Base/RCTURLRequestDelegate.h
@@ -15,6 +15,12 @@
  */
 @protocol RCTURLRequestDelegate <NSObject>
 
+/**
+ * Call this when you first receives a response from the server. This should
+ * include response headers, etc.
+ */
+- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total;
+
 /**
  * Call this when you first receives a response from the server. This should
  * include response headers, etc.

From 9c73e2ff7aeac8c65f25472135e4c0410f797eea Mon Sep 17 00:00:00 2001
From: Adam Roth <adamjroth@gmail.com>
Date: Tue, 21 Jul 2015 05:39:57 -0700
Subject: [PATCH 22/33] [Image] Improved loading of Assets Library and Photos
 Framework images.

Summary:
Update to https://github.com/facebook/react-native/pull/1969

--
Recent improvements allow RCTImageLoader to select a more appropriate sized image based on the layout dimensions. Sizes:

	- asset.thumbnail
	- asset.aspectRatioThumbnail
	- asset.defaultRepresentation.fullScreenImage
	- asset.defaultRepresentation.fullResolutionImage

Prior, only the fullResolutionImage was used. This was memory intensive and resulted in crashes when loading several large images at once. The updated implementation works well, but can be made more efficient:

Consider loading 10 8MP (3264x2448) images in 150x150 pixel containers. The target size (150x150) is larger than asset.thumbnail (approx 100x100), therefore the fullScreenImage representation is used instead (approx 1334x1000).

This commit will scale the asset to the minimum size required while taking into account original aspect ratio and device scale. Memory usage is considerably lower and many more images can be loaded in
sequence without having to worry
Closes https://github.com/facebook/react-native/pull/2008
Github Author: Adam Roth <adamjroth@gmail.com>
---
 .../UIExplorer/AssetScaledImageExample.js     |  98 +++++++++++++++++
 Examples/UIExplorer/CameraRollExample.ios.js  |  39 ++++---
 Examples/UIExplorer/createExamplePage.js      |   1 +
 Libraries/Image/RCTImageLoader.m              | 102 ++++++++++++++----
 4 files changed, 206 insertions(+), 34 deletions(-)
 create mode 100644 Examples/UIExplorer/AssetScaledImageExample.js

diff --git a/Examples/UIExplorer/AssetScaledImageExample.js b/Examples/UIExplorer/AssetScaledImageExample.js
new file mode 100644
index 000000000..dbfe7afb7
--- /dev/null
+++ b/Examples/UIExplorer/AssetScaledImageExample.js
@@ -0,0 +1,98 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  Image,
+  StyleSheet,
+  View,
+  ScrollView
+} = React;
+
+var AssetScaledImageExample = React.createClass({
+
+  getInitialState() {
+    return {
+      asset: this.props.asset
+    };
+  },
+
+  render() {
+    var image = this.state.asset.node.image;
+    return (
+      <ScrollView>
+        <View style={styles.row}>
+          <Image source={image} style={styles.imageWide}/>
+        </View>
+        <View style={styles.row}>
+          <Image source={image} style={styles.imageThumb}/>
+          <Image source={image} style={styles.imageThumb}/>
+          <Image source={image} style={styles.imageThumb}/>
+        </View>
+        <View style={styles.row}>
+          <Image source={image} style={styles.imageT1}/>
+          <Image source={image} style={styles.imageT2}/>
+        </View>
+      </ScrollView>
+    );
+  },
+});
+
+var styles = StyleSheet.create({
+  row: {
+    padding: 5,
+    flex: 1,
+    flexDirection: 'row',
+    alignSelf: 'center',
+  },
+  textColumn: {
+    flex: 1,
+    flexDirection: 'column',
+  },
+  imageWide: {
+    borderWidth: 1,
+    borderColor: 'black',
+    width: 320,
+    height: 240,
+    margin: 5,
+  },
+  imageThumb: {
+    borderWidth: 1,
+    borderColor: 'black',
+    width: 100,
+    height: 100,
+    margin: 5,
+  },
+  imageT1: {
+    borderWidth: 1,
+    borderColor: 'black',
+    width: 212,
+    height: 320,
+    margin: 5,
+  },
+  imageT2: {
+    borderWidth: 1,
+    borderColor: 'black',
+    width: 100,
+    height: 320,
+    margin: 5,
+  },
+});
+
+exports.title = '<AssetScaledImageExample>';
+exports.description = 'Example component that displays the automatic scaling capabilities of the <Image /> tag';
+module.exports = AssetScaledImageExample;
diff --git a/Examples/UIExplorer/CameraRollExample.ios.js b/Examples/UIExplorer/CameraRollExample.ios.js
index 736784072..d783d9d8e 100644
--- a/Examples/UIExplorer/CameraRollExample.ios.js
+++ b/Examples/UIExplorer/CameraRollExample.ios.js
@@ -24,9 +24,11 @@ var {
   SwitchIOS,
   Text,
   View,
+  TouchableOpacity
 } = React;
 
 var CameraRollView = require('./CameraRollView.ios');
+var AssetScaledImageExampleView = require('./AssetScaledImageExample');
 
 var CAMERA_ROLL_VIEW = 'camera_roll_view';
 
@@ -54,7 +56,7 @@ var CameraRollExample = React.createClass({
         <Text>{'Group Type: ' + this.state.groupTypes}</Text>
         <CameraRollView
           ref={CAMERA_ROLL_VIEW}
-          batchSize={5}
+          batchSize={20}
           groupTypes={this.state.groupTypes}
           renderImage={this._renderImage}
         />
@@ -62,24 +64,35 @@ var CameraRollExample = React.createClass({
     );
   },
 
+  loadAsset(asset){
+    this.props.navigator.push({
+      title: 'Camera Roll Image',
+      component: AssetScaledImageExampleView,
+      backButtonTitle: 'Back',
+      passProps: { asset: asset },
+    });
+  },
+
   _renderImage(asset) {
     var imageSize = this.state.bigImages ? 150 : 75;
     var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
     var location = asset.node.location.longitude ?
       JSON.stringify(asset.node.location) : 'Unknown location';
     return (
-      <View key={asset} style={styles.row}>
-        <Image
-          source={asset.node.image}
-          style={imageStyle}
-        />
-        <View style={styles.info}>
-          <Text style={styles.url}>{asset.node.image.uri}</Text>
-          <Text>{location}</Text>
-          <Text>{asset.node.group_name}</Text>
-          <Text>{new Date(asset.node.timestamp).toString()}</Text>
+      <TouchableOpacity onPress={ this.loadAsset.bind( this, asset ) }>
+        <View key={asset} style={styles.row}>
+          <Image
+            source={asset.node.image}
+            style={imageStyle}
+          />
+          <View style={styles.info}>
+            <Text style={styles.url}>{asset.node.image.uri}</Text>
+            <Text>{location}</Text>
+            <Text>{asset.node.group_name}</Text>
+            <Text>{new Date(asset.node.timestamp).toString()}</Text>
+          </View>
         </View>
-      </View>
+      </TouchableOpacity>
     );
   },
 
@@ -115,7 +128,7 @@ var styles = StyleSheet.create({
   },
 });
 
-exports.title = '<CameraRollView>';
+exports.title = 'Camera Roll';
 exports.description = 'Example component that uses CameraRoll to list user\'s photos';
 exports.examples = [
   {
diff --git a/Examples/UIExplorer/createExamplePage.js b/Examples/UIExplorer/createExamplePage.js
index 3d5a1ac88..352f84a4c 100644
--- a/Examples/UIExplorer/createExamplePage.js
+++ b/Examples/UIExplorer/createExamplePage.js
@@ -55,6 +55,7 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
       var result = example.render(null);
       if (result) {
         renderedComponent = result;
+        result.props.navigator = this.props.navigator;
       }
       (React: Object).render = originalRender;
       (React: Object).renderComponent = originalRenderComponent;
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index 4300f3b94..f9bfd1bf7 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -72,6 +72,64 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
                 completionBlock:callback];
 }
 
+//
+// Why use a custom scaling method:
+// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill/
+//     Greater efficiency, reduced memory overhead.
++ (UIImage *)scaledImageForAssetRepresentation:(ALAssetRepresentation *)representation
+                                         size:(CGSize)size
+                                        scale:(CGFloat)scale
+                                  orientation:(UIImageOrientation)orientation
+{
+  UIImage *image = nil;
+  NSData *data = nil;
+
+  uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*(NSUInteger)[representation size]);
+  if (buffer != NULL) {
+    NSError *error = nil;
+    NSUInteger bytesRead = [representation getBytes:buffer fromOffset:0 length:(NSUInteger)[representation size] error:&error];
+    data = [NSData dataWithBytes:buffer length:bytesRead];
+
+    free(buffer);
+  }
+
+  if ([data length]) {
+    CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
+
+    NSMutableDictionary *options = [NSMutableDictionary dictionary];
+
+    CGSize source = representation.dimensions;
+    CGFloat mW = size.width / source.width;
+    CGFloat mH = size.height / source.height;
+
+    if (mH > mW) {
+      size.width = size.height / source.height * source.width;
+    } else if (mW > mH) {
+      size.height = size.width / source.width * source.height;
+    }
+
+    CGFloat maxPixelSize = MAX(size.width, size.height) * scale;
+
+    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
+    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
+    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
+    [options setObject:(id)@(maxPixelSize) forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
+
+    CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
+
+    if (imageRef) {
+      image = [UIImage imageWithCGImage:imageRef scale:[representation scale] orientation:orientation];
+      CGImageRelease(imageRef);
+    }
+
+    if (sourceRef) {
+      CFRelease(sourceRef);
+    }
+  }
+
+  return image;
+}
+
 + (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
                                                size:(CGSize)size
                                               scale:(CGFloat)scale
@@ -94,28 +152,17 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
 
             BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
             ALAssetOrientation orientation = ALAssetOrientationUp;
-            CGImageRef imageRef = NULL;
+            ALAssetRepresentation *representation = [asset defaultRepresentation];
 
-            if (!useMaximumSize) {
-              imageRef = asset.thumbnail;
-            }
-            if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
-              if (!useMaximumSize) {
-                imageRef = asset.aspectRatioThumbnail;
-              }
-              if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
-                ALAssetRepresentation *representation = [asset defaultRepresentation];
-                orientation = [representation orientation];
-                if (!useMaximumSize) {
-                  imageRef = [representation fullScreenImage];
-                }
-                if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
-                  imageRef = [representation fullResolutionImage];
-                }
-              }
+            UIImage *image;
+
+            if (useMaximumSize) {
+              image = [UIImage imageWithCGImage:representation.fullResolutionImage scale:scale orientation:(UIImageOrientation)orientation];
+
+            } else {
+              image = [self scaledImageForAssetRepresentation:representation size:size scale:scale orientation:(UIImageOrientation)orientation];
             }
 
-            UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:(UIImageOrientation)orientation];
             RCTDispatchCallbackOnMainQueue(completion, nil, image);
           }
         });
@@ -145,12 +192,25 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
     }
 
     PHAsset *asset = [results firstObject];
-    CGSize targetSize = CGSizeEqualToSize(size, CGSizeZero) ? PHImageManagerMaximumSize : size;
+
+    PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
+
+    BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
+    CGSize targetSize;
+
+    if ( useMaximumSize ){
+      targetSize = PHImageManagerMaximumSize;
+      imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
+    } else {
+      targetSize = size;
+      imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
+    }
+
     PHImageContentMode contentMode = PHImageContentModeAspectFill;
     if (resizeMode == UIViewContentModeScaleAspectFit) {
       contentMode = PHImageContentModeAspectFit;
     }
-    [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
+    [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
       if (result) {
         RCTDispatchCallbackOnMainQueue(completion, nil, result);
       } else {

From 85cb35c51439a17c0961cc80518d2e979d7279c0 Mon Sep 17 00:00:00 2001
From: Nick Lockwood <nicklockwood@fb.com>
Date: Tue, 21 Jul 2015 05:40:06 -0700
Subject: [PATCH 23/33] Fixed rotation and scaling issues when loading ALAssets
 using RCTImageLoader

---
 .../UIExplorer.xcodeproj/project.pbxproj      |  8 +-
 ...{RCTClippingTests.m => RCTClipRectTests.m} | 21 ++--
 Libraries/Image/RCTImageLoader.m              | 98 ++++++++-----------
 Libraries/Image/RCTImageUtils.m               | 34 ++++++-
 4 files changed, 90 insertions(+), 71 deletions(-)
 rename Examples/UIExplorer/UIExplorerUnitTests/{RCTClippingTests.m => RCTClipRectTests.m} (90%)

diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index 988b3f433..dd7200d21 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -23,7 +23,7 @@
 		13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
 		141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
 		143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
-		144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClippingTests.m */; };
+		144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; };
 		147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
 		1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; };
 		1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; };
@@ -187,7 +187,7 @@
 		143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		143BC5981B21E3E100462512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerSnapshotTests.m; sourceTree = "<group>"; };
-		144D21231B2204C5006DB32B /* RCTClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClippingTests.m; sourceTree = "<group>"; };
+		144D21231B2204C5006DB32B /* RCTClipRectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipRectTests.m; sourceTree = "<group>"; };
 		1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = "<group>"; };
 		1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = "<group>"; };
 		1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = "<group>"; };
@@ -353,7 +353,7 @@
 				1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */,
 				1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */,
 				138D6A151B53CD440074A87E /* RCTCacheTests.m */,
-				144D21231B2204C5006DB32B /* RCTClippingTests.m */,
+				144D21231B2204C5006DB32B /* RCTClipRectTests.m */,
 				1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
 				1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
 				1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
@@ -787,7 +787,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */,
-				144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */,
+				144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */,
 				1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */,
 				1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
 				1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m
similarity index 90%
rename from Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m
rename to Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m
index 1f94a80c1..0041a1b46 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTClippingTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m
@@ -16,10 +16,7 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIView.h>
 #import <XCTest/XCTest.h>
-
-extern CGRect RCTClipRect(CGSize contentSize, CGFloat contentScale,
-                          CGSize targetSize, CGFloat targetScale,
-                          UIViewContentMode resizeMode);
+#import "RCTImageUtils.h"
 
 #define RCTAssertEqualPoints(a, b) { \
 XCTAssertEqual(a.x, b.x); \
@@ -36,11 +33,11 @@ RCTAssertEqualPoints(a.origin, b.origin); \
 RCTAssertEqualSizes(a.size, b.size); \
 }
 
-@interface ClippingTests : XCTestCase
+@interface RCTClipRectTests : XCTestCase
 
 @end
 
-@implementation ClippingTests
+@implementation RCTClipRectTests
 
 - (void)testLandscapeSourceLandscapeTarget
 {
@@ -109,6 +106,18 @@ RCTAssertEqualSizes(a.size, b.size); \
 
   {
     CGRect expected = {{0, -37.5}, {10, 100}};
+    CGRect result = RCTClipRect(content, 2, target, 2, UIViewContentModeScaleAspectFill);
+    RCTAssertEqualRects(expected, result);
+  }
+}
+
+- (void)testRounding
+{
+  CGSize content = {10, 100};
+  CGSize target = {20, 50};
+
+  {
+    CGRect expected = {{0, -38}, {10, 100}};
     CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
     RCTAssertEqualRects(expected, result);
   }
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index f9bfd1bf7..c9aeff5fe 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -72,62 +72,48 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
                 completionBlock:callback];
 }
 
-//
-// Why use a custom scaling method:
-// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill/
-//     Greater efficiency, reduced memory overhead.
-+ (UIImage *)scaledImageForAssetRepresentation:(ALAssetRepresentation *)representation
-                                         size:(CGSize)size
-                                        scale:(CGFloat)scale
-                                  orientation:(UIImageOrientation)orientation
+// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
+// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
+
+static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
+                                       CGSize size, CGFloat scale,
+                                       UIViewContentMode resizeMode,
+                                       NSError **error)
 {
-  UIImage *image = nil;
-  NSData *data = nil;
-
-  uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*(NSUInteger)[representation size]);
-  if (buffer != NULL) {
-    NSError *error = nil;
-    NSUInteger bytesRead = [representation getBytes:buffer fromOffset:0 length:(NSUInteger)[representation size] error:&error];
-    data = [NSData dataWithBytes:buffer length:bytesRead];
-
-    free(buffer);
+  NSUInteger length = (NSUInteger)representation.size;
+  NSMutableData *data = [NSMutableData dataWithLength:length];
+  if (![representation getBytes:data.mutableBytes
+                     fromOffset:0
+                         length:length
+                          error:error]) {
+    return nil;
   }
 
-  if ([data length]) {
-    CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
+  CGSize sourceSize = representation.dimensions;
+  CGRect targetRect = RCTClipRect(sourceSize, representation.scale, size, scale, resizeMode);
+  CGSize targetSize = targetRect.size;
 
-    NSMutableDictionary *options = [NSMutableDictionary dictionary];
+  NSDictionary *options = @{
+    (id)kCGImageSourceShouldAllowFloat: @YES,
+    (id)kCGImageSourceCreateThumbnailWithTransform: @YES,
+    (id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
+    (id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
+  };
 
-    CGSize source = representation.dimensions;
-    CGFloat mW = size.width / source.width;
-    CGFloat mH = size.height / source.height;
-
-    if (mH > mW) {
-      size.width = size.height / source.height * source.width;
-    } else if (mW > mH) {
-      size.height = size.width / source.width * source.height;
-    }
-
-    CGFloat maxPixelSize = MAX(size.width, size.height) * scale;
-
-    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
-    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
-    [options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
-    [options setObject:(id)@(maxPixelSize) forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
-
-    CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
-
-    if (imageRef) {
-      image = [UIImage imageWithCGImage:imageRef scale:[representation scale] orientation:orientation];
-      CGImageRelease(imageRef);
-    }
-
-    if (sourceRef) {
-      CFRelease(sourceRef);
-    }
+  CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
+  CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
+  if (sourceRef) {
+    CFRelease(sourceRef);
   }
 
-  return image;
+  if (imageRef) {
+    UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
+                                   orientation:(UIImageOrientation)representation.orientation];
+    CGImageRelease(imageRef);
+    return image;
+  }
+
+  return nil;
 }
 
 + (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
@@ -139,7 +125,7 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
                                     completionBlock:(RCTImageLoaderCompletionBlock)completion
 {
   if ([imageTag hasPrefix:@"assets-library://"]) {
-    [[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
+    [[self assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
       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,
@@ -151,19 +137,19 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
           @autoreleasepool {
 
             BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
-            ALAssetOrientation orientation = ALAssetOrientationUp;
             ALAssetRepresentation *representation = [asset defaultRepresentation];
 
             UIImage *image;
-
+            NSError *error = nil;
             if (useMaximumSize) {
-              image = [UIImage imageWithCGImage:representation.fullResolutionImage scale:scale orientation:(UIImageOrientation)orientation];
-
+              image = [UIImage imageWithCGImage:representation.fullResolutionImage
+                                          scale:scale
+                                    orientation:(UIImageOrientation)representation.orientation];
             } else {
-              image = [self scaledImageForAssetRepresentation:representation size:size scale:scale orientation:(UIImageOrientation)orientation];
+              image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
             }
 
-            RCTDispatchCallbackOnMainQueue(completion, nil, image);
+            RCTDispatchCallbackOnMainQueue(completion, error, image);
           }
         });
       } else {
diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m
index 89d269532..7b2d88ebc 100644
--- a/Libraries/Image/RCTImageUtils.m
+++ b/Libraries/Image/RCTImageUtils.m
@@ -11,6 +11,24 @@
 
 #import "RCTLog.h"
 
+static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
+{
+  return ceil(value * scale) / scale;
+}
+
+static CGFloat RCTFloorValue(CGFloat value, CGFloat scale)
+{
+  return floor(value * scale) / scale;
+}
+
+static CGSize RCTCeilSize(CGSize size, CGFloat scale)
+{
+  return (CGSize){
+    RCTCeilValue(size.width, scale),
+    RCTCeilValue(size.height, scale)
+  };
+}
+
 CGSize RCTTargetSizeForClipRect(CGRect clipRect)
 {
   return (CGSize){
@@ -48,7 +66,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
 
       sourceSize.width = MIN(destSize.width, sourceSize.width);
       sourceSize.height = MIN(destSize.height, sourceSize.height);
-      return (CGRect){CGPointZero, sourceSize};
+      return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
 
     case UIViewContentModeScaleAspectFit: // contain
 
@@ -62,7 +80,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
         sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
         sourceSize.width = sourceSize.height * aspect;
       }
-      return (CGRect){CGPointZero, sourceSize};
+      return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
 
     case UIViewContentModeScaleAspectFill: // cover
 
@@ -71,20 +89,26 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
         sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
         sourceSize.width = sourceSize.height * aspect;
         destSize.width = destSize.height * targetAspect;
-        return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
+        return (CGRect){
+          {RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale), 0},
+          RCTCeilSize(sourceSize, destScale)
+        };
 
       } else { // target is wider than content
 
         sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
         sourceSize.height = sourceSize.width / aspect;
         destSize.height = destSize.width / targetAspect;
-        return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
+        return (CGRect){
+          {0, RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale)},
+          RCTCeilSize(sourceSize, destScale)
+        };
       }
 
     default:
 
       RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
-      return (CGRect){CGPointZero, destSize};
+      return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
   }
 }
 

From 98ab7581a560c8db4f9637bc5de14f273f599d27 Mon Sep 17 00:00:00 2001
From: Alex Akers <a2@fb.com>
Date: Tue, 21 Jul 2015 09:10:52 -0700
Subject: [PATCH 24/33] Remove images from offscreen image views

---
 Libraries/Image/RCTImageView.m | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m
index 5604ec4b8..ea7b6047e 100644
--- a/Libraries/Image/RCTImageView.m
+++ b/Libraries/Image/RCTImageView.m
@@ -178,4 +178,23 @@ RCT_NOT_IMPLEMENTED(-init)
   }
 }
 
+- (void)willMoveToSuperview:(UIView *)newSuperview
+{
+  [super willMoveToSuperview:newSuperview];
+
+  if (!newSuperview) {
+    [self.layer removeAnimationForKey:@"contents"];
+    self.image = nil;
+  }
+}
+
+- (void)didMoveToSuperview
+{
+  [super didMoveToSuperview];
+
+  if (self.superview && self.src) {
+    [self reloadImage];
+  }
+}
+
 @end

From 4f904b5d68d1176cabf51b3a476d11b61fb07bd1 Mon Sep 17 00:00:00 2001
From: Eric Vicenti <evv@fb.com>
Date: Tue, 21 Jul 2015 09:17:44 -0700
Subject: [PATCH 25/33] [ReactNative] Remove Navigator onItemRef

Summary:
Re-landing D2229686 after fixing bugs mentioned in D2250586

onItemRef is old and no longer needed now that the parent renders the scenes. This removes it from Navigator and all of our clients.

This is a breaking change to users of Navigator, but it is easy to transition to a ref in renderScene instead
---
 .../CustomComponents/Navigator/Navigator.js   | 62 +++++++------------
 1 file changed, 23 insertions(+), 39 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index bc5d65791..a8916ec12 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -67,6 +67,24 @@ function getuid() {
   return __uid++;
 }
 
+function getRouteID(route) {
+  if (route === null || typeof route !== 'object') {
+    return String(route);
+  }
+
+  var key = '__navigatorRouteID';
+
+  if (!route.hasOwnProperty(key)) {
+    Object.defineProperty(route, key, {
+      enumerable: false,
+      configurable: false,
+      writable: false,
+      value: getuid(),
+    });
+  }
+  return route[key];
+}
+
 // styles moved to the top of the file so getDefaultProps can refer to it
 var styles = StyleSheet.create({
   container: {
@@ -220,11 +238,6 @@ var Navigator = React.createClass({
      */
     onDidFocus: PropTypes.func,
 
-    /**
-     * Will be called with (ref, indexInStack, route) when the scene ref changes
-     */
-    onItemRef: PropTypes.func,
-
     /**
      * Optionally provide a navigation bar that persists across scene
      * transitions
@@ -318,7 +331,6 @@ var Navigator = React.createClass({
       onPanResponderMove: this._handlePanResponderMove,
       onPanResponderTerminate: this._handlePanResponderTerminate,
     });
-    this._itemRefs = {};
     this._interactionHandle = null;
     this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]);
   },
@@ -1006,22 +1018,10 @@ var Navigator = React.createClass({
     return this.state.routeStack.slice();
   },
 
-  _handleItemRef: function(itemId, route, ref) {
-    this._itemRefs[itemId] = ref;
-    var itemIndex = this.state.idStack.indexOf(itemId);
-    if (itemIndex === -1) {
-      return;
-    }
-    this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route);
-  },
-
   _cleanScenesPastIndex: function(index) {
     var newStackLength = index + 1;
     // Remove any unneeded rendered routes.
     if (newStackLength < this.state.routeStack.length) {
-      this.state.idStack.slice(newStackLength).map((removingId) => {
-        this._itemRefs[removingId] = null;
-      });
       this.setState({
         sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength),
         idStack: this.state.idStack.slice(0, newStackLength),
@@ -1031,38 +1031,22 @@ var Navigator = React.createClass({
   },
 
   _renderScene: function(route, i) {
-    var child = this.props.renderScene(
-      route,
-      this
-    );
     var disabledSceneStyle = null;
     if (i !== this.state.presentedIndex) {
       disabledSceneStyle = styles.disabledScene;
     }
-    var originalRef = child.ref;
-    if (originalRef != null && typeof originalRef !== 'function') {
-      console.warn(
-        'String refs are not supported for navigator scenes. Use a callback ' +
-        'ref instead. Ignoring ref: ' + originalRef
-      );
-      originalRef = null;
-    }
     return (
       <View
-        key={this.state.idStack[i]}
+        key={'scene_' + getRouteID(route)}
         ref={'scene_' + i}
         onStartShouldSetResponderCapture={() => {
           return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null);
         }}
         style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
-        {React.cloneElement(child, {
-          ref: component => {
-            this._handleItemRef(this.state.idStack[i], route, component);
-            if (originalRef) {
-              originalRef(component);
-            }
-          }
-        })}
+        {this.props.renderScene(
+          route,
+          this
+        )}
       </View>
     );
   },

From 961c1eb42904a4d5516fd7939ba14bc0625309d3 Mon Sep 17 00:00:00 2001
From: Spencer Ahrens <sahrens@fb.com>
Date: Tue, 21 Jul 2015 12:37:24 -0700
Subject: [PATCH 26/33] [ReactNative] TextInput bug fixes and features

Summary:
This introduces event counts to make sure JS doesn't set out of date values on
native text inputs, which can cause dropped characters and can mess with
autocomplete, and obviates the need for the input buffering which added lag and
complexity to the component.  Made sure to test simulated super-slow JS text
event processing to make sure characters aren't dropped, as well as typing
obviously correctable words and making sure autocomplete works as expected.

TextInput is now a controlled input by default without causing any issues for
most cases, so I removed the `controlled` prop.

Fixes selection state jumping by restoring it after setting new text values, so
highlighting the middle of some text in the new ReWrite example and hitting
space will replace that selection with an underscore and keep the cursor at a
sensible position as expected, instead of jumping to the end.

Ads `maxLength` prop to support the most commonly needed syncronous behavior:
preventing the user from typing too many characters.  It can also be used to
prevent users from continuing to type after entering special characters by
changing it to the current length after a regex match.  Made sure to verify it
works well with pasted input (including in the middle of existing text),
truncating it and collapsing the selection the same way it does on the web.

Fixes bug in TextEventsExample where it wouldn't show the submit and end events,
even though there were firing correctly.
---
 Examples/UIExplorer/TextInputExample.js     |  65 +++++++--
 Libraries/Components/TextInput/TextInput.js | 150 +++++++-------------
 Libraries/Text/RCTTextField.h               |   4 +-
 Libraries/Text/RCTTextField.m               |  72 ++++++++--
 Libraries/Text/RCTTextFieldManager.m        |   2 +
 Libraries/Text/RCTTextView.h                |   2 +
 Libraries/Text/RCTTextView.m                |  46 +++++-
 Libraries/Text/RCTTextViewManager.m         |   2 +
 React/Base/RCTEventDispatcher.h             |   6 +-
 React/Base/RCTEventDispatcher.m             |   5 +
 10 files changed, 222 insertions(+), 132 deletions(-)

diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js
index 06cc12ee3..3369b41fb 100644
--- a/Examples/UIExplorer/TextInputExample.js
+++ b/Examples/UIExplorer/TextInputExample.js
@@ -33,7 +33,7 @@ var WithLabel = React.createClass({
         {this.props.children}
       </View>
     );
-  }
+  },
 });
 
 var TextEventsExample = React.createClass({
@@ -41,13 +41,17 @@ var TextEventsExample = React.createClass({
     return {
       curText: '<No Event>',
       prevText: '<No Event>',
+      prev2Text: '<No Event>',
     };
   },
 
   updateText: function(text) {
-    this.setState({
-      curText: text,
-      prevText: this.state.curText,
+    this.setState((state) => {
+      return {
+        curText: text,
+        prevText: state.curText,
+        prev2Text: state.prevText,
+      };
     });
   },
 
@@ -73,13 +77,43 @@ var TextEventsExample = React.createClass({
         />
         <Text style={styles.eventLabel}>
           {this.state.curText}{'\n'}
-          (prev: {this.state.prevText})
+          (prev: {this.state.prevText}){'\n'}
+          (prev2: {this.state.prev2Text})
         </Text>
       </View>
     );
   }
 });
 
+class RewriteExample extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {text: ''};
+  }
+  render() {
+    var limit = 20;
+    var remainder = limit - this.state.text.length;
+    var remainderColor = remainder > 5 ? 'blue' : 'red';
+    return (
+      <View style={styles.rewriteContainer}>
+        <TextInput
+          multiline={false}
+          maxLength={limit}
+          onChangeText={(text) => {
+            text = text.replace(/ /g, '_');
+            this.setState({text});
+          }}
+          style={styles.default}
+          value={this.state.text}
+        />
+        <Text style={[styles.remainder, {color: remainderColor}]}>
+          {remainder}
+        </Text>
+      </View>
+    );
+  }
+}
+
 var styles = StyleSheet.create({
   page: {
     paddingBottom: 300,
@@ -125,12 +159,19 @@ var styles = StyleSheet.create({
     flex: 1,
   },
   label: {
-    width: 120,
-    justifyContent: 'flex-end',
-    flexDirection: 'row',
+    width: 115,
+    alignItems: 'flex-end',
     marginRight: 10,
     paddingTop: 2,
   },
+  rewriteContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  remainder: {
+    textAlign: 'right',
+    width: 24,
+  },
 });
 
 exports.displayName = (undefined: ?string);
@@ -143,6 +184,12 @@ exports.examples = [
       return <TextInput autoFocus={true} style={styles.default} />;
     }
   },
+  {
+    title: "Live Re-Write (<sp>  ->  '_') + maxLength",
+    render: function() {
+      return <RewriteExample />;
+    }
+  },
   {
     title: 'Auto-capitalize',
     render: function() {
@@ -276,7 +323,7 @@ exports.examples = [
   },
   {
     title: 'Event handling',
-    render: function(): ReactElement { return <TextEventsExample /> },
+    render: function(): ReactElement { return <TextEventsExample />; },
   },
   {
     title: 'Colored input text',
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index cc1b00b41..d89291c37 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -31,8 +31,8 @@ var invariant = require('invariant');
 var requireNativeComponent = require('requireNativeComponent');
 
 var onlyMultiline = {
-  onSelectionChange: true,
-  onTextInput: true,
+  onSelectionChange: true, // not supported in Open Source yet
+  onTextInput: true, // not supported in Open Source yet
   children: true,
 };
 
@@ -64,10 +64,6 @@ var viewConfigAndroid = {
 var RCTTextView = requireNativeComponent('RCTTextView', null);
 var RCTTextField = requireNativeComponent('RCTTextField', null);
 
-type DefaultProps = {
-  bufferDelay: number;
-};
-
 type Event = Object;
 
 /**
@@ -77,30 +73,29 @@ type Event = Object;
  * types, such as a numeric keypad.
  *
  * The simplest use case is to plop down a `TextInput` and subscribe to the
- * `onChangeText` events to read the user input.  There are also other events, such
- * as `onSubmitEditing` and `onFocus` that can be subscribed to.  A simple
+ * `onChangeText` events to read the user input.  There are also other events,
+ * such as `onSubmitEditing` and `onFocus` that can be subscribed to.  A simple
  * example:
  *
  * ```
- * <View>
  *   <TextInput
  *     style={{height: 40, borderColor: 'gray', borderWidth: 1}}
- *     onChangeText={(text) => this.setState({input: text})}
+ *     onChangeText={(text) => this.setState({text})}
+ *     value={this.state.text}
  *   />
- *   <Text>{'user input: ' + this.state.input}</Text>
- * </View>
  * ```
  *
- * The `value` prop can be used to set the value of the input in order to make
- * the state of the component clear, but <TextInput> does not behave as a true
- * controlled component by default because all operations are asynchronous.
- * Setting `value` once is like setting the default value, but you can change it
- * continuously based on `onChangeText` events as well.  If you really want to
- * force the component to always revert to the value you are setting, you can
- * set `controlled={true}`.
+ * Note that some props are only available with multiline={true/false}:
  *
- * The `multiline` prop is not supported in all releases, and some props are
- * multiline only.
+ *   var onlyMultiline = {
+ *     onSelectionChange: true, // not supported in Open Source yet
+ *     onTextInput: true, // not supported in Open Source yet
+ *     children: true,
+ *   };
+ *
+ *   var notMultiline = {
+ *     onSubmitEditing: true,
+ *   };
  */
 
 var TextInput = React.createClass({
@@ -179,6 +174,11 @@ var TextInput = React.createClass({
       'done',
       'emergency-call',
     ]),
+    /**
+     * Limits the maximum number of characters that can be entered.  Use this
+     * instead of implementing the logic in JS to avoid flicker.
+     */
+    maxLength: PropTypes.number,
     /**
      * If true, the keyboard disables the return key when there is no text and
      * automatically enables it when there is text. Default value is false.
@@ -236,22 +236,15 @@ var TextInput = React.createClass({
      */
     selectionState: PropTypes.instanceOf(DocumentSelectionState),
     /**
-     * The default value for the text input
+     * The value to show for the text input.  TextInput is a controlled
+     * component, which means the native value will be forced to match this
+     * value prop if provided.  For most uses this works great, but in some
+     * cases this may cause flickering - one common cause is preventing edits
+     * by keeping value the same.  In addition to simply setting the same value,
+     * either set `editable={false}`, or set/update `maxLength` to prevent
+     * unwanted edits without flicker.
      */
     value: PropTypes.string,
-    /**
-     * This helps avoid drops characters due to race conditions between JS and
-     * the native text input.  The default should be fine, but if you're
-     * potentially doing very slow operations on every keystroke then you may
-     * want to try increasing this.
-     */
-    bufferDelay: PropTypes.number,
-    /**
-     * If you really want this to behave as a controlled component, you can set
-     * this true, but you will probably see flickering, dropped keystrokes,
-     * and/or laggy typing, depending on how you process onChange events.
-     */
-    controlled: PropTypes.bool,
     /**
      * When the clear button should appear on the right side of the text view
      */
@@ -297,16 +290,9 @@ var TextInput = React.createClass({
       React.findNodeHandle(this.refs.input);
   },
 
-  getDefaultProps: function(): DefaultProps {
-    return {
-      bufferDelay: 100,
-    };
-  },
-
   getInitialState: function() {
     return {
-      mostRecentEventCounter: 0,
-      bufferedValue: this.props.value,
+      mostRecentEventCount: 0,
     };
   },
 
@@ -346,52 +332,6 @@ var TextInput = React.createClass({
     }
   },
 
-  _bufferTimeout: (undefined: ?number),
-
-  componentWillReceiveProps: function(newProps: {value: any}) {
-    if (newProps.value !== this.props.value) {
-      if (!this.isFocused()) {
-        // Set the value immediately if the input is not focused since that
-        // means there is no risk of the user typing immediately.
-        this.setState({bufferedValue: newProps.value});
-      } else {
-        // The following clear and setTimeout buffers the value such that if more
-        // characters are typed in quick succession, generating new values, the
-        // out of date values will get cancelled before they are ever sent to
-        // native.
-        //
-        // If we don't do this, it's likely the out of date values will blow
-        // away recently typed characters in the native input that JS was not
-        // yet aware of (since it is informed asynchronously), then the next
-        // character will be appended to the older value, dropping the
-        // characters in between.  Here is a potential sequence of events
-        // (recall we have multiple independently serial, interleaved queues):
-        //
-        // 1) User types 'R' => send 'R' to JS queue.
-        // 2) User types 'e' => send 'Re' to JS queue.
-        // 3) JS processes 'R' and sends 'R' back to native.
-        // 4) Native recieves 'R' and changes input from 'Re' back to 'R'.
-        // 5) User types 'a' => send 'Ra' to JS queue.
-        // 6) JS processes 'Re' and sends 'Re' back to native.
-        // 7) Native recieves 'Re' and changes input from 'R' back to 'Re'.
-        // 8) JS processes 'Ra' and sends 'Ra' back to native.
-        // 9) Native recieves final 'Ra' from JS - 'e' has been dropped!
-        //
-        // This isn't 100% foolproop (e.g. if it takes longer than
-        // `props.bufferDelay` ms to process one keystroke), and there are of
-        // course other potential algorithms to deal with this, but this is a
-        // simple solution that seems to reduce the chance of dropped characters
-        // drastically without compromising native input responsiveness (e.g. by
-        // introducing delay from a synchronization protocol).
-        this.clearTimeout(this._bufferTimeout);
-        this._bufferTimeout = this.setTimeout(
-          () => this.setState({bufferedValue: newProps.value}),
-          this.props.bufferDelay
-        );
-      }
-    }
-  },
-
   getChildContext: function(): Object {
     return {isInAParentText: true};
   },
@@ -411,7 +351,7 @@ var TextInput = React.createClass({
   _renderIOS: function() {
     var textContainer;
 
-    var props = Object.assign({},this.props);
+    var props = Object.assign({}, this.props);
     props.style = [styles.input, this.props.style];
 
     if (!props.multiline) {
@@ -430,7 +370,8 @@ var TextInput = React.createClass({
           onBlur={this._onBlur}
           onChange={this._onChange}
           onSelectionChangeShouldSetResponder={() => true}
-          text={this.state.bufferedValue}
+          text={this.props.value}
+          mostRecentEventCount={this.state.mostRecentEventCount}
         />;
     } else {
       for (var propKey in notMultiline) {
@@ -459,14 +400,14 @@ var TextInput = React.createClass({
           ref="input"
           {...props}
           children={children}
-          mostRecentEventCounter={this.state.mostRecentEventCounter}
+          mostRecentEventCount={this.state.mostRecentEventCount}
           onFocus={this._onFocus}
           onBlur={this._onBlur}
           onChange={this._onChange}
           onSelectionChange={this._onSelectionChange}
           onTextInput={this._onTextInput}
           onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
-          text={this.state.bufferedValue}
+          text={this.props.value}
         />;
     }
 
@@ -516,7 +457,7 @@ var TextInput = React.createClass({
         password={this.props.password || this.props.secureTextEntry}
         placeholder={this.props.placeholder}
         placeholderTextColor={this.props.placeholderTextColor}
-        text={this.state.bufferedValue}
+        text={this.props.value}
         underlineColorAndroid={this.props.underlineColorAndroid}
         children={children}
       />;
@@ -543,11 +484,20 @@ var TextInput = React.createClass({
   },
 
   _onChange: function(event: Event) {
-    if (this.props.controlled && event.nativeEvent.text !== this.props.value) {
-      this.refs.input.setNativeProps({text: this.props.value});
-    }
+    var text = event.nativeEvent.text;
+    var eventCount = event.nativeEvent.eventCount;
     this.props.onChange && this.props.onChange(event);
-    this.props.onChangeText && this.props.onChangeText(event.nativeEvent.text);
+    this.props.onChangeText && this.props.onChangeText(text);
+    this.setState({mostRecentEventCount: eventCount}, () => {
+      // This is a controlled component, so make sure to force the native value
+      // to match.  Most usage shouldn't need this, but if it does this will be
+      // more correct but might flicker a bit and/or cause the cursor to jump.
+      if (text !== this.props.value && typeof this.props.value === 'string') {
+        this.refs.input.setNativeProps({
+          text: this.props.value,
+        });
+      }
+    });
   },
 
   _onBlur: function(event: Event) {
@@ -567,10 +517,6 @@ var TextInput = React.createClass({
 
   _onTextInput: function(event: Event) {
     this.props.onTextInput && this.props.onTextInput(event);
-    var counter = event.nativeEvent.eventCounter;
-    if (counter > this.state.mostRecentEventCounter) {
-      this.setState({mostRecentEventCounter: counter});
-    }
   },
 });
 
diff --git a/Libraries/Text/RCTTextField.h b/Libraries/Text/RCTTextField.h
index ef0a07887..0c8266d7e 100644
--- a/Libraries/Text/RCTTextField.h
+++ b/Libraries/Text/RCTTextField.h
@@ -11,13 +11,15 @@
 
 @class RCTEventDispatcher;
 
-@interface RCTTextField : UITextField
+@interface RCTTextField : UITextField<UITextFieldDelegate>
 
 @property (nonatomic, assign) BOOL caretHidden;
 @property (nonatomic, assign) BOOL autoCorrect;
 @property (nonatomic, assign) BOOL selectTextOnFocus;
 @property (nonatomic, assign) UIEdgeInsets contentInset;
 @property (nonatomic, strong) UIColor *placeholderTextColor;
+@property (nonatomic, assign) NSInteger mostRecentEventCount;
+@property (nonatomic, strong) NSNumber *maxLength;
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
 
diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m
index 46e9cc7a4..57e0499bd 100644
--- a/Libraries/Text/RCTTextField.m
+++ b/Libraries/Text/RCTTextField.m
@@ -19,6 +19,7 @@
   RCTEventDispatcher *_eventDispatcher;
   NSMutableArray *_reactSubviews;
   BOOL _jsRequestingFirstResponder;
+  NSInteger _nativeEventCount;
 }
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@@ -31,6 +32,7 @@
     [self addTarget:self action:@selector(_textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
     [self addTarget:self action:@selector(_textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
     _reactSubviews = [[NSMutableArray alloc] init];
+    self.delegate = self;
   }
   return self;
 }
@@ -38,10 +40,40 @@
 RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame)
 RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
 
+- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
+{
+  if (_maxLength == nil || [string isEqualToString:@"\n"]) {  // Make sure forms can be submitted via return
+    return YES;
+  }
+  NSUInteger allowedLength = _maxLength.integerValue - textField.text.length + range.length;
+  if (string.length > allowedLength) {
+    if (string.length > 1) {
+      // Truncate the input string so the result is exactly maxLength
+      NSString *limitedString = [string substringToIndex:allowedLength];
+      NSMutableString *newString = textField.text.mutableCopy;
+      [newString replaceCharactersInRange:range withString:limitedString];
+      textField.text = newString;
+      // Collapse selection at end of insert to match normal paste behavior
+      UITextPosition *insertEnd = [textField positionFromPosition:textField.beginningOfDocument
+                                                          offset:(range.location + allowedLength)];
+      textField.selectedTextRange = [textField textRangeFromPosition:insertEnd toPosition:insertEnd];
+      [self _textFieldDidChange];
+    }
+    return NO;
+  } else {
+    return YES;
+  }
+}
+
 - (void)setText:(NSString *)text
 {
-  if (![text isEqualToString:self.text]) {
+  NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
+  if (eventLag == 0 && ![text isEqualToString:self.text]) {
+    UITextRange *selection = self.selectedTextRange;
     [super setText:text];
+    self.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
+  } else if (eventLag > RCTTextUpdateLagWarningThreshold) {
+    RCTLogWarn(@"Native TextInput(%@) is %ld events ahead of JS - try to make your JS faster.", self.text, (long)eventLag);
   }
 }
 
@@ -122,17 +154,29 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
   return self.autocorrectionType == UITextAutocorrectionTypeYes;
 }
 
-#define RCT_TEXT_EVENT_HANDLER(delegateMethod, eventName) \
-- (void)delegateMethod                                    \
-{                                                         \
-  [_eventDispatcher sendTextEventWithType:eventName       \
-                                 reactTag:self.reactTag   \
-                                     text:self.text];     \
+- (void)_textFieldDidChange
+{
+  _nativeEventCount++;
+  [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
+                                 reactTag:self.reactTag
+                                     text:self.text
+                               eventCount:_nativeEventCount];
 }
 
-RCT_TEXT_EVENT_HANDLER(_textFieldDidChange, RCTTextEventTypeChange)
-RCT_TEXT_EVENT_HANDLER(_textFieldEndEditing, RCTTextEventTypeEnd)
-RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
+- (void)_textFieldEndEditing
+{
+  [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
+                                 reactTag:self.reactTag
+                                     text:self.text
+                               eventCount:_nativeEventCount];
+}
+- (void)_textFieldSubmitEditing
+{
+  [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
+                                 reactTag:self.reactTag
+                                     text:self.text
+                               eventCount:_nativeEventCount];
+}
 
 - (void)_textFieldBeginEditing
 {
@@ -143,11 +187,10 @@ RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
   }
   [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
                                  reactTag:self.reactTag
-                                     text:self.text];
+                                     text:self.text
+                               eventCount:_nativeEventCount];
 }
 
-// TODO: we should support shouldChangeTextInRect (see UITextFieldDelegate)
-
 - (BOOL)becomeFirstResponder
 {
   _jsRequestingFirstResponder = YES;
@@ -163,7 +206,8 @@ RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
   {
     [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
                                    reactTag:self.reactTag
-                                       text:self.text];
+                                       text:self.text
+                                 eventCount:_nativeEventCount];
   }
   return result;
 }
diff --git a/Libraries/Text/RCTTextFieldManager.m b/Libraries/Text/RCTTextFieldManager.m
index cc71b39fa..723ec10f9 100644
--- a/Libraries/Text/RCTTextFieldManager.m
+++ b/Libraries/Text/RCTTextFieldManager.m
@@ -29,6 +29,7 @@ RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
 RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
 RCT_EXPORT_VIEW_PROPERTY(text, NSString)
+RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
 RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
 RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
@@ -56,6 +57,7 @@ RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
 {
   view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
 }
+RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
 
 - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
 {
diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h
index 014e35315..c5012ec09 100644
--- a/Libraries/Text/RCTTextView.h
+++ b/Libraries/Text/RCTTextView.h
@@ -25,6 +25,8 @@
 @property (nonatomic, strong) UIColor *textColor;
 @property (nonatomic, strong) UIColor *placeholderTextColor;
 @property (nonatomic, strong) UIFont *font;
+@property (nonatomic, assign) NSInteger mostRecentEventCount;
+@property (nonatomic, strong) NSNumber *maxLength;
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
 
diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m
index f32debd47..bbb9a6927 100644
--- a/Libraries/Text/RCTTextView.m
+++ b/Libraries/Text/RCTTextView.m
@@ -21,6 +21,7 @@
   NSString *_placeholder;
   UITextView *_placeholderView;
   UITextView *_textView;
+  NSInteger _nativeEventCount;
 }
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@@ -124,11 +125,41 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
   return _textView.text;
 }
 
+- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
+{
+  if (_maxLength == nil) {
+    return YES;
+  }
+  NSUInteger allowedLength = _maxLength.integerValue - textView.text.length + range.length;
+  if (text.length > allowedLength) {
+    if (text.length > 1) {
+      // Truncate the input string so the result is exactly maxLength
+      NSString *limitedString = [text substringToIndex:allowedLength];
+      NSMutableString *newString = textView.text.mutableCopy;
+      [newString replaceCharactersInRange:range withString:limitedString];
+      textView.text = newString;
+      // Collapse selection at end of insert to match normal paste behavior
+      UITextPosition *insertEnd = [textView positionFromPosition:textView.beginningOfDocument
+                                                          offset:(range.location + allowedLength)];
+      textView.selectedTextRange = [textView textRangeFromPosition:insertEnd toPosition:insertEnd];
+      [self textViewDidChange:textView];
+    }
+    return NO;
+  } else {
+    return YES;
+  }
+}
+
 - (void)setText:(NSString *)text
 {
-  if (![text isEqualToString:_textView.text]) {
+  NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
+  if (eventLag == 0 && ![text isEqualToString:_textView.text]) {
+    UITextRange *selection = _textView.selectedTextRange;
     [_textView setText:text];
     [self _setPlaceholderVisibility];
+    _textView.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
+  } else if (eventLag > RCTTextUpdateLagWarningThreshold) {
+    RCTLogWarn(@"Native TextInput(%@) is %ld events ahead of JS - try to make your JS faster.", self.text, (long)eventLag);
   }
 }
 
@@ -170,15 +201,18 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
 
   [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
                                  reactTag:self.reactTag
-                                     text:textView.text];
+                                     text:textView.text
+                               eventCount:_nativeEventCount];
 }
 
 - (void)textViewDidChange:(UITextView *)textView
 {
   [self _setPlaceholderVisibility];
+  _nativeEventCount++;
   [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
                                  reactTag:self.reactTag
-                                     text:textView.text];
+                                     text:textView.text
+                               eventCount:_nativeEventCount];
 
 }
 
@@ -186,7 +220,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
 {
   [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
                                  reactTag:self.reactTag
-                                     text:textView.text];
+                                     text:textView.text
+                               eventCount:_nativeEventCount];
 }
 
 - (BOOL)becomeFirstResponder
@@ -204,7 +239,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
   if (result) {
     [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
                                    reactTag:self.reactTag
-                                       text:_textView.text];
+                                       text:_textView.text
+                                 eventCount:_nativeEventCount];
   }
   return result;
 }
diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m
index 570a51115..f47a106bd 100644
--- a/Libraries/Text/RCTTextViewManager.m
+++ b/Libraries/Text/RCTTextViewManager.m
@@ -29,6 +29,7 @@ RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
 RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
 RCT_EXPORT_VIEW_PROPERTY(text, NSString)
+RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
 RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
 RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
@@ -52,6 +53,7 @@ RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView)
 {
   view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
 }
+RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
 
 - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
 {
diff --git a/React/Base/RCTEventDispatcher.h b/React/Base/RCTEventDispatcher.h
index 5576df64f..ebd58e75e 100644
--- a/React/Base/RCTEventDispatcher.h
+++ b/React/Base/RCTEventDispatcher.h
@@ -28,6 +28,8 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
   RCTScrollEventTypeEndAnimation,
 };
 
+extern const NSInteger RCTTextUpdateLagWarningThreshold;
+
 @protocol RCTEvent <NSObject>
 
 @required
@@ -76,12 +78,14 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
  */
 - (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
 
+
 /**
  * Send a text input/focus event.
  */
 - (void)sendTextEventWithType:(RCTTextEventType)type
                      reactTag:(NSNumber *)reactTag
-                         text:(NSString *)text;
+                         text:(NSString *)text
+                   eventCount:(NSInteger)eventCount;
 
 - (void)sendEvent:(id<RCTEvent>)event;
 
diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m
index ac0d1097b..7638ce99d 100644
--- a/React/Base/RCTEventDispatcher.m
+++ b/React/Base/RCTEventDispatcher.m
@@ -12,6 +12,8 @@
 #import "RCTAssert.h"
 #import "RCTBridge.h"
 
+const NSInteger RCTTextUpdateLagWarningThreshold = 3;
+
 static NSNumber *RCTGetEventID(id<RCTEvent> event)
 {
   return @(
@@ -113,6 +115,7 @@ RCT_EXPORT_MODULE()
 - (void)sendTextEventWithType:(RCTTextEventType)type
                      reactTag:(NSNumber *)reactTag
                          text:(NSString *)text
+                   eventCount:(NSInteger)eventCount
 {
   static NSString *events[] = {
     @"topFocus",
@@ -124,8 +127,10 @@ RCT_EXPORT_MODULE()
 
   [self sendInputEventWithName:events[type] body:text ? @{
     @"text": text,
+    @"eventCount": @(eventCount),
     @"target": reactTag
   } : @{
+    @"eventCount": @(eventCount),
     @"target": reactTag
   }];
 }

From 35aa9f3b9797681461b925af64f274a62bce3e44 Mon Sep 17 00:00:00 2001
From: Eric Vicenti <evv@fb.com>
Date: Tue, 21 Jul 2015 13:08:39 -0700
Subject: [PATCH 27/33] [ReactNative] Remove idStack from Navigator

Summary:
We want to transition to NavigationRouteStack, so we need to simplify and remove all uses of idStack
---
 Libraries/CustomComponents/Navigator/Navigator.js | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index a8916ec12..f55f44e35 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -290,7 +290,6 @@ var Navigator = React.createClass({
       sceneConfigStack: routeStack.map(
         (route) => this.props.configureScene(route)
       ),
-      idStack: routeStack.map(() => getuid()),
       routeStack,
       presentedIndex: initialRouteIndex,
       transitionFromIndex: null,
@@ -357,7 +356,6 @@ var Navigator = React.createClass({
   immediatelyResetRouteStack: function(nextRouteStack) {
     var destIndex = nextRouteStack.length - 1;
     this.setState({
-      idStack: nextRouteStack.map(getuid),
       routeStack: nextRouteStack,
       sceneConfigStack: nextRouteStack.map(
         this.props.configureScene
@@ -882,17 +880,14 @@ var Navigator = React.createClass({
     invariant(!!route, 'Must supply route to push');
     var activeLength = this.state.presentedIndex + 1;
     var activeStack = this.state.routeStack.slice(0, activeLength);
-    var activeIDStack = this.state.idStack.slice(0, activeLength);
     var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
     var nextStack = activeStack.concat([route]);
     var destIndex = nextStack.length - 1;
-    var nextIDStack = activeIDStack.concat([getuid()]);
     var nextAnimationConfigStack = activeAnimationConfigStack.concat([
       this.props.configureScene(route),
     ]);
     this._emitWillFocus(nextStack[destIndex]);
     this.setState({
-      idStack: nextIDStack,
       routeStack: nextStack,
       sceneConfigStack: nextAnimationConfigStack,
     }, () => {
@@ -942,12 +937,8 @@ var Navigator = React.createClass({
       return;
     }
 
-    // I don't believe we need to lock for a replace since there's no
-    // navigation actually happening
-    var nextIDStack = this.state.idStack.slice();
     var nextRouteStack = this.state.routeStack.slice();
     var nextAnimationModeStack = this.state.sceneConfigStack.slice();
-    nextIDStack[index] = getuid();
     nextRouteStack[index] = route;
     nextAnimationModeStack[index] = this.props.configureScene(route);
 
@@ -955,7 +946,6 @@ var Navigator = React.createClass({
       this._emitWillFocus(route);
     }
     this.setState({
-      idStack: nextIDStack,
       routeStack: nextRouteStack,
       sceneConfigStack: nextAnimationModeStack,
     }, () => {
@@ -1024,7 +1014,6 @@ var Navigator = React.createClass({
     if (newStackLength < this.state.routeStack.length) {
       this.setState({
         sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength),
-        idStack: this.state.idStack.slice(0, newStackLength),
         routeStack: this.state.routeStack.slice(0, newStackLength),
       });
     }

From 4e33b801ca1e4cbc906125fd5082e4b66f9798ef Mon Sep 17 00:00:00 2001
From: Rui Chen <halfjuice@fb.com>
Date: Tue, 21 Jul 2015 15:04:02 -0700
Subject: [PATCH 28/33] Allow Animated.parallel to accept empty element in the
 array

---
 Libraries/Animation/Animated/Animated.js               | 10 ++++++++--
 .../Animation/Animated/__tests__/Animated-test.js      | 10 ++++++++++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/Libraries/Animation/Animated/Animated.js b/Libraries/Animation/Animated/Animated.js
index 81223edcf..f99913f50 100644
--- a/Libraries/Animation/Animated/Animated.js
+++ b/Libraries/Animation/Animated/Animated.js
@@ -1177,7 +1177,7 @@ var parallel = function(
       }
 
       animations.forEach((animation, idx) => {
-        animation.start(endResult => {
+        var cb = function(endResult) {
           hasEnded[idx] = true;
           doneCount++;
           if (doneCount === animations.length) {
@@ -1189,7 +1189,13 @@ var parallel = function(
           if (!endResult.finished && stopTogether) {
             result.stop();
           }
-        });
+        };
+
+        if (!animation) {
+          cb({finished: true});
+        } else {
+          animation.start(cb);
+        }
       });
     },
 
diff --git a/Libraries/Animation/Animated/__tests__/Animated-test.js b/Libraries/Animation/Animated/__tests__/Animated-test.js
index 8f9e52211..cad752ff0 100644
--- a/Libraries/Animation/Animated/__tests__/Animated-test.js
+++ b/Libraries/Animation/Animated/__tests__/Animated-test.js
@@ -205,6 +205,16 @@ describe('Animated Parallel', () => {
     expect(cb).toBeCalledWith({finished: true});
   });
 
+  it('works with an empty element in array', () => {
+    var anim1 = {start: jest.genMockFunction()};
+    var cb = jest.genMockFunction();
+    Animated.parallel([null, anim1]).start(cb);
+
+    expect(anim1.start).toBeCalled();
+    anim1.start.mock.calls[0][0]({finished: true});
+
+    expect(cb).toBeCalledWith({finished: true});
+  });
 
   it('parellelizes well', () => {
     var anim1 = {start: jest.genMockFunction()};

From 214c47ed76ed058a5f2fcaa332325488a3bab2d2 Mon Sep 17 00:00:00 2001
From: Felix Oghina <foghina@fb.com>
Date: Wed, 22 Jul 2015 08:28:58 -0700
Subject: [PATCH 29/33] [reactnative] move .android.js files to oss

---
 Libraries/Portal/Portal.js | 139 +++++++++++++++++++++++++++++++++++++
 1 file changed, 139 insertions(+)
 create mode 100644 Libraries/Portal/Portal.js

diff --git a/Libraries/Portal/Portal.js b/Libraries/Portal/Portal.js
new file mode 100644
index 000000000..a029bb99f
--- /dev/null
+++ b/Libraries/Portal/Portal.js
@@ -0,0 +1,139 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule Portal
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var View = require('View');
+
+var _portalRef: any;
+
+// Unique identifiers for modals.
+var lastUsedTag = 0;
+
+/*
+ * A container that renders all the modals on top of everything else in the application.
+ *
+ * Portal makes it possible for application code to pass modal views all the way up to
+ * the root element created in `renderApplication`.
+ *
+ * Never use `<Portal>` in your code. There is only one Portal instance rendered
+ * by the top-level `renderApplication`.
+ */
+var Portal = React.createClass({
+  statics: {
+    /**
+     * Use this to create a new unique tag for your component that renders
+     * modals. A good place to allocate a tag is in `componentWillMount`
+     * of your component.
+     * See `showModal` and `closeModal`.
+     */
+    allocateTag: function(): string {
+      return '__modal_' + (++lastUsedTag);
+    },
+
+    /**
+     * Render a new modal.
+     * @param tag A unique tag identifying the React component to render.
+     * This tag can be later used in `closeModal`.
+     * @param component A React component to be rendered.
+     */
+    showModal: function(tag: string, component: any) {
+      if (!_portalRef) {
+        console.error('Calling showModal but no Portal has been rendered.');
+        return;
+      }
+      _portalRef._showModal(tag, component);
+    },
+
+    /**
+     * Remove a modal from the collection of modals to be rendered.
+     * @param tag A unique tag identifying the React component to remove.
+     * Must exactly match the tag previously passed to `showModal`.
+     */
+    closeModal: function(tag: string) {
+      if (!_portalRef) {
+        console.error('Calling closeModal but no Portal has been rendered.');
+        return;
+      }
+      _portalRef._closeModal(tag);
+    },
+
+    /**
+     * Get an array of all the open modals, as identified by their tag string.
+     */
+    getOpenModals: function(): Array<string> {
+      if (!_portalRef) {
+        console.error('Calling getOpenModals but no Portal has been rendered.');
+        return [];
+      }
+      return _portalRef._getOpenModals();
+    }
+  },
+
+  getInitialState: function() {
+    return {modals: {}};
+  },
+
+  _showModal: function(tag: string, component: any) {
+    // This way state is chained through multiple calls to
+    // _showModal, _closeModal correctly.
+    this.setState((state) => {
+      var modals = state.modals;
+      modals[tag] = component;
+      return {modals};
+    });
+  },
+
+  _closeModal: function(tag: string) {
+    if (!this.state.modals.hasOwnProperty(tag)) {
+      return;
+    }
+    // This way state is chained through multiple calls to
+    // _showModal, _closeModal correctly.
+    this.setState((state) => {
+      var modals = state.modals;
+      delete modals[tag];
+      return {modals};
+    });
+  },
+
+  _getOpenModals: function(): Array<string> {
+    return Object.keys(this.state.modals);
+  },
+
+  render: function() {
+    _portalRef = this;
+    if (!this.state.modals) {
+      return null;
+    }
+    var modals = [];
+    for (var tag in this.state.modals) {
+      modals.push(this.state.modals[tag]);
+    }
+    if (modals.length === 0) {
+      return null;
+    }
+    return (
+      <View style={styles.modalsContainer}>
+        {modals}
+      </View>
+    );
+  }
+});
+
+var styles = StyleSheet.create({
+  modalsContainer: {
+    position: 'absolute',
+    left: 0,
+    top: 0,
+    right: 0,
+    bottom: 0,
+  },
+});
+
+module.exports = Portal;

From 280347d66afa7837a6c8c6e87b77049655b494a6 Mon Sep 17 00:00:00 2001
From: Hedger Wang <hedger@fb.com>
Date: Wed, 22 Jul 2015 11:46:43 -0700
Subject: [PATCH 30/33] [Navigator]: Add method `indexOf` and `slice` to
 NavigationRouteStack

---
 .../Navigation/NavigationRouteStack.js        | 29 +++++--
 .../__tests__/NavigationRouteStack-test.js    | 83 ++++++++++++++-----
 2 files changed, 83 insertions(+), 29 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
index 91344e170..78fbfd157 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
@@ -52,6 +52,16 @@ class RouteStack {
     return this._routes.get(index);
   }
 
+  indexOf(route: any): number {
+    return this._routes.indexOf(route);
+  }
+
+  slice(begin: ?number, end: ?number): RouteStack {
+    var routes = this._routes.slice(begin, end);
+    var index = Math.min(this._index, routes.size - 1);
+    return this._update(index, routes);
+  }
+
   /**
    * Returns a new stack with the provided route appended,
    * starting at this stack size.
@@ -71,7 +81,7 @@ class RouteStack {
       list.slice(0, this._index + 1).push(route);
     });
 
-    return new RouteStack(routes.size - 1, routes);
+    return this._update(routes.size - 1, routes);
   }
 
   /**
@@ -83,7 +93,7 @@ class RouteStack {
 
     // When popping, removes the rest of the routes past the current index.
     var routes = this._routes.slice(0, this._index);
-    return new RouteStack(routes.size - 1, routes);
+    return this._update(routes.size - 1, routes);
   }
 
   jumpToIndex(index: number): RouteStack {
@@ -92,11 +102,7 @@ class RouteStack {
       'index out of bound'
     );
 
-    if (index === this._index) {
-      return this;
-    }
-
-    return new RouteStack(index, this._routes);
+    return this._update(index, this._routes);
   }
 
   /**
@@ -129,7 +135,14 @@ class RouteStack {
     );
 
     var routes = this._routes.set(index, route);
-    return new RouteStack(this._index, routes);
+    return this._update(this._index, routes);
+  }
+
+  _update(index: number, routes: List): RouteStack {
+    if (this._index === index && this._routes === routes) {
+      return this;
+    }
+    return new RouteStack(index, routes);
   }
 }
 
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationRouteStack-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationRouteStack-test.js
index 3b750e054..a80bf8267 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationRouteStack-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationRouteStack-test.js
@@ -36,17 +36,17 @@ describe('NavigationRouteStack:', () => {
   // Basic
   it('gets index', () => {
     var stack = new NavigationRouteStack(1, ['a', 'b', 'c']);
-    expect(stack.index).toEqual(1);
+    expect(stack.index).toBe(1);
   });
 
   it('gets size', () => {
     var stack = new NavigationRouteStack(1, ['a', 'b', 'c']);
-    expect(stack.size).toEqual(3);
+    expect(stack.size).toBe(3);
   });
 
   it('gets route', () => {
     var stack = new NavigationRouteStack(0, ['a', 'b', 'c']);
-    expect(stack.get(2)).toEqual('c');
+    expect(stack.get(2)).toBe('c');
   });
 
   it('converts to an array', () => {
@@ -57,7 +57,7 @@ describe('NavigationRouteStack:', () => {
   it('creates a new stack after mutation', () => {
     var stack1 = new NavigationRouteStack(0, ['a', 'b']);
     var stack2 = stack1.push('c');
-    expect(stack1).not.toEqual(stack2);
+    expect(stack1).not.toBe(stack2);
   });
 
   it('throws at index out of bound', () => {
@@ -70,15 +70,57 @@ describe('NavigationRouteStack:', () => {
     }).toThrow();
   });
 
+  it('finds index', () => {
+    var stack = new NavigationRouteStack(0, ['a', 'b']);
+    expect(stack.indexOf('b')).toBe(1);
+    expect(stack.indexOf('c')).toBe(-1);
+  });
+
+  it('slices', () => {
+    var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']);
+    var stack2 = stack1.slice(1, 3);
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['b', 'c']);
+  });
+
+  it('may update index after slicing', () => {
+    var stack = new NavigationRouteStack(2, ['a', 'b', 'c']);
+    expect(stack.slice().index).toBe(2);
+    expect(stack.slice(0, 1).index).toBe(0);
+    expect(stack.slice(0, 2).index).toBe(1);
+    expect(stack.slice(0, 3).index).toBe(2);
+    expect(stack.slice(0, 100).index).toBe(2);
+    expect(stack.slice(-2).index).toBe(1);
+  });
+
+  it('slices without specifying params', () => {
+    var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
+    var stack2 = stack1.slice();
+    expect(stack2).toBe(stack1);
+  });
+
+  it('slices to from the end', () => {
+    var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']);
+    var stack2 = stack1.slice(-2);
+    expect(stack2.toArray()).toEqual(['c', 'd']);
+  });
+
+  it('throws when slicing to empty', () => {
+      expect(() => {
+        var stack = new NavigationRouteStack(1, ['a', 'b']);
+        stack.slice(100);
+      }).toThrow();
+  });
+
   // Push
   it('pushes route', () => {
     var stack1 = new NavigationRouteStack(1, ['a', 'b']);
     var stack2 = stack1.push('c');
 
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['a', 'b', 'c']);
-    expect(stack2.index).toEqual(2);
-    expect(stack2.size).toEqual(3);
+    expect(stack2.index).toBe(2);
+    expect(stack2.size).toBe(3);
   });
 
   it('throws when pushing empty route', () => {
@@ -101,27 +143,27 @@ describe('NavigationRouteStack:', () => {
   it('replaces routes on push', () => {
     var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
     var stack2 = stack1.push('d');
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['a', 'b', 'd']);
-    expect(stack2.index).toEqual(2);
+    expect(stack2.index).toBe(2);
   });
 
   // Pop
   it('pops route', () => {
     var stack1 = new NavigationRouteStack(2, ['a', 'b', 'c']);
     var stack2 = stack1.pop();
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['a', 'b']);
-    expect(stack2.index).toEqual(1);
-    expect(stack2.size).toEqual(2);
+    expect(stack2.index).toBe(1);
+    expect(stack2.size).toBe(2);
   });
 
   it('replaces routes on pop', () => {
     var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
     var stack2 = stack1.pop();
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['a']);
-    expect(stack2.index).toEqual(0);
+    expect(stack2.index).toBe(0);
   });
 
   it('throws when popping to empty stack', () => {
@@ -136,8 +178,8 @@ describe('NavigationRouteStack:', () => {
     var stack1 = new NavigationRouteStack(0, ['a', 'b', 'c']);
     var stack2 = stack1.jumpToIndex(2);
 
-    expect(stack2).not.toEqual(stack1);
-    expect(stack2.index).toEqual(2);
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.index).toBe(2);
   });
 
   it('throws then jumping to index out of bound', () => {
@@ -157,21 +199,20 @@ describe('NavigationRouteStack:', () => {
     var stack1 = new NavigationRouteStack(1, ['a', 'b']);
     var stack2 = stack1.replaceAtIndex(0, 'x');
 
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['x', 'b']);
-    expect(stack2.index).toEqual(1);
+    expect(stack2.index).toBe(1);
   });
 
   it('replaces route at negative index', () => {
     var stack1 = new NavigationRouteStack(1, ['a', 'b']);
     var stack2 = stack1.replaceAtIndex(-1, 'x');
 
-    expect(stack2).not.toEqual(stack1);
+    expect(stack2).not.toBe(stack1);
     expect(stack2.toArray()).toEqual(['a', 'x']);
-    expect(stack2.index).toEqual(1);
+    expect(stack2.index).toBe(1);
   });
 
-
   it('throws when replacing empty route', () => {
     expect(() => {
       var stack = new NavigationRouteStack(1, ['a', 'b']);

From 8dd1256c25784c51c0d83453152559e3abc9b297 Mon Sep 17 00:00:00 2001
From: Hedger Wang <hedger@fb.com>
Date: Wed, 22 Jul 2015 11:50:46 -0700
Subject: [PATCH 31/33] [Navigation] Implements NavigationContextFuture

---
 .../Navigator/Navigation/NavigationContext.js                 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
index 35ed24e3a..7f6153a33 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
@@ -67,10 +67,10 @@ class NavigationContext {
     }
   }
 
-  emit(eventType: String, data: any): void {
+  emit(eventType: String, data: any, didEmitCallback: ?Function): void {
     var emitter = this._eventEmitter;
     if (emitter) {
-      emitter.emit(eventType, data);
+      emitter.emit(eventType, data, didEmitCallback);
     }
   }
 

From 49b55804b1e842d5154266085f8b6312758ab0f0 Mon Sep 17 00:00:00 2001
From: Tadeu Zagallo <tadeuzagallo@fb.com>
Date: Wed, 22 Jul 2015 10:54:45 -0700
Subject: [PATCH 32/33] [ReactNative] Fix crash when reload during profile
 (attempt #2)

Summary:
Fixes #1642

When reloading during profiling, the profile wouldn't unhook from the instance
being deallocated.
---
 React/Base/RCTBatchedBridge.m | 20 ++++++++++++++++----
 React/Base/RCTProfile.h       | 13 +++++++++++++
 React/Base/RCTProfile.m       | 30 ++++++++++++++++--------------
 React/Views/RCTNavigator.m    |  2 +-
 4 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index 7975b5e7c..a38476f4d 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -106,6 +106,13 @@ id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
      */
     [self registerModules];
 
+    /**
+     * If currently profiling, hook into the current instance
+     */
+    if (RCTProfileIsProfiling()) {
+      RCTProfileHookModules(self);
+    }
+
     /**
      * Start the application script
      */
@@ -361,6 +368,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
     }
     moduleData.queue = nil;
   }
+
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
     [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
       [_jsDisplayLink invalidate];
@@ -368,11 +376,15 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
 
       [_javaScriptExecutor invalidate];
       _javaScriptExecutor = nil;
-    }];
 
-    _modules = nil;
-    _modulesByName = nil;
-    _frameUpdateObservers = nil;
+      if (RCTProfileIsProfiling()) {
+        RCTProfileUnhookModules(self);
+      }
+      _modules = nil;
+      _modulesByName = nil;
+      _frameUpdateObservers = nil;
+
+    }];
   });
 }
 
diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h
index 469a81552..66cf40bf4 100644
--- a/React/Base/RCTProfile.h
+++ b/React/Base/RCTProfile.h
@@ -103,6 +103,16 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *
   RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \
 }
 
+/**
+ * Hook into a bridge instance to log all bridge module's method calls
+ */
+RCT_EXTERN void RCTProfileHookModules(RCTBridge *);
+
+/**
+ * Unhook from a given bridge instance's modules
+ */
+RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *);
+
 #else
 
 #define RCTProfileBeginFlowEvent()
@@ -125,4 +135,7 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *
 
 #define RCTProfileBlock(block, ...) block
 
+#define RCTProfileHookModules(...)
+#define RCTProfileUnhookModules(...)
+
 #endif
diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m
index 62a17fe7b..5174af4f1 100644
--- a/React/Base/RCTProfile.m
+++ b/React/Base/RCTProfile.m
@@ -124,6 +124,8 @@ static void RCTProfileForwardInvocation(NSObject *self, __unused SEL cmd, NSInvo
       RCTProfileBeginEvent();
       [invocation invoke];
       RCTProfileEndEvent(name, @"objc_call,modules,auto", nil);
+    } else if ([self respondsToSelector:invocation.selector]) {
+      [invocation invoke];
     } else {
       // Use original selector to don't change error message
       [self doesNotRecognizeSelector:invocation.selector];
@@ -144,14 +146,17 @@ static IMP RCTProfileMsgForward(NSObject *self, SEL selector)
   return imp;
 }
 
-static void RCTProfileHookModules(RCTBridge *);
-static void RCTProfileHookModules(RCTBridge *bridge)
+void RCTProfileHookModules(RCTBridge *bridge)
 {
   for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
     [moduleData dispatchBlock:^{
       Class moduleClass = moduleData.cls;
       Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
 
+      if (!proxyClass) {
+        return;
+      }
+
       unsigned int methodCount;
       Method *methods = class_copyMethodList(moduleClass, &methodCount);
       for (NSUInteger i = 0; i < methodCount; i++) {
@@ -185,20 +190,17 @@ static void RCTProfileHookModules(RCTBridge *bridge)
   }
 }
 
-void RCTProfileUnhookModules(RCTBridge *);
 void RCTProfileUnhookModules(RCTBridge *bridge)
 {
-  for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
-    [moduleData dispatchBlock:^{
-      RCTProfileLock(
-        Class proxyClass = object_getClass(moduleData.instance);
-        if (moduleData.cls != proxyClass) {
-          object_setClass(moduleData.instance, moduleData.cls);
-          objc_disposeClassPair(proxyClass);
-        }
-      );
-    }];
-  };
+  RCTProfileLock(
+    for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
+      Class proxyClass = object_getClass(moduleData.instance);
+      if (moduleData.cls != proxyClass) {
+        object_setClass(moduleData.instance, moduleData.cls);
+        objc_disposeClassPair(proxyClass);
+      }
+    };
+  );
 }
 
 
diff --git a/React/Views/RCTNavigator.m b/React/Views/RCTNavigator.m
index 63e3d8023..ff91b0b54 100644
--- a/React/Views/RCTNavigator.m
+++ b/React/Views/RCTNavigator.m
@@ -436,7 +436,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
  */
 - (UIView *)reactSuperview
 {
-  RCTAssert(self.superview != nil, @"put reactNavSuperviewLink back");
+  RCTAssert(!_bridge.isValid || self.superview != nil, @"put reactNavSuperviewLink back");
   return self.superview ? self.superview : self.reactNavSuperviewLink;
 }
 

From cec5360f1bb37ef1cec626ae8ec1ff09174426e6 Mon Sep 17 00:00:00 2001
From: Spencer Ahrens <sahrens@fb.com>
Date: Wed, 22 Jul 2015 14:03:32 -0700
Subject: [PATCH 33/33] [RN] Introduce initialValue prop to fix
 TextInputExamples

Summary:
Some of the examples relied on the fact that TextInput wasn't a controlled
component before.  This introduces a new `initialValue` prop which behaves the
way the `value` prop used to - that is, you could type without updating it and
it wouldn't get reset, thus acting as just an initial value - and switches the
examples to use it where appropriate.
---
 Examples/UIExplorer/TextInputExample.js     | 10 +++++-----
 Libraries/Components/TextInput/TextInput.js | 19 +++++++++++++++----
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js
index 3369b41fb..b2a42ffeb 100644
--- a/Examples/UIExplorer/TextInputExample.js
+++ b/Examples/UIExplorer/TextInputExample.js
@@ -315,7 +315,7 @@ exports.examples = [
       return (
         <View>
           <WithLabel label="true">
-            <TextInput password={true} style={styles.default} value="abc" />
+            <TextInput password={true} style={styles.default} defaultValue="abc" />
           </WithLabel>
         </View>
       );
@@ -332,11 +332,11 @@ exports.examples = [
         <View>
           <TextInput
             style={[styles.default, {color: 'blue'}]}
-            value="Blue"
+            defaultValue="Blue"
           />
           <TextInput
             style={[styles.default, {color: 'green'}]}
-            value="Green"
+            defaultValue="Green"
           />
         </View>
       );
@@ -383,7 +383,7 @@ exports.examples = [
           <WithLabel label="clearTextOnFocus">
             <TextInput
               placeholder="text is cleared on focus"
-              value="text is cleared on focus"
+              defaultValue="text is cleared on focus"
               style={styles.default}
               clearTextOnFocus={true}
             />
@@ -391,7 +391,7 @@ exports.examples = [
           <WithLabel label="selectTextOnFocus">
             <TextInput
               placeholder="text is selected on focus"
-              value="text is selected on focus"
+              defaultValue="text is selected on focus"
               style={styles.default}
               selectTextOnFocus={true}
             />
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index d89291c37..01d6dbbfb 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -245,6 +245,12 @@ var TextInput = React.createClass({
      * unwanted edits without flicker.
      */
     value: PropTypes.string,
+    /**
+     * Provides an initial value that will change when the user starts typing.
+     * Useful for simple use-cases where you don't want to deal with listening
+     * to events and updating the value prop to keep the controlled state in sync.
+     */
+    defaultValue: PropTypes.string,
     /**
      * When the clear button should appear on the right side of the text view
      */
@@ -348,12 +354,17 @@ var TextInput = React.createClass({
     }
   },
 
+  _getText: function(): ?string {
+    return typeof this.props.value === 'string' ?
+      this.props.value :
+      this.props.defaultValue;
+  },
+
   _renderIOS: function() {
     var textContainer;
 
     var props = Object.assign({}, this.props);
     props.style = [styles.input, this.props.style];
-
     if (!props.multiline) {
       for (var propKey in onlyMultiline) {
         if (props[propKey]) {
@@ -370,7 +381,7 @@ var TextInput = React.createClass({
           onBlur={this._onBlur}
           onChange={this._onChange}
           onSelectionChangeShouldSetResponder={() => true}
-          text={this.props.value}
+          text={this._getText()}
           mostRecentEventCount={this.state.mostRecentEventCount}
         />;
     } else {
@@ -407,7 +418,7 @@ var TextInput = React.createClass({
           onSelectionChange={this._onSelectionChange}
           onTextInput={this._onTextInput}
           onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
-          text={this.props.value}
+          text={this._getText()}
         />;
     }
 
@@ -457,7 +468,7 @@ var TextInput = React.createClass({
         password={this.props.password || this.props.secureTextEntry}
         placeholder={this.props.placeholder}
         placeholderTextColor={this.props.placeholderTextColor}
-        text={this.props.value}
+        text={this._getText()}
         underlineColorAndroid={this.props.underlineColorAndroid}
         children={children}
       />;