From 2975f26e805ff38af61f57136a0995b23db0e8dc Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 17 Apr 2015 09:10:45 -0700 Subject: [PATCH 01/92] [react-packager] Add asset extensions to file watch glob in the project root --- packager/react-packager/src/Server/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 3c7be0435..e0bfeb2f3 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -71,13 +71,17 @@ function Server(options) { this._packages = Object.create(null); this._changeWatchers = []; + var assetGlobs = opts.assetExts.map(function(ext) { + return '**/*.' + ext; + }); + var watchRootConfigs = opts.projectRoots.map(function(dir) { return { dir: dir, globs: [ '**/*.js', '**/package.json', - ] + ].concat(assetGlobs), }; }); @@ -86,9 +90,7 @@ function Server(options) { opts.assetRoots.map(function(dir) { return { dir: dir, - globs: opts.assetExts.map(function(ext) { - return '**/*.' + ext; - }), + globs: assetGlobs, }; }) ); From 691297ab0d9e92189ee352a3564c592718e591fa Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Fri, 17 Apr 2015 09:10:20 -0700 Subject: [PATCH 02/92] [ReactNative|Easy] Change watchman too-long error message Summary: @wez Mentioned this in Issue #239 -- right now when watchman takes too long we recommend you run `watchman` from your terminal which actually expects some arguments, so it prints out the following: ``` [pcottle:~/Desktop/react-native:changeErrorMessage]$ watchman { "error": "invalid command (expected an array with some elements!)", "cli_validated": true, "version": "3.0.0" } ``` basically this ends up being more confusing since the command we recommend you run errors out, so lets change it to `watchman version` which at least exists cleanly. I kept the troubleshooting link as https://facebook.github.io/watchman/docs/troubleshooting.html since it sounds like we will update that with the issue people run into in #239 Closes https://github.com/facebook/react-native/pull/825 Github Author: Peter Cottle Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index 9af96c144..b629dfbf0 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -73,7 +73,7 @@ function createWatcher(rootConfig) { var rejectTimeout = setTimeout(function() { reject(new Error([ 'Watcher took too long to load', - 'Try running `watchman` from your terminal', + 'Try running `watchman version` from your terminal', 'https://facebook.github.io/watchman/docs/troubleshooting.html', ].join('\n'))); }, MAX_WAIT_TIME); From 0b6dbdb8273f3dfa47208cde92fc2072f4cdf08e Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 17 Apr 2015 09:52:57 -0700 Subject: [PATCH 03/92] [Errors] Fix Red Box by fixing providesModule parsing Summary: cc @amasad An error occurred while trying to display the Red Box since loadSourceMap was not included in the JS bundle. This is because node-haste was treating its docblock as a multiline directive which doesn't make sense for `@providesModule`. In loadSourceMap.js's case, the directive's value was parsed as "loadSourceMap -- disabled flow due to mysterious validation errors --". There are two fixes: add a newline under the `@providesModule` directive, and change the module ID code to look at only the first token of the directive. I opted for the latter so we avoid this class of bugs entirely and AFAIK it's nonsensical to have multiple `@providesModule` values anyway. Closes https://github.com/facebook/react-native/pull/866 Github Author: James Ide Test Plan: Run the packager, trigger an error in an app, see the red box now show up again. --- .../src/DependencyResolver/haste/DependencyGraph/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 9257d788b..dba6265a0 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -437,8 +437,9 @@ DependecyGraph.prototype._processModule = function(modulePath) { .then(function(content) { var moduleDocBlock = docblock.parseAsObject(content); if (moduleDocBlock.providesModule || moduleDocBlock.provides) { - moduleData.id = - moduleDocBlock.providesModule || moduleDocBlock.provides; + moduleData.id = /^(\S*)/.exec( + moduleDocBlock.providesModule || moduleDocBlock.provides + )[1]; // Incase someone wants to require this module via // packageName/path/to/module From dbe8e31c209dd1c4a8f48c5e26cbb67edb30d2a7 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Fri, 17 Apr 2015 15:56:05 -0700 Subject: [PATCH 04/92] [ReactNative][Navigator] Remove another unnecessary use of absolute screen width --- .../Navigator/NavigatorBreadcrumbNavigationBar.js | 2 +- .../NavigatorBreadcrumbNavigationBarStyles.ios.js | 9 ++++----- .../CustomComponents/Navigator/NavigatorNavigationBar.js | 2 +- .../Navigator/NavigatorNavigationBarStyles.ios.js | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index b4bad9982..9fb265f97 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -260,7 +260,7 @@ var styles = StyleSheet.create({ height: NavigatorNavigationBarStyles.General.TotalNavHeight, top: 0, left: 0, - width: NavigatorNavigationBarStyles.General.ScreenWidth, + right: 0, }, }); diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js index 7bc78ee92..69d0d52a2 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js @@ -26,12 +26,13 @@ */ 'use strict'; +var Dimensions = require('Dimensions'); var NavigatorNavigationBarStyles = require('NavigatorNavigationBarStyles'); var buildStyleInterpolator = require('buildStyleInterpolator'); var merge = require('merge'); -var SCREEN_WIDTH = NavigatorNavigationBarStyles.General.ScreenWidth; +var SCREEN_WIDTH = Dimensions.get('window').width; var STATUS_BAR_HEIGHT = NavigatorNavigationBarStyles.General.StatusBarHeight; var NAV_BAR_HEIGHT = NavigatorNavigationBarStyles.General.NavBarHeight; @@ -39,7 +40,6 @@ var SPACING = 4; var ICON_WIDTH = 40; var SEPARATOR_WIDTH = 9; var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH; -var RIGHT_BUTTON_WIDTH = 58; var OPACITY_RATIO = 100; var ICON_INACTIVE_OPACITY = 0.6; @@ -74,18 +74,17 @@ var TITLE_BASE = { // For first title styles, make sure first title is centered var FIRST_TITLE_BASE = merge(TITLE_BASE, { left: 0, + right: 0, alignItems: 'center', - width: SCREEN_WIDTH, height: NAV_BAR_HEIGHT, }); var RIGHT_BUTTON_BASE = { position: 'absolute', top: STATUS_BAR_HEIGHT, - left: SCREEN_WIDTH - SPACING - RIGHT_BUTTON_WIDTH, + right: SPACING, overflow: 'hidden', opacity: 1, - width: RIGHT_BUTTON_WIDTH, height: NAV_BAR_HEIGHT, backgroundColor: 'transparent', }; diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js index 20c43a94a..172819de2 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js @@ -192,7 +192,7 @@ var styles = StyleSheet.create({ height: NavigatorNavigationBarStyles.General.TotalNavHeight, top: 0, left: 0, - width: NavigatorNavigationBarStyles.General.ScreenWidth, + right: 0, backgroundColor: 'transparent', }, }); diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js index 266c5df24..fff116745 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js +++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStyles.ios.js @@ -169,7 +169,6 @@ module.exports = { NavBarHeight: NAV_BAR_HEIGHT, StatusBarHeight: STATUS_BAR_HEIGHT, TotalNavHeight: NAV_HEIGHT, - ScreenWidth: SCREEN_WIDTH, }, Interpolators, Stages, From f1174836d73f5fd248ed58630fa5e15ee2c9207c Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 17 Apr 2015 15:20:52 -0700 Subject: [PATCH 05/92] [react-packager] Add more information to deprecated asset requires --- .../__tests__/DependencyGraph-test.js | 7 ++-- .../haste/DependencyGraph/index.js | 1 + .../src/Packager/__tests__/Packager-test.js | 15 +++++++-- packager/react-packager/src/Packager/index.js | 33 ++++++++++++------- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 98ae7eb73..d3f6ce289 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -129,7 +129,8 @@ describe('DependencyGraph', function() { { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset_DEPRECATED: true + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -288,7 +289,8 @@ describe('DependencyGraph', function() { id: 'image!a', path: '/root/imgs/a.png', dependencies: [], - isAsset_DEPRECATED: true + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -1350,6 +1352,7 @@ describe('DependencyGraph', function() { path: '/root/foo.png', dependencies: [], isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index dba6265a0..665345977 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -642,6 +642,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { path: path.resolve(file), isAsset_DEPRECATED: true, dependencies: [], + resolution: extractAssetResolution(file).resolution, }); } }; diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 0c9d4a84d..333e0f563 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -52,6 +52,7 @@ describe('Packager', function() { path: '/root/img/img.png', isAsset_DEPRECATED: true, dependencies: [], + resolution: 2, }, { id: 'new_image.png', @@ -98,12 +99,22 @@ describe('Packager', function() { 'source /root/bar.js', '/root/bar.js' ]); + + var imgModule_DEPRECATED = { + isStatic: true, + path: '/root/img/img.png', + uri: 'img', + width: 25, + height: 50, + deprecated: true, + }; + expect(p.addModule.mock.calls[2]).toEqual([ 'lol module.exports = ' + - JSON.stringify({ uri: 'img', isStatic: true}) + + JSON.stringify(imgModule_DEPRECATED) + '; lol', 'module.exports = ' + - JSON.stringify({ uri: 'img', isStatic: true}) + + JSON.stringify(imgModule_DEPRECATED) + ';', '/root/img/img.png' ]); diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 74e2ff4c3..2b1eb6b16 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -20,6 +20,8 @@ var Activity = require('../Activity'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); +var sizeOf = Promise.promisify(imageSize); + var validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -142,7 +144,7 @@ Packager.prototype._transformModule = function(module) { var transform; if (module.isAsset_DEPRECATED) { - transform = Promise.resolve(generateAssetModule_DEPRECATED(module)); + transform = generateAssetModule_DEPRECATED(module); } else if (module.isAsset) { transform = generateAssetModule( module, @@ -175,20 +177,27 @@ Packager.prototype.getGraphDebugInfo = function() { }; function generateAssetModule_DEPRECATED(module) { - var code = 'module.exports = ' + JSON.stringify({ - uri: module.id.replace(/^[^!]+!/, ''), - isStatic: true, - }) + ';'; + return sizeOf(module.path).then(function(dimensions) { + var img = { + isStatic: true, + path: module.path, + uri: module.id.replace(/^[^!]+!/, ''), + width: dimensions.width / module.resolution, + height: dimensions.height / module.resolution, + deprecated: true, + }; - return { - code: code, - sourceCode: code, - sourcePath: module.path, - }; + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); } -var sizeOf = Promise.promisify(imageSize); - function generateAssetModule(module, relPath) { return sizeOf(module.path).then(function(dimensions) { var img = { From aaaa9a98ef61e6128a808bec2470b1a076353e8a Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 17 Apr 2015 15:33:54 -0700 Subject: [PATCH 06/92] [ReactNative] Update stacktrace-parser --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5169828e..ce743e3bf 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "rebound": "^0.0.12", "sane": "1.0.3", "source-map": "0.1.31", - "stacktrace-parser": "0.1.1", + "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "1.1.0", From 65b6d209d9095efba2bc1d6f2098fae8b127e1c7 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 17 Apr 2015 15:37:07 -0700 Subject: [PATCH 07/92] [ReactNative] cleanup some requireNativeComponent cruft --- Libraries/Components/ScrollView/ScrollView.js | 4 --- Libraries/Components/SliderIOS/SliderIOS.js | 21 +----------- Libraries/Image/Image.ios.js | 34 ++----------------- 3 files changed, 3 insertions(+), 56 deletions(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 0308688b5..e66612b22 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -371,10 +371,6 @@ if (Platform.OS === 'android') { uiViewClassName: 'AndroidHorizontalScrollView', }); } else if (Platform.OS === 'ios') { - var RCTScrollView = createReactIOSNativeComponentClass({ - validAttributes: validAttributes, - uiViewClassName: 'RCTScrollView', - }); var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView); } diff --git a/Libraries/Components/SliderIOS/SliderIOS.js b/Libraries/Components/SliderIOS/SliderIOS.js index 81815ba34..0c2d02da0 100644 --- a/Libraries/Components/SliderIOS/SliderIOS.js +++ b/Libraries/Components/SliderIOS/SliderIOS.js @@ -12,16 +12,11 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var Platform = require('Platform'); var PropTypes = require('ReactPropTypes'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = - require('createReactIOSNativeComponentClass'); -var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); type Event = Object; @@ -98,20 +93,6 @@ var styles = StyleSheet.create({ }, }); -if (Platform.OS === 'ios') { - var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS); -} else { - var validAttributes = { - ...ReactIOSViewAttributes.UIView, - value: true, - minimumValue: true, - maximumValue: true, - }; - - var RCTSlider = createReactIOSNativeComponentClass({ - validAttributes: validAttributes, - uiViewClassName: 'RCTSlider', - }); -} +var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS); module.exports = SliderIOS; diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index fed358e13..d519c1ac5 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -14,7 +14,6 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModules = require('NativeModules'); -var Platform = require('Platform'); var PropTypes = require('ReactPropTypes'); var ImageResizeMode = require('ImageResizeMode'); var ImageStylePropTypes = require('ImageStylePropTypes'); @@ -23,9 +22,7 @@ var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); var StyleSheetPropType = require('StyleSheetPropType'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); var flattenStyle = require('flattenStyle'); -var insetsDiffer = require('insetsDiffer'); var invariant = require('invariant'); var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); @@ -156,12 +153,6 @@ var Image = React.createClass({ contentMode, tintColor: style.tintColor, }); - if (Platform.OS === 'android') { - // TODO: update android native code to not need this - nativeProps.resizeMode = contentMode; - delete nativeProps.contentMode; - } - if (isStored) { nativeProps.imageTag = source.uri; } else { @@ -180,30 +171,9 @@ var styles = StyleSheet.create({ }, }); -if (Platform.OS === 'android') { - var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, { - accessible: true, - accessibilityLabel: true, - capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero - imageTag: true, - resizeMode: true, - src: true, - testID: PropTypes.string, - }); +var RCTStaticImage = requireNativeComponent('RCTStaticImage', null); +var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null); - var RCTStaticImage = createReactIOSNativeComponentClass({ - validAttributes: merge(CommonImageViewAttributes, { tintColor: true }), - uiViewClassName: 'RCTStaticImage', - }); - - var RCTNetworkImage = createReactIOSNativeComponentClass({ - validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }), - uiViewClassName: 'RCTNetworkImageView', - }); -} else { - var RCTStaticImage = requireNativeComponent('RCTStaticImage', null); - var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null); -} var nativeOnlyProps = { src: true, defaultImageSrc: true, From ab1efbd4c12a5792f55a1021903688d5aa864b15 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 17 Apr 2015 15:47:22 -0700 Subject: [PATCH 08/92] [ReactNative] kill for...in array iteration in deepDiffer --- .../differ/__tests__/deepDiffer-test.js | 102 ++++++++++++++++++ Libraries/Utilities/differ/deepDiffer.js | 29 +++-- 2 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 Libraries/Utilities/differ/__tests__/deepDiffer-test.js diff --git a/Libraries/Utilities/differ/__tests__/deepDiffer-test.js b/Libraries/Utilities/differ/__tests__/deepDiffer-test.js new file mode 100644 index 000000000..745b83ab0 --- /dev/null +++ b/Libraries/Utilities/differ/__tests__/deepDiffer-test.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest.dontMock('deepDiffer'); +var deepDiffer = require('deepDiffer'); + +describe('deepDiffer', function() { + it('should diff primitives of the same type', () => { + expect(deepDiffer(1, 2)).toBe(true); + expect(deepDiffer(42, 42)).toBe(false); + expect(deepDiffer('foo', 'bar')).toBe(true); + expect(deepDiffer('foo', 'foo')).toBe(false); + expect(deepDiffer(true, false)).toBe(true); + expect(deepDiffer(false, true)).toBe(true); + expect(deepDiffer(true, true)).toBe(false); + expect(deepDiffer(false, false)).toBe(false); + expect(deepDiffer(null, null)).toBe(false); + expect(deepDiffer(undefined, undefined)).toBe(false); + }); + it('should diff primitives of different types', () => { + expect(deepDiffer(1, '1')).toBe(true); + expect(deepDiffer(true, 'true')).toBe(true); + expect(deepDiffer(true, 1)).toBe(true); + expect(deepDiffer(false, 0)).toBe(true); + expect(deepDiffer(null, undefined)).toBe(true); + expect(deepDiffer(null, 0)).toBe(true); + expect(deepDiffer(null, false)).toBe(true); + expect(deepDiffer(null, '')).toBe(true); + expect(deepDiffer(undefined, 0)).toBe(true); + expect(deepDiffer(undefined, false)).toBe(true); + expect(deepDiffer(undefined, '')).toBe(true); + }); + it('should diff Objects', () => { + expect(deepDiffer({}, {})).toBe(false); + expect(deepDiffer({}, null)).toBe(true); + expect(deepDiffer(null, {})).toBe(true); + expect(deepDiffer({a: 1}, {a: 1})).toBe(false); + expect(deepDiffer({a: 1}, {a: 2})).toBe(true); + expect(deepDiffer({a: 1}, {a: 1, b: null})).toBe(true); + expect(deepDiffer({a: 1}, {a: 1, b: 1})).toBe(true); + expect(deepDiffer({a: 1, b: 1}, {a: 1})).toBe(true); + expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 1}, b: 1})).toBe(false); + expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 2}, b: 1})).toBe(true); + expect(deepDiffer( + {a: {A: {aA: 1, bB: 1}}, b: 1}, + {a: {A: {aA: 1, bB: 1}}, b: 1} + )).toBe(false); + expect(deepDiffer( + {a: {A: {aA: 1, bB: 1}}, b: 1}, + {a: {A: {aA: 1, cC: 1}}, b: 1} + )).toBe(true); + }); + it('should diff Arrays', () => { + expect(deepDiffer([], [])).toBe(false); + expect(deepDiffer([], null)).toBe(true); + expect(deepDiffer(null, [])).toBe(true); + expect(deepDiffer([42], [42])).toBe(false); + expect(deepDiffer([1], [2])).toBe(true); + expect(deepDiffer([1, 2, 3], [1, 2, 3])).toBe(false); + expect(deepDiffer([1, 2, 3], [1, 2, 4])).toBe(true); + expect(deepDiffer([1, 2, 3], [1, 4, 3])).toBe(true); + expect(deepDiffer([1, 2, 3, 4], [1, 2, 3])).toBe(true); + expect(deepDiffer([1, 2, 3], [1, 2, 3, 4])).toBe(true); + expect(deepDiffer([0, null, false, ''], [0, null, false, ''])).toBe(false); + expect(deepDiffer([0, null, false, ''], ['', false, null, 0])).toBe(true); + }); + it('should diff mixed types', () => { + expect(deepDiffer({}, [])).toBe(true); + expect(deepDiffer([], {})).toBe(true); + expect(deepDiffer( + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}, + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]} + )).toBe(false); + expect(deepDiffer( + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}, + {a: [{A: {aA: 1, bB: 2}}, 'bar'], c: [1, [false]]} + )).toBe(true); + expect(deepDiffer( + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}, + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false], null]} + )).toBe(true); + expect(deepDiffer( + {a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}, + {a: [{A: {aA: 1, bB: 1}}, ['bar']], c: [1, [false]]} + )).toBe(true); + }); + it('should distinguish between proper Array and Object', () => { + expect(deepDiffer(['a', 'b'], {0: 'a', 1: 'b', length: 2})).toBe(true); + expect(deepDiffer(['a', 'b'], {length: 2, 0: 'a', 1: 'b'})).toBe(true); + }); + it('should diff same object', () => { + var obj = [1,[2,3]]; + expect(deepDiffer(obj, obj)).toBe(false); + }); +}); diff --git a/Libraries/Utilities/differ/deepDiffer.js b/Libraries/Utilities/differ/deepDiffer.js index 66dc7fa95..4dec6bd7b 100644 --- a/Libraries/Utilities/differ/deepDiffer.js +++ b/Libraries/Utilities/differ/deepDiffer.js @@ -35,16 +35,29 @@ var deepDiffer = function(one: any, two: any): bool { if (one.constructor !== two.constructor) { return true; } - for (var key in one) { - if (deepDiffer(one[key], two[key])) { + if (Array.isArray(one)) { + // We know two is also an array because the constructors are equal + var len = one.length; + if (two.length !== len) { return true; } - } - for (var twoKey in two) { - // The only case we haven't checked yet is keys that are in two but aren't - // in one, which means they are different. - if (one[twoKey] === undefined && two[twoKey] !== undefined) { - return true; + for (var ii = 0; ii < len; ii++) { + if (deepDiffer(one[ii], two[ii])) { + return true; + } + } + } else { + for (var key in one) { + if (deepDiffer(one[key], two[key])) { + return true; + } + } + for (var twoKey in two) { + // The only case we haven't checked yet is keys that are in two but aren't + // in one, which means they are different. + if (one[twoKey] === undefined && two[twoKey] !== undefined) { + return true; + } } } return false; From f3e7511d2b830fa1b7b60b6d0a95b1a35e2b60d5 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 17 Apr 2015 17:01:18 -0700 Subject: [PATCH 09/92] [ReactNative] Dim packager output --- packager/getFlowTypeCheckMiddleware.js | 15 +++------------ .../src/Activity/__mocks__/chalk.js | 13 +++++++++++++ packager/react-packager/src/Activity/index.js | 12 +++++++----- 3 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 packager/react-packager/src/Activity/__mocks__/chalk.js diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js index 312da45e2..cd910054f 100644 --- a/packager/getFlowTypeCheckMiddleware.js +++ b/packager/getFlowTypeCheckMiddleware.js @@ -10,6 +10,7 @@ var chalk = require('chalk'); var exec = require('child_process').exec; +var Activity = require('./react-packager/src/Activity'); var hasWarned = {}; @@ -44,20 +45,10 @@ function getFlowTypeCheckMiddleware(options) { function doFlowTypecheck(res, flowroot, next) { var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; - var start = Date.now(); - // Log start message if flow is slow to let user know something is happening. - var flowSlow = setTimeout( - function() { - console.log(chalk.gray('flow: Running static typechecks.')); - }, - 500 - ); + var eventId = Activity.startEvent('flow static typechecks'); exec(flowCmd, function(flowError, stdout, stderr) { - clearTimeout(flowSlow); + Activity.endEvent(eventId); if (!flowError) { - console.log(chalk.gray( - 'flow: Typechecks passed (' + (Date.now() - start) + 'ms).') - ); return next(); } else { try { diff --git a/packager/react-packager/src/Activity/__mocks__/chalk.js b/packager/react-packager/src/Activity/__mocks__/chalk.js new file mode 100644 index 000000000..2981f979d --- /dev/null +++ b/packager/react-packager/src/Activity/__mocks__/chalk.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +module.exports = { + dim: function(s) { return s; }, +}; diff --git a/packager/react-packager/src/Activity/index.js b/packager/react-packager/src/Activity/index.js index 05285d0fc..8e593f9ff 100644 --- a/packager/react-packager/src/Activity/index.js +++ b/packager/react-packager/src/Activity/index.js @@ -8,6 +8,8 @@ */ 'use strict'; +var chalk = require('chalk'); + var COLLECTION_PERIOD = 1000; var _endedEvents = Object.create(null); @@ -132,22 +134,22 @@ function _writeAction(action) { switch (action.action) { case 'startEvent': - console.log( + console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + action.eventName + data - ); + )); break; case 'endEvent': var startAction = _eventStarts[action.eventId]; var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; - console.log( + console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + startAction.eventName + - '(' + (action.tstamp - startAction.tstamp) + 'ms)' + + ' (' + (action.tstamp - startAction.tstamp) + 'ms)' + startData - ); + )); delete _eventStarts[action.eventId]; break; From 17be6ba82ae24800e29ceb9a9c8c7a9e64801204 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Fri, 17 Apr 2015 18:09:58 -0700 Subject: [PATCH 10/92] reinstate @flow --- Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js index e168daae6..9ecf2543b 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js @@ -7,8 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule loadSourceMap - * - * -- disabled flow due to mysterious validation errors -- + * @flow */ 'use strict'; From bd5b12c5356c7166d8bb78521c77e710b5fcdab1 Mon Sep 17 00:00:00 2001 From: Bill Fisher Date: Fri, 17 Apr 2015 22:20:13 -0700 Subject: [PATCH 11/92] [ReactNative] implement transform styles --- .../Components/View/ViewStylePropTypes.js | 6 +- Libraries/ReactIOS/NativeMethodsMixin.js | 3 +- Libraries/ReactIOS/ReactIOSNativeComponent.js | 3 +- Libraries/StyleSheet/precomputeStyle.js | 161 ++++++++++++++++++ Libraries/Utilities/MatrixMath.js | 131 ++++++++++++++ 5 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 Libraries/StyleSheet/precomputeStyle.js create mode 100755 Libraries/Utilities/MatrixMath.js diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index bb22c6b26..73afe99bd 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -34,12 +34,8 @@ var ViewStylePropTypes = { ), shadowOpacity: ReactPropTypes.number, shadowRadius: ReactPropTypes.number, + transform: ReactPropTypes.arrayOf(ReactPropTypes.object), transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number), - rotation: ReactPropTypes.number, - scaleX: ReactPropTypes.number, - scaleY: ReactPropTypes.number, - translateX: ReactPropTypes.number, - translateY: ReactPropTypes.number, }; module.exports = ViewStylePropTypes; diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index ec72a0b4f..9d413e5c7 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -19,6 +19,7 @@ var TextInputState = require('TextInputState'); var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); +var precomputeStyle = require('precomputeStyle'); type MeasureOnSuccessCallback = ( x: number, @@ -93,7 +94,7 @@ var NativeMethodsMixin = { break; } } - var style = flattenStyle(nativeProps.style); + var style = precomputeStyle(flattenStyle(nativeProps.style)); var props = null; if (hasOnlyStyle) { diff --git a/Libraries/ReactIOS/ReactIOSNativeComponent.js b/Libraries/ReactIOS/ReactIOSNativeComponent.js index b9abd5965..7f27ae0ea 100644 --- a/Libraries/ReactIOS/ReactIOSNativeComponent.js +++ b/Libraries/ReactIOS/ReactIOSNativeComponent.js @@ -23,6 +23,7 @@ var styleDiffer = require('styleDiffer'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var diffRawProperties = require('diffRawProperties'); var flattenStyle = require('flattenStyle'); +var precomputeStyle = require('precomputeStyle'); var warning = require('warning'); var registrationNames = ReactIOSEventEmitter.registrationNames; @@ -160,7 +161,7 @@ ReactIOSNativeComponent.Mixin = { // before actually doing the expensive flattening operation in order to // compute the diff. if (styleDiffer(nextProps.style, prevProps.style)) { - var nextFlattenedStyle = flattenStyle(nextProps.style); + var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style)); updatePayload = diffRawProperties( updatePayload, this.previousFlattenedStyle, diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/precomputeStyle.js new file mode 100644 index 000000000..c3a1ca8e5 --- /dev/null +++ b/Libraries/StyleSheet/precomputeStyle.js @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule precomputeStyle + * @flow + */ +'use strict'; + +var MatrixMath = require('MatrixMath'); +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +var invariant = require('invariant'); + +/** + * This method provides a hook where flattened styles may be precomputed or + * otherwise prepared to become better input data for native code. + */ +function precomputeStyle(style: ?Object): ?Object { + if (!style || !style.transform) { + return style; + } + invariant( + !style.transformMatrix, + 'transformMatrix and transform styles cannot be used on the same component' + ); + var newStyle = _precomputeTransforms({...style}); + deepFreezeAndThrowOnMutationInDev(newStyle); + return newStyle; +} + +/** + * Generate a transform matrix based on the provided transforms, and use that + * within the style object instead. + * + * This allows us to provide an API that is similar to CSS and to have a + * universal, singular interface to native code. + */ +function _precomputeTransforms(style: Object): Object { + var {transform, transformMatrix, ...style} = style; + var result = MatrixMath.createIdentityMatrix(); + + transform.forEach(transformation => { + var key = Object.keys(transformation)[0]; + var value = transformation[key]; + if (__DEV__) { + _validateTransform(key, value, transformation); + } + + switch (key) { + case 'matrix': + MatrixMath.multiplyInto(result, result, value); + break; + case 'rotate': + _multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]); + break; + case 'scale': + _multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]); + break; + case 'scaleX': + _multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]); + break; + case 'scaleY': + _multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]); + break; + case 'translate': + _multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]); + break; + case 'translateX': + _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]); + break; + case 'translateY': + _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]); + break; + default: + throw new Error('Invalid transform name: ' + key); + } + }); + + return { + ...style, + transformMatrix: result, + }; +} + +/** + * Performs a destructive operation on a transform matrix. + */ +function _multiplyTransform( + result: Array, + matrixMathFunction: Function, + args: Array +): void { + var matrixToApply = MatrixMath.createIdentityMatrix(); + var argsWithIdentity = [matrixToApply].concat(args); + matrixMathFunction.apply(this, argsWithIdentity); + MatrixMath.multiplyInto(result, result, matrixToApply); +} + +/** + * Parses a string like '0.5rad' or '60deg' into radians expressed in a float. + * Note that validation on the string is done in `_validateTransform()`. + */ +function _convertToRadians(value: string): number { + var floatValue = parseFloat(value, 10); + return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180; +} + +function _validateTransform(key, value, transformation) { + var multivalueTransforms = [ + 'matrix', + 'translate', + ]; + if (multivalueTransforms.indexOf(key) !== -1) { + invariant( + Array.isArray(value), + 'Transform with key of %s must have an array as the value: %s', + key, + JSON.stringify(transformation) + ); + } + switch (key) { + case 'matrix': + invariant( + value.length === 9 || value.length === 16, + 'Matrix transform must have a length of 9 (2d) or 16 (3d). ' + + 'Provided matrix has a length of %s: %s', + value.length, + JSON.stringify(transformation) + ); + break; + case 'translate': + break; + case 'rotate': + invariant( + typeof value === 'string', + 'Transform with key of "%s" must be a string: %s', + key, + JSON.stringify(transformation) + ); + invariant( + value.indexOf('deg') > -1 || value.indexOf('rad') > -1, + 'Rotate transform must be expressed in degrees (deg) or radians ' + + '(rad): %s', + JSON.stringify(transformation) + ); + break; + default: + invariant( + typeof value === 'number', + 'Transform with key of "%s" must be a number: %s', + key, + JSON.stringify(transformation) + ); + } +} + +module.exports = precomputeStyle; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js new file mode 100755 index 000000000..7f3d17c46 --- /dev/null +++ b/Libraries/Utilities/MatrixMath.js @@ -0,0 +1,131 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule MatrixMath + */ +'use strict'; + +/** + * Memory conservative (mutative) matrix math utilities. Uses "command" + * matrices, which are reusable. + */ +var MatrixMath = { + createIdentityMatrix: function() { + return [ + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1 + ]; + }, + + createCopy: function(m) { + return [ + m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15], + ]; + }, + + createTranslate2d: function(x, y) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseTranslate2dCommand(mat, x, y); + return mat; + }, + + reuseTranslate2dCommand: function(matrixCommand, x, y) { + matrixCommand[12] = x; + matrixCommand[13] = y; + }, + + reuseTranslate3dCommand: function(matrixCommand, x, y, z) { + matrixCommand[12] = x; + matrixCommand[13] = y; + matrixCommand[14] = z; + }, + + createScale: function(factor) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseScaleCommand(mat, factor); + return mat; + }, + + reuseScaleCommand: function(matrixCommand, factor) { + matrixCommand[0] = factor; + matrixCommand[5] = factor; + }, + + reuseScale3dCommand: function(matrixCommand, x, y, z) { + matrixCommand[0] = x; + matrixCommand[5] = y; + matrixCommand[10] = z; + }, + + reuseScaleXCommand(matrixCommand, factor) { + matrixCommand[0] = factor; + }, + + reuseScaleYCommand(matrixCommand, factor) { + matrixCommand[5] = factor; + }, + + reuseScaleZCommand(matrixCommand, factor) { + matrixCommand[10] = factor; + }, + + reuseRotateYCommand: function(matrixCommand, amount) { + matrixCommand[0] = Math.cos(amount); + matrixCommand[2] = Math.sin(amount); + matrixCommand[8] = Math.sin(-amount); + matrixCommand[10] = Math.cos(amount); + }, + + createRotateZ: function(radians) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateZCommand(mat, radians); + return mat; + }, + + // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix + reuseRotateZCommand: function(matrixCommand, radians) { + matrixCommand[0] = Math.cos(radians); + matrixCommand[1] = Math.sin(radians); + matrixCommand[4] = -Math.sin(radians); + matrixCommand[5] = Math.cos(radians); + }, + + multiplyInto: function(out, a, b) { + var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; + + var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; + out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; + out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; + out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; + out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + } + +}; + +module.exports = MatrixMath; From 2b9aaac2ff9868726e16f90b9de2af444ead230b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Sat, 18 Apr 2015 06:23:24 -0700 Subject: [PATCH 12/92] [ReactNative] Guard against blocks being added to UIManager during dealloc --- React/Modules/RCTUIManager.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index b6a350dcd..ae04f9a1d 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -398,13 +398,15 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa { RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread"); + if (!self.isValid) { + return; + } + __weak RCTUIManager *weakViewManager = self; - __weak RCTSparseArray *weakViewRegistry = _viewRegistry; dispatch_block_t outerBlock = ^{ RCTUIManager *strongViewManager = weakViewManager; - RCTSparseArray *strongViewRegistry = weakViewRegistry; - if (strongViewManager && strongViewRegistry) { - block(strongViewManager, strongViewRegistry); + if (strongViewManager && strongViewManager.isValid) { + block(strongViewManager, strongViewManager->_viewRegistry); } }; From ead0f2e020d16154a11e6d07355ee2ecc0fce6e2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sat, 18 Apr 2015 10:43:20 -0700 Subject: [PATCH 13/92] Implemented thread control for exported methods --- .../testTabBarExampleSnapshot_1@2x.png | Bin 28887 -> 27640 bytes .../UIExplorerTests/UIExplorerTests.m | 12 +- .../ActionSheetIOS/RCTActionSheetManager.m | 120 +++++++------- .../RCTAnimationExperimentalManager.m | 5 + Libraries/Geolocation/RCTLocationObserver.m | 130 +++++++-------- Libraries/LinkingIOS/RCTLinkingManager.m | 6 +- Libraries/RCTTest/RCTTestModule.h | 22 ++- Libraries/RCTTest/RCTTestModule.m | 41 ++--- Libraries/RCTTest/RCTTestRunner.m | 41 ++--- React/Base/RCTBridge.h | 7 - React/Base/RCTBridge.m | 52 ++++-- React/Base/RCTBridgeModule.h | 17 ++ React/Base/RCTConvert.h | 4 +- React/Base/RCTConvert.m | 6 +- React/Modules/RCTAlertManager.m | 49 +++--- React/Modules/RCTAsyncLocalStorage.m | 155 ++++++++---------- React/Modules/RCTExceptionsManager.m | 6 +- React/Modules/RCTSourceCode.m | 1 - React/Modules/RCTStatusBarManager.m | 34 ++-- React/Modules/RCTTiming.m | 16 +- React/Modules/RCTUIManager.m | 34 ++-- React/Views/RCTViewManager.h | 23 +-- React/Views/RCTViewManager.m | 6 + 23 files changed, 384 insertions(+), 403 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp/testTabBarExampleSnapshot_1@2x.png index d3e66652b5c01bcdc1c08114cfea10fba8d5fe05..6fa9c15557705009bd5720dee3e2e02fdcdf88fb 100644 GIT binary patch literal 27640 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 literal 28887 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 XMUQ+qbQTMMe~uoq{I=*Dx2yjLZA`-) diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index fd321546a..2c2359b44 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -59,11 +59,10 @@ return NO; } -// Make sure this test runs first (underscores sort early) otherwise the -// other tests will tear out the rootView -- (void)test__RootViewLoadsAndRenders +// Make sure this test runs first because the other tests will tear out the rootView +- (void)testAAA_RootViewLoadsAndRenders { - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first."); NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; BOOL foundElement = NO; @@ -72,10 +71,8 @@ while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date]; - redboxError = [[RCTRedBox sharedInstance] currentErrorMessage]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) { if ([view respondsToSelector:@selector(attributedText)]) { NSString *text = [(id)view attributedText].string; if ([text isEqualToString:@""]) { @@ -120,6 +117,7 @@ [_runner runTest:_cmd module:@"TabBarExample"]; } +// Make sure this test runs last - (void)testZZZ_NotInRecordMode { RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index c6d6e404c..ac672249f 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -34,92 +34,88 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options failureCallback:(RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; - actionSheet.title = options[@"title"]; + actionSheet.title = options[@"title"]; - for (NSString *option in options[@"options"]) { - [actionSheet addButtonWithTitle:option]; - } + for (NSString *option in options[@"options"]) { + [actionSheet addButtonWithTitle:option]; + } - if (options[@"destructiveButtonIndex"]) { - actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue]; - } - if (options[@"cancelButtonIndex"]) { - actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue]; - } + if (options[@"destructiveButtonIndex"]) { + actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue]; + } + if (options[@"cancelButtonIndex"]) { + actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue]; + } - actionSheet.delegate = self; + actionSheet.delegate = self; - _callbacks[keyForInstance(actionSheet)] = successCallback; + _callbacks[RCTKeyForInstance(actionSheet)] = successCallback; - UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; - if (appWindow == nil) { - RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); - return; - } - [actionSheet showInView:appWindow]; - }); + UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window]; + if (appWindow == nil) { + RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options); + return; + } + [actionSheet showInView:appWindow]; } RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options failureCallback:(RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - dispatch_async(dispatch_get_main_queue(), ^{ - NSMutableArray *items = [NSMutableArray array]; - id message = options[@"message"]; - id url = options[@"url"]; - if ([message isKindOfClass:[NSString class]]) { - [items addObject:message]; - } - if ([url isKindOfClass:[NSString class]]) { - [items addObject:[NSURL URLWithString:url]]; - } - if ([items count] == 0) { - failureCallback(@[@"No `url` or `message` to share"]); - return; - } - UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; - UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) { - share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (activityError) { - failureCallback(@[[activityError localizedDescription]]); - } else { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - } - }; - } else { + NSMutableArray *items = [NSMutableArray array]; + id message = options[@"message"]; + id url = options[@"url"]; + if ([message isKindOfClass:[NSString class]]) { + [items addObject:message]; + } + if ([url isKindOfClass:[NSString class]]) { + [items addObject:[NSURL URLWithString:url]]; + } + if ([items count] == 0) { + failureCallback(@[@"No `url` or `message` to share"]); + return; + } + UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; + UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) { + share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + if (activityError) { + failureCallback(@[[activityError localizedDescription]]); + } else { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + } + }; + } else { #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) { - // Legacy iOS 7 implementation - share.completionHandler = ^(NSString *activityType, BOOL completed) { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - }; - } else + if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) { + // Legacy iOS 7 implementation + share.completionHandler = ^(NSString *activityType, BOOL completed) { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + }; + } else #endif - { - // iOS 8 version - share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - successCallback(@[@(completed), (activityType ?: [NSNull null])]); - }; - } + { + // iOS 8 version + share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + successCallback(@[@(completed), (activityType ?: [NSNull null])]); + }; } - [ctrl presentViewController:share animated:YES completion:nil]; - }); + } + [ctrl presentViewController:share animated:YES completion:nil]; } #pragma mark UIActionSheetDelegate Methods - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { - NSString *key = keyForInstance(actionSheet); + NSString *key = RCTKeyForInstance(actionSheet); RCTResponseSenderBlock callback = _callbacks[key]; if (callback) { callback(@[@(buttonIndex)]); @@ -133,7 +129,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options #pragma mark Private -NS_INLINE NSString *keyForInstance(id instance) +static NSString *RCTKeyForInstance(id instance) { return [NSString stringWithFormat:@"%p", instance]; } diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index eb2ddd1cd..798c1456b 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -68,6 +68,11 @@ RCT_EXPORT_MODULE() return self; } +- (dispatch_queue_t)methodQueue +{ + return _bridge.uiManager.methodQueue; +} + - (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName { if (count == 1) { diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index 6bb95acb0..c5303df15 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -33,7 +33,9 @@ typedef struct { CLLocationAccuracy accuracy; } RCTLocationOptions; -static RCTLocationOptions RCTLocationOptionsWithJSON(id json) +@implementation RCTConvert (RCTLocationOptions) + ++ (RCTLocationOptions)RCTLocationOptions:(id)json { NSDictionary *options = [RCTConvert NSDictionary:json]; return (RCTLocationOptions){ @@ -43,6 +45,8 @@ static RCTLocationOptions RCTLocationOptionsWithJSON(id json) }; } +@end + static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */) { if (!msg) { @@ -121,6 +125,7 @@ RCT_EXPORT_MODULE() - (void)dealloc { [_locationManager stopUpdatingLocation]; + _locationManager.delegate = nil; } #pragma mark - Private API @@ -153,41 +158,33 @@ RCT_EXPORT_MODULE() #pragma mark - Public API -RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON) +RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options) { [self checkLocationConfig]; - dispatch_async(dispatch_get_main_queue(), ^{ + // Select best options + _observerOptions = options; + for (RCTLocationRequest *request in _pendingRequests) { + _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); + } - // Select best options - _observerOptions = RCTLocationOptionsWithJSON(optionsJSON); - for (RCTLocationRequest *request in _pendingRequests) { - _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); - } - - _locationManager.desiredAccuracy = _observerOptions.accuracy; - [self beginLocationUpdates]; - _observingLocation = YES; - - }); + _locationManager.desiredAccuracy = _observerOptions.accuracy; + [self beginLocationUpdates]; + _observingLocation = YES; } RCT_EXPORT_METHOD(stopObserving) { - dispatch_async(dispatch_get_main_queue(), ^{ + // Stop observing + _observingLocation = NO; - // Stop observing - _observingLocation = NO; - - // Stop updating if no pending requests - if (_pendingRequests.count == 0) { - [_locationManager stopUpdatingLocation]; - } - - }); + // Stop updating if no pending requests + if (_pendingRequests.count == 0) { + [_locationManager stopUpdatingLocation]; + } } -RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON +RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options withSuccessCallback:(RCTResponseSenderBlock)successBlock errorCallback:(RCTResponseSenderBlock)errorBlock) { @@ -198,56 +195,49 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON return; } - dispatch_async(dispatch_get_main_queue(), ^{ - - if (![CLLocationManager locationServicesEnabled]) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") - ]); - return; - } - } - - if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorDenied, nil) - ]); - return; - } - } - - // Get options - RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON); - - // Check if previous recorded location exists and is good enough - if (_lastLocationEvent && - CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && - [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { - - // Call success block with most recent known location - successBlock(@[_lastLocationEvent]); + if (![CLLocationManager locationServicesEnabled]) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") + ]); return; } + } - // Create request - RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; - request.successBlock = successBlock; - request.errorBlock = errorBlock ?: ^(NSArray *args){}; - request.options = options; - request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout - target:self - selector:@selector(timeout:) - userInfo:request - repeats:NO]; - [_pendingRequests addObject:request]; + if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorDenied, nil) + ]); + return; + } + } - // Configure location manager and begin updating location - _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy); - [self beginLocationUpdates]; + // Check if previous recorded location exists and is good enough + if (_lastLocationEvent && + CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && + [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { - }); + // Call success block with most recent known location + successBlock(@[_lastLocationEvent]); + return; + } + + // Create request + RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; + request.successBlock = successBlock; + request.errorBlock = errorBlock ?: ^(NSArray *args){}; + request.options = options; + request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout + target:self + selector:@selector(timeout:) + userInfo:request + repeats:NO]; + [_pendingRequests addObject:request]; + + // Configure location manager and begin updating location + _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy); + [self beginLocationUpdates]; } #pragma mark - CLLocationManagerDelegate diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 990c5900f..4be8bfb8e 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -62,8 +62,10 @@ RCT_EXPORT_METHOD(openURL:(NSURL *)URL) RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callback) { - BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; - callback(@[@(canOpen)]); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; + callback(@[@(canOpen)]); + }); } - (NSDictionary *)constantsToExport diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h index 21ed60c6b..a8a2da16e 100644 --- a/Libraries/RCTTest/RCTTestModule.h +++ b/Libraries/RCTTest/RCTTestModule.h @@ -15,14 +15,24 @@ @interface RCTTestModule : NSObject -// This is typically polled while running the runloop until true -@property (nonatomic, readonly, getter=isDone) BOOL done; - -// This is used to give meaningful names to snapshot image files. -@property (nonatomic, assign) SEL testSelector; +/** + * The snapshot test controller for this module. + */ +@property (nonatomic, weak) FBSnapshotTestController *controller; +/** + * This is the view to be snapshotted. + */ @property (nonatomic, weak) UIView *view; -- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view; +/** + * This is used to give meaningful names to snapshot image files. + */ +@property (nonatomic, assign) SEL testSelector; + +/** + * This is typically polled while running the runloop until true. + */ +@property (nonatomic, readonly, getter=isDone) BOOL done; @end diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 58b6572f8..33f562515 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -12,21 +12,25 @@ #import "FBSnapshotTestController.h" #import "RCTAssert.h" #import "RCTLog.h" +#import "RCTUIManager.h" @implementation RCTTestModule { - __weak FBSnapshotTestController *_snapshotController; - __weak UIView *_view; NSMutableDictionary *_snapshotCounter; } +@synthesize bridge = _bridge; + RCT_EXPORT_MODULE() -- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view +- (dispatch_queue_t)methodQueue +{ + return _bridge.uiManager.methodQueue; +} + +- (instancetype)init { if ((self = [super init])) { - _snapshotController = controller; - _view = view; _snapshotCounter = [NSMutableDictionary new]; } return self; @@ -34,30 +38,29 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) { - if (!_snapshotController) { - RCTLogWarn(@"No snapshot controller configured."); - callback(@[]); - return; - } + RCTAssert(_controller != nil, @"No snapshot controller configured."); + + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - dispatch_async(dispatch_get_main_queue(), ^{ NSString *testName = NSStringFromSelector(_testSelector); - _snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1); + _snapshotCounter[testName] = [@([_snapshotCounter[testName] integerValue] + 1) stringValue]; + NSError *error = nil; - BOOL success = [_snapshotController compareSnapshotOfView:_view - selector:_testSelector - identifier:[_snapshotCounter[testName] stringValue] - error:&error]; + BOOL success = [_controller compareSnapshotOfView:_view + selector:_testSelector + identifier:_snapshotCounter[testName] + error:&error]; + RCTAssert(success, @"Snapshot comparison failed: %@", error); callback(@[]); - }); + }]; } RCT_EXPORT_METHOD(markTestCompleted) { - dispatch_async(dispatch_get_main_queue(), ^{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _done = YES; - }); + }]; } @end diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9b3a7d3c8..75a811831 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -19,7 +19,7 @@ @implementation RCTTestRunner { - FBSnapshotTestController *_snapshotController; + FBSnapshotTestController *_testController; } - (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir @@ -27,8 +27,8 @@ if ((self = [super init])) { NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"]; sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; - _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; - _snapshotController.referenceImagesDirectory = referenceDir; + _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; + _testController.referenceImagesDirectory = referenceDir; _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; } return self; @@ -36,12 +36,12 @@ - (void)setRecordMode:(BOOL)recordMode { - _snapshotController.recordMode = recordMode; + _testController.recordMode = recordMode; } - (BOOL)recordMode { - return _snapshotController.recordMode; + return _testController.recordMode; } - (void)runTest:(SEL)test module:(NSString *)moduleName @@ -59,27 +59,22 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock { - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - if ([vc.view isKindOfClass:[RCTRootView class]]) { - [(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere - } - vc.view = [[UIView alloc] init]; - - RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil]; - testModule.testSelector = test; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL - moduleProvider:^(){ - return @[testModule]; - } - launchOptions:nil]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge - moduleName:moduleName]; - testModule.view = rootView; - [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:_scriptURL + moduleName:moduleName + launchOptions:nil]; rootView.initialProperties = initialProps; rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices + NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); + RCTTestModule *testModule = rootView.bridge.modules[testModuleName]; + testModule.controller = _testController; + testModule.testSelector = test; + testModule.view = rootView; + + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; + vc.view = [[UIView alloc] init]; + [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) { diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index ab853851c..544e5e1a2 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -101,13 +101,6 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method; */ @property (nonatomic, copy, readonly) NSDictionary *modules; -/** - * The shadow queue is used to execute callbacks from the JavaScript code. All - * native hooks (e.g. exported module methods) will be executed on the shadow - * queue. - */ -@property (nonatomic, readonly) dispatch_queue_t shadowQueue; - /** * The launch options that were used to initialize the bridge. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 5c6bcf7cb..7b1f95b69 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -792,6 +792,7 @@ static NSDictionary *RCTLocalModulesConfig() @implementation RCTBridge { RCTSparseArray *_modulesByID; + RCTSparseArray *_queuesByID; NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; @@ -824,13 +825,13 @@ static id _latestJSExecutor; return self; } + - (void)setUp { Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class]; _javaScriptExecutor = [[executorClass alloc] init]; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; - _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL); _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; @@ -848,21 +849,29 @@ static id _latestJSExecutor; [RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) { NSString *moduleName = RCTModuleNamesByID[moduleID]; // Check if module instance has already been registered for this name - if ((_modulesByID[moduleID] = modulesByName[moduleName])) { + id module = modulesByName[moduleName]; + if (module) { // Preregistered instances takes precedence, no questions asked if (!preregisteredModules[moduleName]) { // It's OK to have a name collision as long as the second instance is nil RCTAssert([[moduleClass alloc] init] == nil, - @"Attempted to register RCTBridgeModule class %@ for the name '%@', \ - but name was already registered by class %@", moduleClass, + @"Attempted to register RCTBridgeModule class %@ for the name " + "'%@', but name was already registered by class %@", moduleClass, moduleName, [modulesByName[moduleName] class]); } + if ([module class] != moduleClass) { + RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered " + "in the project, but name was already registered by class %@." + "That's fine if it's intentional - just letting you know.", + moduleClass, moduleName, [modulesByName[moduleName] class]); + } } else { // Module name hasn't been used before, so go ahead and instantiate - id module = [[moduleClass alloc] init]; - if (module) { - _modulesByID[moduleID] = modulesByName[moduleName] = module; - } + module = [[moduleClass alloc] init]; + } + if (module) { + // Store module instance + _modulesByID[moduleID] = modulesByName[moduleName] = module; } }]; @@ -876,6 +885,14 @@ static id _latestJSExecutor; } } + // Get method queue + _queuesByID = [[RCTSparseArray alloc] init]; + [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { + if ([module respondsToSelector:@selector(methodQueue)]) { + _queuesByID[moduleID] = [module methodQueue] ?: dispatch_get_main_queue(); + } + }]; + // Inject module data into JS context NSString *configJSON = RCTJSONStringify(@{ @"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName), @@ -1027,6 +1044,7 @@ static id _latestJSExecutor; // Release modules (breaks retain cycle if module has strong bridge reference) _modulesByID = nil; + _queuesByID = nil; _modulesByName = nil; } @@ -1203,6 +1221,8 @@ static id _latestJSExecutor; return; } + // TODO: if we sort the requests by module, we could dispatch once per + // module instead of per request, which would reduce the call overhead. for (NSUInteger i = 0; i < numRequests; i++) { @autoreleasepool { [self _handleRequestNumber:i @@ -1212,14 +1232,15 @@ static id _latestJSExecutor; } } - // TODO: only used by RCTUIManager - can we eliminate this special case? - dispatch_async(self.shadowQueue, ^{ - for (id module in _modulesByID.allObjects) { - if ([module respondsToSelector:@selector(batchDidComplete)]) { + // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? + [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { + if ([module respondsToSelector:@selector(batchDidComplete)]) { + dispatch_queue_t queue = _queuesByID[moduleID]; + dispatch_async(queue ?: dispatch_get_main_queue(), ^{ [module batchDidComplete]; - } + }); } - }); + }]; } - (BOOL)_handleRequestNumber:(NSUInteger)i @@ -1241,7 +1262,8 @@ static id _latestJSExecutor; RCTModuleMethod *method = methods[methodID]; __weak RCTBridge *weakSelf = self; - dispatch_async(self.shadowQueue, ^{ + dispatch_queue_t queue = _queuesByID[moduleID]; + dispatch_async(queue ?: dispatch_get_main_queue(), ^{ __strong RCTBridge *strongSelf = weakSelf; if (!strongSelf.isValid) { diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 70d5c76d0..c8fa41c44 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -89,6 +89,23 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); __attribute__((__aligned__(1))) \ static const char *__rct_export_entry__[] = { __func__, #js_name, NULL } +/** + * The queue that will be used to call all exported methods. If omitted, this + * will default the main queue, which is recommended for any methods that + * interact with UIKit. If your methods perform heavy work such as filesystem + * or network access, you should return a custom serial queue. Example: + * + * - (dispatch_queue_t)methodQueue + * { + * return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL); + * } + * + * Alternatively, if only some methods on the module should be executed on a + * background queue you can leave this method unimplemented, and simply + * dispatch_async() within the method itself. + */ +- (dispatch_queue_t)methodQueue; + /** * Injects constants into JS. These constants are made accessible via * NativeModules.ModuleName.X. This method is called when the module is diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index e3f5e8598..22cc7ec81 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -138,7 +138,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath); /** * Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this. */ -NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id); +NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); #ifdef __cplusplus } @@ -190,7 +190,7 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) dispatch_once(&onceToken, ^{ \ mapping = values; \ }); \ - NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \ + NSNumber *converted = RCTConvertEnumValue(#type, mapping, @(default), json); \ return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 25de29656..2aefe8940 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -58,6 +58,10 @@ RCT_CONVERTER(NSString *, NSString, description) + (NSURL *)NSURL:(id)json { + if (!json || json == (id)kCFNull) { + return nil; + } + if (![json isKindOfClass:[NSString class]]) { RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json); return nil; @@ -115,7 +119,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0 // JS standard for time zones is minutes. RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0]) -NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) +NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) { if (!json || json == (id)kCFNull) { return defaultValue; diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index ae11ce52b..b97364e38 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -64,37 +64,34 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args return; } - dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:self + cancelButtonTitle:nil + otherButtonTitles:nil]; - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:message - delegate:self - cancelButtonTitle:nil - otherButtonTitles:nil]; + NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; - NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; - - NSInteger index = 0; - for (NSDictionary *button in buttons) { - if (button.count != 1) { - RCTLogError(@"Button definitions should have exactly one key."); - } - NSString *buttonKey = [button.allKeys firstObject]; - NSString *buttonTitle = [button[buttonKey] description]; - [alertView addButtonWithTitle:buttonTitle]; - if ([buttonKey isEqualToString: @"cancel"]) { - alertView.cancelButtonIndex = index; - } - [buttonKeys addObject:buttonKey]; - index ++; + NSInteger index = 0; + for (NSDictionary *button in buttons) { + if (button.count != 1) { + RCTLogError(@"Button definitions should have exactly one key."); } + NSString *buttonKey = [button.allKeys firstObject]; + NSString *buttonTitle = [button[buttonKey] description]; + [alertView addButtonWithTitle:buttonTitle]; + if ([buttonKey isEqualToString: @"cancel"]) { + alertView.cancelButtonIndex = index; + } + [buttonKeys addObject:buttonKey]; + index ++; + } - [_alerts addObject:alertView]; - [_alertCallbacks addObject:callback ?: ^(id unused) {}]; - [_alertButtonKeys addObject:buttonKeys]; + [_alerts addObject:alertView]; + [_alertCallbacks addObject:callback ?: ^(id unused) {}]; + [_alertButtonKeys addObject:buttonKeys]; - [alertView show]; - }); + [alertView show]; } #pragma mark - UIAlertViewDelegate diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 8e6d414cf..2c01161d4 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -61,20 +61,6 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } -static dispatch_queue_t RCTFileQueue(void) -{ - static dispatch_queue_t fileQueue = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // All JS is single threaded, so a serial queue is our only option. - fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(fileQueue, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); - }); - - return fileQueue; -} - #pragma mark - RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage @@ -90,6 +76,11 @@ static dispatch_queue_t RCTFileQueue(void) RCT_EXPORT_MODULE() +- (dispatch_queue_t)methodQueue +{ + return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); +} + - (NSString *)_filePathForKey:(NSString *)key { NSString *safeFileName = RCTMD5Hash(key); @@ -196,99 +187,89 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys return; } - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut], [NSNull null]]); - return; - } - NSMutableArray *errors; - NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count]; - for (NSString *key in keys) { - id keyError = [self _appendItemForKey:key toArray:result]; - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - callback(@[errors ?: [NSNull null], result]); - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut], [NSNull null]]); + return; + } + NSMutableArray *errors; + NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count]; + for (NSString *key in keys) { + id keyError = [self _appendItemForKey:key toArray:result]; + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + callback(@[errors ?: [NSNull null], result]); } RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut]]); - return; - } - NSMutableArray *errors; - for (NSArray *entry in kvPairs) { - id keyError = [self _writeEntry:entry]; - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - if (callback) { - callback(@[errors ?: [NSNull null]]); - } - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (NSArray *entry in kvPairs) { + id keyError = [self _writeEntry:entry]; + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[@[errorOut]]); - return; + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (NSString *key in keys) { + id keyError = RCTErrorForKey(key); + if (!keyError) { + NSString *filePath = [self _filePathForKey:key]; + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + [_manifest removeObjectForKey:key]; } - NSMutableArray *errors; - for (NSString *key in keys) { - id keyError = RCTErrorForKey(key); - if (!keyError) { - NSString *filePath = [self _filePathForKey:key]; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; - [_manifest removeObjectForKey:key]; - } - RCTAppendError(keyError, &errors); - } - [self _writeManifest:&errors]; - if (callback) { - callback(@[errors ?: [NSNull null]]); - } - }); + RCTAppendError(keyError, &errors); + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (!errorOut) { - NSError *error; - for (NSString *key in _manifest) { - NSString *filePath = [self _filePathForKey:key]; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - } - [_manifest removeAllObjects]; - errorOut = [self _writeManifest:nil]; + id errorOut = [self _ensureSetup]; + if (!errorOut) { + NSError *error; + for (NSString *key in _manifest) { + NSString *filePath = [self _filePathForKey:key]; + [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; } - if (callback) { - callback(@[errorOut ?: [NSNull null]]); - } - }); + [_manifest removeAllObjects]; + errorOut = [self _writeManifest:nil]; + } + if (callback) { + callback(@[errorOut ?: [NSNull null]]); + } } RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) { - dispatch_async(RCTFileQueue(), ^{ - id errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[errorOut, [NSNull null]]); - } else { - callback(@[[NSNull null], [_manifest allKeys]]); - } - }); + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[errorOut, [NSNull null]]); + } else { + callback(@[[NSNull null], [_manifest allKeys]]); + } } @end diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 5be80133b..9e919f3a3 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -48,13 +48,11 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message } #ifdef DEBUG - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; #else if (RCTReloadRetries < _maxReloadAttempts) { RCTReloadRetries++; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; - }); + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; } else { NSError *error; const NSUInteger MAX_SANITIZED_LENGTH = 75; diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m index 76e9190bc..e29a05637 100644 --- a/React/Modules/RCTSourceCode.m +++ b/React/Modules/RCTSourceCode.m @@ -24,7 +24,6 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback } else { failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]); } - } @end diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index ad8ee1df6..149ad568e 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -29,31 +29,25 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated) { - dispatch_async(dispatch_get_main_queue(), ^{ - - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle - animated:animated]; - } - }); + if (RCTViewControllerBasedStatusBarAppearance()) { + RCTLogError(@"RCTStatusBarManager module requires that the \ + UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); + } else { + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle + animated:animated]; + } } RCT_EXPORT_METHOD(setHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation) { - dispatch_async(dispatch_get_main_queue(), ^{ - - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - } else { - [[UIApplication sharedApplication] setStatusBarHidden:hidden - withAnimation:animation]; - } - }); + if (RCTViewControllerBasedStatusBarAppearance()) { + RCTLogError(@"RCTStatusBarManager module requires that the \ + UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); + } else { + [[UIApplication sharedApplication] setStatusBarHidden:hidden + withAnimation:animation]; + } } - (NSDictionary *)constantsToExport diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index aaab5fae0..1f6e84d6a 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -187,21 +187,17 @@ RCT_EXPORT_METHOD(createTimer:(NSNumber *)callbackID interval:jsDuration targetTime:targetTime repeats:repeats]; - dispatch_async(dispatch_get_main_queue(), ^{ - _timers[callbackID] = timer; - [self startTimers]; - }); + _timers[callbackID] = timer; + [self startTimers]; } RCT_EXPORT_METHOD(deleteTimer:(NSNumber *)timerID) { if (timerID) { - dispatch_async(dispatch_get_main_queue(), ^{ - _timers[timerID] = nil; - if (_timers.count == 0) { - [self stopTimers]; - } - }); + _timers[timerID] = nil; + if (_timers.count == 0) { + [self stopTimers]; + } } else { RCTLogWarn(@"Called deleteTimer: with a nil timerID"); } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index ae04f9a1d..961b5a0b5 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -178,7 +178,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio @implementation RCTUIManager { - __weak dispatch_queue_t _shadowQueue; + dispatch_queue_t _shadowQueue; // Root views are only mutated on the shadow queue NSMutableSet *_rootViewTags; @@ -242,24 +242,12 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa }; } -/** - * This private constructor should only be called when creating - * isolated UIImanager instances for testing. Normal initialization - * is via -init:, which is called automatically by the bridge. - */ -- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue -{ - if ((self = [self init])) { - _shadowQueue = shadowQueue; - _viewManagers = [[NSMutableDictionary alloc] init]; - } - return self; -} - - (instancetype)init { if ((self = [super init])) { + _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL); + _pendingUIBlocksLock = [[NSLock alloc] init]; _defaultShadowViews = [[NSMutableDictionary alloc] init]; @@ -310,7 +298,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance"); _bridge = bridge; - _shadowQueue = _bridge.shadowQueue; _shadowViewRegistry = [[RCTSparseArray alloc] init]; // Get view managers from bridge @@ -328,6 +315,11 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa _viewConfigs = [viewConfigs copy]; } +- (dispatch_queue_t)methodQueue +{ + return _shadowQueue; +} + - (void)registerRootView:(UIView *)rootView; { RCTAssertMainThread(); @@ -366,7 +358,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa NSNumber *reactTag = rootView.reactTag; RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag); - dispatch_async(_bridge.shadowQueue, ^{ + dispatch_async(_shadowQueue, ^{ RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag]; RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag); rootShadowView.frame = frame; @@ -396,8 +388,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa - (void)addUIBlock:(RCTViewManagerUIBlock)block { - RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread"); - if (!self.isValid) { return; } @@ -417,7 +407,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView { - RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread."); + RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread"); NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1]; @@ -679,6 +669,8 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry]; + // TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient + // Figure out what to insert - merge temporary inserts and adds NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary]; for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) { @@ -886,8 +878,6 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag) - (void)flushUIBlocks { - RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread"); - // First copy the previous blocks into a temporary variable, then reset the // pending blocks to a new array. This guards against mutation while // processing the pending blocks in another thread. diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 74c7bbd8c..ed97cf3b2 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -109,8 +109,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v * within the view or shadowView. */ #define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \ -RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ -- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \ +RCT_CUSTOM_VIEW_PROPERTY(name, type, UIView) { \ if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \ (!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \ RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \ @@ -118,8 +117,7 @@ RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ } #define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \ -RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ -- (void)set_##name:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \ +RCT_CUSTOM_SHADOW_PROPERTY(name, type, RCTShadowView) { \ if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \ (!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \ RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \ @@ -132,11 +130,11 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ * refer to "json", "view" and "defaultView" to implement the required logic. */ #define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \ -RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ ++ (NSDictionary *)getPropConfigView_##name { return @{@"name": @#name, @"type": @#type}; } \ - (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView #define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \ -RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ ++ (NSDictionary *)getPropConfigShadow_##name { return @{@"name": @#name, @"type": @#type}; } \ - (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView /** @@ -164,17 +162,4 @@ RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ [self set_##newName:json forView:view withDefaultView:defaultView]; \ } -/** - * PROP_CONFIG macros should only be paired with property setters. - */ -#define RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \ -+ (NSDictionary *)getPropConfigView_##name { \ - return @{@"name": @#name, @"type": @#type}; \ -} - -#define RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \ -+ (NSDictionary *)getPropConfigShadow_##name { \ - return @{@"name": @#name, @"type": @#type}; \ -} - @end diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 3c8485374..8388b83cf 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -14,6 +14,7 @@ #import "RCTEventDispatcher.h" #import "RCTLog.h" #import "RCTShadowView.h" +#import "RCTUIManager.h" #import "RCTUtils.h" #import "RCTView.h" @@ -23,6 +24,11 @@ RCT_EXPORT_MODULE() +- (dispatch_queue_t)methodQueue +{ + return [_bridge.uiManager methodQueue]; +} + - (UIView *)view { return [[RCTView alloc] init]; From 2186691812d5a0b79f768ec5214a87f3a7f28862 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Sat, 18 Apr 2015 18:50:29 -0100 Subject: [PATCH 14/92] Revert "[ReactNative] implement transform styles" --- .../Components/View/ViewStylePropTypes.js | 6 +- Libraries/ReactIOS/NativeMethodsMixin.js | 3 +- Libraries/ReactIOS/ReactIOSNativeComponent.js | 3 +- Libraries/StyleSheet/precomputeStyle.js | 161 ------------------ Libraries/Utilities/MatrixMath.js | 131 -------------- 5 files changed, 7 insertions(+), 297 deletions(-) delete mode 100644 Libraries/StyleSheet/precomputeStyle.js delete mode 100755 Libraries/Utilities/MatrixMath.js diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index 73afe99bd..bb22c6b26 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -34,8 +34,12 @@ var ViewStylePropTypes = { ), shadowOpacity: ReactPropTypes.number, shadowRadius: ReactPropTypes.number, - transform: ReactPropTypes.arrayOf(ReactPropTypes.object), transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number), + rotation: ReactPropTypes.number, + scaleX: ReactPropTypes.number, + scaleY: ReactPropTypes.number, + translateX: ReactPropTypes.number, + translateY: ReactPropTypes.number, }; module.exports = ViewStylePropTypes; diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index 9d413e5c7..ec72a0b4f 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -19,7 +19,6 @@ var TextInputState = require('TextInputState'); var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); -var precomputeStyle = require('precomputeStyle'); type MeasureOnSuccessCallback = ( x: number, @@ -94,7 +93,7 @@ var NativeMethodsMixin = { break; } } - var style = precomputeStyle(flattenStyle(nativeProps.style)); + var style = flattenStyle(nativeProps.style); var props = null; if (hasOnlyStyle) { diff --git a/Libraries/ReactIOS/ReactIOSNativeComponent.js b/Libraries/ReactIOS/ReactIOSNativeComponent.js index 7f27ae0ea..b9abd5965 100644 --- a/Libraries/ReactIOS/ReactIOSNativeComponent.js +++ b/Libraries/ReactIOS/ReactIOSNativeComponent.js @@ -23,7 +23,6 @@ var styleDiffer = require('styleDiffer'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var diffRawProperties = require('diffRawProperties'); var flattenStyle = require('flattenStyle'); -var precomputeStyle = require('precomputeStyle'); var warning = require('warning'); var registrationNames = ReactIOSEventEmitter.registrationNames; @@ -161,7 +160,7 @@ ReactIOSNativeComponent.Mixin = { // before actually doing the expensive flattening operation in order to // compute the diff. if (styleDiffer(nextProps.style, prevProps.style)) { - var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style)); + var nextFlattenedStyle = flattenStyle(nextProps.style); updatePayload = diffRawProperties( updatePayload, this.previousFlattenedStyle, diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/precomputeStyle.js deleted file mode 100644 index c3a1ca8e5..000000000 --- a/Libraries/StyleSheet/precomputeStyle.js +++ /dev/null @@ -1,161 +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. - * - * @providesModule precomputeStyle - * @flow - */ -'use strict'; - -var MatrixMath = require('MatrixMath'); -var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -var invariant = require('invariant'); - -/** - * This method provides a hook where flattened styles may be precomputed or - * otherwise prepared to become better input data for native code. - */ -function precomputeStyle(style: ?Object): ?Object { - if (!style || !style.transform) { - return style; - } - invariant( - !style.transformMatrix, - 'transformMatrix and transform styles cannot be used on the same component' - ); - var newStyle = _precomputeTransforms({...style}); - deepFreezeAndThrowOnMutationInDev(newStyle); - return newStyle; -} - -/** - * Generate a transform matrix based on the provided transforms, and use that - * within the style object instead. - * - * This allows us to provide an API that is similar to CSS and to have a - * universal, singular interface to native code. - */ -function _precomputeTransforms(style: Object): Object { - var {transform, transformMatrix, ...style} = style; - var result = MatrixMath.createIdentityMatrix(); - - transform.forEach(transformation => { - var key = Object.keys(transformation)[0]; - var value = transformation[key]; - if (__DEV__) { - _validateTransform(key, value, transformation); - } - - switch (key) { - case 'matrix': - MatrixMath.multiplyInto(result, result, value); - break; - case 'rotate': - _multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]); - break; - case 'scale': - _multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]); - break; - case 'scaleX': - _multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]); - break; - case 'scaleY': - _multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]); - break; - case 'translate': - _multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]); - break; - case 'translateX': - _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]); - break; - case 'translateY': - _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]); - break; - default: - throw new Error('Invalid transform name: ' + key); - } - }); - - return { - ...style, - transformMatrix: result, - }; -} - -/** - * Performs a destructive operation on a transform matrix. - */ -function _multiplyTransform( - result: Array, - matrixMathFunction: Function, - args: Array -): void { - var matrixToApply = MatrixMath.createIdentityMatrix(); - var argsWithIdentity = [matrixToApply].concat(args); - matrixMathFunction.apply(this, argsWithIdentity); - MatrixMath.multiplyInto(result, result, matrixToApply); -} - -/** - * Parses a string like '0.5rad' or '60deg' into radians expressed in a float. - * Note that validation on the string is done in `_validateTransform()`. - */ -function _convertToRadians(value: string): number { - var floatValue = parseFloat(value, 10); - return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180; -} - -function _validateTransform(key, value, transformation) { - var multivalueTransforms = [ - 'matrix', - 'translate', - ]; - if (multivalueTransforms.indexOf(key) !== -1) { - invariant( - Array.isArray(value), - 'Transform with key of %s must have an array as the value: %s', - key, - JSON.stringify(transformation) - ); - } - switch (key) { - case 'matrix': - invariant( - value.length === 9 || value.length === 16, - 'Matrix transform must have a length of 9 (2d) or 16 (3d). ' + - 'Provided matrix has a length of %s: %s', - value.length, - JSON.stringify(transformation) - ); - break; - case 'translate': - break; - case 'rotate': - invariant( - typeof value === 'string', - 'Transform with key of "%s" must be a string: %s', - key, - JSON.stringify(transformation) - ); - invariant( - value.indexOf('deg') > -1 || value.indexOf('rad') > -1, - 'Rotate transform must be expressed in degrees (deg) or radians ' + - '(rad): %s', - JSON.stringify(transformation) - ); - break; - default: - invariant( - typeof value === 'number', - 'Transform with key of "%s" must be a number: %s', - key, - JSON.stringify(transformation) - ); - } -} - -module.exports = precomputeStyle; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js deleted file mode 100755 index 7f3d17c46..000000000 --- a/Libraries/Utilities/MatrixMath.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule MatrixMath - */ -'use strict'; - -/** - * Memory conservative (mutative) matrix math utilities. Uses "command" - * matrices, which are reusable. - */ -var MatrixMath = { - createIdentityMatrix: function() { - return [ - 1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1 - ]; - }, - - createCopy: function(m) { - return [ - m[0], m[1], m[2], m[3], - m[4], m[5], m[6], m[7], - m[8], m[9], m[10], m[11], - m[12], m[13], m[14], m[15], - ]; - }, - - createTranslate2d: function(x, y) { - var mat = MatrixMath.createIdentityMatrix(); - MatrixMath.reuseTranslate2dCommand(mat, x, y); - return mat; - }, - - reuseTranslate2dCommand: function(matrixCommand, x, y) { - matrixCommand[12] = x; - matrixCommand[13] = y; - }, - - reuseTranslate3dCommand: function(matrixCommand, x, y, z) { - matrixCommand[12] = x; - matrixCommand[13] = y; - matrixCommand[14] = z; - }, - - createScale: function(factor) { - var mat = MatrixMath.createIdentityMatrix(); - MatrixMath.reuseScaleCommand(mat, factor); - return mat; - }, - - reuseScaleCommand: function(matrixCommand, factor) { - matrixCommand[0] = factor; - matrixCommand[5] = factor; - }, - - reuseScale3dCommand: function(matrixCommand, x, y, z) { - matrixCommand[0] = x; - matrixCommand[5] = y; - matrixCommand[10] = z; - }, - - reuseScaleXCommand(matrixCommand, factor) { - matrixCommand[0] = factor; - }, - - reuseScaleYCommand(matrixCommand, factor) { - matrixCommand[5] = factor; - }, - - reuseScaleZCommand(matrixCommand, factor) { - matrixCommand[10] = factor; - }, - - reuseRotateYCommand: function(matrixCommand, amount) { - matrixCommand[0] = Math.cos(amount); - matrixCommand[2] = Math.sin(amount); - matrixCommand[8] = Math.sin(-amount); - matrixCommand[10] = Math.cos(amount); - }, - - createRotateZ: function(radians) { - var mat = MatrixMath.createIdentityMatrix(); - MatrixMath.reuseRotateZCommand(mat, radians); - return mat; - }, - - // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix - reuseRotateZCommand: function(matrixCommand, radians) { - matrixCommand[0] = Math.cos(radians); - matrixCommand[1] = Math.sin(radians); - matrixCommand[4] = -Math.sin(radians); - matrixCommand[5] = Math.cos(radians); - }, - - multiplyInto: function(out, a, b) { - var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], - a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], - a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], - a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; - - var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; - out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; - out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; - out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; - out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; - - b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; - out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; - out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; - out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; - out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; - - b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; - out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; - out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; - out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; - out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; - - b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; - out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; - out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; - out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; - out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; - } - -}; - -module.exports = MatrixMath; From 0b21df4a34eaa0d2df88f7183bcb1dd1c07efee0 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sun, 19 Apr 2015 12:55:46 -0700 Subject: [PATCH 15/92] Improved logging and dev menu --- .../RCTAnimationExperimentalManager.m | 4 +- React/Base/RCTDevMenu.h | 45 +++- React/Base/RCTDevMenu.m | 240 +++++++++++++----- React/Base/RCTJavaScriptLoader.h | 9 +- React/Base/RCTJavaScriptLoader.m | 9 +- React/Base/RCTLog.m | 4 +- React/Base/RCTRootView.h | 6 - React/Base/RCTRootView.m | 20 -- React/Executors/RCTContextExecutor.m | 48 ++-- React/Modules/RCTExceptionsManager.m | 47 ++-- React/Modules/RCTUIManager.m | 13 +- 11 files changed, 306 insertions(+), 139 deletions(-) diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index 798c1456b..64ee577fe 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -264,7 +264,9 @@ RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag) RCTAnimationExperimentalManager *strongSelf = weakSelf; NSNumber *reactTag = strongSelf->_animationRegistry[animationTag]; - if (!reactTag) return; + if (!reactTag) { + return; + } UIView *view = viewRegistry[reactTag]; for (NSString *animationKey in view.layer.animationKeys) { diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h index a49e076e6..8057e5708 100644 --- a/React/Base/RCTDevMenu.h +++ b/React/Base/RCTDevMenu.h @@ -9,11 +9,50 @@ #import -@class RCTBridge; +#import "RCTBridge.h" +#import "RCTBridgeModule.h" +#import "RCTInvalidating.h" -@interface RCTDevMenu : NSObject +/** + * Developer menu, useful for exposing extra functionality when debugging. + */ +@interface RCTDevMenu : NSObject -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; +/** + * Is the menu enabled. The menu is enabled by default in debug mode, but + * you may wish to disable it so that you can provide your own shake handler. + */ +@property (nonatomic, assign) BOOL shakeToShow; + +/** + * Enables performance profiling. + */ +@property (nonatomic, assign) BOOL profilingEnabled; + +/** + * Enables automatic polling for JS code changes. Only applicable when + * running the app from a server. + */ +@property (nonatomic, assign) BOOL liveReloadEnabled; + +/** + * The time between checks for code changes. Defaults to 1 second. + */ +@property (nonatomic, assign) NSTimeInterval liveReloadPeriod; + +/** + * Manually show the menu. This will. + */ - (void)show; @end + +/** + * This category makes the developer menu instance available via the + * RCTBridge, which is useful for any class that needs to access the menu. + */ +@interface RCTBridge (RCTDevMenu) + +@property (nonatomic, readonly) RCTDevMenu *devMenu; + +@end diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 7621b1955..f29201f9a 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -9,12 +9,13 @@ #import "RCTDevMenu.h" -#import "RCTRedBox.h" +#import "RCTBridge.h" +#import "RCTLog.h" #import "RCTRootView.h" #import "RCTSourceCode.h" -#import "RCTWebViewExecutor.h" +#import "RCTUtils.h" -@interface RCTBridge (RCTDevMenu) +@interface RCTBridge (Profiling) @property (nonatomic, copy, readonly) NSArray *profile; @@ -23,87 +24,206 @@ @end +static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification"; + +@implementation UIWindow (RCTDevMenu) + +- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ + if (event.subtype == UIEventSubtypeMotionShake) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + } +} + +@end + @interface RCTDevMenu () @end @implementation RCTDevMenu { - BOOL _liveReload; - __weak RCTBridge *_bridge; + NSTimer *_updateTimer; + UIActionSheet *_actionSheet; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + ++ (void)initialize { - if (self = [super init]) { - _bridge = bridge; + // We're swizzling here because it's poor form to override methods in a category, + // however UIWindow doesn't actually implement motionEnded:withEvent:, so there's + // no need to call the original implementation. + RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:)); +} + +- (instancetype)init +{ + if ((self = [super init])) { + + _shakeToShow = YES; + _liveReloadPeriod = 1.0; // 1 second + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(showOnShake) + name:RCTShowDevMenuNotification + object:nil]; } return self; } +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)showOnShake +{ + if (_shakeToShow) { + [self show]; + } +} + - (void)show { - NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; - NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; - NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; + if (_actionSheet) { + return; + } + + NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; + NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; + NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling"; - UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" - delegate:self - cancelButtonTitle:@"Cancel" - destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil]; + + UIActionSheet *actionSheet = + [[UIActionSheet alloc] initWithTitle:@"React Native: Development" + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil]; + actionSheet.actionSheetStyle = UIBarStyleBlack; - [actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]]; + [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { - if (buttonIndex == 0) { - [_bridge reload]; - } else if (buttonIndex == 1) { - Class cls = NSClassFromString(@"RCTWebSocketExecutor"); - _bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil; - [_bridge reload]; - } else if (buttonIndex == 2) { - Class cls = [RCTWebViewExecutor class]; - _bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil; - [_bridge reload]; - } else if (buttonIndex == 3) { - _liveReload = !_liveReload; - [self _pollAndReload]; - } else if (buttonIndex == 4) { - if (_bridge.profile) { - [_bridge stopProfiling]; - } else { - [_bridge startProfiling]; - } - } -} + _actionSheet = nil; -- (void)_pollAndReload -{ - if (_liveReload) { - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; - NSURL *url = sourceCodeModule.scriptURL; - NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; - [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; - } -} - -- (void)_checkForUpdates:(NSURL *)URL -{ - NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL]; - longPollRequest.timeoutInterval = 30; - NSHTTPURLResponse *response; - [NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil]; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (_liveReload && response.statusCode == 205) { - [[RCTRedBox sharedInstance] dismiss]; + switch (buttonIndex) { + case 0: { [_bridge reload]; + break; } - [self _pollAndReload]; - }); + case 1: { + Class cls = NSClassFromString(@"RCTWebSocketExecutor"); + _bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil; + [_bridge reload]; + break; + } + case 2: { + Class cls = NSClassFromString(@"RCTWebViewExecutor"); + _bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil; + [_bridge reload]; + break; + } + case 3: { + self.liveReloadEnabled = !_liveReloadEnabled; + break; + } + case 4: { + self.profilingEnabled = !_profilingEnabled; + break; + } + default: + break; + } +} + +- (void)setProfilingEnabled:(BOOL)enabled +{ + if (_profilingEnabled == enabled) { + return; + } + + _profilingEnabled = enabled; + if (_bridge.profile) { + [_bridge stopProfiling]; + } else { + [_bridge startProfiling]; + } +} + +- (void)setLiveReloadEnabled:(BOOL)enabled +{ + if (_liveReloadEnabled == enabled) { + return; + } + + _liveReloadEnabled = enabled; + if (_liveReloadEnabled) { + + _updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod + target:self + selector:@selector(pollForUpdates) + userInfo:nil + repeats:YES]; + } else { + + [_updateTimer invalidate]; + _updateTimer = nil; + } +} + +- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod +{ + _liveReloadPeriod = liveReloadPeriod; + if (_liveReloadEnabled) { + self.liveReloadEnabled = NO; + self.liveReloadEnabled = YES; + } +} + +- (void)pollForUpdates +{ + RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + if (!sourceCodeModule) { + RCTLogError(@"RCTSourceCode module not found"); + self.liveReloadEnabled = NO; + } + + NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL]; + [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL] + queue:[[NSOperationQueue alloc] init] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + + NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response; + if (_liveReloadEnabled && HTTPResponse.statusCode == 205) { + [_bridge reload]; + } + }]; +} + +- (BOOL)isValid +{ + return !_liveReloadEnabled || _updateTimer != nil; +} + +- (void)invalidate +{ + [_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES]; + [_updateTimer invalidate]; + _updateTimer = nil; +} + +@end + +@implementation RCTBridge (RCTDevMenu) + +- (RCTDevMenu *)devMenu +{ + return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])]; } @end diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index bdc551b4d..8d52529e6 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -1,4 +1,11 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index baf2ca344..02785fb41 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -1,4 +1,11 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import "RCTJavaScriptLoader.h" diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 1770a20a2..4b9653650 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -31,8 +31,8 @@ const char *RCTLogLevels[] = { static RCTLogFunction RCTCurrentLogFunction; static RCTLogLevel RCTCurrentLogThreshold; -void RCTLogSetup(void) __attribute__((constructor)); -void RCTLogSetup() +__attribute__((constructor)) +static void RCTLogSetup() { RCTCurrentLogFunction = RCTDefaultLogFunction; diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index 1227eba94..ee5a35d7f 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -57,12 +57,6 @@ */ @property (nonatomic, strong) Class executorClass; -/** - * If YES will watch for shake gestures and show development menu - * with options like "Reload", "Enable Debugging", etc. - */ -@property (nonatomic, assign) BOOL enableDevMenu; - /** * The backing view controller of the root view. */ diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index c9a97dfd1..24cfe8417 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -13,7 +13,6 @@ #import "RCTBridge.h" #import "RCTContextExecutor.h" -#import "RCTDevMenu.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -42,7 +41,6 @@ @implementation RCTRootView { - RCTDevMenu *_devMenu; RCTBridge *_bridge; RCTTouchHandler *_touchHandler; NSString *_moduleName; @@ -60,12 +58,6 @@ self.backgroundColor = [UIColor whiteColor]; -#ifdef DEBUG - - _enableDevMenu = YES; - -#endif - _bridge = bridge; _moduleName = moduleName; @@ -120,18 +112,6 @@ return YES; } -- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event -{ - if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { - if (!_devMenu) { - _devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge]; - } - [_devMenu show]; - } else { - [super motionEnded:motion withEvent:event]; - } -} - RCT_IMPORT_METHOD(AppRegistry, runApplication) RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 8475e2afa..39616bf35 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -85,7 +85,14 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, range:(NSRange){0, message.length} withTemplate:@"[$4$5] \t$2"]; - _RCTLogFormat(RCTLogLevelInfo, NULL, -1, @"%@", message); + // TODO: it would be good if log level was sent as a param, instead of this hack + RCTLogLevel level = RCTLogLevelInfo; + if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelError; + } else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelWarning; + } + _RCTLogFormat(level, NULL, -1, @"%@", message); } return JSValueMakeUndefined(context); @@ -126,8 +133,6 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) + (void)runRunLoopThread { - // TODO (#5906496): Investigate exactly what this does and why - @autoreleasepool { // copy thread name to pthread name pthread_setname_np([[[NSThread currentThread] name] UTF8String]); @@ -273,11 +278,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } - (void)executeApplicationScript:(NSString *)script - sourceURL:(NSURL *)url + sourceURL:(NSURL *)sourceURL onComplete:(RCTJavaScriptCompleteBlock)onComplete { - RCTAssert(url != nil, @"url should not be nil"); - RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + RCTAssert(sourceURL != nil, @"url should not be nil"); + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ RCTContextExecutor *strongSelf = weakSelf; @@ -286,17 +291,18 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } JSValueRef jsError = NULL; JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script); - JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString); - JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError); - JSStringRelease(sourceURL); + JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString); + JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError); + JSStringRelease(jsURL); JSStringRelease(execJSString); - NSError *error; - if (!result) { - error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError); + if (onComplete) { + NSError *error; + if (!result) { + error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError); + } + onComplete(error); } - - onComplete(error); }]; } @@ -314,7 +320,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { - RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + #if DEBUG RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script); #endif @@ -333,19 +339,21 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script]; RCTLogError(@"%@", errorDesc); - NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; - onComplete(error); + if (onComplete) { + NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; + onComplete(error); + } return; } JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx); - JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName); JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); - onComplete(nil); + if (onComplete) { + onComplete(nil); + } }]; - } @end diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 9e919f3a3..ed30d8741 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -19,10 +19,6 @@ NSUInteger _reloadRetries; } -#ifndef DEBUG -static NSUInteger RCTReloadRetries = 0; -#endif - RCT_EXPORT_MODULE() - (instancetype)initWithDelegate:(id)delegate @@ -47,27 +43,32 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message return; } -#ifdef DEBUG +#if DEBUG + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + #else - if (RCTReloadRetries < _maxReloadAttempts) { - RCTReloadRetries++; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; + + static NSUInteger reloadRetries = 0; + const NSUInteger maxMessageLength = 75; + + if (reloadRetries < _maxReloadAttempts) { + + reloadRetries++; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification + object:nil]; + } else { - NSError *error; - const NSUInteger MAX_SANITIZED_LENGTH = 75; + // Filter out numbers so the same base errors are mapped to the same categories independent of incorrect values. NSString *pattern = @"[+-]?\\d+[,.]?\\d*"; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; - RCTAssert(error == nil, @"Bad regex pattern: %@", pattern); - NSString *sanitizedMessage = [regex stringByReplacingMatchesInString:message - options:0 - range:NSMakeRange(0, message.length) - withTemplate:@""]; - if (sanitizedMessage.length > MAX_SANITIZED_LENGTH) { - sanitizedMessage = [[sanitizedMessage substringToIndex:MAX_SANITIZED_LENGTH] stringByAppendingString:@"..."]; + NSString *sanitizedMessage = [message stringByReplacingOccurrencesOfString:pattern withString:@"" options:NSRegularExpressionSearch range:(NSRange){0, message.length}]; + + if (sanitizedMessage.length > maxMessageLength) { + sanitizedMessage = [[sanitizedMessage substringToIndex:maxMessageLength] stringByAppendingString:@"..."]; } - NSMutableString *prettyStack = [@"\n" mutableCopy]; + + NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"]; for (NSDictionary *frame in stack) { [prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]]; } @@ -75,13 +76,21 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage]; [NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack]; } + #endif + } RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message stack:(NSArray *)stack) { + +#if DEBUG + [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; + +#endif + } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 961b5a0b5..2830ce6b0 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -999,7 +999,7 @@ RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag * Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the * passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts. */ -RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect +RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect parentView:(NSNumber *)reactTag errorCallback:(RCTResponseSenderBlock)errorCallback callback:(RCTResponseSenderBlock)callback) @@ -1011,7 +1011,7 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect } NSArray *childShadowViews = [shadowView reactSubviews]; NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[childShadowViews count]]; - CGRect layoutRect = [RCTConvert CGRect:rect]; + [childShadowViews enumerateObjectsUsingBlock:^(RCTShadowView *childShadowView, NSUInteger idx, BOOL *stop) { CGRect childLayout = [childShadowView measureLayoutRelativeToAncestor:shadowView]; @@ -1026,10 +1026,11 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect CGFloat width = childLayout.size.width; CGFloat height = childLayout.size.height; - if (leftOffset <= layoutRect.origin.x + layoutRect.size.width && - leftOffset + width >= layoutRect.origin.x && - topOffset <= layoutRect.origin.y + layoutRect.size.height && - topOffset + height >= layoutRect.origin.y) { + if (leftOffset <= rect.origin.x + rect.size.width && + leftOffset + width >= rect.origin.x && + topOffset <= rect.origin.y + rect.size.height && + topOffset + height >= rect.origin.y) { + // This view is within the layout rect NSDictionary *result = @{@"index": @(idx), @"left": @(leftOffset), From 0e67e33534e8f2e7652ad83f4395a64d955517d4 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 02:09:11 -0700 Subject: [PATCH 16/92] [ReactNative] Ensure JS calls scheduled by a deallocated context don't fire --- .../RCTWebSocketExecutor.m | 12 ++-- React/Base/RCTBridge.m | 70 ++++++++++++------- React/Base/RCTJavaScriptExecutor.h | 17 +++++ React/Base/RCTJavaScriptLoader.m | 4 +- React/Executors/RCTContextExecutor.m | 3 +- React/Executors/RCTWebViewExecutor.m | 5 ++ 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index 784c91e12..7fd817d53 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -82,7 +82,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); { __block NSError *initError; dispatch_semaphore_t s = dispatch_semaphore_create(0); - [self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) { + [self sendMessage:@{@"method": @"prepareJSRuntime"} context:nil waitForReply:^(NSError *error, NSDictionary *reply) { initError = error; dispatch_semaphore_signal(s); }]; @@ -111,7 +111,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); RCTLogError(@"WebSocket connection failed with error %@", error); } -- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback +- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(WSMessageCallback)callback { static NSUInteger lastID = 10000; @@ -122,6 +122,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); }]; callback(error, nil); return; + } else if (executorID && ![RCTGetExecutorID(self) isEqualToNumber:executorID]) { + return; } NSNumber *expectedID = @(lastID++); @@ -135,12 +137,12 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); - (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete { NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects}; - [self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) { + [self sendMessage:message context:nil waitForReply:^(NSError *error, NSDictionary *reply) { onComplete(error); }]; } -- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete +- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"callback was missing for exec JS call"); NSDictionary *message = @{ @@ -149,7 +151,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); @"moduleMethod": method, @"arguments": arguments }; - [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) { + [self sendMessage:message context:executorID waitForReply:^(NSError *socketError, NSDictionary *reply) { if (socketError) { onComplete(nil, socketError); return; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 7b1f95b69..a2ff0146b 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -234,12 +234,13 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method - arguments:(NSArray *)args; + arguments:(NSArray *)args + context:(NSNumber *)context; - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method - arguments:(NSArray *)args; - + arguments:(NSArray *)args + context:(NSNumber *)context; @end /** @@ -338,7 +339,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; #define RCT_ARG_BLOCK(_logic) \ - [argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \ + [argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \ _logic \ [invocation setArgument:&value atIndex:index]; \ }]; \ @@ -355,7 +356,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) __autoreleasing id value = (json ? ^(NSArray *args) { [bridge _invokeAndProcessModule:@"BatchedBridge" method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, args]]; + arguments:@[json, args] + context:context]; } : ^(NSArray *unused) {}); ) }; @@ -477,6 +479,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) - (void)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments + context:(NSNumber *)context { #if DEBUG @@ -503,8 +506,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) NSUInteger index = 0; for (id json in arguments) { id arg = (json == [NSNull null]) ? nil : json; - void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index]; - block(bridge, invocation, index + 2, arg); + void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index]; + block(bridge, context, invocation, index + 2, arg); index++; } @@ -653,7 +656,6 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) return moduleConfig; } - /** * As above, but for local modules/methods, which represent JS classes * and methods that will be called by the native code via the bridge. @@ -801,7 +803,7 @@ static NSDictionary *RCTLocalModulesConfig() RCTDisplayLink *_displayLink; NSMutableSet *_frameUpdateObservers; NSMutableArray *_scheduledCalls; - NSMutableArray *_scheduledCallbacks; + RCTSparseArray *_scheduledCallbacks; BOOL _loading; NSUInteger _startingTime; @@ -829,13 +831,13 @@ static id _latestJSExecutor; - (void)setUp { Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class]; - _javaScriptExecutor = [[executorClass alloc] init]; + _javaScriptExecutor = RCTCreateExecutor(executorClass); _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; - _scheduledCallbacks = [[NSMutableArray alloc] init]; + _scheduledCallbacks = [[RCTSparseArray alloc] init]; // Register passed-in module instances NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; @@ -991,7 +993,6 @@ static id _latestJSExecutor; } - - (NSDictionary *)modules { RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. " @@ -1072,7 +1073,8 @@ static id _latestJSExecutor; if (!_loading) { [self _invokeAndProcessModule:@"BatchedBridge" method:@"callFunctionReturnFlushedQueue" - arguments:@[moduleID, methodID, args ?: @[]]]; + arguments:@[moduleID, methodID, args ?: @[]] + context:RCTGetExecutorID(_javaScriptExecutor)]; } } @@ -1093,13 +1095,15 @@ static id _latestJSExecutor; #if BATCHED_BRIDGE [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"callFunctionReturnFlushedQueue" - arguments:@[moduleID, methodID, @[@[timer]]]]; + arguments:@[moduleID, methodID, @[@[timer]]] + context:RCTGetExecutorID(_javaScriptExecutor)]; #else [self _invokeAndProcessModule:@"BatchedBridge" method:@"callFunctionReturnFlushedQueue" - arguments:@[moduleID, methodID, @[@[timer]]]]; + arguments:@[moduleID, methodID, @[@[timer]]] + context:RCTGetExecutorID(_javaScriptExecutor)]; #endif } } @@ -1108,6 +1112,7 @@ static id _latestJSExecutor; { RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); RCT_PROFILE_START(); + NSNumber *context = RCTGetExecutorID(_javaScriptExecutor); [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script"); if (scriptLoadError) { @@ -1119,10 +1124,11 @@ static id _latestJSExecutor; [_javaScriptExecutor executeJSCall:@"BatchedBridge" method:@"flushedQueue" arguments:@[] + context:context callback:^(id json, NSError *error) { RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue"); RCT_PROFILE_START(); - [self _handleBuffer:json]; + [self _handleBuffer:json context:context]; RCT_PROFILE_END(objc_call, json, @"batched_js_calls"); onComplete(error); }]; @@ -1131,7 +1137,7 @@ static id _latestJSExecutor; #pragma mark - Payload Generation -- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args +- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context { #if BATCHED_BRIDGE RCT_PROFILE_START(); @@ -1148,10 +1154,11 @@ static id _latestJSExecutor; @"module": module, @"method": method, @"args": args, + @"context": context ?: @0, }; if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) { - [_scheduledCallbacks addObject:call]; + _scheduledCallbacks[args[0]] = call; } else { [_scheduledCalls addObject:call]; } @@ -1159,7 +1166,7 @@ static id _latestJSExecutor; RCT_PROFILE_END(js_call, args, @"schedule", module, method); } -- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args +- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context { #endif [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; @@ -1171,19 +1178,20 @@ static id _latestJSExecutor; RCT_PROFILE_END(js_call, args, moduleDotMethod); RCT_PROFILE_START(); - [self _handleBuffer:json]; + [self _handleBuffer:json context:context]; RCT_PROFILE_END(objc_call, json, @"batched_js_calls"); }; [_javaScriptExecutor executeJSCall:module method:method arguments:args + context:context callback:processResponse]; } #pragma mark - Payload Processing -- (void)_handleBuffer:(id)buffer +- (void)_handleBuffer:(id)buffer context:(NSNumber *)context { if (buffer == nil || buffer == (id)kCFNull) { return; @@ -1228,7 +1236,8 @@ static id _latestJSExecutor; [self _handleRequestNumber:i moduleID:[moduleIDs[i] integerValue] methodID:[methodIDs[i] integerValue] - params:paramsArrays[i]]; + params:paramsArrays[i] + context:context]; } } @@ -1247,6 +1256,7 @@ static id _latestJSExecutor; moduleID:(NSUInteger)moduleID methodID:(NSUInteger)methodID params:(NSArray *)params + context:(NSNumber *)context { if (![params isKindOfClass:[NSArray class]]) { RCTLogError(@"Invalid module/method/params tuple for request #%zd", i); @@ -1280,7 +1290,7 @@ static id _latestJSExecutor; } @try { - [method invokeWithBridge:strongSelf module:module arguments:params]; + [method invokeWithBridge:strongSelf module:module arguments:params context:context]; } @catch (NSException *exception) { RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); @@ -1313,13 +1323,18 @@ static id _latestJSExecutor; { #if BATCHED_BRIDGE - NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls]; + NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; + NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor); + calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { + return [call[@"context"] isEqualToNumber:currentExecutorID]; + }]]; if (calls.count > 0) { _scheduledCalls = [[NSMutableArray alloc] init]; - _scheduledCallbacks = [[NSMutableArray alloc] init]; + _scheduledCallbacks = [[RCTSparseArray alloc] init]; [self _actuallyInvokeAndProcessModule:@"BatchedBridge" - method:@"processBatch" - arguments:@[calls]]; + method:@"processBatch" + arguments:@[calls] + context:RCTGetExecutorID(_javaScriptExecutor)]; } #endif @@ -1357,6 +1372,7 @@ static id _latestJSExecutor; [_latestJSExecutor executeJSCall:@"RCTLog" method:@"logIfNoNativeHook" arguments:@[level, message] + context:RCTGetExecutorID(_latestJSExecutor) callback:^(id json, NSError *error) {}]; } diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 57dff78e7..2816c7a7a 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -7,6 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import + #import #import "RCTInvalidating.h" @@ -27,6 +29,7 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments + context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete; /** @@ -40,3 +43,17 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete; @end + +static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; +__used static id RCTCreateExecutor(Class executorClass) +{ + static NSUInteger executorID = 0; + id executor = [[executorClass alloc] init]; + objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); + return executor; +} + +__used static NSNumber *RCTGetExecutorID(id executor) +{ + return objc_getAssociatedObject(executor, RCTJavaScriptExecutorID); +} diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 02785fb41..4eaaf278d 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -140,9 +140,9 @@ sourceCodeModule.scriptURL = scriptURL; sourceCodeModule.scriptText = rawText; - [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) { + [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) { dispatch_async(dispatch_get_main_queue(), ^{ - onComplete(_error); + onComplete(scriptError); }); }]; }]; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 39616bf35..71e4f45c3 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -229,13 +229,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments + context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"onComplete block should not be nil"); __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ RCTContextExecutor *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.isValid) { + if (!strongSelf || !strongSelf.isValid || ![RCTGetExecutorID(strongSelf) isEqualToNumber:executorID]) { return; } NSError *error; diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index 55de44ab9..0bc6fdfc8 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -76,10 +76,15 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments + context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @""); [self executeBlockOnJavaScriptQueue:^{ + if (!self.isValid || ![RCTGetExecutorID(self) isEqualToNumber:executorID]) { + return; + } + NSError *error; NSString *argsString = RCTJSONStringify(arguments, &error); if (!argsString) { From fde476f4e56bb9d07312743ef9a6529e398830f9 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Mon, 20 Apr 2015 02:47:20 -0700 Subject: [PATCH 17/92] [react_native] JS files from D2001617: [react_native] Add support for rendering to hardware textures on Android --- Libraries/Components/View/View.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index c981e4129..0fdfa1fc8 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -136,6 +136,20 @@ var View = React.createClass({ * (or one of its superviews). */ removeClippedSubviews: PropTypes.bool, + + /** + * Whether this view should render itself (and all of its children) into a + * single hardware texture on the GPU. + * + * On Android, this is useful for animations and interactions that only + * modify opacity, rotation, translation, and/or scale: in those cases, the + * view doesn't have to be redrawn and display lists don't need to be + * re-executed. The texture can just be re-used and re-composited with + * different parameters. The downside is that this can use up limited video + * memory, so this prop should be set back to false at the end of the + * interaction/animation. + */ + renderToHardwareTextureAndroid: PropTypes.bool, }, render: function() { From fb1fa12e89912ee5c19cc10ca95fa24577673639 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 04:55:05 -0700 Subject: [PATCH 18/92] [ReactNative] Better profiling API + Fix overlaping events --- React/Base/RCTBridge.m | 109 ++++++------------- React/Base/RCTDevMenu.m | 7 +- React/Base/RCTLog.h | 5 + React/Base/RCTLog.m | 29 ++--- React/Base/RCTProfile.h | 103 ++++++++++++++++++ React/Base/RCTProfile.m | 149 ++++++++++++++++++++++++++ React/Executors/RCTContextExecutor.m | 13 +-- React/Modules/RCTUIManager.m | 5 + React/React.xcodeproj/project.pbxproj | 6 ++ 9 files changed, 328 insertions(+), 98 deletions(-) create mode 100644 React/Base/RCTProfile.h create mode 100644 React/Base/RCTProfile.m diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index a2ff0146b..46dcc1dfe 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -22,6 +22,7 @@ #import "RCTJavaScriptLoader.h" #import "RCTKeyCommands.h" #import "RCTLog.h" +#import "RCTProfile.h" #import "RCTRedBox.h" #import "RCTRootView.h" #import "RCTSparseArray.h" @@ -48,39 +49,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { */ #define BATCHED_BRIDGE 1 -#ifdef DEBUG - -#define RCT_PROFILE_START() \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSTimeInterval __rct_profile_start = CACurrentMediaTime() \ -_Pragma("clang diagnostic pop") - -#define RCT_PROFILE_END(cat, args, profileName...) \ -do { \ -if (_profile) { \ - [_profileLock lock]; \ - [_profile addObject:@{ \ - @"name": [@[profileName] componentsJoinedByString: @"_"], \ - @"cat": @ #cat, \ - @"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \ - @"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \ - @"ph": @"X", \ - @"pid": @([[NSProcessInfo processInfo] processIdentifier]), \ - @"tid": [[NSThread currentThread] description], \ - @"args": args ?: [NSNull null], \ - }]; \ - [_profileLock unlock]; \ -} \ -} while(0) - -#else - -#define RCT_PROFILE_START(...) -#define RCT_PROFILE_END(...) - -#endif - #ifdef __LP64__ typedef uint64_t RCTHeaderValue; typedef struct section_64 RCTHeaderSection; @@ -230,8 +198,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) @interface RCTBridge () -@property (nonatomic, copy, readonly) NSArray *profile; - - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args @@ -250,6 +216,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) @property (nonatomic, copy, readonly) NSString *moduleClassName; @property (nonatomic, copy, readonly) NSString *JSMethodName; +@property (nonatomic, assign, readonly) SEL selector; @end @@ -805,10 +772,6 @@ static NSDictionary *RCTLocalModulesConfig() NSMutableArray *_scheduledCalls; RCTSparseArray *_scheduledCallbacks; BOOL _loading; - - NSUInteger _startingTime; - NSMutableArray *_profile; - NSLock *_profileLock; } static id _latestJSExecutor; @@ -1111,25 +1074,28 @@ static id _latestJSExecutor; - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete { RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); - RCT_PROFILE_START(); - NSNumber *context = RCTGetExecutorID(_javaScriptExecutor); + RCTProfileBeginEvent(); [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { - RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script"); + RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError); if (scriptLoadError) { onComplete(scriptLoadError); return; } - RCT_PROFILE_START(); + RCTProfileBeginEvent(); + NSNumber *context = RCTGetExecutorID(_javaScriptExecutor); [_javaScriptExecutor executeJSCall:@"BatchedBridge" method:@"flushedQueue" arguments:@[] context:context callback:^(id json, NSError *error) { - RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue"); - RCT_PROFILE_START(); + RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{ + @"json": json ?: [NSNull null], + @"error": error ?: [NSNull null], + }); + [self _handleBuffer:json context:context]; - RCT_PROFILE_END(objc_call, json, @"batched_js_calls"); + onComplete(error); }]; }]; @@ -1140,7 +1106,7 @@ static id _latestJSExecutor; - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context { #if BATCHED_BRIDGE - RCT_PROFILE_START(); + RCTProfileBeginEvent(); if ([module isEqualToString:@"RCTEventEmitter"]) { for (NSDictionary *call in _scheduledCalls) { @@ -1163,7 +1129,7 @@ static id _latestJSExecutor; [_scheduledCalls addObject:call]; } - RCT_PROFILE_END(js_call, args, @"schedule", module, method); + RCTProfileEndEvent(@"enqueue_call", @"objc_call", call); } - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context @@ -1171,15 +1137,9 @@ static id _latestJSExecutor; #endif [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; - NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method]; - RCT_PROFILE_START(); RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; - RCT_PROFILE_END(js_call, args, moduleDotMethod); - - RCT_PROFILE_START(); [self _handleBuffer:json context:context]; - RCT_PROFILE_END(objc_call, json, @"batched_js_calls"); }; [_javaScriptExecutor executeJSCall:module @@ -1271,9 +1231,17 @@ static id _latestJSExecutor; } RCTModuleMethod *method = methods[methodID]; + // Look up module + id module = self->_modulesByID[moduleID]; + if (!module) { + RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]); + return NO; + } + __weak RCTBridge *weakSelf = self; dispatch_queue_t queue = _queuesByID[moduleID]; dispatch_async(queue ?: dispatch_get_main_queue(), ^{ + RCTProfileBeginEvent(); __strong RCTBridge *strongSelf = weakSelf; if (!strongSelf.isValid) { @@ -1282,13 +1250,6 @@ static id _latestJSExecutor; return; } - // Look up module - id module = strongSelf->_modulesByID[moduleID]; - if (!module) { - RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]); - return; - } - @try { [method invokeWithBridge:strongSelf module:module arguments:params context:context]; } @@ -1298,6 +1259,12 @@ static id _latestJSExecutor; @throw; } } + + RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ + @"module": method.moduleClassName, + @"method": method.JSMethodName, + @"selector": NSStringFromSelector(method.selector), + }); }); return YES; @@ -1305,7 +1272,8 @@ static id _latestJSExecutor; - (void)_update:(CADisplayLink *)displayLink { - RCT_PROFILE_START(); + RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); + RCTProfileBeginEvent(); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (id observer in _frameUpdateObservers) { @@ -1316,7 +1284,7 @@ static id _latestJSExecutor; [self _runScheduledCalls]; - RCT_PROFILE_END(display_link, nil, @"main_thread"); + RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); } - (void)_runScheduledCalls @@ -1382,23 +1350,12 @@ static id _latestJSExecutor; RCTLogError(@"To run the profiler you must be running from the dev server"); return; } - _profileLock = [[NSLock alloc] init]; - _startingTime = CACurrentMediaTime(); - - [_profileLock lock]; - _profile = [[NSMutableArray alloc] init]; - [_profileLock unlock]; + RCTProfileInit(); } - (void)stopProfiling { - [_profileLock lock]; - NSArray *profile = _profile; - _profile = nil; - [_profileLock unlock]; - _profileLock = nil; - - NSString *log = RCTJSONStringify(profile, NULL); + NSString *log = RCTProfileEnd(); NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port]; NSURL *URL = [NSURL URLWithString:URLString]; NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index f29201f9a..4af9d4e62 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -11,14 +11,13 @@ #import "RCTBridge.h" #import "RCTLog.h" +#import "RCTProfile.h" #import "RCTRootView.h" #import "RCTSourceCode.h" #import "RCTUtils.h" @interface RCTBridge (Profiling) -@property (nonatomic, copy, readonly) NSArray *profile; - - (void)startProfiling; - (void)stopProfiling; @@ -94,7 +93,7 @@ RCT_EXPORT_MODULE() NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; - NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling"; + NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling"; UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" @@ -148,7 +147,7 @@ RCT_EXPORT_MODULE() } _profilingEnabled = enabled; - if (_bridge.profile) { + if (RCTProfileIsProfiling()) { [_bridge stopProfiling]; } else { [_bridge startProfiling]; diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h index 7ffd86006..3a8a70d5b 100644 --- a/React/Base/RCTLog.h +++ b/React/Base/RCTLog.h @@ -45,6 +45,11 @@ typedef void (^RCTLogFunction)( NSString *message ); +/** + * Get a given thread's name (or the current queue, iff in debug mode) + */ +NSString *RCTThreadName(NSThread *); + /** * A method to generate a string from a collection of log data. To omit any * particular data from the log, just pass nil or zero for the argument. diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 4b9653650..449980fc3 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -98,6 +98,22 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix) [prefixStack removeLastObject]; } +NSString *RCTThreadName(NSThread *thread) +{ + NSString *threadName = [thread isMainThread] ? @"main" : thread.name; + if (threadName.length == 0) { +#if DEBUG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + threadName = @(dispatch_queue_get_label(dispatch_get_current_queue())); +#pragma clang diagnostic pop +#else + threadName = [NSString stringWithFormat:@"%p", thread]; +#endif + } + return threadName; +} + NSString *RCTFormatLog( NSDate *timestamp, NSThread *thread, @@ -121,18 +137,7 @@ NSString *RCTFormatLog( [log appendFormat:@"[%s]", RCTLogLevels[level - 1]]; } if (thread) { - NSString *threadName = [thread isMainThread] ? @"main" : thread.name; - if (threadName.length == 0) { -#if DEBUG -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - threadName = @(dispatch_queue_get_label(dispatch_get_current_queue())); -#pragma clang diagnostic pop -#else - threadName = [NSString stringWithFormat:@"%p", thread]; -#endif - } - [log appendFormat:@"[tid:%@]", threadName]; + [log appendFormat:@"[tid:%@]", RCTThreadName(thread)]; } if (fileName) { fileName = [fileName lastPathComponent]; diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h new file mode 100644 index 000000000..b3a1683d1 --- /dev/null +++ b/React/Base/RCTProfile.h @@ -0,0 +1,103 @@ +/** + * 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 + +/** + * RCTProfile + * + * This file provides a set of functions and macros for performance profiling + * + * NOTE: This API is a work in a work in progress, please consider it before + * before using. + */ + +#if DEBUG + +/** + * Returns YES if the profiling information is currently being collected + */ +BOOL RCTProfileIsProfiling(void); + +/** + * Start collecting profiling information + */ +void RCTProfileInit(void); + +/** + * Stop profiling and return a JSON string of the collected data - The data + * returned is compliant with google's trace event format - the format used + * as input to trace-viewer + */ +NSString *RCTProfileEnd(void); + +/** + * Collects the initial event information for the event and returns a reference ID + */ +NSNumber *_RCTProfileBeginEvent(void); + +/** + * The ID returned by BeginEvent should then be passed into EndEvent, with the + * rest of the event information. Just at this point the event will actually be + * registered + */ +void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id); + +/** + * This pair of macros implicitly handle the event ID when beginning and ending + * an event, for both simplicity and performance reasons, this method is preferred + * + * NOTE: The EndEvent call has to be either, in the same scope of BeginEvent, + * or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible + * for EndEvent, in this case you may want to use the actual C functions. + */ +#define RCTProfileBeginEvent() \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +NSNumber *__rct_profile_id = _RCTProfileBeginEvent(); \ +_Pragma("clang diagnostic pop") + +#define RCTProfileEndEvent(name, category, args...) \ +_RCTProfileEndEvent(__rct_profile_id, name, category, args) + +/** + * An event that doesn't have a duration (i.e. Notification, VSync, etc) + */ +void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *); + +/** + * Helper to profile the duration of the execution of a block. This method uses + * self and _cmd to name this event for simplicity sake. + * + * NOTE: The block can't expect any argument + */ +#define RCTProfileBlock(block, category, arguments) \ +^{ \ + RCTProfileBeginEvent(); \ + block(); \ + RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \ +} + +#else + +#define RCTProfileIsProfiling(...) NO +#define RCTProfileInit(...) +#define RCTProfileEnd(...) @"" + +#define _RCTProfileBeginEvent(...) @0 +#define RCTProfileBeginEvent(...) + +#define _RCTProfileEndEvent(...) +#define RCTProfileEndEvent(...) + +#define RCTProfileImmediateEvent(...) + +#define RCTProfileBlock(block, ...) block + +#endif diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m new file mode 100644 index 000000000..4ba99725b --- /dev/null +++ b/React/Base/RCTProfile.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 "RCTProfile.h" + +#import + +#import + +#import "RCTLog.h" +#import "RCTUtils.h" + +#pragma mark - Prototypes + +NSNumber *RCTProfileTimestamp(NSTimeInterval); +NSString *RCTProfileMemory(vm_size_t); +NSDictionary *RCTProfileGetMemoryUsage(void); + +#pragma mark - Constants + +NSString const *RCTProfileTraceEvents = @"traceEvents"; +NSString const *RCTProfileSamples = @"samples"; + +#pragma mark - Variables + +NSDictionary *RCTProfileInfo; +NSUInteger RCTProfileEventID = 0; +NSMutableDictionary *RCTProfileOngoingEvents; +NSTimeInterval RCTProfileStartTime; + +#pragma mark - Macros + +#define RCTProfileAddEvent(type, props...) \ +[RCTProfileInfo[type] addObject:@{ \ + @"pid": @([[NSProcessInfo processInfo] processIdentifier]), \ + @"tid": RCTThreadName([NSThread currentThread]), \ + props \ +}]; + +#define CHECK(...) \ +if (!RCTProfileIsProfiling()) { \ + return __VA_ARGS__; \ +} + +#pragma mark - Private Helpers + +NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) +{ + return @((timestamp - RCTProfileStartTime) * 1e6); +} + +NSString *RCTProfileMemory(vm_size_t memory) +{ + double mem = ((double)memory) / 1024 / 1024; + return [NSString stringWithFormat:@"%.2lfmb", mem]; +} + +NSDictionary *RCTProfileGetMemoryUsage(void) +{ + CHECK(@{}); + struct task_basic_info info; + mach_msg_type_number_t size = sizeof(info); + kern_return_t kerr = task_info(mach_task_self(), + TASK_BASIC_INFO, + (task_info_t)&info, + &size); + if( kerr == KERN_SUCCESS ) { + return @{ + @"suspend_count": @(info.suspend_count), + @"virtual_size": RCTProfileMemory(info.virtual_size), + @"resident_size": RCTProfileMemory(info.resident_size), + }; + } else { + return @{}; + } +} + +#pragma mark - Public Functions + +BOOL RCTProfileIsProfiling(void) +{ + return RCTProfileInfo != nil; +} + +void RCTProfileInit(void) +{ + RCTProfileStartTime = CACurrentMediaTime(); + RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; + RCTProfileInfo = @{ + RCTProfileTraceEvents: [[NSMutableArray alloc] init], + RCTProfileSamples: [[NSMutableArray alloc] init], + }; +} + +NSString *RCTProfileEnd(void) +{ + NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); + RCTProfileEventID = 0; + RCTProfileInfo = nil; + RCTProfileOngoingEvents = nil; + return log; +} + +NSNumber *_RCTProfileBeginEvent(void) +{ + CHECK(@0); + NSNumber *eventID = @(++RCTProfileEventID); + RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); + return eventID; +} + +void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args) +{ + CHECK(); + NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; + if (!startTimestamp) { + return; + } + + NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); + + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"cat": categories, + @"ph": @"X", + @"ts": startTimestamp, + @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), + @"args": args ?: @[], + ); + [RCTProfileOngoingEvents removeObjectForKey:eventID]; +} + +void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope) +{ + CHECK(); + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"ts": RCTProfileTimestamp(timestamp), + @"scope": scope, + @"ph": @"i", + @"args": RCTProfileGetMemoryUsage(), + ); +} diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 71e4f45c3..3de6182d1 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -15,6 +15,7 @@ #import "RCTAssert.h" #import "RCTLog.h" +#import "RCTProfile.h" #import "RCTUtils.h" @interface RCTJavaScriptContext : NSObject @@ -234,7 +235,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) { RCTAssert(onComplete != nil, @"onComplete block should not be nil"); __weak RCTContextExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ + [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ RCTContextExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid || ![RCTGetExecutorID(strongSelf) isEqualToNumber:executorID]) { return; @@ -275,7 +276,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(objcValue, nil); - }]; + }), @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSString *)script @@ -285,7 +286,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) RCTAssert(sourceURL != nil, @"url should not be nil"); __weak RCTContextExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ + [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ RCTContextExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; @@ -304,7 +305,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(error); } - }]; + }), @"js_call", (@{ @"url": sourceURL }))]; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block @@ -327,7 +328,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) #endif __weak RCTContextExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ + [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ RCTContextExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; @@ -354,7 +355,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) if (onComplete) { onComplete(nil); } - }]; + }), @"js_call,json_call", (@{@"objectName": objectName}))]; } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 2830ce6b0..e2dc8d560 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -19,6 +19,7 @@ #import "RCTBridge.h" #import "RCTConvert.h" #import "RCTLog.h" +#import "RCTProfile.h" #import "RCTRootView.h" #import "RCTScrollableProtocol.h" #import "RCTShadowView.h" @@ -888,9 +889,13 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag) // Execute the previously queued UI blocks dispatch_async(dispatch_get_main_queue(), ^{ + RCTProfileBeginEvent(); for (dispatch_block_t block in previousPendingUIBlocks) { block(); } + RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{ + @"count": @(previousPendingUIBlocks.count), + }); }); } diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 294bf4145..48d7aee04 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; }; 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; + 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F4D38A1AE1B7E40049C042 /* RCTProfile.m */; }; 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; }; 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; @@ -165,6 +166,8 @@ 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = ""; }; 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = ""; }; 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = ""; }; + 14F4D3891AE1B7E40049C042 /* RCTProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProfile.h; sourceTree = ""; }; + 14F4D38A1AE1B7E40049C042 /* RCTProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProfile.m; sourceTree = ""; }; 58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; }; 58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; }; 58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; }; @@ -393,6 +396,8 @@ 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */, 83CBBA501A601E3B00E9B192 /* RCTUtils.m */, 1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */, + 14F4D3891AE1B7E40049C042 /* RCTProfile.h */, + 14F4D38A1AE1B7E40049C042 /* RCTProfile.m */, ); path = Base; sourceTree = ""; @@ -483,6 +488,7 @@ 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, + 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */, From 88b6df99000b001717541fcf59390829a291de07 Mon Sep 17 00:00:00 2001 From: Marek Cirkos Date: Mon, 20 Apr 2015 05:09:54 -0700 Subject: [PATCH 19/92] Fixed way that ScrollView handles removeClippedSubviews flag --- React/Views/RCTScrollView.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 0376d2a9c..1b3170064 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -175,7 +175,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; scrollBounds.origin.y += self.contentInset.top; NSInteger i = 0; - for (UIView *subview in contentView.subviews) { + for (UIView *subview in contentView.reactSubviews) { CGRect rowFrame = [RCTCustomScrollView _calculateUntransformedFrame:subview]; if (CGRectIntersectsRect(scrollBounds, rowFrame)) { firstIndexInView = i; @@ -198,8 +198,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; NSInteger nextDockedIndex = (stickyHeaderii < _stickyHeaderIndices.count - 1) ? [_stickyHeaderIndices[stickyHeaderii + 1] integerValue] : -1; - UIView *currentHeader = contentView.subviews[currentlyDockedIndex]; - UIView *previousHeader = previouslyDockedIndex >= 0 ? contentView.subviews[previouslyDockedIndex] : nil; + UIView *currentHeader = contentView.reactSubviews[currentlyDockedIndex]; + UIView *previousHeader = previouslyDockedIndex >= 0 ? contentView.reactSubviews[previouslyDockedIndex] : nil; CGRect curFrame = [RCTCustomScrollView _calculateUntransformedFrame:currentHeader]; if (previousHeader) { @@ -210,7 +210,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset); } - UIView *nextHeader = nextDockedIndex >= 0 ? contentView.subviews[nextDockedIndex] : nil; + UIView *nextHeader = nextDockedIndex >= 0 ? contentView.reactSubviews[nextDockedIndex] : nil; CGRect nextFrame = [RCTCustomScrollView _calculateUntransformedFrame:nextHeader]; if (curFrame.origin.y < scrollBounds.origin.y) { From 915925db9d823aabda2c833f8c35ae83e07642a9 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 05:40:42 -0700 Subject: [PATCH 20/92] [ReactNative] Add tests on root view, bridge, modules and js context deallocation --- React/Executors/RCTContextExecutor.m | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 3de6182d1..d2aac3fbe 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -47,9 +47,11 @@ - (void)invalidate { - JSGlobalContextRelease(_ctx); - _ctx = NULL; - _self = nil; + if (self.isValid) { + JSGlobalContextRelease(_ctx); + _ctx = NULL; + _self = nil; + } } @end @@ -216,10 +218,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)invalidate { - if (self.isValid) { - [_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; - _context = nil; - } + [_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; } - (void)dealloc From 2d5d55d17e2068dab2281570f8c9fa1e65c95a8b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 12:33:52 -0100 Subject: [PATCH 21/92] [ReactNative] Add if DEBUG to profile functions declarations --- React/Base/RCTProfile.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 4ba99725b..71d34551e 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -16,6 +16,8 @@ #import "RCTLog.h" #import "RCTUtils.h" +#if DEBUG + #pragma mark - Prototypes NSNumber *RCTProfileTimestamp(NSTimeInterval); @@ -147,3 +149,5 @@ void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString @"args": RCTProfileGetMemoryUsage(), ); } + +#endif From bbd52595864a3ac6469b46b3789b882b51f33e86 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 20 Apr 2015 08:31:15 -0700 Subject: [PATCH 22/92] Fixed reachability --- Examples/2048/2048/AppDelegate.m | 39 ++++++++++++-------- Examples/Movies/Movies/AppDelegate.m | 39 ++++++++++++-------- Examples/SampleApp/iOS/AppDelegate.m | 39 ++++++++++++-------- Examples/TicTacToe/TicTacToe/AppDelegate.m | 39 ++++++++++++-------- Examples/UIExplorer/UIExplorer/AppDelegate.m | 39 ++++++++++++-------- Libraries/Network/RCTReachability.m | 6 +-- 6 files changed, 123 insertions(+), 78 deletions(-) diff --git a/Examples/2048/2048/AppDelegate.m b/Examples/2048/2048/AppDelegate.m index bc089038c..004e854a7 100644 --- a/Examples/2048/2048/AppDelegate.m +++ b/Examples/2048/2048/AppDelegate.m @@ -22,24 +22,33 @@ { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle"]; - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle -o main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder in the terminal, and run + * + * $ curl 'http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + +// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"Game2048" diff --git a/Examples/Movies/Movies/AppDelegate.m b/Examples/Movies/Movies/AppDelegate.m index b56722712..74aed2cc4 100644 --- a/Examples/Movies/Movies/AppDelegate.m +++ b/Examples/Movies/Movies/AppDelegate.m @@ -23,24 +23,33 @@ { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"]; - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle -o main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder in the terminal, and run + * + * $ curl 'http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + +// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"MoviesApp" diff --git a/Examples/SampleApp/iOS/AppDelegate.m b/Examples/SampleApp/iOS/AppDelegate.m index 5b6cc2d58..777072c6c 100644 --- a/Examples/SampleApp/iOS/AppDelegate.m +++ b/Examples/SampleApp/iOS/AppDelegate.m @@ -17,24 +17,33 @@ { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"]; - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder in the terminal, and run + * + * $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + +// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"SampleApp" diff --git a/Examples/TicTacToe/TicTacToe/AppDelegate.m b/Examples/TicTacToe/TicTacToe/AppDelegate.m index a118b94dc..9c328a3a8 100644 --- a/Examples/TicTacToe/TicTacToe/AppDelegate.m +++ b/Examples/TicTacToe/TicTacToe/AppDelegate.m @@ -22,24 +22,33 @@ { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle"]; - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle -o main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder in the terminal, and run + * + * $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + +// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"TicTacToeApp" diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 84734f12f..d72262e78 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -22,24 +22,33 @@ { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle?dev=true"]; - // OPTION 2 - // Load from pre-bundled file on disk. To re-generate the static bundle, run - // - // $ curl http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle -o main.jsbundle - // - // and uncomment the next following line - // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` + * to your Xcode project folder and run + * + * $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle' -o main.jsbundle + * + * then add the `main.jsbundle` file to your project and uncomment this line: + */ + +// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" diff --git a/Libraries/Network/RCTReachability.m b/Libraries/Network/RCTReachability.m index b5f30de30..e0711571b 100644 --- a/Libraries/Network/RCTReachability.m +++ b/Libraries/Network/RCTReachability.m @@ -25,6 +25,8 @@ static NSString *const RCTReachabilityStateCell = @"cell"; @synthesize bridge = _bridge; +RCT_EXPORT_MODULE() + static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) { RCTReachability *self = (__bridge id)info; @@ -53,8 +55,6 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC } } -RCT_EXPORT_MODULE() - #pragma mark - Lifecycle - (instancetype)initWithHost:(NSString *)host @@ -71,7 +71,7 @@ RCT_EXPORT_MODULE() - (instancetype)init { - return [self initWithHost:@"http://apple.com"]; + return [self initWithHost:@"apple.com"]; } - (void)dealloc From d6afe1b1249741512d78fbef04949c38e10c7f5d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 11:03:56 -0700 Subject: [PATCH 23/92] [ReactNative] Don't break when can't create executor --- React/Base/RCTJavaScriptExecutor.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 2816c7a7a..8ff5a1658 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -49,11 +49,13 @@ __used static id RCTCreateExecutor(Class executorClass) { static NSUInteger executorID = 0; id executor = [[executorClass alloc] init]; - objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); + if (executor) { + objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); + } return executor; } __used static NSNumber *RCTGetExecutorID(id executor) { - return objc_getAssociatedObject(executor, RCTJavaScriptExecutorID); + return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0; } From 5e2f90a73e710e6ad94cf33218113fa090d359e5 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 20 Apr 2015 12:41:29 -0700 Subject: [PATCH 24/92] [ReactNative] Skip flow checks for URLs that are not bundles --- packager/getFlowTypeCheckMiddleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js index cd910054f..059f0e319 100644 --- a/packager/getFlowTypeCheckMiddleware.js +++ b/packager/getFlowTypeCheckMiddleware.js @@ -16,7 +16,8 @@ var hasWarned = {}; function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { - if (options.skipflow) { + var isBundle = req.url.indexOf('.bundle') !== -1; + if (options.skipflow || !isBundle) { return next(); } if (options.flowroot || options.projectRoots.length === 1) { From 5ce9fa4dda79bd925e075afcb552893acdc83628 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 20 Apr 2015 12:06:02 -0700 Subject: [PATCH 25/92] Changed default method queue to a background queue. --- .../ActionSheetIOS/RCTActionSheetManager.m | 5 +++ Libraries/Geolocation/RCTLocationObserver.m | 5 +++ Libraries/LinkingIOS/RCTLinkingManager.m | 13 ++++--- React/Base/RCTBridge.h | 10 +++--- React/Base/RCTBridge.m | 36 ++++++++++--------- React/Base/RCTBridgeModule.h | 26 ++++++++++---- React/Modules/RCTAlertManager.m | 5 +++ React/Modules/RCTStatusBarManager.m | 5 +++ React/Modules/RCTTiming.m | 5 +++ 9 files changed, 78 insertions(+), 32 deletions(-) diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index ac672249f..75798efaf 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -30,6 +30,11 @@ RCT_EXPORT_MODULE() return self; } +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options failureCallback:(RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index c5303df15..3e864657b 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -128,6 +128,11 @@ RCT_EXPORT_MODULE() _locationManager.delegate = nil; } +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + #pragma mark - Private API - (void)beginLocationUpdates diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 4be8bfb8e..eec17a012 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -36,6 +36,11 @@ RCT_EXPORT_MODULE() [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (dispatch_queue_t)methodQueue +{ + return dispatch_queue_create("com.facebook.React.LinkingManager", DISPATCH_QUEUE_SERIAL); +} + + (BOOL)application:(UIApplication *)application openURL:(NSURL *)URL sourceApplication:(NSString *)sourceApplication @@ -56,16 +61,16 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(openURL:(NSURL *)URL) { + // Doesn't really matter what thread we call this on since it exits the app [[UIApplication sharedApplication] openURL:URL]; } RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callback) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; - callback(@[@(canOpen)]); - }); + // This can be expensive, so we deliberately don't call on main thread + BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL]; + callback(@[@(canOpen)]); } - (NSDictionary *)constantsToExport diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 544e5e1a2..c0f3a1a91 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -62,9 +62,9 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); /** * This method is used to call functions in the JavaScript application context. * It is primarily intended for use by modules that require two-way communication - * with the JavaScript code. Method should be regsitered using the + * with the JavaScript code. Method should be registered using the * RCT_IMPORT_METHOD macro below. Attempting to call a method that has not been - * registered will result in an error. + * registered will result in an error. Safe to call from any thread. */ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; @@ -112,17 +112,17 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method; @property (nonatomic, readonly, getter=isLoading) BOOL loading; /** - * Reload the bundle and reset executor and modules. + * Reload the bundle and reset executor & modules. Safe to call from any thread. */ - (void)reload; /** - * Add a new observer that will be called on every screen refresh + * Add a new observer that will be called on every screen refresh. */ - (void)addFrameUpdateObserver:(id)observer; /** - * Stop receiving screen refresh updates for the given observer + * Stop receiving screen refresh updates for the given observer. */ - (void)removeFrameUpdateObserver:(id)observer; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 46dcc1dfe..31a8bea77 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -228,6 +228,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) NSMethodSignature *_methodSignature; NSArray *_argumentBlocks; NSString *_methodName; + dispatch_block_t _methodQueue; } static Class _globalExecutorClass; @@ -762,6 +763,7 @@ static NSDictionary *RCTLocalModulesConfig() { RCTSparseArray *_modulesByID; RCTSparseArray *_queuesByID; + dispatch_queue_t _methodQueue; NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; @@ -787,7 +789,6 @@ static id _latestJSExecutor; [self setUp]; [self bindKeys]; } - return self; } @@ -797,6 +798,7 @@ static id _latestJSExecutor; _javaScriptExecutor = RCTCreateExecutor(executorClass); _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; + _methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL); _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; @@ -850,11 +852,14 @@ static id _latestJSExecutor; } } - // Get method queue + // Get method queues _queuesByID = [[RCTSparseArray alloc] init]; [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { if ([module respondsToSelector:@selector(methodQueue)]) { - _queuesByID[moduleID] = [module methodQueue] ?: dispatch_get_main_queue(); + dispatch_queue_t queue = [module methodQueue]; + if (queue) { + _queuesByID[moduleID] = queue; + } } }]; @@ -895,11 +900,6 @@ static id _latestJSExecutor; } else { [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reload) - name:RCTReloadNotification - object:nil]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) @@ -1205,7 +1205,7 @@ static id _latestJSExecutor; [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, BOOL *stop) { if ([module respondsToSelector:@selector(batchDidComplete)]) { dispatch_queue_t queue = _queuesByID[moduleID]; - dispatch_async(queue ?: dispatch_get_main_queue(), ^{ + dispatch_async(queue ?: _methodQueue, ^{ [module batchDidComplete]; }); } @@ -1240,7 +1240,7 @@ static id _latestJSExecutor; __weak RCTBridge *weakSelf = self; dispatch_queue_t queue = _queuesByID[moduleID]; - dispatch_async(queue ?: dispatch_get_main_queue(), ^{ + dispatch_async(queue ?: _methodQueue, ^{ RCTProfileBeginEvent(); __strong RCTBridge *strongSelf = weakSelf; @@ -1320,13 +1320,15 @@ static id _latestJSExecutor; - (void)reload { - if (!_loading) { - // If the bridge has not loaded yet, the context will be already invalid at - // the time the javascript gets executed. - // It will crash the javascript, and even the next `load` won't render. - [self invalidate]; - [self setUp]; - } + dispatch_async(dispatch_get_main_queue(), ^{ + if (!_loading) { + // If the bridge has not loaded yet, the context will be already invalid at + // the time the javascript gets executed. + // It will crash the javascript, and even the next `load` won't render. + [self invalidate]; + [self setUp]; + } + }); } + (void)logMessage:(NSString *)message level:(NSString *)level diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index c8fa41c44..12f7803ea 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -91,18 +91,32 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); /** * The queue that will be used to call all exported methods. If omitted, this - * will default the main queue, which is recommended for any methods that - * interact with UIKit. If your methods perform heavy work such as filesystem - * or network access, you should return a custom serial queue. Example: + * will call on the default background queue, which is avoids blocking the main + * thread. + * + * If the methods in your module need to interact with UIKit methods, they will + * probably need to call those on the main thread, as most of UIKit is main- + * thread-only. You can tell React Native to call your module methods on the + * main thread by returning a reference to the main queue, like this: + * + * - (dispatch_queue_t)methodQueue + * { + * return dispatch_get_main_queue(); + * } + * + * If your methods perform heavy work such as synchronous filesystem or network + * access, you probably don't want to block the default background queue, as + * this will stall other methods. Instead, you should return a custom serial + * queue, like this: * * - (dispatch_queue_t)methodQueue * { * return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL); * } * - * Alternatively, if only some methods on the module should be executed on a - * background queue you can leave this method unimplemented, and simply - * dispatch_async() within the method itself. + * Alternatively, if only some methods of the module should be executed on a + * particular queue you can leave this method unimplemented, and simply + * dispatch_async() to the required queue within the method itself. */ - (dispatch_queue_t)methodQueue; diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index b97364e38..2690de1df 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -35,6 +35,11 @@ RCT_EXPORT_MODULE() return self; } +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + /** * @param {NSDictionary} args Dictionary of the form * diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 149ad568e..04bb39038 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -26,6 +26,11 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() RCT_EXPORT_MODULE() +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated) { diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 1f6e84d6a..62d42a7bb 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -108,6 +108,11 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + - (BOOL)isValid { return _bridge != nil; From 0e8bc08d3fcaa20543a4c77ec90615ae18a7a6ab Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 20 Apr 2015 12:03:58 -0700 Subject: [PATCH 26/92] [ReactNative] Update method name on chrome debugger --- packager/debugger.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/debugger.html b/packager/debugger.html index d0d4aba54..6e002a225 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -47,7 +47,7 @@ var messageHandlers = { } loadScript(message.url, sendReply.bind(null, null)); }, - 'executeJSCall:method:arguments:callback:': function(message, sendReply) { + 'executeJSCall:method:arguments:context:callback:': function(message, sendReply) { var returnValue = [[], [], [], [], []]; try { if (window && window.require) { From 2434512847516e149b80b6a32bef7f4f7794782c Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 20 Apr 2015 14:04:53 -0700 Subject: [PATCH 27/92] [ReactNative] Allow JS know its URL --- React/Base/RCTBridge.h | 5 +++++ React/Modules/RCTSourceCode.m | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index c0f3a1a91..0a82b05e2 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -88,6 +88,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method; url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete; +/** + * URL of the script that was loaded into the bridge. + */ +@property (nonatomic, copy, readonly) NSURL *bundleURL; + @property (nonatomic, strong) Class executorClass; /** diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m index e29a05637..1b6eb842e 100644 --- a/React/Modules/RCTSourceCode.m +++ b/React/Modules/RCTSourceCode.m @@ -10,12 +10,15 @@ #import "RCTSourceCode.h" #import "RCTAssert.h" +#import "RCTBridge.h" #import "RCTUtils.h" @implementation RCTSourceCode RCT_EXPORT_MODULE() +@synthesize bridge = _bridge; + RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback failureCallback:(RCTResponseSenderBlock)failureCallback) { @@ -26,4 +29,10 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback } } +- (NSDictionary *)constantsToExport +{ + NSString *URL = [self.bridge.bundleURL absoluteString] ?: @""; + return @{@"scriptURL": URL}; +} + @end From a8a179844924d689aef16e9ed9742c236627689b Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 20 Apr 2015 14:35:45 -0700 Subject: [PATCH 28/92] [ReactNative] Fix Chrome debugger --- Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m | 4 ++-- packager/debugger.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index 7fd817d53..4bdfab1bd 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -136,7 +136,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); - (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete { - NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects}; + NSDictionary *message = @{@"method": @"executeApplicationScript", @"url": [URL absoluteString], @"inject": _injectedObjects}; [self sendMessage:message context:nil waitForReply:^(NSError *error, NSDictionary *reply) { onComplete(error); }]; @@ -146,7 +146,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); { RCTAssert(onComplete != nil, @"callback was missing for exec JS call"); NSDictionary *message = @{ - @"method": NSStringFromSelector(_cmd), + @"method": @"executeJSCall", @"moduleName": name, @"moduleMethod": method, @"arguments": arguments diff --git a/packager/debugger.html b/packager/debugger.html index 6e002a225..d72e40ead 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -41,13 +41,13 @@ var messageHandlers = { window.localStorage.setItem('sessionID', message.id); window.location.reload(); }, - 'executeApplicationScript:sourceURL:onComplete:': function(message, sendReply) { + 'executeApplicationScript': function(message, sendReply) { for (var key in message.inject) { window[key] = JSON.parse(message.inject[key]); } loadScript(message.url, sendReply.bind(null, null)); }, - 'executeJSCall:method:arguments:context:callback:': function(message, sendReply) { + 'executeJSCall': function(message, sendReply) { var returnValue = [[], [], [], [], []]; try { if (window && window.require) { From 82704adeadd15fcbe595f502541929641e87e100 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 20 Apr 2015 15:54:24 -0700 Subject: [PATCH 29/92] [react-packager] Implement Packager::getAssets --- .../react-packager/src/Packager/Package.js | 11 +++++++ .../src/Packager/__tests__/Package-test.js | 12 +++++++ .../src/Packager/__tests__/Packager-test.js | 8 +++++ packager/react-packager/src/Packager/index.js | 33 ++++++++++--------- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/packager/react-packager/src/Packager/Package.js b/packager/react-packager/src/Packager/Package.js index 0f55c8edc..67e31e47e 100644 --- a/packager/react-packager/src/Packager/Package.js +++ b/packager/react-packager/src/Packager/Package.js @@ -17,6 +17,7 @@ module.exports = Package; function Package(sourceMapUrl) { this._finalized = false; this._modules = []; + this._assets = []; this._sourceMapUrl = sourceMapUrl; } @@ -36,6 +37,10 @@ Package.prototype.addModule = function( }); }; +Package.prototype.addAsset = function(asset) { + this._assets.push(asset); +}; + Package.prototype.finalize = function(options) { options = options || {}; if (options.runMainModule) { @@ -49,6 +54,8 @@ Package.prototype.finalize = function(options) { Object.freeze(this._modules); Object.seal(this._modules); + Object.freeze(this._assets); + Object.seal(this._assets); this._finalized = true; }; @@ -146,6 +153,10 @@ Package.prototype.getSourceMap = function(options) { return map; }; +Package.prototype.getAssets = function() { + return this._assets; +}; + Package.prototype._getMappings = function() { var modules = this._modules; diff --git a/packager/react-packager/src/Packager/__tests__/Package-test.js b/packager/react-packager/src/Packager/__tests__/Package-test.js index 5a7438d27..db596a7bc 100644 --- a/packager/react-packager/src/Packager/__tests__/Package-test.js +++ b/packager/react-packager/src/Packager/__tests__/Package-test.js @@ -76,6 +76,18 @@ describe('Package', function() { expect(s).toEqual(genSourceMap(p._modules)); }); }); + + describe('getAssets()', function() { + it('should save and return asset objects', function() { + var p = new Package('test_url'); + var asset1 = {}; + var asset2 = {}; + p.addAsset(asset1); + p.addAsset(asset2); + p.finalize(); + expect(p.getAssets()).toEqual([asset1, asset2]); + }); + }); }); function genSourceMap(modules) { diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 333e0f563..c17732165 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -140,6 +140,14 @@ describe('Packager', function() { expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); + + expect(p.addAsset.mock.calls[0]).toEqual([ + imgModule_DEPRECATED + ]); + + expect(p.addAsset.mock.calls[1]).toEqual([ + imgModule + ]); }); }); diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 2b1eb6b16..aab55c084 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -100,9 +100,9 @@ Packager.prototype.kill = function() { }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { - var transformModule = this._transformModule.bind(this); var ppackage = new Package(sourceMapUrl); + var transformModule = this._transformModule.bind(this, ppackage); var findEventId = Activity.startEvent('find dependencies'); var transformEventId; @@ -140,16 +140,13 @@ Packager.prototype.getDependencies = function(main, isDev) { return this._resolver.getDependencies(main, { dev: isDev }); }; -Packager.prototype._transformModule = function(module) { +Packager.prototype._transformModule = function(ppackage, module) { var transform; if (module.isAsset_DEPRECATED) { - transform = generateAssetModule_DEPRECATED(module); + transform = this.generateAssetModule_DEPRECATED(ppackage, module); } else if (module.isAsset) { - transform = generateAssetModule( - module, - getPathRelativeToRoot(this._projectRoots, module.path) - ); + transform = this.generateAssetModule(ppackage, module); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -166,17 +163,11 @@ Packager.prototype._transformModule = function(module) { }); }; - -function verifyRootExists(root) { - // Verify that the root exists. - assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); -} - Packager.prototype.getGraphDebugInfo = function() { return this._resolver.getDebugInfo(); }; -function generateAssetModule_DEPRECATED(module) { +Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { return sizeOf(module.path).then(function(dimensions) { var img = { isStatic: true, @@ -187,6 +178,7 @@ function generateAssetModule_DEPRECATED(module) { deprecated: true, }; + ppackage.addAsset(img); var code = 'module.exports = ' + JSON.stringify(img) + ';'; @@ -196,9 +188,11 @@ function generateAssetModule_DEPRECATED(module) { sourcePath: module.path, }; }); -} +}; + +Packager.prototype.generateAssetModule = function(ppackage, module) { + var relPath = getPathRelativeToRoot(this._projectRoots, module.path); -function generateAssetModule(module, relPath) { return sizeOf(module.path).then(function(dimensions) { var img = { isStatic: true, @@ -208,6 +202,8 @@ function generateAssetModule(module, relPath) { height: dimensions.height / module.resolution, }; + ppackage.addAsset(img); + var code = 'module.exports = ' + JSON.stringify(img) + ';'; return { @@ -231,4 +227,9 @@ function getPathRelativeToRoot(roots, absPath) { ); } +function verifyRootExists(root) { + // Verify that the root exists. + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); +} + module.exports = Packager; From 765779a4bd16144924e0d03aa726401e2ea4a63e Mon Sep 17 00:00:00 2001 From: Lochlan Wansbrough Date: Mon, 20 Apr 2015 18:01:46 -0700 Subject: [PATCH 30/92] Adds `opaque` and `underlayColor` to WebView. Summary: Enables overwriting of underlying colors for WebViews. Especially useful if you want to give your WebView a transparent background. Closes https://github.com/facebook/react-native/pull/767 Github Author: Lochlan Wansbrough Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/WebView/WebView.ios.js | 11 ++++++++++- Libraries/RCTWebSocketDebugger/SRWebSocket.m | 2 +- React/Views/RCTWebView.m | 13 +++++++++++++ React/Views/RCTWebViewManager.m | 2 ++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 83c90a1fd..6257c12b7 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -87,6 +87,8 @@ var WebView = React.createClass({ html: PropTypes.string, renderError: PropTypes.func, // view to show if there's an error renderLoading: PropTypes.func, // loading indicator to show + bounces: PropTypes.bool, + scrollEnabled: PropTypes.bool, automaticallyAdjustContentInsets: PropTypes.bool, shouldInjectAJAXHandler: PropTypes.bool, contentInset: EdgeInsetsPropType, @@ -131,7 +133,7 @@ var WebView = React.createClass({ ); } - var webViewStyles = [styles.container, this.props.style]; + var webViewStyles = [styles.container, styles.webView, this.props.style]; if (this.state.viewState === WebViewState.LOADING || this.state.viewState === WebViewState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView @@ -145,6 +147,8 @@ var WebView = React.createClass({ style={webViewStyles} url={this.props.url} html={this.props.html} + bounces={this.props.bounces} + scrollEnabled={this.props.scrollEnabled} shouldInjectAJAXHandler={this.props.shouldInjectAJAXHandler} contentInset={this.props.contentInset} automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets} @@ -213,6 +217,8 @@ var RCTWebView = createReactIOSNativeComponentClass({ validAttributes: merge(ReactIOSViewAttributes.UIView, { url: true, html: true, + bounces: true, + scrollEnabled: true, contentInset: {diff: insetsDiffer}, automaticallyAdjustContentInsets: true, shouldInjectAJAXHandler: true @@ -250,6 +256,9 @@ var styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + webView: { + backgroundColor: '#ffffff', + } }); module.exports = WebView; diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.m b/Libraries/RCTWebSocketDebugger/SRWebSocket.m index 3fd675103..589ab75dd 100644 --- a/Libraries/RCTWebSocketDebugger/SRWebSocket.m +++ b/Libraries/RCTWebSocketDebugger/SRWebSocket.m @@ -1702,7 +1702,7 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { for (int i = 0; i < maxCodepointSize; i++) { NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; if (str) { - return data.length - i; + return (int32_t)(data.length - i); } } diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index bb9bb2acf..56cda0c88 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -31,6 +31,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { + super.backgroundColor = [UIColor clearColor]; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; _eventDispatcher = eventDispatcher; @@ -95,6 +96,18 @@ updateOffset:NO]; } +- (void)setBackgroundColor:(UIColor *)backgroundColor +{ + CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); + self.opaque = _webView.opaque = (alpha == 1.0); + _webView.backgroundColor = backgroundColor; +} + +- (UIColor *)backgroundColor +{ + return _webView.backgroundColor; +} + - (NSMutableDictionary *)baseEvent { NSURL *url = _webView.request.URL; diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index e25a7da68..015285871 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -25,6 +25,8 @@ RCT_EXPORT_MODULE() RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL); RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString); +RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL); +RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL); RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); From b0348edcaee00241e5a624bfc8d49ae67abc1923 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Tue, 21 Apr 2015 04:14:17 -0700 Subject: [PATCH 31/92] [react_native] JS files from D2001635: [react_native] Use hardware layers during adsmanager Navigator navigation --- .../CustomComponents/Navigator/Navigator.js | 31 +++++++++++++++++++ .../NavigatorBreadcrumbNavigationBar.js | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index f783c34ea..7a030b455 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -494,6 +494,7 @@ var Navigator = React.createClass({ _completeTransition: function() { if (this.spring.getCurrentValue() === 1) { + this._onAnimationEnd(); var presentedIndex = this.state.toIndex; this.state.presentedIndex = presentedIndex; this.state.fromIndex = presentedIndex; @@ -515,6 +516,7 @@ var Navigator = React.createClass({ // For visual consistency, the from index is always used to configure the spring this.state.sceneConfigStack[this.state.fromIndex] ); + this._onAnimationStart(); this.state.isAnimating = true; this.spring.setVelocity(v); this.spring.setEndValue(1); @@ -573,6 +575,34 @@ var Navigator = React.createClass({ } }, + _onAnimationStart: function() { + this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, true); + this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, true); + + var navBar = this._navBar; + if (navBar && navBar.onAnimationStart) { + navBar.onAnimationStart(this.state.fromIndex, this.state.toIndex); + } + }, + + _onAnimationEnd: function() { + this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, false); + this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, false); + + var navBar = this._navBar; + if (navBar && navBar.onAnimationEnd) { + navBar.onAnimationEnd(this.state.fromIndex, this.state.toIndex); + } + }, + + _setRenderSceneToHarwareTextureAndroid: function(sceneIndex, shouldRenderToHardwareTexture) { + var viewAtIndex = this.refs['scene_' + sceneIndex]; + if (viewAtIndex === null || viewAtIndex === undefined) { + return; + } + viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture}); + }, + /** * Becomes the responder on touch start (capture) while animating so that it * blocks all touch interactions inside of it. However, this responder lock @@ -610,6 +640,7 @@ var Navigator = React.createClass({ this.state.fromIndex = this.state.presentedIndex; var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction); this.state.toIndex = this.state.presentedIndex + gestureSceneDelta; + this.onAnimationStart(); } }, diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index 9fb265f97..fa62b3f45 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -138,6 +138,37 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ } }, + onAnimationStart: function(fromIndex, toIndex) { + var max = Math.max(fromIndex, toIndex); + var min = Math.min(fromIndex, toIndex); + for (var index = min; index <= max; index++) { + this._setRenderViewsToHardwareTextureAndroid(index, true); + } + }, + + onAnimationEnd: function(fromIndex, toIndex) { + var max = Math.max(fromIndex, toIndex); + var min = Math.min(fromIndex, toIndex); + for (var index = min; index <= max; index++) { + this._setRenderViewsToHardwareTextureAndroid(index, false); + } + }, + + _setRenderViewsToHardwareTextureAndroid: function(index, renderToHardwareTexture) { + var props = { + renderToHardwareTextureAndroid: renderToHardwareTexture, + }; + + this.refs['crumb_' + index].setNativeProps(props); + this.refs['icon_' + index].setNativeProps(props); + this.refs['separator_' + index].setNativeProps(props); + this.refs['title_' + index].setNativeProps(props); + var right = this.refs['right_' + index]; + if (right) { + right.setNativeProps(props); + } + }, + render: function() { var navState = this.props.navState; var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb); From 8e15a0d5e716ca63d0610e4b947ff2ab784e3ca2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 21 Apr 2015 05:26:51 -0700 Subject: [PATCH 32/92] Added RCT_DEBUG --- React/Base/RCTAssert.h | 31 +++--------- React/Base/RCTBridge.h | 3 +- React/Base/RCTBridge.m | 73 ++++++++++++++-------------- React/Base/RCTConvert.h | 15 ++---- React/Base/RCTDefines.h | 55 +++++++++++++++++++++ React/Base/RCTLog.h | 27 ++++------ React/Base/RCTLog.m | 30 +++++------- React/Base/RCTProfile.h | 20 ++++---- React/Base/RCTProfile.m | 3 +- React/Base/RCTRedBox.m | 23 +++------ React/Base/RCTUtils.h | 39 ++++++--------- React/Executors/RCTContextExecutor.m | 8 +-- React/Modules/RCTExceptionsManager.m | 23 +++------ React/Modules/RCTUIManager.m | 23 ++++----- 14 files changed, 184 insertions(+), 189 deletions(-) create mode 100644 React/Base/RCTDefines.h diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 7e73aed7d..b0a3c5c52 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -9,21 +9,7 @@ #import -#ifdef __cplusplus -extern "C" { -#endif - -/** - * By default, only raise an NSAssertion in debug mode - * (custom assert functions will still be called). - */ -#ifndef RCT_ASSERT -#if DEBUG -#define RCT_ASSERT 1 -#else -#define RCT_ASSERT 0 -#endif -#endif +#import "RCTDefines.h" /** * The default error domain to be used for React errors. @@ -44,13 +30,14 @@ typedef void (^RCTAssertFunction)( /** * Private logging function - ignore this. */ -void _RCTAssertFormat(BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6); +RCT_EXTERN void _RCTAssertFormat( + BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6); /** * This is the main assert macro that you should use. */ #define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \ -if (RCT_ASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \ +if (RCT_NSASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \ file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \ _RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \ } while (false) @@ -66,16 +53,12 @@ _RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \ * macros. You can use these to replace the standard behavior with custom log * functionality. */ -void RCTSetAssertFunction(RCTAssertFunction assertFunction); -RCTAssertFunction RCTGetAssertFunction(void); +RCT_EXTERN void RCTSetAssertFunction(RCTAssertFunction assertFunction); +RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void); /** * This appends additional code to the existing assert function, without * replacing the existing functionality. Useful if you just want to forward * assert info to an extra service without changing the default behavior. */ -void RCTAddAssertFunction(RCTAssertFunction assertFunction); - -#ifdef __cplusplus -} -#endif +RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction); diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 0a82b05e2..a37b9ac7d 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -10,6 +10,7 @@ #import #import "RCTBridgeModule.h" +#import "RCTDefines.h" #import "RCTFrameUpdate.h" #import "RCTInvalidating.h" #import "RCTJavaScriptExecutor.h" @@ -40,7 +41,7 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void); /** * This function returns the module name for a given class. */ -extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); +RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); /** * Async batched bridge used to communicate with the JavaScript application. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 31a8bea77..40dfceec8 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -143,7 +143,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) // Get data entry NSString *entry = @(*(const char **)(mach_header + addr)); - NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] componentsSeparatedByString:@" "]; + NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] + componentsSeparatedByString:@" "]; // Parse class name NSString *moduleClassName = parts[0]; @@ -164,33 +165,32 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) } } -#if DEBUG + if (RCT_DEBUG) { - // We may be able to get rid of this check in future, once people - // get used to the new registration system. That would potentially - // allow you to create modules that are not automatically registered + // We may be able to get rid of this check in future, once people + // get used to the new registration system. That would potentially + // allow you to create modules that are not automatically registered - static unsigned int classCount; - Class *classes = objc_copyClassList(&classCount); - for (unsigned int i = 0; i < classCount; i++) - { - Class cls = classes[i]; - Class superclass = cls; - while (superclass) + static unsigned int classCount; + Class *classes = objc_copyClassList(&classCount); + for (unsigned int i = 0; i < classCount; i++) { - if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule))) + Class cls = classes[i]; + Class superclass = cls; + while (superclass) { - if (![RCTModuleClassesByID containsObject:cls]) { - RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls)); + if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule))) + { + if (![RCTModuleClassesByID containsObject:cls]) { + RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls)); + } + break; } - break; + superclass = class_getSuperclass(superclass); } - superclass = class_getSuperclass(superclass); } } -#endif - }); return RCTModuleClassesByID; @@ -289,13 +289,13 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) _isClassMethod = [reactMethodName characterAtIndex:0] == '+'; _moduleClass = NSClassFromString(_moduleClassName); -#if DEBUG + if (RCT_DEBUG) { - // Sanity check - RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], - @"You are attempting to export the method %@, but %@ does not \ - conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName); -#endif + // Sanity check + RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], + @"You are attempting to export the method %@, but %@ does not \ + conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName); + } // Get method signature _methodSignature = _isClassMethod ? @@ -449,20 +449,19 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) arguments:(NSArray *)arguments context:(NSNumber *)context { + if (RCT_DEBUG) { -#if DEBUG + // Sanity check + RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ + %@ on a module of class %@", _methodName, [module class]); - // Sanity check - RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ - %@ on a module of class %@", _methodName, [module class]); -#endif - - // Safety check - if (arguments.count != _argumentBlocks.count) { - RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", - RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, - arguments.count, _argumentBlocks.count); - return; + // Safety check + if (arguments.count != _argumentBlocks.count) { + RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", + RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, + arguments.count, _argumentBlocks.count); + return; + } } // Create invocation (we can't re-use this as it wouldn't be thread-safe) diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 22cc7ec81..e664f06e0 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -16,6 +16,7 @@ #import "../Views/RCTAnimationType.h" #import "../Views/RCTPointerEvents.h" +#import "RCTDefines.h" #import "RCTLog.h" /** @@ -116,33 +117,25 @@ typedef BOOL css_overflow; @end -#ifdef __cplusplus -extern "C" { -#endif - /** * This function will attempt to set a property using a json value by first * inferring the correct type from all available information, and then * applying an appropriate conversion method. If the property does not * exist, or the type cannot be inferred, the function will return NO. */ -BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json); +RCT_EXTERN BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json); /** * This function attempts to copy a property from the source object to the * destination object using KVC. If the property does not exist, or cannot * be set, it will do nothing and return NO. */ -BOOL RCTCopyProperty(id target, id source, NSString *keyPath); +RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath); /** * Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this. */ -NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); - -#ifdef __cplusplus -} -#endif +RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); /** * This macro is used for creating simple converter functions that just call diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h new file mode 100644 index 000000000..71550a30d --- /dev/null +++ b/React/Base/RCTDefines.h @@ -0,0 +1,55 @@ +/** + * 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 + +/** + * Make global functions usable in C++ + */ +#if defined(__cplusplus) +#define RCT_EXTERN extern "C" __attribute__((visibility("default"))) +#else +#define RCT_EXTERN extern __attribute__((visibility("default"))) +#endif + +/** + * The RCT_DEBUG macro can be used to exclude error checking and logging code + * from release builds to improve performance and reduce binary size. + */ +#ifndef RCT_DEBUG +#if DEBUG +#define RCT_DEBUG 1 +#else +#define RCT_DEBUG 0 +#endif +#endif + +/** + * The RCT_DEV macro can be used to enable or disable development tools + * such as the debug executors, dev menu, red box, etc. + */ +#ifndef RCT_DEV +#if DEBUG +#define RCT_DEV 1 +#else +#define RCT_DEV 0 +#endif +#endif + +/** + * By default, only raise an NSAssertion in debug mode + * (custom assert functions will still be called). + */ +#ifndef RCT_NSASSERT +#if RCT_DEBUG +#define RCT_NSASSERT 1 +#else +#define RCT_NSASSERT 0 +#endif +#endif diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h index 3a8a70d5b..7fa25e6a8 100644 --- a/React/Base/RCTLog.h +++ b/React/Base/RCTLog.h @@ -10,10 +10,7 @@ #import #import "RCTAssert.h" - -#ifdef __cplusplus -extern "C" { -#endif +#import "RCTDefines.h" /** * Thresholds for logs to raise an assertion, or display redbox, respectively. @@ -46,9 +43,9 @@ typedef void (^RCTLogFunction)( ); /** - * Get a given thread's name (or the current queue, iff in debug mode) + * Get a given thread's name (or the current queue, if in debug mode) */ -NSString *RCTThreadName(NSThread *); +RCT_EXTERN NSString *RCTThreadName(NSThread *); /** * A method to generate a string from a collection of log data. To omit any @@ -73,35 +70,35 @@ extern RCTLogFunction RCTDefaultLogFunction; * below which logs will be ignored. Default is RCTLogLevelInfo for debug and * RCTLogLevelError for production. */ -void RCTSetLogThreshold(RCTLogLevel threshold); -RCTLogLevel RCTGetLogThreshold(void); +RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold); +RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void); /** * These methods get and set the current logging function called by the RCTLogXX * macros. You can use these to replace the standard behavior with custom log * functionality. */ -void RCTSetLogFunction(RCTLogFunction logFunction); -RCTLogFunction RCTGetLogFunction(void); +RCT_EXTERN void RCTSetLogFunction(RCTLogFunction logFunction); +RCT_EXTERN RCTLogFunction RCTGetLogFunction(void); /** * This appends additional code to the existing log function, without replacing * the existing functionality. Useful if you just want to forward logs to an * extra service without changing the default behavior. */ -void RCTAddLogFunction(RCTLogFunction logFunction); +RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction); /** * This method adds a conditional prefix to any messages logged within the scope * of the passed block. This is useful for adding additional context to log * messages. The block will be performed synchronously on the current thread. */ -void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix); +RCT_EXTERN void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix); /** * Private logging functions - ignore these. */ -void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5); +RCT_EXTERN void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5); #define _RCTLog(lvl, ...) do { \ if (lvl >= RCTLOG_FATAL_LEVEL) { RCTAssert(NO, __VA_ARGS__); } \ _RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \ @@ -116,7 +113,3 @@ void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FU #define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__) #define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__) #define RCTLogMustFix(...) _RCTLog(RCTLogLevelMustFix, __VA_ARGS__) - -#ifdef __cplusplus -} -#endif diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 449980fc3..6ca2d4eb8 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -11,6 +11,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTDefines.h" #import "RCTRedBox.h" @interface RCTBridge (Logging) @@ -36,7 +37,7 @@ static void RCTLogSetup() { RCTCurrentLogFunction = RCTDefaultLogFunction; -#if DEBUG +#if RCT_DEBUG RCTCurrentLogThreshold = RCTLogLevelInfo - 1; #else RCTCurrentLogThreshold = RCTLogLevelError; @@ -102,7 +103,7 @@ NSString *RCTThreadName(NSThread *thread) { NSString *threadName = [thread isMainThread] ? @"main" : thread.name; if (threadName.length == 0) { -#if DEBUG +#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" threadName = @(dispatch_queue_get_label(dispatch_get_current_queue())); @@ -161,12 +162,7 @@ void _RCTLogFormat( NSString *format, ...) { -#if DEBUG - BOOL log = YES; -#else - BOOL log = (RCTCurrentLogFunction != nil); -#endif - + BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil); if (log && level >= RCTCurrentLogThreshold) { // Get message @@ -188,17 +184,15 @@ void _RCTLogFormat( level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message ); -#if DEBUG + if (RCT_DEBUG) { - // Log to red box - if (level >= RCTLOG_REDBOX_LEVEL) { - [[RCTRedBox sharedInstance] showErrorMessage:message]; + // Log to red box + if (level >= RCTLOG_REDBOX_LEVEL) { + [[RCTRedBox sharedInstance] showErrorMessage:message]; + } + + // Log to JS executor + [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; } - - // Log to JS executor - [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; - -#endif - } } diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index b3a1683d1..0c254c80a 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -9,45 +9,47 @@ #import +#import "RCTDefines.h" + /** * RCTProfile * * This file provides a set of functions and macros for performance profiling * - * NOTE: This API is a work in a work in progress, please consider it before - * before using. + * NOTE: This API is a work in a work in progress, please consider carefully + * before before using it. */ -#if DEBUG +#if RCT_DEV /** * Returns YES if the profiling information is currently being collected */ -BOOL RCTProfileIsProfiling(void); +RCT_EXTERN BOOL RCTProfileIsProfiling(void); /** * Start collecting profiling information */ -void RCTProfileInit(void); +RCT_EXTERN void RCTProfileInit(void); /** * Stop profiling and return a JSON string of the collected data - The data * returned is compliant with google's trace event format - the format used * as input to trace-viewer */ -NSString *RCTProfileEnd(void); +RCT_EXTERN NSString *RCTProfileEnd(void); /** * Collects the initial event information for the event and returns a reference ID */ -NSNumber *_RCTProfileBeginEvent(void); +RCT_EXTERN NSNumber *_RCTProfileBeginEvent(void); /** * The ID returned by BeginEvent should then be passed into EndEvent, with the * rest of the event information. Just at this point the event will actually be * registered */ -void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id); +RCT_EXTERN void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id); /** * This pair of macros implicitly handle the event ID when beginning and ending @@ -69,7 +71,7 @@ _RCTProfileEndEvent(__rct_profile_id, name, category, args) /** * An event that doesn't have a duration (i.e. Notification, VSync, etc) */ -void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *); +RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *); /** * Helper to profile the duration of the execution of a block. This method uses diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 71d34551e..929a026a9 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -13,10 +13,11 @@ #import +#import "RCTDefines.h" #import "RCTLog.h" #import "RCTUtils.h" -#if DEBUG +#if RCT_DEV #pragma mark - Prototypes diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 3bed31505..882a87bfe 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -282,23 +282,14 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow { - -#if DEBUG - - dispatch_block_t block = ^{ - if (!_window) { - _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - } - [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; - }; - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), block); + if (RCT_DEBUG) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (!_window) { + _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + } + [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; + }); } - -#endif - } - (NSString *)currentErrorMessage diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index d20ba8a5f..812a65122 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -13,43 +13,36 @@ #import #import "RCTAssert.h" - -#ifdef __cplusplus -extern "C" { -#endif +#import "RCTDefines.h" // Utility functions for JSON object <-> string serialization/deserialization -NSString *RCTJSONStringify(id jsonObject, NSError **error); -id RCTJSONParse(NSString *jsonString, NSError **error); +RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); +RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); // Get MD5 hash of a string (TODO: currently unused. Remove?) -NSString *RCTMD5Hash(NSString *string); +RCT_EXTERN NSString *RCTMD5Hash(NSString *string); // Get screen metrics in a thread-safe way -CGFloat RCTScreenScale(void); -CGSize RCTScreenSize(void); +RCT_EXTERN CGFloat RCTScreenScale(void); +RCT_EXTERN CGSize RCTScreenSize(void); // Round float coordinates to nearest whole screen pixel (not point) -CGFloat RCTRoundPixelValue(CGFloat value); -CGFloat RCTCeilPixelValue(CGFloat value); -CGFloat RCTFloorPixelValue(CGFloat value); +RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value); +RCT_EXTERN CGFloat RCTCeilPixelValue(CGFloat value); +RCT_EXTERN CGFloat RCTFloorPixelValue(CGFloat value); // Get current time, for precise performance metrics -NSTimeInterval RCTTGetAbsoluteTime(void); +RCT_EXTERN NSTimeInterval RCTTGetAbsoluteTime(void); // Method swizzling -void RCTSwapClassMethods(Class cls, SEL original, SEL replacement); -void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement); +RCT_EXTERN void RCTSwapClassMethods(Class cls, SEL original, SEL replacement); +RCT_EXTERN void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement); // Module subclass support -BOOL RCTClassOverridesClassMethod(Class cls, SEL selector); -BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector); +RCT_EXTERN BOOL RCTClassOverridesClassMethod(Class cls, SEL selector); +RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector); // Creates a standardized error object // TODO(#6472857): create NSErrors and automatically convert them over the bridge. -NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData); -NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData); - -#ifdef __cplusplus -} -#endif +RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData); +RCT_EXTERN NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData); diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index d2aac3fbe..86444dd2a 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -14,6 +14,7 @@ #import #import "RCTAssert.h" +#import "RCTDefines.h" #import "RCTLog.h" #import "RCTProfile.h" #import "RCTUtils.h" @@ -321,10 +322,9 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { - -#if DEBUG - RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script); -#endif + if (RCT_DEBUG) { + RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script); + } __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index ed30d8741..ece5d0668 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -9,6 +9,7 @@ #import "RCTExceptionsManager.h" +#import "RCTDefines.h" #import "RCTLog.h" #import "RCTRedBox.h" #import "RCTRootView.h" @@ -43,11 +44,10 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message return; } -#if DEBUG - - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; - -#else + if (RCT_DEBUG) { + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + return; + } static NSUInteger reloadRetries = 0; const NSUInteger maxMessageLength = 75; @@ -76,21 +76,14 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage]; [NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack]; } - -#endif - } RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message stack:(NSArray *)stack) { - -#if DEBUG - - [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; - -#endif - + if (RCT_DEBUG) { + [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; + } } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index e2dc8d560..3f3ea7596 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -18,6 +18,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTConvert.h" +#import "RCTDefines.h" #import "RCTLog.h" #import "RCTProfile.h" #import "RCTRootView.h" @@ -703,20 +704,16 @@ static BOOL RCTCallPropertySetter(NSString *key, SEL setter, id value, id view, ((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView); }; -#if DEBUG + if (RCT_DEBUG) { + NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([manager class])); + NSString *logPrefix = [NSString stringWithFormat: + @"Error setting property '%@' of %@ with tag #%@: ", + key, viewName, [view reactTag]]; - NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([manager class])); - NSString *logPrefix = [NSString stringWithFormat: - @"Error setting property '%@' of %@ with tag #%@: ", - key, viewName, [view reactTag]]; - - RCTPerformBlockWithLogPrefix(block, logPrefix); - -#else - - block(); - -#endif + RCTPerformBlockWithLogPrefix(block, logPrefix); + } else { + block(); + } return YES; } From a0db658982f0b9e8a42fb17bfbbf32da5d876572 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Tue, 21 Apr 2015 09:01:09 -0700 Subject: [PATCH 33/92] [MAdMan] flowify AdsManagerGeoUtils --- Libraries/Components/MapView/MapView.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 7beeabbea..2f02b1b9d 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -23,6 +23,12 @@ var insetsDiffer = require('insetsDiffer'); var merge = require('merge'); type Event = Object; +type MapRegion = { + latitude: number; + longitude: number; + latitudeDelta: number; + longitudeDelta: number; +}; var MapView = React.createClass({ mixins: [NativeMethodsMixin], From 25ae5485da2236e2ace73b7e9d27fb41c3f62ead Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Tue, 21 Apr 2015 09:18:51 -0700 Subject: [PATCH 34/92] [ReactNative][MAdMan] helper for GraphQL-safe pixel size calculation --- Libraries/Utilities/PixelRatio.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js index f8e23398a..e6c96b7fa 100644 --- a/Libraries/Utilities/PixelRatio.js +++ b/Libraries/Utilities/PixelRatio.js @@ -36,8 +36,8 @@ var Dimensions = require('Dimensions'); * * ``` * var image = getImage({ - * width: 200 * PixelRatio.get(), - * height: 100 * PixelRatio.get() + * width: PixelRatio.getPixelSizeForLayoutSize(200), + * height: PixelRatio.getPixelSizeForLayoutSize(100), * }); * * ``` @@ -52,10 +52,21 @@ class PixelRatio { * - iPhone 6 * - PixelRatio.get() === 3 * - iPhone 6 plus + * - PixelRatio.get() === 3.5 + * - Nexus 6 */ static get(): number { return Dimensions.get('window').scale; } + + /** + * Converts a layout size (dp) to pixel size (px). + * + * Guaranteed to return an integer number. + */ + static getPixelSizeForLayoutSize(layoutSize: number): number { + return Math.round(layoutSize * PixelRatio.get()); + } } // No-op for iOS, but used on the web. Should not be documented. From ee898c24c79c40653cbba7d2789e7c88ef73cde4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 21 Apr 2015 09:48:29 -0700 Subject: [PATCH 35/92] Removed debug code from release builds --- .../project.pbxproj | 12 +- .../RCTWebSocketExecutor.h | 6 + .../RCTWebSocketExecutor.m | 6 + Libraries/RCTWebSocketDebugger/SRWebSocket.m | 2 + React/Base/RCTBridge.m | 55 +++++-- React/Base/RCTConvert.h | 40 +++-- React/Base/RCTConvert.m | 137 +++++++++++------- React/Base/RCTLog.h | 2 +- React/Base/RCTLog.m | 6 +- React/Base/RCTRedBox.h | 6 + React/Base/RCTRedBox.m | 20 ++- React/Executors/RCTWebViewExecutor.h | 6 + React/Executors/RCTWebViewExecutor.m | 13 +- React/Modules/RCTExceptionsManager.m | 20 ++- React/Modules/RCTUIManager.m | 10 +- React/React.xcodeproj/project.pbxproj | 2 + React/Views/RCTViewManager.h | 4 +- 17 files changed, 226 insertions(+), 121 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj index acb5daa3e..4a9b59916 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */; }; - 00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277181AB8C35800DC1E48 /* SRWebSocket.m */; }; + 00D277191AB8C35800DC1E48 /* RCT_SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -26,8 +26,8 @@ /* Begin PBXFileReference section */ 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = ""; }; 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = ""; }; - 00D277171AB8C35800DC1E48 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; - 00D277181AB8C35800DC1E48 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; + 00D277171AB8C35800DC1E48 /* RCT_SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCT_SRWebSocket.h; sourceTree = ""; }; + 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCT_SRWebSocket.m; sourceTree = ""; }; 832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocketDebugger.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -45,8 +45,8 @@ 832C81771AAF6DEF007FA2F7 = { isa = PBXGroup; children = ( - 00D277171AB8C35800DC1E48 /* SRWebSocket.h */, - 00D277181AB8C35800DC1E48 /* SRWebSocket.m */, + 00D277171AB8C35800DC1E48 /* RCT_SRWebSocket.h */, + 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */, 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */, 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */, 832C81811AAF6DEF007FA2F7 /* Products */, @@ -119,7 +119,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */, + 00D277191AB8C35800DC1E48 /* RCT_SRWebSocket.m in Sources */, 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h index 3fc062a37..9993cbc5a 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.h @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEV // Debug executors are only supported in dev mode + #import "RCTJavaScriptExecutor.h" @interface RCTWebSocketExecutor : NSObject @@ -14,3 +18,5 @@ - (instancetype)initWithURL:(NSURL *)URL; @end + +#endif diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index 4bdfab1bd..eb6428fc2 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEV // Debug executors are only supported in dev mode + #import "RCTWebSocketExecutor.h" #import "RCTLog.h" @@ -189,3 +193,5 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); } @end + +#endif diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.m b/Libraries/RCTWebSocketDebugger/SRWebSocket.m index 589ab75dd..e98e9e456 100644 --- a/Libraries/RCTWebSocketDebugger/SRWebSocket.m +++ b/Libraries/RCTWebSocketDebugger/SRWebSocket.m @@ -19,6 +19,8 @@ #import +#pragma clang diagnostic ignored "-Wshadow" + //NOTE: libicucore ins't actually needed for the socket to function //and by commenting this out, we avoid the need to import it into every app. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 40dfceec8..1c676640e 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -314,7 +314,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) void (^addBlockArgument)(void) = ^{ RCT_ARG_BLOCK( - if (json && ![json isKindOfClass:[NSNumber class]]) { + + if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); return; @@ -391,7 +392,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) #define RCT_CASE(_value, _class, _logic) \ case _value: { \ RCT_ARG_BLOCK( \ - if (json && ![json isKindOfClass:[_class class]]) { \ + if (RCT_DEBUG && json && ![json isKindOfClass:[_class class]]) { \ RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \ json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \ return; \ @@ -407,7 +408,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) #define RCT_SIMPLE_CASE(_value, _type, _selector) \ case _value: { \ RCT_ARG_BLOCK( \ - if (json && ![json respondsToSelector:@selector(_selector)]) { \ + if (RCT_DEBUG && json && ![json respondsToSelector:@selector(_selector)]) { \ RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \ index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \ return; \ @@ -888,6 +889,9 @@ static id _latestJSExecutor; [loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) { _loading = NO; if (error != nil) { + +#if RCT_DEBUG // Red box is only available in debug mode + NSArray *stack = [[error userInfo] objectForKey:@"stack"]; if (stack) { [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] @@ -896,7 +900,11 @@ static id _latestJSExecutor; [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]]; } + +#endif + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self]; } @@ -936,6 +944,9 @@ static id _latestJSExecutor; strongSelf.executorClass = Nil; [strongSelf reload]; }]; + +#if RCT_DEV // Debug executors are only available in dev mode + // reload in debug mode [commands registerKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand @@ -952,6 +963,7 @@ static id _latestJSExecutor; [strongSelf reload]; }]; #endif +#endif } @@ -1156,14 +1168,18 @@ static id _latestJSExecutor; return; } + NSArray *requestsArray = [RCTConvert NSArray:buffer]; + +#if RCT_DEBUG + if (![buffer isKindOfClass:[NSArray class]]) { RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class])); return; } - NSArray *requestsArray = (NSArray *)buffer; NSUInteger bufferRowCount = [requestsArray count]; NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1; + if (bufferRowCount != expectedFieldsCount) { RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount); return; @@ -1177,13 +1193,15 @@ static id _latestJSExecutor; } } +#endif + NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs]; NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs]; NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss]; NSUInteger numRequests = [moduleIDs count]; - BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count]; - if (!allSame) { + + if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) { RCTLogError(@"Invalid data message - all must be length: %zd", numRequests); return; } @@ -1217,22 +1235,25 @@ static id _latestJSExecutor; params:(NSArray *)params context:(NSNumber *)context { - if (![params isKindOfClass:[NSArray class]]) { + + if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) { RCTLogError(@"Invalid module/method/params tuple for request #%zd", i); return NO; } // Look up method NSArray *methods = RCTExportedMethodsByModuleID()[moduleID]; - if (methodID >= methods.count) { + + if (RCT_DEBUG && methodID >= methods.count) { RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]); return NO; } + RCTModuleMethod *method = methods[methodID]; // Look up module id module = self->_modulesByID[moduleID]; - if (!module) { + if (RCT_DEBUG && !module) { RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]); return NO; } @@ -1249,13 +1270,17 @@ static id _latestJSExecutor; return; } - @try { + if (!RCT_DEBUG) { [method invokeWithBridge:strongSelf module:module arguments:params context:context]; - } - @catch (NSException *exception) { - RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); - if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) { - @throw; + } else { + @try { + [method invokeWithBridge:strongSelf module:module arguments:params context:context]; + } + @catch (NSException *exception) { + RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception); + if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) { + @throw; + } } } diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index e664f06e0..59ba79d27 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -133,9 +133,11 @@ RCT_EXTERN BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json); RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath); /** - * Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this. + * Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these. */ RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id); +RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id); +RCT_EXTERN void RCTLogConvertError(id, const char *); /** * This macro is used for creating simple converter functions that just call @@ -150,18 +152,19 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter]) #define RCT_CUSTOM_CONVERTER(type, name, code) \ + (type)name:(id)json \ { \ - if (json == [NSNull null]) { \ - json = nil; \ - } \ - @try { \ + json = (json == (id)kCFNull) ? nil : json; \ + if (!RCT_DEBUG) { \ return code; \ + } else { \ + @try { \ + return code; \ + } \ + @catch (__unused NSException *e) { \ + RCTLogConvertError(json, #type); \ + json = nil; \ + return code; \ + } \ } \ - @catch (__unused NSException *e) { \ - RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \ - json, [json classForCoder], #type); \ - json = nil; \ - return code; \ - } \ } /** @@ -190,15 +193,8 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) /** * This macro is used for creating converter functions for typed arrays. */ -#define RCT_ARRAY_CONVERTER(type) \ -+ (type##Array *)type##Array:(id)json \ -{ \ - NSMutableArray *values = [[NSMutableArray alloc] init]; \ - for (id jsonValue in [self NSArray:json]) { \ - id value = [self type:jsonValue]; \ - if (value) { \ - [values addObject:value]; \ - } \ - } \ - return values; \ +#define RCT_ARRAY_CONVERTER(type) \ ++ (NSArray *)type##Array:(id)json \ +{ \ + return RCTConvertArrayValue(@selector(type:), json); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 2aefe8940..f1ed77298 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -11,8 +11,16 @@ #import +#import "RCTDefines.h" + @implementation RCTConvert +void RCTLogConvertError(id json, const char *type) +{ + RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to %s", + json, [json classForCoder], type); +} + RCT_CONVERTER(BOOL, BOOL, boolValue) RCT_NUMBER_CONVERTER(double, doubleValue) RCT_NUMBER_CONVERTER(float, floatValue) @@ -228,57 +236,52 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{ }), UIBarStyleDefault, integerValue) // TODO: normalise the use of w/width so we can do away with the alias values (#6566645) +static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDictionary *aliases, CGFloat *result, id json) +{ + NSUInteger count = fields.count; + if ([json isKindOfClass:[NSArray class]]) { + if (RCT_DEBUG && [json count] != count) { + RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); + } else { + for (NSUInteger i = 0; i < count; i++) { + result[i] = [RCTConvert CGFloat:json[i]]; + } + } + } else if ([json isKindOfClass:[NSDictionary class]]) { + if (aliases.count) { + json = [json mutableCopy]; + for (NSString *alias in aliases) { + NSString *key = aliases[alias]; + NSNumber *number = json[alias]; + if (number) { + RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, type, key); + ((NSMutableDictionary *)json)[key] = number; + } + } + } + for (NSUInteger i = 0; i < count; i++) { + result[i] = [RCTConvert CGFloat:json[fields[i]]]; + } + } else if (RCT_DEBUG && json && json != (id)kCFNull) { + RCTLogConvertError(json, type); + } +} + /** * This macro is used for creating converter functions for structs that consist * of a number of CGFloat properties, such as CGPoint, CGRect, etc. */ -#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \ -+ (type)type:(id)json \ -{ \ - @try { \ - static NSArray *fields; \ - static NSUInteger count; \ - static dispatch_once_t onceToken; \ - dispatch_once(&onceToken, ^{ \ - fields = values; \ - count = [fields count]; \ - }); \ - type result; \ - if ([json isKindOfClass:[NSArray class]]) { \ - if ([json count] != count) { \ - RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \ - } else { \ - for (NSUInteger i = 0; i < count; i++) { \ - ((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \ - } \ - } \ - } else if ([json isKindOfClass:[NSDictionary class]]) { \ - NSDictionary *aliases = _aliases; \ - if (aliases.count) { \ - json = [json mutableCopy]; \ - for (NSString *alias in aliases) { \ - NSString *key = aliases[alias]; \ - NSNumber *number = json[alias]; \ - if (number) { \ - RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \ - ((NSMutableDictionary *)json)[key] = number; \ - } \ - } \ - } \ - for (NSUInteger i = 0; i < count; i++) { \ - ((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \ - } \ - } else if (json && json != [NSNull null]) { \ - RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", \ - #type, [json classForCoder], json); \ - } \ - return result; \ - } \ - @catch (__unused NSException *e) { \ - RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \ - type result; \ - return result; \ - } \ +#define RCT_CGSTRUCT_CONVERTER(type, values, aliases) \ ++ (type)type:(id)json \ +{ \ + static NSArray *fields; \ + static dispatch_once_t onceToken; \ + dispatch_once(&onceToken, ^{ \ + fields = values; \ + }); \ + type result; \ + RCTConvertCGStructValue(#type, fields, aliases, (CGFloat *)&result, json); \ + return result; \ } RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json]) @@ -525,9 +528,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ } else if ([json isKindOfClass:[NSArray class]]) { if ([json count] < 3 || [json count] > 4) { - RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json); - } else { // Color array @@ -545,10 +546,9 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ blue:[self double:json[@"b"]] alpha:[self double:json[@"a"] ?: @1]]; - } else if (json && ![json isKindOfClass:[NSNull class]]) { - - RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@", - [json classForCoder], json); + } + else if (RCT_DEBUG && json && json != (id)kCFNull) { + RCTLogConvertError(json, "a color"); } // Default color @@ -573,8 +573,12 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ // TODO: we might as well cache the result of these checks (and possibly the // image itself) so as to reduce overhead on subsequent checks of the same input - if (![json isKindOfClass:[NSString class]]) { - RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json classForCoder], json); + if (!json || json == (id)kCFNull) { + return nil; + } + + if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) { + RCTLogConvertError(json, "an image"); return nil; } @@ -761,6 +765,29 @@ static BOOL RCTFontIsCondensed(UIFont *font) return bestMatch; } +NSArray *RCTConvertArrayValue(SEL type, id json) +{ + __block BOOL copy = NO; + __block NSArray *values = json = [RCTConvert NSArray:json]; + [json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, BOOL *stop) { + id value = ((id(*)(Class, SEL, id))objc_msgSend)([RCTConvert class], type, jsonValue); + if (copy) { + if (value) { + [(NSMutableArray *)values addObject:value]; + } + } else if (value != jsonValue) { + // Converted value is different, so we'll need to copy the array + values = [[NSMutableArray alloc] initWithCapacity:values.count]; + for (NSInteger i = 0; i < idx; i++) { + [(NSMutableArray *)values addObject:json[i]]; + } + [(NSMutableArray *)values addObject:value]; + copy = YES; + } + }]; + return values; +} + RCT_ARRAY_CONVERTER(NSString) RCT_ARRAY_CONVERTER(NSDictionary) RCT_ARRAY_CONVERTER(NSURL) diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h index 7fa25e6a8..75cbe722e 100644 --- a/React/Base/RCTLog.h +++ b/React/Base/RCTLog.h @@ -51,7 +51,7 @@ RCT_EXTERN NSString *RCTThreadName(NSThread *); * A method to generate a string from a collection of log data. To omit any * particular data from the log, just pass nil or zero for the argument. */ -NSString *RCTFormatLog( +RCT_EXTERN NSString *RCTFormatLog( NSDate *timestamp, NSThread *thread, RCTLogLevel level, diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 6ca2d4eb8..fb70fe6d3 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -184,7 +184,7 @@ void _RCTLogFormat( level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message ); - if (RCT_DEBUG) { +#if RCT_DEBUG // Red box is only available in debug mode // Log to red box if (level >= RCTLOG_REDBOX_LEVEL) { @@ -193,6 +193,8 @@ void _RCTLogFormat( // Log to JS executor [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; - } + +#endif + } } diff --git a/React/Base/RCTRedBox.h b/React/Base/RCTRedBox.h index 9a3a9b49a..058759da7 100644 --- a/React/Base/RCTRedBox.h +++ b/React/Base/RCTRedBox.h @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEBUG // Red box is only available in debug mode + #import @interface RCTRedBox : NSObject @@ -23,3 +27,5 @@ - (void)dismiss; @end + +#endif diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 882a87bfe..626840252 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEBUG // Red box is only available in debug mode + #import "RCTRedBox.h" #import "RCTBridge.h" @@ -282,14 +286,12 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow { - if (RCT_DEBUG) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!_window) { - _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - } - [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; - }); - } + dispatch_async(dispatch_get_main_queue(), ^{ + if (!_window) { + _window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + } + [_window showErrorMessage:message withStack:stack showIfHidden:shouldShow]; + }); } - (NSString *)currentErrorMessage @@ -307,3 +309,5 @@ } @end + +#endif diff --git a/React/Executors/RCTWebViewExecutor.h b/React/Executors/RCTWebViewExecutor.h index 77d8a8310..db8710c7d 100644 --- a/React/Executors/RCTWebViewExecutor.h +++ b/React/Executors/RCTWebViewExecutor.h @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEV // Debug executors are only supported in dev mode + #import #import "RCTJavaScriptExecutor.h" @@ -40,3 +44,5 @@ - (UIWebView *)invalidateAndReclaimWebView; @end + +#endif diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index 0bc6fdfc8..09628850f 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -7,6 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "RCTDefines.h" + +#if RCT_DEV // Debug executors are only supported in dev mode + #import "RCTWebViewExecutor.h" #import @@ -188,9 +192,14 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...) asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { - RCTAssert(!_objectsToInject[objectName], - @"already injected object named %@", _objectsToInject[objectName]); + if (RCT_DEBUG) { + RCTAssert(!_objectsToInject[objectName], + @"already injected object named %@", _objectsToInject[objectName]); + } _objectsToInject[objectName] = script; onComplete(nil); } + @end + +#endif diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index ece5d0668..878138282 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -44,10 +44,11 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message return; } - if (RCT_DEBUG) { - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; - return; - } +#if RCT_DEBUG // Red box is only available in debug mode + + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + +#else static NSUInteger reloadRetries = 0; const NSUInteger maxMessageLength = 75; @@ -76,14 +77,21 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage]; [NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack]; } + +#endif + } RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message stack:(NSArray *)stack) { - if (RCT_DEBUG) { + +#if RCT_DEBUG // Red box is only available in debug mode + [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; - } + +#endif + } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 3f3ea7596..fc688e491 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -222,6 +222,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) return name; } +// TODO: only send name once instead of a dictionary of name and type keyed by name static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewName) { NSMutableDictionary *nativeProps = [[NSMutableDictionary alloc] init]; @@ -234,8 +235,13 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa SEL getInfo = method_getName(method); const char *selName = sel_getName(getInfo); if (strlen(selName) > prefixLength && strncmp(selName, prefix, prefixLength) == 0) { - NSDictionary *info = ((NSDictionary *(*)(id, SEL))method_getImplementation(method))(managerClass, getInfo); - nativeProps[info[@"name"]] = info; + NSString *name = @(selName); + NSRange nameRange = [name rangeOfString:@"_"]; + if (nameRange.length) { + name = [name substringFromIndex:nameRange.location + 1]; + NSString *type = ((NSString *(*)(id, SEL))method_getImplementation(method))(managerClass, getInfo); + nativeProps[name] = @{@"name": name, @"type": type}; + } } } return @{ diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 48d7aee04..f32e86ff1 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -106,6 +106,7 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = ""; }; 137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = ""; }; 137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = ""; }; + 1384149E1ADFCA4A003E0667 /* RCTDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = ""; }; 13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = ""; }; @@ -364,6 +365,7 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 1384149E1ADFCA4A003E0667 /* RCTDefines.h */, 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */, 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index ed97cf3b2..3788832ef 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -130,11 +130,11 @@ RCT_CUSTOM_SHADOW_PROPERTY(name, type, RCTShadowView) { \ * refer to "json", "view" and "defaultView" to implement the required logic. */ #define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \ -+ (NSDictionary *)getPropConfigView_##name { return @{@"name": @#name, @"type": @#type}; } \ ++ (NSString *)getPropConfigView_##name { return @#type; } \ - (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView #define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \ -+ (NSDictionary *)getPropConfigShadow_##name { return @{@"name": @#name, @"type": @#type}; } \ ++ (NSString *)getPropConfigShadow_##name { return @#type; } \ - (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView /** From 2294da777cf08f899dcc95256056e2c1958b8a88 Mon Sep 17 00:00:00 2001 From: Jiajie Zhu Date: Tue, 21 Apr 2015 10:19:18 -0700 Subject: [PATCH 36/92] [catlyst|madman] make map fire onRegionChange for user zoom in/out again --- React/Views/RCTMapManager.m | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 8de351415..f9a6b9175 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -67,15 +67,13 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) { [self _regionChanged:mapView]; - if (animated) { - mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval - target:self - selector:@selector(_onTick:) - userInfo:@{ RCTMapViewKey: mapView } - repeats:YES]; + mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval + target:self + selector:@selector(_onTick:) + userInfo:@{ RCTMapViewKey: mapView } + repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes]; - } + [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes]; } - (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated From 77d908b975a652f8f7e657e9afc801892cc6a060 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:57:53 -0700 Subject: [PATCH 37/92] [react-packager] Allow json files as modules --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 40 +++++++++++++++++++ .../haste/DependencyGraph/index.js | 13 +++++- .../src/Packager/__tests__/Packager-test.js | 18 ++++++++- packager/react-packager/src/Packager/index.js | 15 +++++++ packager/react-packager/src/Server/index.js | 2 +- 6 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 3cdfa1c6a..90db1c4ad 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -45,6 +45,8 @@ function ModuleDescriptor(fields) { this.altId = fields.altId; + this.isJSON = fields.isJSON; + this._fields = fields; } diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index d3f6ce289..cda717e87 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -101,6 +101,46 @@ describe('DependencyGraph', function() { }); }); + pit('should get json dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./a.json")' + ].join('\n'), + 'a.json': JSON.stringify({}), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher + }); + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { + id: 'index', + altId: 'package/index', + path: '/root/index.js', + dependencies: ['./a.json'] + }, + { + id: 'package/a.json', + isJSON: true, + path: '/root/a.json', + dependencies: [] + }, + ]); + }); + }); + pit('should get dependencies with deprecated assets', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 665345977..6afb5f25c 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -66,7 +66,7 @@ function DependecyGraph(options) { this._debugUpdateEvents = []; this._moduleExtPattern = new RegExp( - '\.(' + ['js'].concat(this._assetExts).join('|') + ')$' + '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); // Kick off the search process to precompute the dependency graph. @@ -259,7 +259,7 @@ DependecyGraph.prototype.resolveDependency = function( } // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath)) { + if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { modulePath = withExtJs(modulePath); } @@ -432,6 +432,15 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (extname(modulePath) === 'json') { + moduleData.id = this._lookupName(modulePath); + moduleData.isJSON = true; + moduleData.dependencies = []; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index c17732165..763e6dd6d 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,6 +43,10 @@ describe('Packager', function() { }; }); + require('fs').readFile.mockImpl(function(file, callback) { + callback(null, '{"json":true}'); + }); + var packager = new Packager({projectRoots: ['/root']}); var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, @@ -60,7 +64,13 @@ describe('Packager', function() { isAsset: true, resolution: 2, dependencies: [] - } + }, + { + id: 'package/file.json', + path: '/root/file.json', + isJSON: true, + dependencies: [], + }, ]; getDependencies.mockImpl(function() { @@ -137,6 +147,12 @@ describe('Packager', function() { '/root/img/new_image.png' ]); + expect(p.addModule.mock.calls[4]).toEqual([ + 'lol module.exports = {"json":true}; lol', + 'module.exports = {"json":true};', + '/root/file.json' + ]); + expect(p.finalize.mock.calls[0]).toEqual([ {runMainModule: true} ]); diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index aab55c084..c651dde78 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -21,6 +21,7 @@ var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); var sizeOf = Promise.promisify(imageSize); +var readFile = Promise.promisify(fs.readFile); var validateOpts = declareOpts({ projectRoots: { @@ -147,6 +148,8 @@ Packager.prototype._transformModule = function(ppackage, module) { transform = this.generateAssetModule_DEPRECATED(ppackage, module); } else if (module.isAsset) { transform = this.generateAssetModule(ppackage, module); + } else if (module.isJSON) { + transform = generateJSONModule(module); } else { transform = this._transformer.loadFileAndTransform( path.resolve(module.path) @@ -206,6 +209,18 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { var code = 'module.exports = ' + JSON.stringify(img) + ';'; + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +}; + +function generateJSONModule(module) { + return readFile(module.path).then(function(data) { + var code = 'module.exports = ' + data.toString('utf8') + ';'; + return { code: code, sourceCode: code, diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index e0bfeb2f3..525636cb5 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -80,7 +80,7 @@ function Server(options) { dir: dir, globs: [ '**/*.js', - '**/package.json', + '**/*.json', ].concat(assetGlobs), }; }); From c46c4a0ad4261ab3bd94568bafa43bda0475281a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:59:29 -0700 Subject: [PATCH 39/92] [react-packager] bump watchman watch timeout to 10 seconds --- packager/react-packager/src/FileWatcher/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index b629dfbf0..38ad19bf9 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -26,7 +26,7 @@ var detectingWatcherClass = new Promise(function(resolve) { module.exports = FileWatcher; -var MAX_WAIT_TIME = 3000; +var MAX_WAIT_TIME = 10000; // Singleton var fileWatcher = null; From 173615ae266f70d31088c5486fb5f810d1524e93 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:58:20 -0700 Subject: [PATCH 40/92] [react-packager] Add jpe?g to asset extensions --- packager/packager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packager/packager.js b/packager/packager.js index 23da3a78c..212e17f71 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -200,6 +200,7 @@ function getAppMiddleware(options) { cacheVersion: '2', transformModulePath: require.resolve('./transformer.js'), assetRoots: options.assetRoots, + assetExts: ['png', 'jpeg', 'jpg'] }); } From 5fb5148e3d8c0bc045692b7f8ce1ee75f2253544 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Tue, 21 Apr 2015 10:50:10 -0700 Subject: [PATCH 41/92] [SliderIOS] Apply value after minimum/maximumValue in order to ensure it is properly set Summary: `value` is clamped between min/max and so order of prop application matters - `value` always ended up being set first in my tests, and consequently a value outside of the default range 0-1 would not work. So this applies the value when the min/max are set. [Gist of broken example](https://gist.github.com/brentvatne/fc637b3e21d012966f3a) ![screenshot](http://url.brentvatne.ca/SQPC.png) ^ the second slider here should have it's cursor in the middle /cc @tadeuzagallo Closes https://github.com/facebook/react-native/pull/835 Github Author: Brent Vatne Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Views/RCTSlider.h | 14 ++++++++++++++ React/Views/RCTSlider.m | 35 ++++++++++++++++++++++++++++++++++ React/Views/RCTSliderManager.m | 20 +++++++++---------- 3 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 React/Views/RCTSlider.h create mode 100644 React/Views/RCTSlider.m diff --git a/React/Views/RCTSlider.h b/React/Views/RCTSlider.h new file mode 100644 index 000000000..916419a29 --- /dev/null +++ b/React/Views/RCTSlider.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface RCTSlider : UISlider + +@end diff --git a/React/Views/RCTSlider.m b/React/Views/RCTSlider.m new file mode 100644 index 000000000..04e8d841e --- /dev/null +++ b/React/Views/RCTSlider.m @@ -0,0 +1,35 @@ +/** + * 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 "RCTSlider.h" + +@implementation RCTSlider +{ + float _unclippedValue; +} + +- (void)setValue:(float)value +{ + _unclippedValue = value; + super.value = value; +} + +- (void)setMinimumValue:(float)minimumValue +{ + super.minimumValue = minimumValue; + super.value = _unclippedValue; +} + +- (void)setMaximumValue:(float)maximumValue +{ + super.maximumValue = maximumValue; + super.value = _unclippedValue; +} + +@end diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m index 58b763b92..a103da98d 100644 --- a/React/Views/RCTSliderManager.m +++ b/React/Views/RCTSliderManager.m @@ -11,6 +11,7 @@ #import "RCTBridge.h" #import "RCTEventDispatcher.h" +#import "RCTSlider.h" #import "UIView+React.h" @implementation RCTSliderManager @@ -19,32 +20,31 @@ RCT_EXPORT_MODULE() - (UIView *)view { - UISlider *slider = [[UISlider alloc] init]; + RCTSlider *slider = [[RCTSlider alloc] init]; [slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside]; return slider; } -- (void)sliderValueChanged:(UISlider *)sender +static void RCTSendSliderEvent(RCTSliderManager *self, UISlider *sender, BOOL continuous) { NSDictionary *event = @{ @"target": sender.reactTag, @"value": @(sender.value), - @"continuous": @YES, + @"continuous": @(continuous), }; [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; } +- (void)sliderValueChanged:(UISlider *)sender +{ + RCTSendSliderEvent(self, sender, YES); +} + - (void)sliderTouchEnd:(UISlider *)sender { - NSDictionary *event = @{ - @"target": sender.reactTag, - @"value": @(sender.value), - @"continuous": @NO, - }; - - [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; + RCTSendSliderEvent(self, sender, NO); } RCT_EXPORT_VIEW_PROPERTY(value, float); From 45c10ffc538de36a1508572de869c7b7e5bd01e5 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Tue, 21 Apr 2015 11:41:34 -0700 Subject: [PATCH 42/92] [ReactNative] Navigator touch grant bug from D2001635 --- Libraries/CustomComponents/Navigator/Navigator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 7a030b455..2d7a4a68f 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -640,7 +640,7 @@ var Navigator = React.createClass({ this.state.fromIndex = this.state.presentedIndex; var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction); this.state.toIndex = this.state.presentedIndex + gestureSceneDelta; - this.onAnimationStart(); + this._onAnimationStart(); } }, From c0c2d4ca00cdbca93dacd5eea8a8dc07ef9dbae9 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 21 Apr 2015 11:54:14 -0700 Subject: [PATCH 43/92] [react_native] JS files from D2009265: Fix resizeMode for images --- Libraries/Image/ImageResizeMode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Image/ImageResizeMode.js b/Libraries/Image/ImageResizeMode.js index 9a7a2f954..b947b9525 100644 --- a/Libraries/Image/ImageResizeMode.js +++ b/Libraries/Image/ImageResizeMode.js @@ -30,8 +30,8 @@ var ImageResizeMode = keyMirror({ cover: null, /** * stretch - The image will be stretched to fill the entire frame of the - * view without clipping. This may change the aspect ratio of the image, - * distoring it. Only supported on iOS. + * view without clipping. This may change the aspect ratio of the image, + * distoring it. */ stretch: null, }); From c6ad7b85d1c67dd85ebc659f6560d061e1ade2d2 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 21 Apr 2015 10:48:54 -0700 Subject: [PATCH 44/92] [ReactNative] Use network image for new image assets --- Libraries/Image/Image.ios.js | 20 +++-- .../__tests__/resolveAssetSource-test.js | 76 +++++++++++++++++++ Libraries/Image/resolveAssetSource.js | 66 ++++++++++++++++ 3 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 Libraries/Image/__tests__/resolveAssetSource-test.js create mode 100644 Libraries/Image/resolveAssetSource.js diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index d519c1ac5..be95a3f3f 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -12,11 +12,11 @@ 'use strict'; var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var ImageResizeMode = require('ImageResizeMode'); +var ImageStylePropTypes = require('ImageStylePropTypes'); var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); -var ImageResizeMode = require('ImageResizeMode'); -var ImageStylePropTypes = require('ImageStylePropTypes'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); @@ -26,8 +26,9 @@ var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); -var warning = require('warning'); +var resolveAssetSource = require('resolveAssetSource'); var verifyPropTypes = require('verifyPropTypes'); +var warning = require('warning'); /** * A react component for displaying different types of images, @@ -122,10 +123,15 @@ var Image = React.createClass({ 'not be set directly on Image.'); } } - var style = flattenStyle([styles.base, this.props.style]); - invariant(style, "style must be initialized"); var source = this.props.source; - invariant(source, "source must be initialized"); + invariant(source, 'source must be initialized'); + + var {width, height} = source; + var style = flattenStyle([{width, height}, styles.base, this.props.style]); + invariant(style, 'style must be initialized'); + + source = resolveAssetSource(source); + var isNetwork = source.uri && source.uri.match(/^https?:/); invariant( !(isNetwork && source.isStatic), @@ -171,8 +177,8 @@ var styles = StyleSheet.create({ }, }); -var RCTStaticImage = requireNativeComponent('RCTStaticImage', null); var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null); +var RCTStaticImage = requireNativeComponent('RCTStaticImage', null); var nativeOnlyProps = { src: true, diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js new file mode 100644 index 000000000..69bcb116a --- /dev/null +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest.dontMock('../resolveAssetSource'); + +var resolveAssetSource; +var SourceCode; + +describe('resolveAssetSource', () => { + beforeEach(() => { + jest.resetModuleRegistry(); + SourceCode = require('NativeModules').SourceCode; + resolveAssetSource = require('../resolveAssetSource'); + }); + + it('returns same source for simple static and network images', () => { + var source1 = {uri: 'https://www.facebook.com/logo'}; + expect(resolveAssetSource(source1)).toBe(source1); + + var source2 = {isStatic: true, uri: 'logo'}; + expect(resolveAssetSource(source2)).toBe(source2); + }); + + describe('bundle was loaded from network', () => { + beforeEach(() => { + SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle'; + }); + + it('uses network image', () => { + var source = { + path: '/Users/react/project/logo.png', + uri: 'assets/logo.png', + }; + expect(resolveAssetSource(source)).toEqual({ + isStatic: false, + uri: 'http://10.0.0.1:8081/assets/logo.png', + }); + }); + + it('does not change deprecated assets', () => { + // Deprecated require('image!logo') should stay unchanged + var source = { + path: '/Users/react/project/logo.png', + uri: 'logo', + deprecated: true, + }; + expect(resolveAssetSource(source)).toEqual({ + isStatic: true, + uri: 'logo', + }); + }); + }); + + describe('bundle was loaded from file', () => { + it('uses pre-packed image', () => { + SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle'; + + var source = { + path: '/Users/react/project/logo.png', + uri: 'assets/logo.png', + }; + expect(resolveAssetSource(source)).toEqual({ + isStatic: true, + uri: 'assets/logo.png', + }); + }); + }); + +}); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js new file mode 100644 index 000000000..137f92f1c --- /dev/null +++ b/Libraries/Image/resolveAssetSource.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule resolveAssetSource + */ +'use strict'; + +var SourceCode = require('NativeModules').SourceCode; + +var _serverURL; + +function getServerURL() { + if (_serverURL === undefined) { + var scriptURL = SourceCode.scriptURL; + var serverURLMatch = scriptURL && scriptURL.match(/^https?:\/\/.*?\//); + if (serverURLMatch) { + _serverURL = serverURLMatch[0]; + } else { + _serverURL = null; + } + } + + return _serverURL; +} + +// TODO(frantic): +// * Use something other than `path`/`isStatic` for asset identification, `__packager_asset`? +// * Add cache invalidating hashsum +// * Move code that selects scale to client +function resolveAssetSource(source) { + if (source.deprecated) { + return { + ...source, + path: undefined, + isStatic: true, + deprecated: undefined, + }; + } + + var serverURL = getServerURL(); + if (source.path) { + if (serverURL) { + return { + ...source, + path: undefined, + uri: serverURL + source.uri, + isStatic: false, + }; + } else { + return { + ...source, + path: undefined, + isStatic: true, + }; + } + } + + return source; +} + +module.exports = resolveAssetSource; From 40eeaf5b37daeeacd0d612e086426c32de23967e Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Tue, 21 Apr 2015 13:31:20 -0700 Subject: [PATCH 45/92] [ReactNative] Navigator contextual popToRoute and imperitive vs contextual docs --- .../CustomComponents/Navigator/Navigator.js | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 2d7a4a68f..f4fcdd443 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -135,11 +135,12 @@ var GESTURE_ACTIONS = [ * /> * ``` * - * ### Navigation Methods + * ### Navigator Methods * - * `Navigator` can be told to navigate in two ways. If you have a ref to - * the element, you can invoke several methods on it to trigger navigation: + * If you have a ref to the Navigator element, you can invoke several methods + * on it to trigger navigation: * + * - `getCurrentRoutes()` - returns the current list of routes * - `jumpBack()` - Jump backward without unmounting the current scene * - `jumpForward()` - Jump forward to the next scene in the route stack * - `jumpTo(route)` - Transition to an existing scene without unmounting @@ -156,18 +157,39 @@ var GESTURE_ACTIONS = [ * - `popToTop()` - Pop to the first scene in the stack, unmounting every * other scene * - * ### Navigator Object + * ### Navigation Context * - * The navigator object is made available to scenes through the `renderScene` - * function. The object has all of the navigation methods on it, as well as a - * few utilities: + * The navigator context object is made available to scenes through the + * `renderScene` function. Alternatively, any scene or component inside a + * Navigator can get the navigation context by calling + * `Navigator.getContext(this)`. * - * - `parentNavigator` - a refrence to the parent navigator object that was - * passed in through props.navigator - * - `onWillFocus` - used to pass a navigation focus event up to the parent - * navigator - * - `onDidFocus` - used to pass a navigation focus event up to the parent - * navigator + * Unlike the Navigator methods, the functions in navigation context do not + * directly control a specific navigator. Instead, the navigator context allows + * a scene to request navigation from its parents. Navigation requests will + * travel up through the hierarchy of Navigators, and will be resolved by the + * deepest active navigator. + * + * Navigation context objects contain the following: + * + * - `getCurrentRoutes()` - returns the routes for the closest navigator + * - `jumpBack()` - Jump backward without unmounting the current scene + * - `jumpForward()` - Jump forward to the next scene in the route stack + * - `jumpTo(route)` - Transition to an existing scene without unmounting + * - `parentNavigator` - a refrence to the parent navigation context + * - `push(route)` - Navigate forward to a new scene, squashing any scenes + * that you could `jumpForward` to + * - `pop()` - Transition back and unmount the current scene + * - `replace(route)` - Replace the current scene with a new route + * - `replaceAtIndex(route, index)` - Replace a scene as specified by an index + * - `replacePrevious(route)` - Replace the previous scene + * - `route` - The route that was used to render the scene with this context + * - `immediatelyResetRouteStack(routeStack)` - Reset every scene with an + * array of routes + * - `popToRoute(route)` - Pop to a particular scene, as specified by it's + * route. All scenes after it will be unmounted + * - `popToTop()` - Pop to the first scene in the stack, unmounting every + * other scene * */ var Navigator = React.createClass({ @@ -306,25 +328,30 @@ var Navigator = React.createClass({ this.parentNavigator = getNavigatorContext(this) || this.props.navigator; this._subRouteFocus = []; this.navigatorContext = { + // Actions for child navigators or interceptors: setHandlerForRoute: this.setHandlerForRoute, request: this.request, + // Contextual utilities parentNavigator: this.parentNavigator, getCurrentRoutes: this.getCurrentRoutes, + // `route` is injected by NavigatorStaticContextContainer - // Legacy, imperitive nav actions. Use request when possible. + // Contextual nav actions + pop: this.requestPop, + popToRoute: this.requestPopTo, + + // Legacy, imperitive nav actions. Will transition these to contextual actions jumpBack: this.jumpBack, jumpForward: this.jumpForward, jumpTo: this.jumpTo, push: this.push, - pop: this.pop, replace: this.replace, replaceAtIndex: this.replaceAtIndex, replacePrevious: this.replacePrevious, replacePreviousAndPop: this.replacePreviousAndPop, immediatelyResetRouteStack: this.immediatelyResetRouteStack, resetTo: this.resetTo, - popToRoute: this.popToRoute, popToTop: this.popToTop, }; this._handlers = {}; @@ -349,6 +376,14 @@ var Navigator = React.createClass({ return this._handleRequest.apply(null, arguments); }, + requestPop: function() { + return this.request('pop'); + }, + + requestPopTo: function(route) { + return this.request('pop', route); + }, + _handleRequest: function(action, arg1, arg2) { var childHandler = this._handlers[this.state.presentedIndex]; if (childHandler && childHandler(action, arg1, arg2)) { @@ -356,7 +391,7 @@ var Navigator = React.createClass({ } switch (action) { case 'pop': - return this._handlePop(); + return this._handlePop(arg1); case 'push': return this._handlePush(arg1); default: @@ -365,11 +400,20 @@ var Navigator = React.createClass({ } }, - _handlePop: function() { + _handlePop: function(route) { + if (route) { + var hasRoute = this.state.routeStack.indexOf(route) !== -1; + if (hasRoute) { + this.popToRoute(route); + return true; + } else { + return false; + } + } if (this.state.presentedIndex === 0) { return false; } - this._popN(1); + this.pop(); return true; }, @@ -904,7 +948,7 @@ var Navigator = React.createClass({ }, pop: function() { - return this.request('pop'); + this._popN(1); }, /** From 32084c90a22846efed9c92f0b89c9c8addb3dfa4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 21 Apr 2015 13:25:19 -0700 Subject: [PATCH 46/92] Added missing RCTDefines.h to React lib --- React/React.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index f32e86ff1..38c207459 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ 1384149E1ADFCA4A003E0667 /* RCTDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = ""; }; + 13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; 13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = ""; }; 13B07FC81A68125100A75B9A /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = ""; }; 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = ""; }; @@ -377,6 +378,7 @@ 830BA4541A8E3BDA00D53203 /* RCTCache.m */, 83CBBACA1A6023D300E9B192 /* RCTConvert.h */, 83CBBACB1A6023D300E9B192 /* RCTConvert.m */, + 13AF1F851AE6E777005F5298 /* RCTDefines.h */, 83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */, 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, From 2ee7ebae1fc42b83f7c57e5a93f77e01a0d0b2e3 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 21 Apr 2015 21:08:20 -0100 Subject: [PATCH 47/92] Fixed broken font weight on iPhone 5 --- .../RCTWebSocketDebugger.xcodeproj/project.pbxproj | 12 ++++++------ React/Base/RCTConvert.h | 5 +---- React/React.xcodeproj/project.pbxproj | 8 ++++++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj index 4a9b59916..d9e3b9f5b 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */; }; - 00D277191AB8C35800DC1E48 /* RCT_SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */; }; + 13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20411AE707C5005F5298 /* SRWebSocket.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -26,8 +26,8 @@ /* Begin PBXFileReference section */ 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = ""; }; 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = ""; }; - 00D277171AB8C35800DC1E48 /* RCT_SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCT_SRWebSocket.h; sourceTree = ""; }; - 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCT_SRWebSocket.m; sourceTree = ""; }; + 13AF20401AE707C5005F5298 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; + 13AF20411AE707C5005F5298 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; 832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocketDebugger.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -45,8 +45,8 @@ 832C81771AAF6DEF007FA2F7 = { isa = PBXGroup; children = ( - 00D277171AB8C35800DC1E48 /* RCT_SRWebSocket.h */, - 00D277181AB8C35800DC1E48 /* RCT_SRWebSocket.m */, + 13AF20401AE707C5005F5298 /* SRWebSocket.h */, + 13AF20411AE707C5005F5298 /* SRWebSocket.m */, 00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */, 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */, 832C81811AAF6DEF007FA2F7 /* Products */, @@ -119,7 +119,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 00D277191AB8C35800DC1E48 /* RCT_SRWebSocket.m in Sources */, + 13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */, 00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 59ba79d27..46bbbf8a4 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -7,8 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import - #import #import @@ -186,8 +184,7 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) dispatch_once(&onceToken, ^{ \ mapping = values; \ }); \ - NSNumber *converted = RCTConvertEnumValue(#type, mapping, @(default), json); \ - return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \ + return [RCTConvertEnumValue(#type, mapping, @(default), json) getter]; \ } /** diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 38c207459..8823e1565 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; }; 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; + 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; }; 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; }; 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; }; 13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEE1A69327A00A75B9A /* RCTTiming.m */; }; @@ -106,10 +107,11 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = ""; }; 137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = ""; }; 137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = ""; }; - 1384149E1ADFCA4A003E0667 /* RCTDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = ""; }; 13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; + 13AF20431AE707F8005F5298 /* RCTSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSlider.h; sourceTree = ""; }; + 13AF20441AE707F9005F5298 /* RCTSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSlider.m; sourceTree = ""; }; 13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = ""; }; 13B07FC81A68125100A75B9A /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = ""; }; 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = ""; }; @@ -295,6 +297,8 @@ 13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */, 13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */, 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */, + 13AF20431AE707F8005F5298 /* RCTSlider.h */, + 13AF20441AE707F9005F5298 /* RCTSlider.m */, 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */, 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */, 14F362071AABD06A001CE568 /* RCTSwitch.h */, @@ -366,7 +370,6 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( - 1384149E1ADFCA4A003E0667 /* RCTDefines.h */, 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */, 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, @@ -490,6 +493,7 @@ 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, 83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, + 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, From 404f7d9dbfbf6e1552f4502b35d067a4c54ecd15 Mon Sep 17 00:00:00 2001 From: xcatliu Date: Tue, 21 Apr 2015 16:09:56 -0700 Subject: [PATCH 48/92] Fix AlertIOS Docs Summary: The curly braces seems to be redundant. Closes https://github.com/facebook/react-native/pull/811 Github Author: xcatliu Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Utilities/AlertIOS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js index de72d7b9f..0d1612bc9 100644 --- a/Libraries/Utilities/AlertIOS.js +++ b/Libraries/Utilities/AlertIOS.js @@ -37,7 +37,7 @@ var DEFAULT_BUTTON = { * {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, * {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, * ] - * )} + * ) * ``` */ From 17e5b04d1a84483244d8ad91f8f1fd0b5c78ef7e Mon Sep 17 00:00:00 2001 From: Mike Wilcox Date: Tue, 21 Apr 2015 16:37:54 -0700 Subject: [PATCH 49/92] Fix Typo Summary: * This PR fixes a typo for the NetInfo docs page Closes https://github.com/facebook/react-native/pull/937 Github Author: Mike Wilcox Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/NetInfoExample.js | 4 ++-- Libraries/Network/NetInfo.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js index 18a79ae4a..c322a7432 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/NetInfoExample.js @@ -131,12 +131,12 @@ exports.description = 'Monitor network status'; exports.examples = [ { title: 'NetInfo.isConnected', - description: 'Asyncronously load and observe connectivity', + description: 'Asynchronously load and observe connectivity', render(): ReactElement { return ; } }, { title: 'NetInfo.reachabilityIOS', - description: 'Asyncronously load and observe iOS reachability', + description: 'Asynchronously load and observe iOS reachability', render(): ReactElement { return ; } }, { diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index d98b997ca..2b65671a9 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -34,7 +34,7 @@ type ReachabilityStateIOS = $Enum<{ * * ### reachabilityIOS * - * Asyncronously determine if the device is online and on a cellular network. + * Asynchronously determine if the device is online and on a cellular network. * * - `none` - device is offline * - `wifi` - device is online and connected via wifi, or is the iOS simulator @@ -60,7 +60,7 @@ type ReachabilityStateIOS = $Enum<{ * * ### isConnected * - * Available on all platforms. Asyncronously fetch a boolean to determine + * Available on all platforms. Asynchronously fetch a boolean to determine * internet connectivity. * * ``` From 368e507b3879231e07b5ee869f688eecc71ea213 Mon Sep 17 00:00:00 2001 From: Josh Zana Date: Tue, 21 Apr 2015 16:43:07 -0700 Subject: [PATCH 50/92] Implement XmlHttpRequestBase#getAllResponseHeaders and getResponseHeader Summary: Used https://github.com/facebook/react-native/pull/382 as inspiration but modified to return null instead of undefined as per the spec at https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest This unblocks use of Dropbox.js within a react-native app, as well as any other libraries that make use of these methods in XHR usage. Closes https://github.com/facebook/react-native/issues/872 Closes https://github.com/facebook/react-native/pull/892 Github Author: Josh Zana Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Network/XMLHttpRequestBase.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js index d7619d075..cfd1e35cc 100644 --- a/Libraries/Network/XMLHttpRequestBase.js +++ b/Libraries/Network/XMLHttpRequestBase.js @@ -53,13 +53,22 @@ class XMLHttpRequestBase { } getAllResponseHeaders(): ?string { - /* Stub */ - return ''; + if (this.responseHeaders) { + var headers = []; + for (var headerName in this.responseHeaders) { + headers.push(headerName + ': ' + this.responseHeaders[headerName]); + } + return headers.join('\n'); + } + return null; } getResponseHeader(header: string): ?string { - /* Stub */ - return ''; + if (this.responseHeaders) { + var value = this.responseHeaders[header]; + return value !== undefined ? value : null; + } + return null; } setRequestHeader(header: string, value: any): void { From b1a15004de3027bb3d454cfdea61841a8c867426 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 21 Apr 2015 18:26:26 -0700 Subject: [PATCH 51/92] Fixed release builds on UIExplorer --- Libraries/RCTTest/RCTTestModule.h | 1 + Libraries/RCTTest/RCTTestRunner.m | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h index a8a2da16e..f248cbfca 100644 --- a/Libraries/RCTTest/RCTTestModule.h +++ b/Libraries/RCTTest/RCTTestModule.h @@ -10,6 +10,7 @@ #import #import "RCTBridgeModule.h" +#import "RCTDefines.h" @class FBSnapshotTestController; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 75a811831..4d5963743 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -10,6 +10,7 @@ #import "RCTTestRunner.h" #import "FBSnapshotTestController.h" +#import "RCTDefines.h" #import "RCTRedBox.h" #import "RCTRootView.h" #import "RCTTestModule.h" @@ -75,6 +76,8 @@ vc.view = [[UIView alloc] init]; [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized +#if RCT_DEBUG // Prevents build errors, as RCTRedBox is underfined if RCT_DEBUG=0 + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) { @@ -95,6 +98,13 @@ } else { RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS); } + +#else + + expectErrorBlock(@"RCTRedBox unavailable. Set RCT_DEBUG=1 for testing."); + +#endif + } @end From 901c24ebb8d81885043c8d236ed07e756ab81c6a Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 21 Apr 2015 19:13:40 -0700 Subject: [PATCH 52/92] [Text] Ensure that the text background is transparent by default Summary: For a very simple view I was observing that the text background was black and had to manually be set to transparent. This ensures that text nodes have a transparent background by default. Closes https://github.com/facebook/react-native/pull/256 Github Author: James Ide Test Plan: This example component no longer renders what looks like a black block, and instead displays legible text. var Example = React.createClass({ render: function() { return ( hello ); }, }); var styles = StyleSheet.create({ container: { flex: 1, }, }; --- Libraries/Text/RCTText.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index 84f6b85e1..e51686ac7 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -25,6 +25,7 @@ if ((self = [super initWithFrame:frame])) { _textStorage = [[NSTextStorage alloc] init]; + self.opaque = NO; self.contentMode = UIViewContentModeRedraw; } From 58a550fa0625f31108e06f32ebb2958e2ffefcf0 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 21 Apr 2015 21:07:17 -0700 Subject: [PATCH 53/92] [ReactNative] use requireNativeComponent to clean up a bunch of boilerplate --- .../ActivityIndicatorIOS.ios.js | 26 ++++---- .../DatePicker/DatePickerIOS.ios.js | 19 +----- Libraries/Components/MapView/MapView.js | 62 +++++++------------ .../Components/SwitchIOS/SwitchIOS.ios.js | 27 ++------ .../Components/TabBarIOS/TabBarIOS.ios.js | 9 +-- .../Components/TabBarIOS/TabBarItemIOS.ios.js | 17 +---- Libraries/Components/View/View.js | 16 ++++- Libraries/Components/WebView/WebView.ios.js | 20 +----- Libraries/ReactIOS/verifyPropTypes.js | 2 +- React/Views/RCTSwitchManager.m | 11 +++- React/Views/RCTTabBarItemManager.m | 2 +- 11 files changed, 77 insertions(+), 134 deletions(-) diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js index f2bcbfd51..3a44020a6 100644 --- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js +++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js @@ -15,13 +15,12 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); var keyMirror = require('keyMirror'); -var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); +var verifyPropTypes = require('verifyPropTypes'); var SpinnerSize = keyMirror({ large: null, @@ -100,14 +99,17 @@ var styles = StyleSheet.create({ } }); -var UIActivityIndicatorView = createReactIOSNativeComponentClass({ - validAttributes: merge( - ReactIOSViewAttributes.UIView, { - activityIndicatorViewStyle: true, // UIActivityIndicatorViewStyle=UIActivityIndicatorViewStyleWhite - animating: true, - color: true, - }), - uiViewClassName: 'UIActivityIndicatorView', -}); +var UIActivityIndicatorView = requireNativeComponent( + 'UIActivityIndicatorView', + null +); +if (__DEV__) { + var nativeOnlyProps = {activityIndicatorViewStyle: true}; + verifyPropTypes( + ActivityIndicatorIOS, + UIActivityIndicatorView.viewConfig, + nativeOnlyProps + ); +} module.exports = ActivityIndicatorIOS; diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index 9bd0a2ac4..41fc9b877 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -16,14 +16,11 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var PropTypes = require('ReactPropTypes'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var RCTDatePickerIOSConsts = require('NativeModules').UIManager.RCTDatePicker.Constants; var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = - require('createReactIOSNativeComponentClass'); -var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); var DATEPICKER = 'datepicker'; @@ -148,18 +145,6 @@ var styles = StyleSheet.create({ }, }); -var rkDatePickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, { - date: true, - maximumDate: true, - minimumDate: true, - mode: true, - minuteInterval: true, - timeZoneOffsetInMinutes: true, -}); - -var RCTDatePickerIOS = createReactIOSNativeComponentClass({ - validAttributes: rkDatePickerIOSAttributes, - uiViewClassName: 'RCTDatePicker', -}); +var RCTDatePickerIOS = requireNativeComponent('RCTDatePicker', DatePickerIOS); module.exports = DatePickerIOS; diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 2f02b1b9d..e38dd9564 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -13,6 +13,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var NativeMethodsMixin = require('NativeMethodsMixin'); +var Platform = require('Platform'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var View = require('View'); @@ -21,6 +22,7 @@ var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentC var deepDiffer = require('deepDiffer'); var insetsDiffer = require('insetsDiffer'); var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); type Event = Object; type MapRegion = { @@ -156,46 +158,30 @@ var MapView = React.createClass({ }, render: function() { - return ( - - ); + return ; }, - }); -var RCTMap = createReactIOSNativeComponentClass({ - validAttributes: merge( - ReactIOSViewAttributes.UIView, { - showsUserLocation: true, - zoomEnabled: true, - rotateEnabled: true, - pitchEnabled: true, - scrollEnabled: true, - region: {diff: deepDiffer}, - annotations: {diff: deepDiffer}, - maxDelta: true, - minDelta: true, - legalLabelInsets: {diff: insetsDiffer}, - } - ), - uiViewClassName: 'RCTMap', -}); +if (Platform.OS === 'android') { + var RCTMap = createReactIOSNativeComponentClass({ + validAttributes: merge( + ReactIOSViewAttributes.UIView, { + showsUserLocation: true, + zoomEnabled: true, + rotateEnabled: true, + pitchEnabled: true, + scrollEnabled: true, + region: {diff: deepDiffer}, + annotations: {diff: deepDiffer}, + maxDelta: true, + minDelta: true, + legalLabelInsets: {diff: insetsDiffer}, + } + ), + uiViewClassName: 'RCTMap', + }); +} else { + var RCTMap = requireNativeComponent('RCTMap', MapView); +} module.exports = MapView; diff --git a/Libraries/Components/SwitchIOS/SwitchIOS.ios.js b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js index 702227947..5a56e36b7 100644 --- a/Libraries/Components/SwitchIOS/SwitchIOS.ios.js +++ b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js @@ -16,11 +16,9 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var PropTypes = require('ReactPropTypes'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); -var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); var SWITCH = 'switch'; @@ -88,20 +86,16 @@ var SwitchIOS = React.createClass({ // The underlying switch might have changed, but we're controlled, // and so want to ensure it represents our value. - this.refs[SWITCH].setNativeProps({on: this.props.value}); + this.refs[SWITCH].setNativeProps({value: this.props.value}); }, render: function() { return ( ); } @@ -114,17 +108,6 @@ var styles = StyleSheet.create({ }, }); -var rkSwitchAttributes = merge(ReactIOSViewAttributes.UIView, { - onTintColor: true, - tintColor: true, - thumbTintColor: true, - on: true, - enabled: true, -}); - -var RCTSwitch = createReactIOSNativeComponentClass({ - validAttributes: rkSwitchAttributes, - uiViewClassName: 'RCTSwitch', -}); +var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS); module.exports = SwitchIOS; diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js index 05ac37c74..4163b2d78 100644 --- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js @@ -12,12 +12,11 @@ 'use strict'; var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheet = require('StyleSheet'); var TabBarItemIOS = require('TabBarItemIOS'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var requireNativeComponent = require('requireNativeComponent'); var TabBarIOS = React.createClass({ statics: { @@ -43,10 +42,6 @@ var styles = StyleSheet.create({ } }); -var config = { - validAttributes: ReactIOSViewAttributes.UIView, - uiViewClassName: 'RCTTabBar', -}; -var RCTTabBar = createReactIOSNativeComponentClass(config); +var RCTTabBar = requireNativeComponent('RCTTabBar', TabBarIOS); module.exports = TabBarIOS; diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index 86b38a8cd..32945c434 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -13,13 +13,11 @@ var Image = require('Image'); var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); -var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); var TabBarItemIOS = React.createClass({ propTypes: { @@ -121,7 +119,7 @@ var TabBarItemIOS = React.createClass({ selectedIcon={this.props.selectedIcon && this.props.selectedIcon.uri} onPress={this.props.onPress} selected={this.props.selected} - badgeValue={badge} + badge={badge} title={this.props.title} style={[styles.tab, this.props.style]}> {tabContents} @@ -140,15 +138,6 @@ var styles = StyleSheet.create({ } }); -var RCTTabBarItem = createReactIOSNativeComponentClass({ - validAttributes: merge(ReactIOSViewAttributes.UIView, { - title: true, - icon: true, - selectedIcon: true, - selected: true, - badgeValue: true, - }), - uiViewClassName: 'RCTTabBarItem', -}); +var RCTTabBarItem = requireNativeComponent('RCTTabBarItem', TabBarItemIOS); module.exports = TabBarItemIOS; diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 0fdfa1fc8..0da57f554 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -13,12 +13,13 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var PropTypes = require('ReactPropTypes'); +var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); +var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); var ViewStylePropTypes = require('ViewStylePropTypes'); - var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); var stylePropType = StyleSheetPropType(ViewStylePropTypes); @@ -157,17 +158,26 @@ var View = React.createClass({ }, }); - var RCTView = createReactIOSNativeComponentClass({ validAttributes: ReactIOSViewAttributes.RCTView, uiViewClassName: 'RCTView', }); RCTView.propTypes = View.propTypes; +if (__DEV__) { + var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {}; + for (var prop in viewConfig.nativeProps) { + var viewAny: any = View; // Appease flow + if (!viewAny.propTypes[prop] && !ReactIOSStyleAttributes[prop]) { + throw new Error( + 'View is missing propType for native prop `' + prop + '`' + ); + } + } +} var ViewToExport = RCTView; if (__DEV__) { ViewToExport = View; } - module.exports = ViewToExport; diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 6257c12b7..c4e4fbcd3 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -19,16 +19,13 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var invariant = require('invariant'); var keyMirror = require('keyMirror'); -var insetsDiffer = require('insetsDiffer'); -var merge = require('merge'); +var requireNativeComponent = require('requireNativeComponent'); var PropTypes = React.PropTypes; var RCTWebViewManager = require('NativeModules').WebViewManager; -var invariant = require('invariant'); - var BGWASH = 'rgba(255,255,255,0.8)'; var RCT_WEBVIEW_REF = 'webview'; @@ -213,18 +210,7 @@ var WebView = React.createClass({ }, }); -var RCTWebView = createReactIOSNativeComponentClass({ - validAttributes: merge(ReactIOSViewAttributes.UIView, { - url: true, - html: true, - bounces: true, - scrollEnabled: true, - contentInset: {diff: insetsDiffer}, - automaticallyAdjustContentInsets: true, - shouldInjectAJAXHandler: true - }), - uiViewClassName: 'RCTWebView', -}); +var RCTWebView = requireNativeComponent('RCTWebView', WebView); var styles = StyleSheet.create({ container: { diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js index 032e572ec..ab1d61728 100644 --- a/Libraries/ReactIOS/verifyPropTypes.js +++ b/Libraries/ReactIOS/verifyPropTypes.js @@ -23,7 +23,7 @@ function verifyPropTypes( return; // This happens for UnimplementedView. } var nativeProps = viewConfig.nativeProps; - for (var prop in viewConfig.nativeProps) { + for (var prop in nativeProps) { if (!component.propTypes[prop] && !View.propTypes[prop] && !ReactIOSStyleAttributes[prop] && diff --git a/React/Views/RCTSwitchManager.m b/React/Views/RCTSwitchManager.m index eb0d626e6..c60d83e81 100644 --- a/React/Views/RCTSwitchManager.m +++ b/React/Views/RCTSwitchManager.m @@ -42,7 +42,14 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor); RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor); RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor); -RCT_EXPORT_VIEW_PROPERTY(on, BOOL); -RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL); +RCT_REMAP_VIEW_PROPERTY(value, on, BOOL); +RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSwitch) +{ + if (json) { + view.enabled = !([RCTConvert BOOL:json]); + } else { + view.enabled = defaultView.enabled; + } +} @end diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m index 8bbe782b7..cdfa8669c 100644 --- a/React/Views/RCTTabBarItemManager.m +++ b/React/Views/RCTTabBarItemManager.m @@ -24,7 +24,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(selected, BOOL); RCT_EXPORT_VIEW_PROPERTY(icon, NSString); RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage); -RCT_REMAP_VIEW_PROPERTY(badgeValue, barItem.badgeValue, NSString); +RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString); RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem) { view.barItem.title = json ? [RCTConvert NSString:json] : defaultView.barItem.title; From 0f7ebf23a92f464e3b124a40f9bb89b8505ff874 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 22 Apr 2015 03:45:08 -0700 Subject: [PATCH 54/92] [react_native] JS files from D1999034: [react_native] Fix source maps on Android --- .../JavaScriptAppEngine/Initialization/loadSourceMap.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js index 9ecf2543b..4d4d21e25 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js @@ -39,7 +39,10 @@ function fetchSourceMap(): Promise { .then(response => response.text()) } -function extractSourceMapURL({url, text}): string { +function extractSourceMapURL({url, text, fullSourceMappingURL}): string { + if (fullSourceMappingURL) { + return fullSourceMappingURL; + } var mapURL = SourceMapURL.getFrom(text); var baseURL = url.match(/(.+:\/\/.*?)\//)[1]; return baseURL + mapURL; From 462224727aa5ee5f0d8db8d94a13fd8e305e2e45 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 22 Apr 2015 04:03:46 -0700 Subject: [PATCH 55/92] Reduced prop mapping overhead --- Libraries/ReactIOS/requireNativeComponent.js | 9 ++-- React/Modules/RCTUIManager.m | 44 ++++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index 55ad8a6b9..4d9241853 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -41,18 +41,19 @@ function requireNativeComponent( viewName: string, wrapperComponent: ?Function ): Function { - var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName]; - if (!viewConfig) { + var viewConfig = RCTUIManager[viewName]; + if (!viewConfig || !viewConfig.nativeProps) { return UnimplementedView; } var nativeProps = { - ...RCTUIManager.viewConfigs.RCTView.nativeProps, + ...RCTUIManager.RCTView.nativeProps, ...viewConfig.nativeProps, }; + viewConfig.uiViewClassName = viewName; viewConfig.validAttributes = {}; for (var key in nativeProps) { // TODO: deep diff by default in diffRawProperties instead of setting it here - var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer; + var differ = TypeToDifferMap[nativeProps[key]] || deepDiffer; viewConfig.validAttributes[key] = {diff: differ}; } if (__DEV__) { diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index fc688e491..b7ae182ba 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -225,29 +225,23 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) // TODO: only send name once instead of a dictionary of name and type keyed by name static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewName) { - NSMutableDictionary *nativeProps = [[NSMutableDictionary alloc] init]; - static const char *prefix = "getPropConfig"; - static const NSUInteger prefixLength = sizeof("getPropConfig") - 1; - unsigned int methodCount = 0; - Method *methods = class_copyMethodList(objc_getMetaClass(class_getName(managerClass)), &methodCount); - for (unsigned int i = 0; i < methodCount; i++) { + unsigned int count = 0; + Method *methods = class_copyMethodList(object_getClass(managerClass), &count); + NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithCapacity:count]; + for (unsigned int i = 0; i < count; i++) { Method method = methods[i]; - SEL getInfo = method_getName(method); - const char *selName = sel_getName(getInfo); - if (strlen(selName) > prefixLength && strncmp(selName, prefix, prefixLength) == 0) { - NSString *name = @(selName); - NSRange nameRange = [name rangeOfString:@"_"]; + NSString *methodName = NSStringFromSelector(method_getName(method)); + if ([methodName hasPrefix:@"getPropConfig"]) { + NSRange nameRange = [methodName rangeOfString:@"_"]; if (nameRange.length) { - name = [name substringFromIndex:nameRange.location + 1]; - NSString *type = ((NSString *(*)(id, SEL))method_getImplementation(method))(managerClass, getInfo); - nativeProps[name] = @{@"name": name, @"type": type}; + NSString *name = [methodName substringFromIndex:nameRange.location + 1]; + NSString *type = [managerClass valueForKey:methodName]; + props[name] = type; } } } - return @{ - @"uiViewClassName": viewName, - @"nativeProps": nativeProps - }; + free(methods); + return props; } - (instancetype)init @@ -1390,17 +1384,22 @@ RCT_EXPORT_METHOD(clearJSResponder) } mutableCopy]; [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) { + NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; + + // Add custom constants // TODO: should these be inherited? NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil; if (constants.count) { - NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name); // add an additional 'Constants' namespace for each class constantsNamespace[@"Constants"] = constants; - allJSConstants[name] = [constantsNamespace copy]; } + + // Add native props + constantsNamespace[@"nativeProps"] = _viewConfigs[name]; + + allJSConstants[name] = [constantsNamespace copy]; }]; - allJSConstants[@"viewConfigs"] = _viewConfigs; return allJSConstants; } @@ -1409,8 +1408,7 @@ RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config errorCallback:(RCTResponseSenderBlock)errorCallback) { if (_nextLayoutAnimation) { - RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", - _nextLayoutAnimation, config); + RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config); } if (config[@"delete"] != nil) { RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config); From 3595b79ec3d270107e797d3f1ad5583098d8147d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 22 Apr 2015 07:03:55 -0700 Subject: [PATCH 56/92] [ReactNative] Move VSync bound events to JS thread --- .../RCTWebSocketExecutor.m | 5 ++ React/Base/RCTBridge.m | 39 +++++--- React/Base/RCTJavaScriptExecutor.h | 7 ++ React/Base/RCTProfile.m | 88 ++++++++++++------- React/Modules/RCTTiming.m | 2 - 5 files changed, 91 insertions(+), 50 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index eb6428fc2..753c8d76d 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -175,6 +175,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); }); } +- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block +{ + dispatch_async(dispatch_get_main_queue(), block); +} + - (void)invalidate { _socket.delegate = nil; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 1c676640e..a4a4362b0 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -694,7 +694,7 @@ static NSDictionary *RCTLocalModulesConfig() @interface RCTDisplayLink : NSObject -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER; @end @@ -708,14 +708,16 @@ static NSDictionary *RCTLocalModulesConfig() { __weak RCTBridge *_bridge; CADisplayLink *_displayLink; + SEL _selector; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector { if ((self = [super init])) { _bridge = bridge; + _selector = selector; _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } return self; } @@ -735,7 +737,10 @@ static NSDictionary *RCTLocalModulesConfig() - (void)_update:(CADisplayLink *)displayLink { - [_bridge _update:displayLink]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [_bridge performSelector:_selector withObject:displayLink]; +#pragma clang diagnostic pop } @end @@ -770,6 +775,7 @@ static NSDictionary *RCTLocalModulesConfig() NSURL *_bundleURL; RCTBridgeModuleProviderBlock _moduleProvider; RCTDisplayLink *_displayLink; + RCTDisplayLink *_vsyncDisplayLink; NSMutableSet *_frameUpdateObservers; NSMutableArray *_scheduledCalls; RCTSparseArray *_scheduledCallbacks; @@ -799,11 +805,15 @@ static id _latestJSExecutor; _latestJSExecutor = _javaScriptExecutor; _eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self]; _methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL); - _displayLink = [[RCTDisplayLink alloc] initWithBridge:self]; _frameUpdateObservers = [[NSMutableSet alloc] init]; _scheduledCalls = [[NSMutableArray alloc] init]; _scheduledCallbacks = [[RCTSparseArray alloc] init]; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + _displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)]; + }]; + _vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)]; + // Register passed-in module instances NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; for (id module in _moduleProvider ? _moduleProvider() : nil) { @@ -1008,6 +1018,7 @@ static id _latestJSExecutor; _javaScriptExecutor = nil; [_displayLink invalidate]; + [_vsyncDisplayLink invalidate]; _frameUpdateObservers = nil; // Invalidate modules @@ -1294,9 +1305,9 @@ static id _latestJSExecutor; return YES; } -- (void)_update:(CADisplayLink *)displayLink +- (void)_jsThreadUpdate:(CADisplayLink *)displayLink { - RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); RCTProfileBeginEvent(); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; @@ -1306,13 +1317,6 @@ static id _latestJSExecutor; } } - [self _runScheduledCalls]; - - RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); -} - -- (void)_runScheduledCalls -{ #if BATCHED_BRIDGE NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; @@ -1330,6 +1334,13 @@ static id _latestJSExecutor; } #endif + + RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); +} + +- (void)_mainThreadUpdate:(CADisplayLink *)displayLink +{ + RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); } - (void)addFrameUpdateObserver:(id)observer diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 8ff5a1658..eb7fd7d31 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -42,6 +42,13 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete; + +/** + * Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async` + * on the main queue if the executor doesn't own a thread. + */ +- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; + @end static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 929a026a9..19c6900c7 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -36,6 +36,7 @@ NSDictionary *RCTProfileInfo; NSUInteger RCTProfileEventID = 0; NSMutableDictionary *RCTProfileOngoingEvents; NSTimeInterval RCTProfileStartTime; +NSLock *_RCTProfileLock; #pragma mark - Macros @@ -51,6 +52,11 @@ if (!RCTProfileIsProfiling()) { \ return __VA_ARGS__; \ } +#define RCTProfileLock(...) \ +[_RCTProfileLock lock]; \ +__VA_ARGS__ \ +[_RCTProfileLock unlock] + #pragma mark - Private Helpers NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) @@ -66,7 +72,6 @@ NSString *RCTProfileMemory(vm_size_t memory) NSDictionary *RCTProfileGetMemoryUsage(void) { - CHECK(@{}); struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), @@ -88,66 +93,81 @@ NSDictionary *RCTProfileGetMemoryUsage(void) BOOL RCTProfileIsProfiling(void) { - return RCTProfileInfo != nil; + RCTProfileLock( + BOOL profiling = RCTProfileInfo != nil; + ); + return profiling; } void RCTProfileInit(void) { - RCTProfileStartTime = CACurrentMediaTime(); - RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; - RCTProfileInfo = @{ - RCTProfileTraceEvents: [[NSMutableArray alloc] init], - RCTProfileSamples: [[NSMutableArray alloc] init], - }; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _RCTProfileLock = [[NSLock alloc] init]; + }); + RCTProfileLock( + RCTProfileStartTime = CACurrentMediaTime(); + RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; + RCTProfileInfo = @{ + RCTProfileTraceEvents: [[NSMutableArray alloc] init], + RCTProfileSamples: [[NSMutableArray alloc] init], + }; + ); } NSString *RCTProfileEnd(void) { - NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); - RCTProfileEventID = 0; - RCTProfileInfo = nil; - RCTProfileOngoingEvents = nil; + RCTProfileLock( + NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); + RCTProfileEventID = 0; + RCTProfileInfo = nil; + RCTProfileOngoingEvents = nil; + ); return log; } NSNumber *_RCTProfileBeginEvent(void) { CHECK(@0); - NSNumber *eventID = @(++RCTProfileEventID); - RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); + RCTProfileLock( + NSNumber *eventID = @(++RCTProfileEventID); + RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); + ); return eventID; } void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args) { CHECK(); - NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; - if (!startTimestamp) { - return; - } + RCTProfileLock( + NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; + if (startTimestamp) { + NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); - NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); - - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"cat": categories, - @"ph": @"X", - @"ts": startTimestamp, - @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), - @"args": args ?: @[], + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"cat": categories, + @"ph": @"X", + @"ts": startTimestamp, + @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), + @"args": args ?: @[], + ); + [RCTProfileOngoingEvents removeObjectForKey:eventID]; + } ); - [RCTProfileOngoingEvents removeObjectForKey:eventID]; } void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope) { CHECK(); - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"ts": RCTProfileTimestamp(timestamp), - @"scope": scope, - @"ph": @"i", - @"args": RCTProfileGetMemoryUsage(), + RCTProfileLock( + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": name, + @"ts": RCTProfileTimestamp(timestamp), + @"scope": scope, + @"ph": @"i", + @"args": RCTProfileGetMemoryUsage(), + ); ); } diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 62d42a7bb..1d99c1a2d 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -142,8 +142,6 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers) - (void)didUpdateFrame:(RCTFrameUpdate *)update { - RCTAssertMainThread(); - NSMutableArray *timersToCall = [[NSMutableArray alloc] init]; for (RCTTimer *timer in _timers.allObjects) { if ([timer updateFoundNeedsJSUpdate]) { From eafe93096caa3cad22c5ccbaa64abd9708825f70 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 22 Apr 2015 09:10:58 -0700 Subject: [PATCH 57/92] [react_native] JS files from D2012956: [react_native] Never return null from XHR.getAllResponseHeaders if request has completed --- Libraries/Network/XMLHttpRequestBase.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js index cfd1e35cc..4a4f16ac6 100644 --- a/Libraries/Network/XMLHttpRequestBase.js +++ b/Libraries/Network/XMLHttpRequestBase.js @@ -60,6 +60,7 @@ class XMLHttpRequestBase { } return headers.join('\n'); } + // according to the spec, return null <==> no response has been received return null; } @@ -131,7 +132,7 @@ class XMLHttpRequestBase { return; } this.status = status; - this.responseHeaders = responseHeaders; + this.responseHeaders = responseHeaders || {}; this.responseText = responseText; this._setReadyState(this.DONE); this._sendLoad(); From 7aa413d6198fef69be1ac98200d4fa75daa54519 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 22 Apr 2015 10:09:02 -0700 Subject: [PATCH 58/92] [ReactNative] Fix Android back btn regression from D2010265 --- Libraries/CustomComponents/Navigator/Navigator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index f4fcdd443..ea8977ff8 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -459,7 +459,7 @@ var Navigator = React.createClass({ }, _handleAndroidBackPress: function() { - var didPop = this.pop(); + var didPop = this.requestPop(); if (!didPop) { BackAndroid.exitApp(); } From 0727cde42cda9d1e8c495297a789e4166ae12455 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 22 Apr 2015 10:10:56 -0700 Subject: [PATCH 59/92] [ReactNative] Quick fix to busted redboxes --- .../JavaScriptAppEngine/Initialization/ExceptionsManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index d9118b748..c5476eaab 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -28,7 +28,7 @@ type Exception = { function handleException(e: Exception) { var stack = parseErrorStack(e); console.error( - 'Error: ' + + 'Err0r: ' + '\n stack: \n' + stackToString(stack) + '\n URL: ' + e.sourceURL + '\n line: ' + e.line + From b4c82a4089d2eed5ed79120886852cfeb35fae67 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 22 Apr 2015 11:04:24 -0700 Subject: [PATCH 60/92] [react-packager] Additional data to asset modules --- .../AssetServer/__tests__/AssetServer-test.js | 145 ++++++++++++------ .../react-packager/src/AssetServer/index.js | 63 ++++++-- .../__tests__/DependencyGraph-test.js | 2 +- .../haste/DependencyGraph/index.js | 6 +- .../src/Packager/__tests__/Packager-test.js | 31 +++- packager/react-packager/src/Packager/index.js | 21 ++- packager/react-packager/src/Server/index.js | 9 +- packager/react-packager/src/__mocks__/fs.js | 12 +- .../__tests__/extractAssetResolution-test.js | 24 ++- ...tResolution.js => getAssetDataFromName.js} | 9 +- 10 files changed, 227 insertions(+), 95 deletions(-) rename packager/react-packager/src/lib/{extractAssetResolution.js => getAssetDataFromName.js} (63%) diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js index eede72c0c..94dba8cff 100644 --- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -3,6 +3,7 @@ jest .autoMockOff() .mock('../../lib/declareOpts') + .mock('crypto') .mock('fs'); var fs = require('fs'); @@ -10,63 +11,65 @@ var AssetServer = require('../'); var Promise = require('bluebird'); describe('AssetServer', function() { - pit('should work for the simple case', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); + describe('assetServer.get', function() { + pit('should work for the simple case', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', - 'b@2x.png': 'b2 image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + 'b@2x.png': 'b2 image', + } } - } - }); + }); - return Promise.all([ - server.get('imgs/b.png'), - server.get('imgs/b@1x.png'), - ]).then(function(resp) { - resp.forEach(function(data) { - expect(data).toBe('b image'); + return Promise.all([ + server.get('imgs/b.png'), + server.get('imgs/b@1x.png'), + ]).then(function(resp) { + resp.forEach(function(data) { + expect(data).toBe('b image'); + }); }); }); - }); - pit.only('should pick the bigger one', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); + pit('should pick the bigger one', function() { + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.png': 'b1 image', - 'b@2x.png': 'b2 image', - 'b@4x.png': 'b4 image', - 'b@4.5x.png': 'b4.5 image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + } } - } + }); + + return server.get('imgs/b@3x.png').then(function(data) { + expect(data).toBe('b4 image'); + }); }); - return server.get('imgs/b@3x.png').then(function(data) { - expect(data).toBe('b4 image'); - }); - }); + pit('should support multiple project roots', function() { + var server = new AssetServer({ + projectRoots: ['/root', '/root2'], + assetExts: ['png'], + }); - pit('should support multiple project roots', function() { - var server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.png': 'b image', + }, }, 'root2': { 'newImages': { @@ -75,11 +78,53 @@ describe('AssetServer', function() { }, }, }, - } - }); + }); - return server.get('newImages/imgs/b.png').then(function(data) { - expect(data).toBe('b1 image'); + return server.get('newImages/imgs/b.png').then(function(data) { + expect(data).toBe('b1 image'); + }); + }); + }); + + describe('assetSerer.getAssetData', function() { + pit('should get assetData', function() { + var hash = { + update: jest.genMockFn(), + digest: jest.genMockFn(), + }; + + hash.digest.mockImpl(function() { + return 'wow such hash'; + }); + require('crypto').createHash.mockImpl(function() { + return hash; + }); + + var server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + } + } + }); + + return server.getAssetData('imgs/b.png').then(function(data) { + expect(hash.update.mock.calls.length).toBe(4); + expect(data).toEqual({ + type: 'png', + name: 'b', + scales: [1, 2, 4, 4.5], + hash: 'wow such hash', + }); + }); }); }); }); diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js index bdabafff4..6f07dd01d 100644 --- a/packager/react-packager/src/AssetServer/index.js +++ b/packager/react-packager/src/AssetServer/index.js @@ -9,10 +9,11 @@ 'use strict'; var declareOpts = require('../lib/declareOpts'); -var extractAssetResolution = require('../lib/extractAssetResolution'); +var getAssetDataFromName = require('../lib/getAssetDataFromName'); var path = require('path'); var Promise = require('bluebird'); var fs = require('fs'); +var crypto = require('crypto'); var lstat = Promise.promisify(fs.lstat); var readDir = Promise.promisify(fs.readdir); @@ -44,11 +45,11 @@ function AssetServer(options) { * * 1. We first parse the directory of the asset * 2. We check to find a matching directory in one of the project roots - * 3. We then build a map of all assets and their resolutions in this directory + * 3. We then build a map of all assets and their scales in this directory * 4. Then pick the closest resolution (rounding up) to the requested one */ -AssetServer.prototype.get = function(assetPath) { +AssetServer.prototype._getAssetRecord = function(assetPath) { var filename = path.basename(assetPath); return findRoot( @@ -60,13 +61,7 @@ AssetServer.prototype.get = function(assetPath) { readDir(dir), ]; }).spread(function(dir, files) { - // Easy case. File exactly what the client requested. - var index = files.indexOf(filename); - if (index > -1) { - return readFile(path.join(dir, filename)); - } - - var assetData = extractAssetResolution(filename); + var assetData = getAssetDataFromName(filename); var map = buildAssetMap(dir, files); var record = map[assetData.assetName]; @@ -74,8 +69,15 @@ AssetServer.prototype.get = function(assetPath) { throw new Error('Asset not found'); } - for (var i = 0; i < record.resolutions.length; i++) { - if (record.resolutions[i] >= assetData.resolution) { + return record; + }); +}; + +AssetServer.prototype.get = function(assetPath) { + var assetData = getAssetDataFromName(assetPath); + return this._getAssetRecord(assetPath).then(function(record) { + for (var i = 0; i < record.scales.length; i++) { + if (record.scales[i] >= assetData.resolution) { return readFile(record.files[i]); } } @@ -84,6 +86,33 @@ AssetServer.prototype.get = function(assetPath) { }); }; +AssetServer.prototype.getAssetData = function(assetPath) { + var nameData = getAssetDataFromName(assetPath); + var data = { + name: nameData.name, + type: 'png', + }; + + return this._getAssetRecord(assetPath).then(function(record) { + data.scales = record.scales; + + return Promise.all( + record.files.map(function(file) { + return lstat(file); + }) + ); + }).then(function(stats) { + var hash = crypto.createHash('md5'); + + stats.forEach(function(stat) { + hash.update(stat.mtime.getTime().toString()); + }); + + data.hash = hash.digest('hex'); + return data; + }); +}; + function findRoot(roots, dir) { return Promise.some( roots.map(function(root) { @@ -105,26 +134,26 @@ function findRoot(roots, dir) { } function buildAssetMap(dir, files) { - var assets = files.map(extractAssetResolution); + var assets = files.map(getAssetDataFromName); var map = Object.create(null); assets.forEach(function(asset, i) { var file = files[i]; var record = map[asset.assetName]; if (!record) { record = map[asset.assetName] = { - resolutions: [], + scales: [], files: [], }; } var insertIndex; - var length = record.resolutions.length; + var length = record.scales.length; for (insertIndex = 0; insertIndex < length; insertIndex++) { - if (asset.resolution < record.resolutions[insertIndex]) { + if (asset.resolution < record.scales[insertIndex]) { break; } } - record.resolutions.splice(insertIndex, 0, asset.resolution); + record.scales.splice(insertIndex, 0, asset.resolution); record.files.splice(insertIndex, 0, path.join(dir, file)); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index cda717e87..9cb08122c 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -14,7 +14,7 @@ jest .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') - .dontMock('../../../../lib/extractAssetResolution') + .dontMock('../../../../lib/getAssetDataFromName') .setMock('../../../ModuleDescriptor', function(data) {return data;}); describe('DependencyGraph', function() { diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 6afb5f25c..08a4b513b 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -18,7 +18,7 @@ var isAbsolutePath = require('absolute-path'); var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); -var extractAssetResolution = require('../../../lib/extractAssetResolution'); +var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -422,7 +422,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { var module; if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = extractAssetResolution(this._lookupName(modulePath)); + var assetData = getAssetDataFromName(this._lookupName(modulePath)); moduleData.id = assetData.assetName; moduleData.resolution = assetData.resolution; moduleData.isAsset = true; @@ -651,7 +651,7 @@ DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { path: path.resolve(file), isAsset_DEPRECATED: true, dependencies: [], - resolution: extractAssetResolution(file).resolution, + resolution: getAssetDataFromName(file).resolution, }); } }; diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 763e6dd6d..8e1420a3a 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -43,11 +43,20 @@ describe('Packager', function() { }; }); + require('fs').readFile.mockImpl(function(file, callback) { callback(null, '{"json":true}'); }); - var packager = new Packager({projectRoots: ['/root']}); + var assetServer = { + getAssetData: jest.genMockFn(), + }; + + var packager = new Packager({ + projectRoots: ['/root'], + assetServer: assetServer, + }); + var modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, @@ -97,6 +106,15 @@ describe('Packager', function() { cb(null, { width: 50, height: 100 }); }); + assetServer.getAssetData.mockImpl(function() { + return { + scales: [1,2,3], + hash: 'i am a hash', + name: 'img', + type: 'png', + }; + }); + return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0]).toEqual([ @@ -111,6 +129,7 @@ describe('Packager', function() { ]); var imgModule_DEPRECATED = { + __packager_asset: true, isStatic: true, path: '/root/img/img.png', uri: 'img', @@ -130,11 +149,15 @@ describe('Packager', function() { ]); var imgModule = { - isStatic: true, - path: '/root/img/new_image.png', - uri: 'assets/img/new_image.png', + __packager_asset: true, + fileSystemLocation: '/root/img', + httpServerLocation: '/assets/img', width: 25, height: 50, + scales: [1, 2, 3], + hash: 'i am a hash', + name: 'img', + type: 'png', }; expect(p.addModule.mock.calls[3]).toEqual([ diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index c651dde78..8563e2728 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -67,6 +67,10 @@ var validateOpts = declareOpts({ type: 'object', required: true, }, + assetServer: { + type: 'object', + required: true, + } }); function Packager(options) { @@ -94,6 +98,7 @@ function Packager(options) { }); this._projectRoots = opts.projectRoots; + this._assetServer = opts.assetServer; } Packager.prototype.kill = function() { @@ -173,6 +178,7 @@ Packager.prototype.getGraphDebugInfo = function() { Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { return sizeOf(module.path).then(function(dimensions) { var img = { + __packager_asset: true, isStatic: true, path: module.path, uri: module.id.replace(/^[^!]+!/, ''), @@ -196,13 +202,20 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { Packager.prototype.generateAssetModule = function(ppackage, module) { var relPath = getPathRelativeToRoot(this._projectRoots, module.path); - return sizeOf(module.path).then(function(dimensions) { + return Promise.all([ + sizeOf(module.path), + this._assetServer.getAssetData(relPath), + ]).spread(function(dimensions, assetData) { var img = { - isStatic: true, - path: module.path, //TODO(amasad): this should be path inside tar file. - uri: path.join('assets', relPath), + __packager_asset: true, + fileSystemLocation: path.dirname(module.path), + httpServerLocation: path.join('/assets', path.dirname(relPath)), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, + scales: assetData.scales, + hash: assetData.hash, + name: assetData.name, + type: assetData.type, }; ppackage.addAsset(img); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 525636cb5..79022b211 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -100,15 +100,16 @@ function Server(options) { ? FileWatcher.createDummyWatcher() : new FileWatcher(watchRootConfigs); - var packagerOpts = Object.create(opts); - packagerOpts.fileWatcher = this._fileWatcher; - this._packager = new Packager(packagerOpts); - this._assetServer = new AssetServer({ projectRoots: opts.projectRoots, assetExts: opts.assetExts, }); + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + packagerOpts.assetServer = this._assetServer; + this._packager = new Packager(packagerOpts); + var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); diff --git a/packager/react-packager/src/__mocks__/fs.js b/packager/react-packager/src/__mocks__/fs.js index 0ea13d15d..d0e08a2f4 100644 --- a/packager/react-packager/src/__mocks__/fs.js +++ b/packager/react-packager/src/__mocks__/fs.js @@ -67,6 +67,12 @@ fs.lstat.mockImpl(function(filepath, callback) { return callback(e); } + var mtime = { + getTime: function() { + return Math.ceil(Math.random() * 10000000); + } + }; + if (node && typeof node === 'object' && node.SYMLINK == null) { callback(null, { isDirectory: function() { @@ -74,7 +80,8 @@ fs.lstat.mockImpl(function(filepath, callback) { }, isSymbolicLink: function() { return false; - } + }, + mtime: mtime, }); } else { callback(null, { @@ -86,7 +93,8 @@ fs.lstat.mockImpl(function(filepath, callback) { return true; } return false; - } + }, + mtime: mtime, }); } }); diff --git a/packager/react-packager/src/lib/__tests__/extractAssetResolution-test.js b/packager/react-packager/src/lib/__tests__/extractAssetResolution-test.js index ad5ac3fbf..d0309ca6a 100644 --- a/packager/react-packager/src/lib/__tests__/extractAssetResolution-test.js +++ b/packager/react-packager/src/lib/__tests__/extractAssetResolution-test.js @@ -1,42 +1,52 @@ 'use strict'; jest.autoMockOff(); -var extractAssetResolution = require('../extractAssetResolution'); +var getAssetDataFromName = require('../getAssetDataFromName'); -describe('extractAssetResolution', function() { +describe('getAssetDataFromName', function() { it('should extract resolution simple case', function() { - var data = extractAssetResolution('test@2x.png'); + var data = getAssetDataFromName('test@2x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 2, + type: 'png', + name: 'test', }); }); it('should default resolution to 1', function() { - var data = extractAssetResolution('test.png'); + var data = getAssetDataFromName('test.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 1, + type: 'png', + name: 'test', }); }); it('should support float', function() { - var data = extractAssetResolution('test@1.1x.png'); + var data = getAssetDataFromName('test@1.1x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 1.1, + type: 'png', + name: 'test', }); - data = extractAssetResolution('test@.1x.png'); + data = getAssetDataFromName('test@.1x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 0.1, + type: 'png', + name: 'test', }); - data = extractAssetResolution('test@0.2x.png'); + data = getAssetDataFromName('test@0.2x.png'); expect(data).toEqual({ assetName: 'test.png', resolution: 0.2, + type: 'png', + name: 'test', }); }); }); diff --git a/packager/react-packager/src/lib/extractAssetResolution.js b/packager/react-packager/src/lib/getAssetDataFromName.js similarity index 63% rename from packager/react-packager/src/lib/extractAssetResolution.js rename to packager/react-packager/src/lib/getAssetDataFromName.js index 8fb91afc4..c4848fd17 100644 --- a/packager/react-packager/src/lib/extractAssetResolution.js +++ b/packager/react-packager/src/lib/getAssetDataFromName.js @@ -2,7 +2,7 @@ var path = require('path'); -function extractAssetResolution(filename) { +function getAssetDataFromName(filename) { var ext = path.extname(filename); var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$'); @@ -19,10 +19,13 @@ function extractAssetResolution(filename) { } } + var assetName = match ? filename.replace(re, ext) : filename; return { resolution: resolution, - assetName: match ? filename.replace(re, ext) : filename, + assetName: assetName, + type: ext.slice(1), + name: path.basename(assetName, ext) }; } -module.exports = extractAssetResolution; +module.exports = getAssetDataFromName; From fc6e209223fb80316e5e2cd1dd654af6ae0eb273 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 22 Apr 2015 13:23:48 -0700 Subject: [PATCH 61/92] Fixed broken struct arguments --- Libraries/Geolocation/RCTLocationObserver.m | 7 ++++--- React/Base/RCTBridge.m | 9 ++++++--- React/Modules/RCTUIManager.m | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index 3e864657b..5d56caccb 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -163,12 +163,12 @@ RCT_EXPORT_MODULE() #pragma mark - Public API -RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options) +RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON) { [self checkLocationConfig]; // Select best options - _observerOptions = options; + _observerOptions = [RCTConvert RCTLocationOptions:optionsJSON]; for (RCTLocationRequest *request in _pendingRequests) { _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); } @@ -189,7 +189,7 @@ RCT_EXPORT_METHOD(stopObserving) } } -RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options +RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON withSuccessCallback:(RCTResponseSenderBlock)successBlock errorCallback:(RCTResponseSenderBlock)errorBlock) { @@ -219,6 +219,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options } // Check if previous recorded location exists and is good enough + RCTLocationOptions options = [RCTConvert RCTLocationOptions:optionsJSON]; if (_lastLocationEvent && CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index a4a4362b0..48fd672a3 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -375,10 +375,14 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) RCT_CONVERT_CASE('B', BOOL) RCT_CONVERT_CASE('@', id) RCT_CONVERT_CASE('^', void *) - + case '{': + RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() " + "does not currently support struct-type arguments.", i - 2, + [reactMethodName characterAtIndex:0], _moduleClassName, + objCMethodName, argumentName); + break; default: defaultCase(argumentType); - break; } } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { addBlockArgument(); @@ -434,7 +438,6 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) default: defaultCase(argumentType); - break; } } } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index b7ae182ba..451a343d0 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1001,11 +1001,12 @@ RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag * Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the * passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts. */ -RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect +RCT_EXPORT_METHOD(measureViewsInRect:(id)rectJSON parentView:(NSNumber *)reactTag errorCallback:(RCTResponseSenderBlock)errorCallback callback:(RCTResponseSenderBlock)callback) { + CGRect rect = [RCTConvert CGRect:rectJSON]; RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; if (!shadowView) { RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag); @@ -1101,8 +1102,9 @@ RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag } RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag - withRect:(CGRect)rect) + withRect:(id)rectJSON) { + CGRect rect = [RCTConvert CGRect:rectJSON]; [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ UIView *view = viewRegistry[reactTag]; if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { From c5ea25f7fb9cc162552c71c644d7bf8bd6954024 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 22 Apr 2015 13:11:30 -0700 Subject: [PATCH 62/92] [ReactNative] Adopt client asset managing code to server changes --- Libraries/Image/Image.ios.js | 4 +- .../__tests__/resolveAssetSource-test.js | 67 +++++++++++++------ Libraries/Image/resolveAssetSource.js | 63 ++++++++++------- 3 files changed, 88 insertions(+), 46 deletions(-) diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index be95a3f3f..e917b6b63 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -123,15 +123,13 @@ var Image = React.createClass({ 'not be set directly on Image.'); } } - var source = this.props.source; + var source = resolveAssetSource(this.props.source); invariant(source, 'source must be initialized'); var {width, height} = source; var style = flattenStyle([{width, height}, styles.base, this.props.style]); invariant(style, 'style must be initialized'); - source = resolveAssetSource(source); - var isNetwork = source.uri && source.uri.match(/^https?:/); invariant( !(isNetwork && source.isStatic), diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index 69bcb116a..b385e29aa 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -13,6 +13,10 @@ jest.dontMock('../resolveAssetSource'); var resolveAssetSource; var SourceCode; +function expectResolvesAsset(input, expectedSource) { + expect(resolveAssetSource(input)).toEqual(expectedSource); +} + describe('resolveAssetSource', () => { beforeEach(() => { jest.resetModuleRegistry(); @@ -34,41 +38,66 @@ describe('resolveAssetSource', () => { }); it('uses network image', () => { - var source = { - path: '/Users/react/project/logo.png', - uri: 'assets/logo.png', - }; - expect(resolveAssetSource(source)).toEqual({ + expectResolvesAsset({ + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/module/a', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: 'logo', + type: 'png', + }, { isStatic: false, - uri: 'http://10.0.0.1:8081/assets/logo.png', + width: 100, + height: 200, + uri: 'http://10.0.0.1:8081/assets/module/a/logo.png?hash=5b6f00f', }); }); it('does not change deprecated assets', () => { - // Deprecated require('image!logo') should stay unchanged - var source = { - path: '/Users/react/project/logo.png', - uri: 'logo', + expectResolvesAsset({ + __packager_asset: true, deprecated: true, - }; - expect(resolveAssetSource(source)).toEqual({ + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/module/a', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: 'logo', + type: 'png', + }, { isStatic: true, + width: 100, + height: 200, uri: 'logo', }); }); }); describe('bundle was loaded from file', () => { - it('uses pre-packed image', () => { + beforeEach(() => { SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle'; + }); - var source = { - path: '/Users/react/project/logo.png', - uri: 'assets/logo.png', - }; - expect(resolveAssetSource(source)).toEqual({ + it('uses pre-packed image', () => { + expectResolvesAsset({ + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/module/a', + width: 100, + height: 200, + scales: [1], + hash: '5b6f00f', + name: 'logo', + type: 'png', + }, { isStatic: true, - uri: 'assets/logo.png', + width: 100, + height: 200, + uri: 'assets/module/a/logo.png', }); }); }); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 137f92f1c..2325e2c8c 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -17,9 +17,9 @@ var _serverURL; function getServerURL() { if (_serverURL === undefined) { var scriptURL = SourceCode.scriptURL; - var serverURLMatch = scriptURL && scriptURL.match(/^https?:\/\/.*?\//); - if (serverURLMatch) { - _serverURL = serverURLMatch[0]; + var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//); + if (match) { + _serverURL = match[0]; } else { _serverURL = null; } @@ -29,35 +29,50 @@ function getServerURL() { } // TODO(frantic): -// * Use something other than `path`/`isStatic` for asset identification, `__packager_asset`? -// * Add cache invalidating hashsum -// * Move code that selects scale to client +// * Pick best scale and append @Nx to file path +// * We are currently using httpServerLocation for both http and in-app bundle function resolveAssetSource(source) { + if (!source.__packager_asset) { + return source; + } + + // Deprecated assets are managed by Xcode for now, + // just returning image name as `uri` + // Examples: + // require('image!deprecatd_logo_example') + // require('./new-hotness-logo-example.png') if (source.deprecated) { return { - ...source, - path: undefined, + width: source.width, + height: source.height, isStatic: true, - deprecated: undefined, + uri: source.name || source.uri, // TODO(frantic): remove uri }; } + // TODO(frantic): currently httpServerLocation is used both as + // path in http URL and path within IPA. Should we have zipArchiveLocation? + var path = source.httpServerLocation; + if (path[0] === '/') { + path = path.substr(1); + } + var serverURL = getServerURL(); - if (source.path) { - if (serverURL) { - return { - ...source, - path: undefined, - uri: serverURL + source.uri, - isStatic: false, - }; - } else { - return { - ...source, - path: undefined, - isStatic: true, - }; - } + if (serverURL) { + return { + width: source.width, + height: source.height, + uri: serverURL + path + '/' + source.name + '.' + source.type + + '?hash=' + source.hash, + isStatic: false, + }; + } else { + return { + width: source.width, + height: source.height, + uri: path + '/' + source.name + '.' + source.type, + isStatic: true, + }; } return source; From e63bfae8f648e3136e713736a6d12e1ccc43b8d3 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 22 Apr 2015 15:43:11 -0700 Subject: [PATCH 63/92] [ReactNative] console.error shows RedBox with pretty stack trace --- Libraries/AppRegistry/AppRegistry.js | 2 +- .../Initialization/ExceptionsManager.js | 28 +++++++++++-------- .../InitializeJavaScriptAppEngine.js | 3 +- React/Executors/RCTContextExecutor.m | 17 +++++------ .../haste/polyfills/console.js | 18 ++++++++---- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index f36f88132..63bd1d9a0 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -69,7 +69,7 @@ var AppRegistry = { 'Running application "' + appKey + '" with appParams: ' + JSON.stringify(appParameters) + '. ' + '__DEV__ === ' + __DEV__ + - ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') + + ', development-level warnings are ' + (__DEV__ ? 'ON' : 'OFF') + ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON') ); invariant( diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index c5476eaab..fb57a956a 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -25,17 +25,11 @@ type Exception = { message: string; } -function handleException(e: Exception) { - var stack = parseErrorStack(e); - console.error( - 'Err0r: ' + - '\n stack: \n' + stackToString(stack) + - '\n URL: ' + e.sourceURL + - '\n line: ' + e.line + - '\n message: ' + e.message - ); - +function reportException(e: Exception, stack?: any) { if (RCTExceptionsManager) { + if (!stack) { + stack = parseErrorStack(e); + } RCTExceptionsManager.reportUnhandledException(e.message, stack); if (__DEV__) { (sourceMapPromise = sourceMapPromise || loadSourceMap()) @@ -50,6 +44,18 @@ function handleException(e: Exception) { } } +function handleException(e: Exception) { + var stack = parseErrorStack(e); + console.log( + 'Error: ' + + '\n stack: \n' + stackToString(stack) + + '\n URL: ' + e.sourceURL + + '\n line: ' + e.line + + '\n message: ' + e.message + ); + reportException(e, stack); +} + function stackToString(stack) { var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length)); return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n'); @@ -71,4 +77,4 @@ function fillSpaces(n) { return new Array(n + 1).join(' '); } -module.exports = { handleException }; +module.exports = { handleException, reportException }; diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 51f6809cc..bbfee4eb3 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -77,6 +77,7 @@ function handleErrorWithRedBox(e) { function setupRedBoxErrorHandler() { var ErrorUtils = require('ErrorUtils'); ErrorUtils.setGlobalHandler(handleErrorWithRedBox); + GLOBAL.reportException = require('ExceptionsManager').reportException; } /** @@ -134,8 +135,8 @@ function setupGeolocation() { } setupDocumentShim(); -setupRedBoxErrorHandler(); setupTimers(); +setupRedBoxErrorHandler(); // needs to happen after setupTimers setupAlert(); setupPromise(); setupXHR(); diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 86444dd2a..8f931d96a 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -74,12 +74,12 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount > 0) { - JSStringRef string = JSValueToStringCopy(context, arguments[0], exception); - if (!string) { + JSStringRef messageRef = JSValueToStringCopy(context, arguments[0], exception); + if (!messageRef) { return JSValueMakeUndefined(context); } - NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string); - JSStringRelease(string); + NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef); + JSStringRelease(messageRef); NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: @"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)" options:NSRegularExpressionCaseInsensitive @@ -89,14 +89,11 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, range:(NSRange){0, message.length} withTemplate:@"[$4$5] \t$2"]; - // TODO: it would be good if log level was sent as a param, instead of this hack RCTLogLevel level = RCTLogLevelInfo; - if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) { - level = RCTLogLevelError; - } else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) { - level = RCTLogLevelWarning; + if (argumentCount > 1) { + level = MAX(level, JSValueToNumber(context, arguments[1], exception) - 1); } - _RCTLogFormat(level, NULL, -1, @"%@", message); + RCTGetLogFunction()(level, nil, nil, message); } return JSValueMakeUndefined(context); diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js index 91fb970f8..4f513f45b 100644 --- a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -23,7 +23,7 @@ log: 1, info: 2, warn: 3, - error: 4 + error: 4, }; function setupConsole(global) { @@ -35,8 +35,10 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - if (arg == null) { - return arg === null ? 'null' : 'undefined'; + if (arg === undefined) { + return 'undefined'; + } else if (arg === null) { + return 'null'; } else if (typeof arg === 'string') { return '"' + arg + '"'; } else { @@ -48,14 +50,18 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (E) { - return 'unknown'; - } + } catch (E) {} } + return '["' + typeof arg + '" failed to stringify]'; } } }).join(', '); global.nativeLoggingHook(str, level); + if (global.reportException && level === LOG_LEVELS.error) { + var error = new Error(str); + error.framesToPop = 1; + global.reportException(error); + } }; } From 27252e611c0f7b0c82df79ec67e7b31be502e93d Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 22 Apr 2015 16:16:48 -0700 Subject: [PATCH 64/92] [FBRhinos] add sms cmd for device configuration --- React/Base/RCTBridge.h | 2 +- React/Base/RCTJavaScriptLoader.m | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index a37b9ac7d..6dfbe2414 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -92,7 +92,7 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method; /** * URL of the script that was loaded into the bridge. */ -@property (nonatomic, copy, readonly) NSURL *bundleURL; +@property (nonatomic, copy) NSURL *bundleURL; @property (nonatomic, strong) Class executorClass; diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 4eaaf278d..dd8fab461 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -85,8 +85,9 @@ // Handle general request errors if (error) { if ([[error domain] isEqualToString:NSURLErrorDomain]) { + NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]]; NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root", + NSLocalizedDescriptionKey: desc, NSLocalizedFailureReasonErrorKey: [error localizedDescription], NSUnderlyingErrorKey: error, }; From af61b13b9eef968898955b01d4b02915aa7a3667 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 22 Apr 2015 16:12:46 -0700 Subject: [PATCH 65/92] [RootView] Fix positioning of the root view content (frame -> bounds) Summary: The root view's content was being rendered at the wrong offset when it was not positioned at (0, 0) exactly, because the shadow view's frame was set to the root view's frame when it should have been set to the root view's bounds instead. Closes https://github.com/facebook/react-native/pull/963 Github Author: James Ide Test Plan: Render a root view positioned at (0, 100) and see that its content is positioned where the root view is, not at (0, 200). --- React/Base/RCTRootView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 24cfe8417..45624efdc 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -149,7 +149,7 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) [super layoutSubviews]; if (_contentView) { _contentView.frame = self.bounds; - [_bridge.uiManager setFrame:self.frame forRootView:_contentView]; + [_bridge.uiManager setFrame:self.bounds forRootView:_contentView]; } } From b2e8dc983486f1f7502f137082b78381b63c1545 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 22 Apr 2015 16:30:27 -0700 Subject: [PATCH 66/92] [ReactNative] Backport packager logs redirect --- packager/packager.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packager/packager.sh b/packager/packager.sh index 93a017c35..f763b9bae 100755 --- a/packager/packager.sh +++ b/packager/packager.sh @@ -7,6 +7,12 @@ # 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. +if [ $REACT_PACKAGER_LOG ]; +then + echo "Logs will be redirected to $REACT_PACKAGER_LOG" + exec &> $REACT_PACKAGER_LOG +fi + ulimit -n 4096 THIS_DIR=$(dirname "$0") From ffb3026419561d0cc7518ea2f8e9c0b675a9ff23 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 22 Apr 2015 16:31:13 -0700 Subject: [PATCH 67/92] [ReactNative] Pick correct assets depending on device scale --- .../__tests__/resolveAssetSource-test.js | 30 +++++++++++++++++++ Libraries/Image/resolveAssetSource.js | 24 +++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index b385e29aa..26f3c9ea3 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -56,6 +56,25 @@ describe('resolveAssetSource', () => { }); }); + it('picks matching scale', () => { + expectResolvesAsset({ + __packager_asset: true, + fileSystemLocation: '/root/app/module/a', + httpServerLocation: '/assets/module/a', + width: 100, + height: 200, + scales: [1, 2, 3], + hash: '5b6f00f', + name: 'logo', + type: 'png', + }, { + isStatic: false, + width: 100, + height: 200, + uri: 'http://10.0.0.1:8081/assets/module/a/logo@2x.png?hash=5b6f00f', + }); + }); + it('does not change deprecated assets', () => { expectResolvesAsset({ __packager_asset: true, @@ -103,3 +122,14 @@ describe('resolveAssetSource', () => { }); }); + +describe('resolveAssetSource.pickScale', () => { + it('picks matching scale', () => { + expect(resolveAssetSource.pickScale([1], 2)).toBe(1); + expect(resolveAssetSource.pickScale([1, 2, 3], 2)).toBe(2); + expect(resolveAssetSource.pickScale([1, 2], 3)).toBe(2); + expect(resolveAssetSource.pickScale([1, 2, 3, 4], 3.5)).toBe(4); + expect(resolveAssetSource.pickScale([3, 4], 2)).toBe(3); + expect(resolveAssetSource.pickScale([], 2)).toBe(1); + }); +}); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 2325e2c8c..da136e9a7 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -10,6 +10,7 @@ */ 'use strict'; +var PixelRatio = require('PixelRatio'); var SourceCode = require('NativeModules').SourceCode; var _serverURL; @@ -28,6 +29,20 @@ function getServerURL() { return _serverURL; } +function pickScale(scales, deviceScale) { + // Packager guarantees that `scales` array is sorted + for (var i = 0; i < scales.length; i++) { + if (scales[i] >= deviceScale) { + return scales[i]; + } + } + + // If nothing matches, device scale is larger than any available + // scales, so we return the biggest one. Unless the array is empty, + // in which case we default to 1 + return scales[scales.length - 1] || 1; +} + // TODO(frantic): // * Pick best scale and append @Nx to file path // * We are currently using httpServerLocation for both http and in-app bundle @@ -57,12 +72,16 @@ function resolveAssetSource(source) { path = path.substr(1); } + var scale = pickScale(source.scales, PixelRatio.get()); + var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; + + var fileName = source.name + scaleSuffix + '.' + source.type; var serverURL = getServerURL(); if (serverURL) { return { width: source.width, height: source.height, - uri: serverURL + path + '/' + source.name + '.' + source.type + + uri: serverURL + path + '/' + fileName + '?hash=' + source.hash, isStatic: false, }; @@ -70,7 +89,7 @@ function resolveAssetSource(source) { return { width: source.width, height: source.height, - uri: path + '/' + source.name + '.' + source.type, + uri: path + '/' + fileName, isStatic: true, }; } @@ -79,3 +98,4 @@ function resolveAssetSource(source) { } module.exports = resolveAssetSource; +module.exports.pickScale = pickScale; From 2bda21fbf0e6cd5de0619b776bdaf30ec42dd6c3 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 22 Apr 2015 17:50:54 -0700 Subject: [PATCH 68/92] [ReactNative] Flush ReactUpdates only once per batch --- Libraries/Utilities/MessageQueue.js | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index c047d06de..576aaa991 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -10,7 +10,9 @@ * @flow */ 'use strict'; + var ErrorUtils = require('ErrorUtils'); +var ReactUpdates = require('ReactUpdates'); var invariant = require('invariant'); var warning = require('warning'); @@ -307,21 +309,23 @@ var MessageQueueMixin = { ); }, - processBatch: function (batch) { + processBatch: function(batch) { var self = this; - batch.forEach(function (call) { - invariant( - call.module === 'BatchedBridge', - 'All the calls should pass through the BatchedBridge module' - ); - if (call.method === 'callFunctionReturnFlushedQueue') { - self.callFunction.apply(self, call.args); - } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { - self.invokeCallback.apply(self, call.args); - } else { - throw new Error( - 'Unrecognized method called on BatchedBridge: ' + call.method); - } + ReactUpdates.batchedUpdates(function() { + batch.forEach(function(call) { + invariant( + call.module === 'BatchedBridge', + 'All the calls should pass through the BatchedBridge module' + ); + if (call.method === 'callFunctionReturnFlushedQueue') { + self.callFunction.apply(self, call.args); + } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { + self.invokeCallback.apply(self, call.args); + } else { + throw new Error( + 'Unrecognized method called on BatchedBridge: ' + call.method); + } + }); }); return this.flushedQueue(); }, From 621a30c8b80d8f52eaac9065b0f9b2171919885c Mon Sep 17 00:00:00 2001 From: "Dr. Kibitz" Date: Thu, 23 Apr 2015 04:13:30 -0700 Subject: [PATCH 69/92] Fixes #813 Summary: Also fix RCTShadowText export name. Closes https://github.com/facebook/react-native/pull/857 Github Author: "Dr. Kibitz" Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Text/RCTShadowText.m | 8 +++++++- Libraries/Text/RCTTextManager.m | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 4201b1b4e..4ea9d3bd6 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -22,15 +22,21 @@ static css_dim_t RCTMeasure(void *context, float width) RCTShadowText *shadowText = (__bridge RCTShadowText *)context; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]]; + NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage; + if (previousTextStorage) { + [previousTextStorage removeLayoutManager:shadowText.layoutManager]; + } [textStorage addLayoutManager:shadowText.layoutManager]; shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX); - shadowText.layoutManager.textStorage = textStorage; [shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer]; CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size; [textStorage removeLayoutManager:shadowText.layoutManager]; + if (previousTextStorage) { + [previousTextStorage addLayoutManager:shadowText.layoutManager]; + } css_dim_t result; result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width); diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index b8dabcf2c..e5e9ad00a 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -46,7 +46,7 @@ RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString) RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString) RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL) RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat) -RCT_EXPORT_SHADOW_PROPERTY(maxNumberOfLines, NSInteger) +RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize) RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment) RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor) From c219f618186ba274504493a537b7844e069a81c4 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Thu, 23 Apr 2015 05:42:54 -0700 Subject: [PATCH 70/92] Add minimumTrackTintColor and maximumTrackTintColor to SliderIOS Summary: There are still many other props that can be added to further customize the SliderIOS component, but I had a specific need for these two so I just went ahead and added them. Closes https://github.com/facebook/react-native/pull/799 Github Author: Brent Vatne Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/SliderIOS/SliderIOS.js | 14 ++++++++++++++ React/Views/RCTSliderManager.m | 2 ++ 2 files changed, 16 insertions(+) diff --git a/Libraries/Components/SliderIOS/SliderIOS.js b/Libraries/Components/SliderIOS/SliderIOS.js index 0c2d02da0..bfb2b9271 100644 --- a/Libraries/Components/SliderIOS/SliderIOS.js +++ b/Libraries/Components/SliderIOS/SliderIOS.js @@ -51,6 +51,18 @@ var SliderIOS = React.createClass({ */ maximumValue: PropTypes.number, + /** + * The color used for the track to the left of the button. Overrides the + * default blue gradient image. + */ + minimumTrackTintColor: PropTypes.string, + + /** + * The color used for the track to the right of the button. Overrides the + * default blue gradient image. + */ + maximumTrackTintColor: PropTypes.string, + /** * Callback continuously called while the user is dragging the slider. */ @@ -81,6 +93,8 @@ var SliderIOS = React.createClass({ value={this.props.value} maximumValue={this.props.maximumValue} minimumValue={this.props.minimumValue} + minimumTrackTintColor={this.props.minimumTrackTintColor} + maximumTrackTintColor={this.props.maximumTrackTintColor} onChange={this._onValueChange} /> ); diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m index a103da98d..f57e1f362 100644 --- a/React/Views/RCTSliderManager.m +++ b/React/Views/RCTSliderManager.m @@ -50,5 +50,7 @@ static void RCTSendSliderEvent(RCTSliderManager *self, UISlider *sender, BOOL co RCT_EXPORT_VIEW_PROPERTY(value, float); RCT_EXPORT_VIEW_PROPERTY(minimumValue, float); RCT_EXPORT_VIEW_PROPERTY(maximumValue, float); +RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor); +RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor); @end From a36da5130bbf023ab3a8ece77a81c7a5298541e7 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Thu, 23 Apr 2015 06:10:36 -0700 Subject: [PATCH 71/92] Add Linear easing to AnimationUtils Summary: Simple linear easing also very useful. Closes https://github.com/facebook/react-native/pull/794 Github Author: Ivan Starkov Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Animation/AnimationUtils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Libraries/Animation/AnimationUtils.js b/Libraries/Animation/AnimationUtils.js index d6d95f62d..ac62465a0 100644 --- a/Libraries/Animation/AnimationUtils.js +++ b/Libraries/Animation/AnimationUtils.js @@ -20,6 +20,9 @@ type EasingFunction = (t: number) => number; var defaults = { + linear: function(t: number): number { + return t; + }, easeInQuad: function(t: number): number { return t * t; }, From aa3d3435474ef88b7bdd728ad5f9df0f60fd83ee Mon Sep 17 00:00:00 2001 From: Eduardo Date: Thu, 23 Apr 2015 06:22:16 -0700 Subject: [PATCH 72/92] NavigatorIOS custom nav bar colors Summary: NavigatorIOS with custom nav bar custom colors, working example included in UIExplorer. Based on pull request #318 to complete pending work based on comments. Closes https://github.com/facebook/react-native/pull/695 Github Author: Eduardo Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../UIExplorer/Navigator/NavigatorExample.js | 2 + .../UIExplorer/NavigatorIOSColorsExample.js | 90 +++++++++++++++++++ Examples/UIExplorer/UIExplorerList.js | 10 +-- .../Components/Navigation/NavigatorIOS.ios.js | 14 ++- React/Views/RCTWrapperViewController.m | 5 +- 5 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 Examples/UIExplorer/NavigatorIOSColorsExample.js diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js index ef8001f94..4a182a5a4 100644 --- a/Examples/UIExplorer/Navigator/NavigatorExample.js +++ b/Examples/UIExplorer/Navigator/NavigatorExample.js @@ -175,4 +175,6 @@ var styles = StyleSheet.create({ } }); +TabBarExample.external = true; + module.exports = TabBarExample; diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js new file mode 100644 index 000000000..386586f23 --- /dev/null +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -0,0 +1,90 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigatorIOS, + StatusBarIOS, + StyleSheet, + Text, + View +} = React; + +var EmptyPage = React.createClass({ + + render: function() { + return ( + + + {this.props.text} + + + ); + }, + +}); + +var NavigatorIOSColors = React.createClass({ + + statics: { + title: ' - Custom', + description: 'iOS navigation with custom nav bar colors', + }, + + render: function() { + // Set StatusBar with light contents to get better contrast + StatusBarIOS.setStyle(StatusBarIOS.Style['lightContent']); + + return ( + ', + rightButtonTitle: 'Done', + onRightButtonPress: () => { + StatusBarIOS.setStyle(StatusBarIOS.Style['default']); + this.props.onExampleExit(); + }, + passProps: { + text: 'The nav bar has custom colors with tintColor, ' + + 'barTintColor and titleTextColor props.', + }, + }} + tintColor="#FFFFFF" + barTintColor="#183E63" + titleTextColor="#FFFFFF" + /> + ); + }, + +}); + +var styles = StyleSheet.create({ + container: { + flex: 1, + }, + emptyPage: { + flex: 1, + paddingTop: 64, + }, + emptyPageText: { + margin: 10, + }, +}); + +NavigatorIOSColors.external = true; + +module.exports = NavigatorIOSColors; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 308249572..730106177 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -26,7 +26,6 @@ var { TouchableHighlight, View, } = React; -var NavigatorExample = require('./Navigator/NavigatorExample'); var { TestModule } = React.addons; @@ -39,7 +38,8 @@ var COMPONENTS = [ require('./ListViewExample'), require('./ListViewPagingExample'), require('./MapViewExample'), - NavigatorExample, + require('./Navigator/NavigatorExample'), + require('./NavigatorIOSColorsExample'), require('./NavigatorIOSExample'), require('./PickerIOSExample'), require('./ScrollViewExample'), @@ -181,10 +181,8 @@ class UIExplorerList extends React.Component { } _onPressRow(example) { - if (example === NavigatorExample) { - this.props.onExternalExampleRequested( - NavigatorExample - ); + if (example.external) { + this.props.onExternalExampleRequested(example); return; } var Component = makeRenderable(example); diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index fd90847f0..3babd1409 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -252,6 +252,16 @@ var NavigatorIOS = React.createClass({ */ tintColor: PropTypes.string, + /** + * The background color of the navigation bar + */ + barTintColor: PropTypes.string, + + /** + * The text color of the navigation bar title + */ + titleTextColor: PropTypes.string, + }, navigator: (undefined: ?Object), @@ -554,7 +564,9 @@ var NavigatorIOS = React.createClass({ rightButtonTitle={route.rightButtonTitle} onNavRightButtonTap={route.onRightButtonPress} navigationBarHidden={this.props.navigationBarHidden} - tintColor={this.props.tintColor}> + tintColor={this.props.tintColor} + barTintColor={this.props.barTintColor} + titleTextColor={this.props.titleTextColor}> Date: Thu, 23 Apr 2015 08:04:16 -0700 Subject: [PATCH 73/92] Bump .buckversion to 6cdb82cb7493a86c39d0f0dc3c102d0f470f55de. --- Libraries/LinkingIOS/RCTLinkingManager.h | 2 +- .../PushNotificationIOS/RCTPushNotificationManager.h | 2 +- React/Base/RCTConvert.h | 7 +++---- React/Executors/RCTContextExecutor.h | 2 +- React/Modules/RCTUIManager.h | 8 ++++---- React/Views/RCTShadowView.h | 3 +-- React/Views/RCTViewManager.h | 6 +++--- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Libraries/LinkingIOS/RCTLinkingManager.h b/Libraries/LinkingIOS/RCTLinkingManager.h index 81680f056..caa3aa2a3 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.h +++ b/Libraries/LinkingIOS/RCTLinkingManager.h @@ -9,7 +9,7 @@ #import -#import "../../React/Base/RCTBridgeModule.h" +#import "RCTBridgeModule.h" @interface RCTLinkingManager : NSObject diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index 4e791c680..b60a646e4 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -9,7 +9,7 @@ #import -#import "../../React/Base/RCTBridgeModule.h" +#import "RCTBridgeModule.h" @interface RCTPushNotificationManager : NSObject diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 46bbbf8a4..eb81c167c 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -10,12 +10,11 @@ #import #import -#import "../Layout/Layout.h" -#import "../Views/RCTAnimationType.h" -#import "../Views/RCTPointerEvents.h" - +#import "Layout.h" +#import "RCTAnimationType.h" #import "RCTDefines.h" #import "RCTLog.h" +#import "RCTPointerEvents.h" /** * This class provides a collection of conversion functions for mapping diff --git a/React/Executors/RCTContextExecutor.h b/React/Executors/RCTContextExecutor.h index 6e62d87b6..159965a2f 100644 --- a/React/Executors/RCTContextExecutor.h +++ b/React/Executors/RCTContextExecutor.h @@ -9,7 +9,7 @@ #import -#import "../Base/RCTJavaScriptExecutor.h" +#import "RCTJavaScriptExecutor.h" // TODO (#5906496): Might RCTJSCoreExecutor be a better name for this? diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 4f42cd0b7..6fa0e3249 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -9,10 +9,10 @@ #import -#import "../Base/RCTBridge.h" -#import "../Base/RCTBridgeModule.h" -#import "../Base/RCTInvalidating.h" -#import "../Views/RCTViewManager.h" +#import "RCTBridge.h" +#import "RCTBridgeModule.h" +#import "RCTInvalidating.h" +#import "RCTViewManager.h" @protocol RCTScrollableProtocol; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 6efb0c1d1..8d68855f7 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -9,8 +9,7 @@ #import -#import "../Layout/Layout.h" - +#import "Layout.h" #import "RCTViewNodeProtocol.h" @class RCTSparseArray; diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 3788832ef..77dc66697 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -9,9 +9,9 @@ #import -#import "../Base/RCTBridgeModule.h" -#import "../Base/RCTConvert.h" -#import "../Base/RCTLog.h" +#import "RCTBridgeModule.h" +#import "RCTConvert.h" +#import "RCTLog.h" @class RCTBridge; @class RCTEventDispatcher; From b72acc2313db90b414e53b4ede533071284de4c2 Mon Sep 17 00:00:00 2001 From: Robert Payne Date: Thu, 23 Apr 2015 09:28:09 -0700 Subject: [PATCH 74/92] Add support for exporting Swift modules Summary: External modules are any Objective-C class in which the implementation is private. This currently will be most useful for Swift classes but also has potential to allow exposing methods on 3rd party libraries to the bridge. Closes https://github.com/facebook/react-native/pull/982 Github Author: Robert Payne Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Base/RCTBridgeModule.h | 59 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 12f7803ea..dd6f61e23 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -73,12 +73,67 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); * { ... } */ #define RCT_REMAP_METHOD(js_name, method) \ + RCT_EXTERN_REMAP_METHOD(js_name, method) \ + - (void)method + +/** + * Use this macro in a private Objective-C implementation file to automatically + * register an external module with the bridge when it loads. This allows you to + * register Swift or private Objective-C classes with the bridge. + * + * For example if one wanted to export a Swift class to the bridge: + * + * MyModule.swift: + * + * @objc(MyModule) class MyModule: NSObject { + * + * @objc func doSomething(string: String! withFoo a: Int, bar b: Int) { ... } + * + * } + * + * MyModuleExport.m: + * + * #import "RCTBridgeModule.h" + * + * @interface RCT_EXTERN_MODULE(MyModule, NSObject) + * + * RCT_EXTERN_METHOD(doSomething:(NSString *)string withFoo:(NSInteger)a bar:(NSInteger)b) + * + * @end + * + * This will now expose MyModule and the method to JavaScript via + * `NativeModules.MyModule.doSomething` + */ +#define RCT_EXTERN_MODULE(objc_name, objc_supername) \ + RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername) + +/** + * Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name + */ +#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \ + objc_name : objc_supername \ + @end \ + @interface objc_name (RCTExternModule) \ + @end \ + @implementation objc_name (RCTExternModule) \ + RCT_EXPORT_MODULE(js_name) + +/** + * Use this macro in accordance with RCT_EXTERN_MODULE to export methods + * of an external module. + */ +#define RCT_EXTERN_METHOD(method) \ + RCT_EXTERN_REMAP_METHOD(, method) + +/** + * Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name + */ +#define RCT_EXTERN_REMAP_METHOD(js_name, method) \ - (void)__rct_export__##method { \ __attribute__((used, section("__DATA,RCTExport"))) \ __attribute__((__aligned__(1))) \ static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \ - } \ - - (void)method + } /** * Deprecated, do not use. From 7de74b129d62bfd6d02470d2b01713d40f840684 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Thu, 23 Apr 2015 09:41:45 -0700 Subject: [PATCH 75/92] [ReactNative] Move NativeModules mock to OSS --- .../__mocks__/NativeModules.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js new file mode 100644 index 000000000..28da1bc32 --- /dev/null +++ b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js @@ -0,0 +1,44 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var NativeModules = { + I18n: { + translationsDictionary: { + 'Good bye, {name}!|Bye message': '¡Adiós {name}!', + }, + }, + Timing: { + createTimer: jest.genMockFunction(), + deleteTimer: jest.genMockFunction(), + }, + GraphPhotoUpload: { + upload: jest.genMockFunction(), + }, + FacebookSDK: { + login: jest.genMockFunction(), + logout: jest.genMockFunction(), + queryGraphPath: jest.genMockFunction().mockImpl( + (path, method, params, callback) => callback() + ), + }, + DataManager: { + queryData: jest.genMockFunction(), + }, + UIManager: { + customBubblingEventTypes: {}, + customDirectEventTypes: {}, + }, + AsyncLocalStorage: { + getItem: jest.genMockFunction(), + setItem: jest.genMockFunction(), + removeItem: jest.genMockFunction(), + clear: jest.genMockFunction(), + }, + SourceCode: { + scriptURL: null, + }, +}; + +module.exports = NativeModules; From d2dbf4e0edf7167c036a6346f974750ee1749d2d Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Thu, 23 Apr 2015 10:42:00 -0700 Subject: [PATCH 76/92] [ReactNative] Disable console.error => redboxes to unwedge Android --- .../src/DependencyResolver/haste/polyfills/console.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js index 4f513f45b..11464cce5 100644 --- a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -60,7 +60,11 @@ if (global.reportException && level === LOG_LEVELS.error) { var error = new Error(str); error.framesToPop = 1; - global.reportException(error); + // TODO(sahrens): re-enable this when we have a way to turn + // it off by default for MAdMan/Android, and/or all + // consumers of console.error() are fixed, including + // CatalystErrorHandlerModuleTestCase + // global.reportException(error); } }; } From 357a54500e5d50402aef4e55631d7b5fd4153042 Mon Sep 17 00:00:00 2001 From: Bill Fisher Date: Thu, 23 Apr 2015 10:23:07 -0700 Subject: [PATCH 77/92] Implement transform styles, redux --- .../Components/View/ViewStylePropTypes.js | 3 + Libraries/ReactIOS/NativeMethodsMixin.js | 3 +- Libraries/ReactIOS/ReactIOSNativeComponent.js | 3 +- Libraries/StyleSheet/precomputeStyle.js | 161 ++++++++++++++++++ Libraries/Utilities/MatrixMath.js | 131 ++++++++++++++ 5 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 Libraries/StyleSheet/precomputeStyle.js create mode 100755 Libraries/Utilities/MatrixMath.js diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index bb22c6b26..7bb795f16 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -34,7 +34,10 @@ var ViewStylePropTypes = { ), shadowOpacity: ReactPropTypes.number, shadowRadius: ReactPropTypes.number, + transform: ReactPropTypes.arrayOf(ReactPropTypes.object), transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number), + + // DEPRECATED rotation: ReactPropTypes.number, scaleX: ReactPropTypes.number, scaleY: ReactPropTypes.number, diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index ec72a0b4f..9d413e5c7 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -19,6 +19,7 @@ var TextInputState = require('TextInputState'); var flattenStyle = require('flattenStyle'); var invariant = require('invariant'); var mergeFast = require('mergeFast'); +var precomputeStyle = require('precomputeStyle'); type MeasureOnSuccessCallback = ( x: number, @@ -93,7 +94,7 @@ var NativeMethodsMixin = { break; } } - var style = flattenStyle(nativeProps.style); + var style = precomputeStyle(flattenStyle(nativeProps.style)); var props = null; if (hasOnlyStyle) { diff --git a/Libraries/ReactIOS/ReactIOSNativeComponent.js b/Libraries/ReactIOS/ReactIOSNativeComponent.js index b9abd5965..7f27ae0ea 100644 --- a/Libraries/ReactIOS/ReactIOSNativeComponent.js +++ b/Libraries/ReactIOS/ReactIOSNativeComponent.js @@ -23,6 +23,7 @@ var styleDiffer = require('styleDiffer'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var diffRawProperties = require('diffRawProperties'); var flattenStyle = require('flattenStyle'); +var precomputeStyle = require('precomputeStyle'); var warning = require('warning'); var registrationNames = ReactIOSEventEmitter.registrationNames; @@ -160,7 +161,7 @@ ReactIOSNativeComponent.Mixin = { // before actually doing the expensive flattening operation in order to // compute the diff. if (styleDiffer(nextProps.style, prevProps.style)) { - var nextFlattenedStyle = flattenStyle(nextProps.style); + var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style)); updatePayload = diffRawProperties( updatePayload, this.previousFlattenedStyle, diff --git a/Libraries/StyleSheet/precomputeStyle.js b/Libraries/StyleSheet/precomputeStyle.js new file mode 100644 index 000000000..ea2990b94 --- /dev/null +++ b/Libraries/StyleSheet/precomputeStyle.js @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule precomputeStyle + * @flow + */ +'use strict'; + +var MatrixMath = require('MatrixMath'); +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +var invariant = require('invariant'); + +/** + * This method provides a hook where flattened styles may be precomputed or + * otherwise prepared to become better input data for native code. + */ +function precomputeStyle(style: ?Object): ?Object { + if (!style || !style.transform) { + return style; + } + invariant( + !style.transformMatrix, + 'transformMatrix and transform styles cannot be used on the same component' + ); + var newStyle = _precomputeTransforms({...style}); + deepFreezeAndThrowOnMutationInDev(newStyle); + return newStyle; +} + +/** + * Generate a transform matrix based on the provided transforms, and use that + * within the style object instead. + * + * This allows us to provide an API that is similar to CSS and to have a + * universal, singular interface to native code. + */ +function _precomputeTransforms(style: Object): Object { + var {transform} = style; + var result = MatrixMath.createIdentityMatrix(); + + transform.forEach(transformation => { + var key = Object.keys(transformation)[0]; + var value = transformation[key]; + if (__DEV__) { + _validateTransform(key, value, transformation); + } + + switch (key) { + case 'matrix': + MatrixMath.multiplyInto(result, result, value); + break; + case 'rotate': + _multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]); + break; + case 'scale': + _multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]); + break; + case 'scaleX': + _multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]); + break; + case 'scaleY': + _multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]); + break; + case 'translate': + _multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]); + break; + case 'translateX': + _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]); + break; + case 'translateY': + _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]); + break; + default: + throw new Error('Invalid transform name: ' + key); + } + }); + + return { + ...style, + transformMatrix: result, + }; +} + +/** + * Performs a destructive operation on a transform matrix. + */ +function _multiplyTransform( + result: Array, + matrixMathFunction: Function, + args: Array +): void { + var matrixToApply = MatrixMath.createIdentityMatrix(); + var argsWithIdentity = [matrixToApply].concat(args); + matrixMathFunction.apply(this, argsWithIdentity); + MatrixMath.multiplyInto(result, result, matrixToApply); +} + +/** + * Parses a string like '0.5rad' or '60deg' into radians expressed in a float. + * Note that validation on the string is done in `_validateTransform()`. + */ +function _convertToRadians(value: string): number { + var floatValue = parseFloat(value, 10); + return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180; +} + +function _validateTransform(key, value, transformation) { + var multivalueTransforms = [ + 'matrix', + 'translate', + ]; + if (multivalueTransforms.indexOf(key) !== -1) { + invariant( + Array.isArray(value), + 'Transform with key of %s must have an array as the value: %s', + key, + JSON.stringify(transformation) + ); + } + switch (key) { + case 'matrix': + invariant( + value.length === 9 || value.length === 16, + 'Matrix transform must have a length of 9 (2d) or 16 (3d). ' + + 'Provided matrix has a length of %s: %s', + value.length, + JSON.stringify(transformation) + ); + break; + case 'translate': + break; + case 'rotate': + invariant( + typeof value === 'string', + 'Transform with key of "%s" must be a string: %s', + key, + JSON.stringify(transformation) + ); + invariant( + value.indexOf('deg') > -1 || value.indexOf('rad') > -1, + 'Rotate transform must be expressed in degrees (deg) or radians ' + + '(rad): %s', + JSON.stringify(transformation) + ); + break; + default: + invariant( + typeof value === 'number', + 'Transform with key of "%s" must be a number: %s', + key, + JSON.stringify(transformation) + ); + } +} + +module.exports = precomputeStyle; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js new file mode 100755 index 000000000..7f3d17c46 --- /dev/null +++ b/Libraries/Utilities/MatrixMath.js @@ -0,0 +1,131 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule MatrixMath + */ +'use strict'; + +/** + * Memory conservative (mutative) matrix math utilities. Uses "command" + * matrices, which are reusable. + */ +var MatrixMath = { + createIdentityMatrix: function() { + return [ + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1 + ]; + }, + + createCopy: function(m) { + return [ + m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15], + ]; + }, + + createTranslate2d: function(x, y) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseTranslate2dCommand(mat, x, y); + return mat; + }, + + reuseTranslate2dCommand: function(matrixCommand, x, y) { + matrixCommand[12] = x; + matrixCommand[13] = y; + }, + + reuseTranslate3dCommand: function(matrixCommand, x, y, z) { + matrixCommand[12] = x; + matrixCommand[13] = y; + matrixCommand[14] = z; + }, + + createScale: function(factor) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseScaleCommand(mat, factor); + return mat; + }, + + reuseScaleCommand: function(matrixCommand, factor) { + matrixCommand[0] = factor; + matrixCommand[5] = factor; + }, + + reuseScale3dCommand: function(matrixCommand, x, y, z) { + matrixCommand[0] = x; + matrixCommand[5] = y; + matrixCommand[10] = z; + }, + + reuseScaleXCommand(matrixCommand, factor) { + matrixCommand[0] = factor; + }, + + reuseScaleYCommand(matrixCommand, factor) { + matrixCommand[5] = factor; + }, + + reuseScaleZCommand(matrixCommand, factor) { + matrixCommand[10] = factor; + }, + + reuseRotateYCommand: function(matrixCommand, amount) { + matrixCommand[0] = Math.cos(amount); + matrixCommand[2] = Math.sin(amount); + matrixCommand[8] = Math.sin(-amount); + matrixCommand[10] = Math.cos(amount); + }, + + createRotateZ: function(radians) { + var mat = MatrixMath.createIdentityMatrix(); + MatrixMath.reuseRotateZCommand(mat, radians); + return mat; + }, + + // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix + reuseRotateZCommand: function(matrixCommand, radians) { + matrixCommand[0] = Math.cos(radians); + matrixCommand[1] = Math.sin(radians); + matrixCommand[4] = -Math.sin(radians); + matrixCommand[5] = Math.cos(radians); + }, + + multiplyInto: function(out, a, b) { + var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; + + var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; + out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; + out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; + out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + + b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; + out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; + out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; + out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; + out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; + } + +}; + +module.exports = MatrixMath; From 4242bd9c8345e48a062e59f742f9ab4258136437 Mon Sep 17 00:00:00 2001 From: Herman Schaaf Date: Thu, 23 Apr 2015 10:37:09 -0700 Subject: [PATCH 78/92] Replace percent escapes in file URL before using as path Summary: This is to fix a bug that prevents bundling of projects that contain spaces (or other special characters) in their names. #### Reproduction steps before the fix 1. Create a project with a space in the name: ![screen shot 2015-04-16 at 17 23 46](https://cloud.githubusercontent.com/assets/1121616/7176887/63af36de-e45d-11e4-9aa9-40586560b716.png) 2. Follow the steps in `OPTION 2` for running from a bundled file, i.e. create the `main.bundle` file, add it to the project if is not there already, and uncomment `jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];` 3. Run the application. This is what happens: ![screen shot 2015-04-16 at 17 27 48](https://cloud.githubusercontent.com/assets/1121616/7176955/f139764a-e45d-11e4-8dc8-3c13aab70828.png) To prove that it has to do with a space in the name, refactor the project name to not contain a space: ![screen shot 2015-04-16 at 17 28 27](https://cloud.githubusercontent.com/assets/1121616/7176966/056b6c9a Closes https://github.com/facebook/react-native/pull/876 Github Author: Herman Schaaf Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Base/RCTJavaScriptLoader.m | 173 ++++++++++++------------------- 1 file changed, 68 insertions(+), 105 deletions(-) diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index dd8fab461..e632505a4 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -10,47 +10,15 @@ #import "RCTJavaScriptLoader.h" #import "RCTBridge.h" -#import "RCTInvalidating.h" -#import "RCTLog.h" -#import "RCTRedBox.h" +#import "RCTConvert.h" #import "RCTSourceCode.h" #import "RCTUtils.h" -#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading." -#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists." - -#define CACHE_DIR @"RCTJSBundleCache" - -#pragma mark - Application Engine - -/** - * TODO: - * - Add window resize rotation events matching the DOM API. - * - Device pixel ration hooks. - * - Source maps. - */ @implementation RCTJavaScriptLoader { __weak RCTBridge *_bridge; } -/** - * `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore - * engine in its own dedicated thread. - * - * TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one - * additional GCD dispatch per frame and likely makes it so that other UIThread - * operations don't delay the dispatch (so we can begin working in JS much - * faster.) Event handling must still be sent via a GCD dispatch, of course. - * - * We must add the display link to two runloops in order to get setTimeouts to - * fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`) - * TODO: We can invent a `requestAnimationFrame` and - * `requestAvailableAnimationFrame` to control if callbacks can be fired during - * an animation. - * http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink - * - */ - (instancetype)initWithBridge:(RCTBridge *)bridge { if ((self = [super init])) { @@ -61,92 +29,87 @@ - (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete { + NSURL *originalURL = scriptURL; + if (!scriptURL.scheme || [scriptURL isFileURL]) { + scriptURL = [RCTConvert NSURL:scriptURL.path]; + } + if (scriptURL == nil) { NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{ - NSLocalizedDescriptionKey: @"No script URL provided" + NSLocalizedDescriptionKey: originalURL ? [NSString stringWithFormat:@"Script URL '%@' could not be found.", originalURL] : @"No script URL provided." }]; onComplete(error); return; } - if ([scriptURL isFileURL]) { - NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath]; - NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length]; - - if (![localPath hasPrefix:bundlePath]) { - NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath]; - scriptURL = [NSURL fileURLWithPath:absolutePath]; - } - } - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { - // Handle general request errors - if (error) { - if ([[error domain] isEqualToString:NSURLErrorDomain]) { - NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]]; - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: desc, - NSLocalizedFailureReasonErrorKey: [error localizedDescription], - NSUnderlyingErrorKey: error, - }; - error = [NSError errorWithDomain:@"JSServer" - code:error.code - userInfo:userInfo]; - } - onComplete(error); - return; - } + // Handle general request errors + if (error) { + if ([[error domain] isEqualToString:NSURLErrorDomain]) { + NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]]; + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: desc, + NSLocalizedFailureReasonErrorKey: [error localizedDescription], + NSUnderlyingErrorKey: error, + }; + error = [NSError errorWithDomain:@"JSServer" + code:error.code + userInfo:userInfo]; + } + onComplete(error); + return; + } - // Parse response as text - NSStringEncoding encoding = NSUTF8StringEncoding; - if (response.textEncodingName != nil) { - CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - if (cfEncoding != kCFStringEncodingInvalidId) { - encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); - } - } - NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding]; + // Parse response as text + NSStringEncoding encoding = NSUTF8StringEncoding; + if (response.textEncodingName != nil) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); + if (cfEncoding != kCFStringEncodingInvalidId) { + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + } + NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding]; - // Handle HTTP errors - if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { - NSDictionary *userInfo; - NSDictionary *errorDetails = RCTJSONParse(rawText, nil); - if ([errorDetails isKindOfClass:[NSDictionary class]] && - [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { - NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; - for (NSDictionary *err in errorDetails[@"errors"]) { - [fakeStack addObject: @{ - @"methodName": err[@"description"] ?: @"", - @"file": err[@"filename"] ?: @"", - @"lineNumber": err[@"lineNumber"] ?: @0 - }]; - } - userInfo = @{ - NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", - @"stack": fakeStack, - }; - } else { - userInfo = @{NSLocalizedDescriptionKey: rawText}; - } - error = [NSError errorWithDomain:@"JSServer" - code:[(NSHTTPURLResponse *)response statusCode] - userInfo:userInfo]; + // Handle HTTP errors + if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { + NSDictionary *userInfo; + NSDictionary *errorDetails = RCTJSONParse(rawText, nil); + if ([errorDetails isKindOfClass:[NSDictionary class]] && + [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { + NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; + for (NSDictionary *err in errorDetails[@"errors"]) { + [fakeStack addObject: @{ + @"methodName": err[@"description"] ?: @"", + @"file": err[@"filename"] ?: @"", + @"lineNumber": err[@"lineNumber"] ?: @0 + }]; + } + userInfo = @{ + NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", + @"stack": fakeStack, + }; + } else { + userInfo = @{NSLocalizedDescriptionKey: rawText}; + } + error = [NSError errorWithDomain:@"JSServer" + code:[(NSHTTPURLResponse *)response statusCode] + userInfo:userInfo]; - onComplete(error); - return; - } - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; - sourceCodeModule.scriptURL = scriptURL; - sourceCodeModule.scriptText = rawText; + onComplete(error); + return; + } + RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + sourceCodeModule.scriptURL = scriptURL; + sourceCodeModule.scriptText = rawText; - [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) { - dispatch_async(dispatch_get_main_queue(), ^{ - onComplete(scriptError); - }); - }]; - }]; + [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) { + dispatch_async(dispatch_get_main_queue(), ^{ + onComplete(scriptError); + }); + }]; + }]; [task resume]; } From 4f89d1f76c179bf363b44c6a659331febdd9a6d0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 23 Apr 2015 11:30:39 -0700 Subject: [PATCH 79/92] [react-packager] Fix jest tests --- .../AssetServer/__tests__/AssetServer-test.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 94dba8cff..ba804b5f1 100644 --- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,16 +1,23 @@ 'use strict'; jest - .autoMockOff() - .mock('../../lib/declareOpts') - .mock('crypto') - .mock('fs'); + .dontMock('path') + .dontMock('../../lib/getAssetDataFromName') + .dontMock('../'); -var fs = require('fs'); -var AssetServer = require('../'); var Promise = require('bluebird'); describe('AssetServer', function() { + var AssetServer; + var crypto; + var fs; + + beforeEach(function() { + AssetServer = require('../'); + crypto = require('crypto'); + fs = require('fs'); + }); + describe('assetServer.get', function() { pit('should work for the simple case', function() { var server = new AssetServer({ @@ -96,7 +103,7 @@ describe('AssetServer', function() { hash.digest.mockImpl(function() { return 'wow such hash'; }); - require('crypto').createHash.mockImpl(function() { + crypto.createHash.mockImpl(function() { return hash; }); From 24095fcc2df06cac0ab606b981c4dcc53bb54299 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 23 Apr 2015 11:44:02 -0700 Subject: [PATCH 80/92] [react-packager] Change uri to name --- packager/react-packager/src/Packager/__tests__/Packager-test.js | 2 +- packager/react-packager/src/Packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 8e1420a3a..3040e0982 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -132,7 +132,7 @@ describe('Packager', function() { __packager_asset: true, isStatic: true, path: '/root/img/img.png', - uri: 'img', + name: 'img', width: 25, height: 50, deprecated: true, diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 8563e2728..79dd6f23d 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -181,7 +181,7 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { __packager_asset: true, isStatic: true, path: module.path, - uri: module.id.replace(/^[^!]+!/, ''), + name: module.id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, From e88ba1a6a319e2b559365b213534f2d4d4926b26 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Thu, 23 Apr 2015 11:34:25 -0700 Subject: [PATCH 81/92] [ReactNative] Back out D2014163 entirely --- Libraries/AppRegistry/AppRegistry.js | 2 +- .../Initialization/ExceptionsManager.js | 28 ++++++++----------- .../InitializeJavaScriptAppEngine.js | 3 +- React/Executors/RCTContextExecutor.m | 17 ++++++----- .../haste/polyfills/console.js | 22 ++++----------- 5 files changed, 29 insertions(+), 43 deletions(-) diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index 63bd1d9a0..f36f88132 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -69,7 +69,7 @@ var AppRegistry = { 'Running application "' + appKey + '" with appParams: ' + JSON.stringify(appParameters) + '. ' + '__DEV__ === ' + __DEV__ + - ', development-level warnings are ' + (__DEV__ ? 'ON' : 'OFF') + + ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') + ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON') ); invariant( diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index fb57a956a..c5476eaab 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -25,11 +25,17 @@ type Exception = { message: string; } -function reportException(e: Exception, stack?: any) { +function handleException(e: Exception) { + var stack = parseErrorStack(e); + console.error( + 'Err0r: ' + + '\n stack: \n' + stackToString(stack) + + '\n URL: ' + e.sourceURL + + '\n line: ' + e.line + + '\n message: ' + e.message + ); + if (RCTExceptionsManager) { - if (!stack) { - stack = parseErrorStack(e); - } RCTExceptionsManager.reportUnhandledException(e.message, stack); if (__DEV__) { (sourceMapPromise = sourceMapPromise || loadSourceMap()) @@ -44,18 +50,6 @@ function reportException(e: Exception, stack?: any) { } } -function handleException(e: Exception) { - var stack = parseErrorStack(e); - console.log( - 'Error: ' + - '\n stack: \n' + stackToString(stack) + - '\n URL: ' + e.sourceURL + - '\n line: ' + e.line + - '\n message: ' + e.message - ); - reportException(e, stack); -} - function stackToString(stack) { var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length)); return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n'); @@ -77,4 +71,4 @@ function fillSpaces(n) { return new Array(n + 1).join(' '); } -module.exports = { handleException, reportException }; +module.exports = { handleException }; diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index bbfee4eb3..51f6809cc 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -77,7 +77,6 @@ function handleErrorWithRedBox(e) { function setupRedBoxErrorHandler() { var ErrorUtils = require('ErrorUtils'); ErrorUtils.setGlobalHandler(handleErrorWithRedBox); - GLOBAL.reportException = require('ExceptionsManager').reportException; } /** @@ -135,8 +134,8 @@ function setupGeolocation() { } setupDocumentShim(); +setupRedBoxErrorHandler(); setupTimers(); -setupRedBoxErrorHandler(); // needs to happen after setupTimers setupAlert(); setupPromise(); setupXHR(); diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 8f931d96a..86444dd2a 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -74,12 +74,12 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount > 0) { - JSStringRef messageRef = JSValueToStringCopy(context, arguments[0], exception); - if (!messageRef) { + JSStringRef string = JSValueToStringCopy(context, arguments[0], exception); + if (!string) { return JSValueMakeUndefined(context); } - NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef); - JSStringRelease(messageRef); + NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string); + JSStringRelease(string); NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: @"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)" options:NSRegularExpressionCaseInsensitive @@ -89,11 +89,14 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, range:(NSRange){0, message.length} withTemplate:@"[$4$5] \t$2"]; + // TODO: it would be good if log level was sent as a param, instead of this hack RCTLogLevel level = RCTLogLevelInfo; - if (argumentCount > 1) { - level = MAX(level, JSValueToNumber(context, arguments[1], exception) - 1); + if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelError; + } else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelWarning; } - RCTGetLogFunction()(level, nil, nil, message); + _RCTLogFormat(level, NULL, -1, @"%@", message); } return JSValueMakeUndefined(context); diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js index 11464cce5..91fb970f8 100644 --- a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js @@ -23,7 +23,7 @@ log: 1, info: 2, warn: 3, - error: 4, + error: 4 }; function setupConsole(global) { @@ -35,10 +35,8 @@ function getNativeLogFunction(level) { return function() { var str = Array.prototype.map.call(arguments, function(arg) { - if (arg === undefined) { - return 'undefined'; - } else if (arg === null) { - return 'null'; + if (arg == null) { + return arg === null ? 'null' : 'undefined'; } else if (typeof arg === 'string') { return '"' + arg + '"'; } else { @@ -50,22 +48,14 @@ if (typeof arg.toString === 'function') { try { return arg.toString(); - } catch (E) {} + } catch (E) { + return 'unknown'; + } } - return '["' + typeof arg + '" failed to stringify]'; } } }).join(', '); global.nativeLoggingHook(str, level); - if (global.reportException && level === LOG_LEVELS.error) { - var error = new Error(str); - error.framesToPop = 1; - // TODO(sahrens): re-enable this when we have a way to turn - // it off by default for MAdMan/Android, and/or all - // consumers of console.error() are fixed, including - // CatalystErrorHandlerModuleTestCase - // global.reportException(error); - } }; } From 34a5aa1d0ab1f4c46760a73a81348d1972dcf460 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 23 Apr 2015 14:39:51 -0700 Subject: [PATCH 82/92] [ReactNative][madman] Revert D2001353 --- React/Base/RCTJavaScriptLoader.m | 173 +++++++++++++++++++------------ 1 file changed, 105 insertions(+), 68 deletions(-) diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index e632505a4..dd8fab461 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -10,15 +10,47 @@ #import "RCTJavaScriptLoader.h" #import "RCTBridge.h" -#import "RCTConvert.h" +#import "RCTInvalidating.h" +#import "RCTLog.h" +#import "RCTRedBox.h" #import "RCTSourceCode.h" #import "RCTUtils.h" +#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading." +#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists." + +#define CACHE_DIR @"RCTJSBundleCache" + +#pragma mark - Application Engine + +/** + * TODO: + * - Add window resize rotation events matching the DOM API. + * - Device pixel ration hooks. + * - Source maps. + */ @implementation RCTJavaScriptLoader { __weak RCTBridge *_bridge; } +/** + * `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore + * engine in its own dedicated thread. + * + * TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one + * additional GCD dispatch per frame and likely makes it so that other UIThread + * operations don't delay the dispatch (so we can begin working in JS much + * faster.) Event handling must still be sent via a GCD dispatch, of course. + * + * We must add the display link to two runloops in order to get setTimeouts to + * fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`) + * TODO: We can invent a `requestAnimationFrame` and + * `requestAvailableAnimationFrame` to control if callbacks can be fired during + * an animation. + * http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink + * + */ - (instancetype)initWithBridge:(RCTBridge *)bridge { if ((self = [super init])) { @@ -29,87 +61,92 @@ - (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete { - NSURL *originalURL = scriptURL; - if (!scriptURL.scheme || [scriptURL isFileURL]) { - scriptURL = [RCTConvert NSURL:scriptURL.path]; - } - if (scriptURL == nil) { NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{ - NSLocalizedDescriptionKey: originalURL ? [NSString stringWithFormat:@"Script URL '%@' could not be found.", originalURL] : @"No script URL provided." + NSLocalizedDescriptionKey: @"No script URL provided" }]; onComplete(error); return; } + if ([scriptURL isFileURL]) { + NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath]; + NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length]; + + if (![localPath hasPrefix:bundlePath]) { + NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath]; + scriptURL = [NSURL fileURLWithPath:absolutePath]; + } + } + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { - // Handle general request errors - if (error) { - if ([[error domain] isEqualToString:NSURLErrorDomain]) { - NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]]; - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: desc, - NSLocalizedFailureReasonErrorKey: [error localizedDescription], - NSUnderlyingErrorKey: error, - }; - error = [NSError errorWithDomain:@"JSServer" - code:error.code - userInfo:userInfo]; - } - onComplete(error); - return; - } + // Handle general request errors + if (error) { + if ([[error domain] isEqualToString:NSURLErrorDomain]) { + NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]]; + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: desc, + NSLocalizedFailureReasonErrorKey: [error localizedDescription], + NSUnderlyingErrorKey: error, + }; + error = [NSError errorWithDomain:@"JSServer" + code:error.code + userInfo:userInfo]; + } + onComplete(error); + return; + } - // Parse response as text - NSStringEncoding encoding = NSUTF8StringEncoding; - if (response.textEncodingName != nil) { - CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - if (cfEncoding != kCFStringEncodingInvalidId) { - encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); - } - } - NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding]; + // Parse response as text + NSStringEncoding encoding = NSUTF8StringEncoding; + if (response.textEncodingName != nil) { + CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); + if (cfEncoding != kCFStringEncodingInvalidId) { + encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); + } + } + NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding]; - // Handle HTTP errors - if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { - NSDictionary *userInfo; - NSDictionary *errorDetails = RCTJSONParse(rawText, nil); - if ([errorDetails isKindOfClass:[NSDictionary class]] && - [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { - NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; - for (NSDictionary *err in errorDetails[@"errors"]) { - [fakeStack addObject: @{ - @"methodName": err[@"description"] ?: @"", - @"file": err[@"filename"] ?: @"", - @"lineNumber": err[@"lineNumber"] ?: @0 - }]; - } - userInfo = @{ - NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", - @"stack": fakeStack, - }; - } else { - userInfo = @{NSLocalizedDescriptionKey: rawText}; - } - error = [NSError errorWithDomain:@"JSServer" - code:[(NSHTTPURLResponse *)response statusCode] - userInfo:userInfo]; + // Handle HTTP errors + if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { + NSDictionary *userInfo; + NSDictionary *errorDetails = RCTJSONParse(rawText, nil); + if ([errorDetails isKindOfClass:[NSDictionary class]] && + [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { + NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; + for (NSDictionary *err in errorDetails[@"errors"]) { + [fakeStack addObject: @{ + @"methodName": err[@"description"] ?: @"", + @"file": err[@"filename"] ?: @"", + @"lineNumber": err[@"lineNumber"] ?: @0 + }]; + } + userInfo = @{ + NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", + @"stack": fakeStack, + }; + } else { + userInfo = @{NSLocalizedDescriptionKey: rawText}; + } + error = [NSError errorWithDomain:@"JSServer" + code:[(NSHTTPURLResponse *)response statusCode] + userInfo:userInfo]; - onComplete(error); - return; - } - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; - sourceCodeModule.scriptURL = scriptURL; - sourceCodeModule.scriptText = rawText; + onComplete(error); + return; + } + RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + sourceCodeModule.scriptURL = scriptURL; + sourceCodeModule.scriptText = rawText; - [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) { - dispatch_async(dispatch_get_main_queue(), ^{ - onComplete(scriptError); - }); - }]; - }]; + [_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) { + dispatch_async(dispatch_get_main_queue(), ^{ + onComplete(scriptError); + }); + }]; + }]; [task resume]; } From 36afc46274157a35bdfa8997e40b20b02ead4cf9 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 23 Apr 2015 14:16:10 -0700 Subject: [PATCH 83/92] [ReactNative] Fix for Navigator.replacePreviousAndPop --- Libraries/CustomComponents/Navigator/Navigator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index ea8977ff8..da4d2ab10 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1079,8 +1079,7 @@ var Navigator = React.createClass({ // To avoid visual glitches, we never re-render scenes during a transition. // We assume that `state.updatingRangeLength` will have a length during the // initial render of any scene - var shouldRenderScenes = !this.state.isAnimating && - this.state.updatingRangeLength !== 0; + var shouldRenderScenes = this.state.updatingRangeLength !== 0; if (shouldRenderScenes) { return ( From ee3986342a1ab780f0c7cfc73dabf6f0b8dec161 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 23 Apr 2015 16:11:34 -0700 Subject: [PATCH 84/92] [ReactNative][madman] Revert D2011598 temporarily --- Libraries/Utilities/MessageQueue.js | 32 +++++++++++++---------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 576aaa991..c047d06de 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -10,9 +10,7 @@ * @flow */ 'use strict'; - var ErrorUtils = require('ErrorUtils'); -var ReactUpdates = require('ReactUpdates'); var invariant = require('invariant'); var warning = require('warning'); @@ -309,23 +307,21 @@ var MessageQueueMixin = { ); }, - processBatch: function(batch) { + processBatch: function (batch) { var self = this; - ReactUpdates.batchedUpdates(function() { - batch.forEach(function(call) { - invariant( - call.module === 'BatchedBridge', - 'All the calls should pass through the BatchedBridge module' - ); - if (call.method === 'callFunctionReturnFlushedQueue') { - self.callFunction.apply(self, call.args); - } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { - self.invokeCallback.apply(self, call.args); - } else { - throw new Error( - 'Unrecognized method called on BatchedBridge: ' + call.method); - } - }); + batch.forEach(function (call) { + invariant( + call.module === 'BatchedBridge', + 'All the calls should pass through the BatchedBridge module' + ); + if (call.method === 'callFunctionReturnFlushedQueue') { + self.callFunction.apply(self, call.args); + } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { + self.invokeCallback.apply(self, call.args); + } else { + throw new Error( + 'Unrecognized method called on BatchedBridge: ' + call.method); + } }); return this.flushedQueue(); }, From 1f1c6af9cd195763b4b313884c7c00eb5be30bc6 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 21 Apr 2015 14:47:40 -0700 Subject: [PATCH 85/92] [ReactNative] Add instructions for new `react-native bundle` --- Examples/SampleApp/iOS/AppDelegate.m | 8 ++++---- Examples/SampleApp/iOS/main.jsbundle | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Examples/SampleApp/iOS/AppDelegate.m b/Examples/SampleApp/iOS/AppDelegate.m index 777072c6c..7e8d5fecf 100644 --- a/Examples/SampleApp/iOS/AppDelegate.m +++ b/Examples/SampleApp/iOS/AppDelegate.m @@ -35,12 +35,12 @@ /** * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle, `cd` - * to your Xcode project folder in the terminal, and run + * Load from pre-bundled file on disk. To re-generate the static bundle + * from the root of your project directory, run * - * $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o main.jsbundle + * $ react-native bundle --minify * - * then add the `main.jsbundle` file to your project and uncomment this line: + * see http://facebook.github.io/react-native/docs/runningondevice.html */ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; diff --git a/Examples/SampleApp/iOS/main.jsbundle b/Examples/SampleApp/iOS/main.jsbundle index 7cc6a2adc..b702b30c6 100644 --- a/Examples/SampleApp/iOS/main.jsbundle +++ b/Examples/SampleApp/iOS/main.jsbundle @@ -1,5 +1,8 @@ // Offline JS -// To re-generate the offline bundle, run this from root of your project -// $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle +// To re-generate the offline bundle, run this from the root of your project: +// +// $ react-native bundle --minify +// +// See http://facebook.github.io/react-native/docs/runningondevice.html for more details. throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions'); From 4c9ed22ff62fcfc118d4089498059db97af833ef Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 23 Apr 2015 17:04:11 -0700 Subject: [PATCH 86/92] [ReactNative][madman] Reverted D2014357 --- packager/react-packager/src/Packager/__tests__/Packager-test.js | 2 +- packager/react-packager/src/Packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 3040e0982..8e1420a3a 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -132,7 +132,7 @@ describe('Packager', function() { __packager_asset: true, isStatic: true, path: '/root/img/img.png', - name: 'img', + uri: 'img', width: 25, height: 50, deprecated: true, diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 79dd6f23d..8563e2728 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -181,7 +181,7 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { __packager_asset: true, isStatic: true, path: module.path, - name: module.id.replace(/^[^!]+!/, ''), + uri: module.id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, From dd6f7743eb626a2b22e1d97130b3fea931f73b55 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 23 Apr 2015 16:00:34 -0700 Subject: [PATCH 87/92] Fix updating RCTText with new text of the same size Summary: Fixes #979. Previously, a Text whose width is determined automatically (as opposed to set by a container) would position the text incorrectly after an update to the text *if* the text's width did not change (i.e., when changing only digits in a font with tabular numbers). Every time RCTShadowText's RCTMeasure runs, it sets the text container's size to be the maximum allowed size for the text. When RCTText's drawRect is called later, it relied on layoutSubviews having been called to set the text container's size back to the proper width. But if RCTMeasure returned the same dimensions as last time, then RCTText's frame wasn't reset and so layoutSubviews was never re-called. With this change, we set the textContainer's size each time we draw the text. We could also fix this by using a different NSTextContainer instance in RCTMeasure. Not sure what the pros and cons of that are. Closes https://github.com/facebook/react-native/pull/989 Github Author: Ben Alpert Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Text/RCTText.m | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index e51686ac7..9d2ade8e4 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -95,21 +95,18 @@ return UIEdgeInsetsInsetRect(self.bounds, _contentInset); } -- (void)layoutSubviews -{ - [super layoutSubviews]; - - // The header comment for `size` says that a height of 0.0 should be enough, - // but it isn't. - _textContainer.size = CGSizeMake([self textFrame].size.width, CGFLOAT_MAX); -} - - (void)drawRect:(CGRect)rect { - CGPoint origin = [self textFrame].origin; + CGRect textFrame = [self textFrame]; + + // We reset the text container size every time because RCTShadowText's + // RCTMeasure overrides it. The header comment for `size` says that a height + // of 0.0 should be enough, but it isn't. + _textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX); + NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer]; - [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin]; - [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin]; + [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin]; + [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin]; } - (NSNumber *)reactTagAtPoint:(CGPoint)point From d354c7dd91bb64ca1c0b4ca1c0725cae0981a741 Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Fri, 24 Apr 2015 02:30:24 -0700 Subject: [PATCH 88/92] [react_native] JS files from D2009062: add geolocation support --- Libraries/Geolocation/{Geolocation.ios.js => Geolocation.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Libraries/Geolocation/{Geolocation.ios.js => Geolocation.js} (100%) diff --git a/Libraries/Geolocation/Geolocation.ios.js b/Libraries/Geolocation/Geolocation.js similarity index 100% rename from Libraries/Geolocation/Geolocation.ios.js rename to Libraries/Geolocation/Geolocation.js From d293bed5ab66c002beb5331304c8e94f5de41c49 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 24 Apr 2015 08:31:07 -0700 Subject: [PATCH 89/92] [ReactNative] Fix analyze errors on oss --- .../RCTActionSheet.xcodeproj/project.pbxproj | 4 ++++ .../RCTAdSupport.xcodeproj/project.pbxproj | 3 +++ .../RCTGeolocation.xcodeproj/project.pbxproj | 3 +++ Libraries/Image/RCTGIFImage.m | 1 - .../Image/RCTImage.xcodeproj/project.pbxproj | 4 ++++ .../Network/RCTNetwork.xcodeproj/project.pbxproj | 3 +++ .../RCTPushNotification.xcodeproj/project.pbxproj | 3 +++ .../RCTPushNotificationManager.m | 2 +- .../RCTTest/RCTTest.xcodeproj/project.pbxproj | 3 +++ .../project.pbxproj | 3 +++ Libraries/Text/RCTText.xcodeproj/project.pbxproj | 3 +++ .../RCTVibration.xcodeproj/project.pbxproj | 3 +++ React/Layout/Layout.c | 15 +-------------- React/React.xcodeproj/project.pbxproj | 4 ++++ 14 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj b/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj index 7e420235e..8434df87d 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj +++ b/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -206,6 +207,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTActionSheet; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -213,6 +215,7 @@ 58B511F11A9E6C8500147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -221,6 +224,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTActionSheet; + RUN_CLANG_STATIC_ANALYZER = NO; SKIP_INSTALL = YES; }; name = Release; diff --git a/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj b/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj index 811d25e63..1b89d7bfa 100644 --- a/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj +++ b/Libraries/AdSupport/RCTAdSupport.xcodeproj/project.pbxproj @@ -208,6 +208,7 @@ 832C81951AAF6DF0007FA2F7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -215,6 +216,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -222,6 +224,7 @@ 832C81961AAF6DF0007FA2F7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj index dd17e5808..ee79e7571 100644 --- a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj +++ b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -206,6 +207,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTGeolocation; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -213,6 +215,7 @@ 58B511F11A9E6C8500147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/Image/RCTGIFImage.m b/Libraries/Image/RCTGIFImage.m index e0975f4ac..99704e305 100644 --- a/Libraries/Image/RCTGIFImage.m +++ b/Libraries/Image/RCTGIFImage.m @@ -14,7 +14,6 @@ static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource) { if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) { - CFRelease(imageSource); return nil; } diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 33a5a65a8..3431def50 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -240,6 +240,7 @@ 58B511721A9E6B3D00147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -251,6 +252,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTImage; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -258,6 +260,7 @@ 58B511731A9E6B3D00147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -269,6 +272,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTImage; + RUN_CLANG_STATIC_ANALYZER = NO; SKIP_INSTALL = YES; }; name = Release; diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj index 652952994..1dca7fe6d 100644 --- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -212,6 +213,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTNetwork; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -219,6 +221,7 @@ 58B511F11A9E6C8500147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj/project.pbxproj b/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj/project.pbxproj index 10d8ce169..c65b8d3c5 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj/project.pbxproj +++ b/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 58B511F01A9E6C8500147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -206,6 +207,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RCTPushNotification; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -213,6 +215,7 @@ 58B511F11A9E6C8500147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 90f2d8786..c0bedee5e 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -113,7 +113,7 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) #endif - NSUInteger types; + NSUInteger types = 0; if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) { types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; } else { diff --git a/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj index d4945f460..6ab58a8a2 100644 --- a/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj +++ b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj @@ -246,6 +246,7 @@ 580C37841AB104AF0015E709 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -257,6 +258,7 @@ XCTest, ); PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -264,6 +266,7 @@ 580C37851AB104AF0015E709 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj index d9e3b9f5b..38ac20c73 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketDebugger.xcodeproj/project.pbxproj @@ -214,6 +214,7 @@ 832C81951AAF6DF0007FA2F7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -224,6 +225,7 @@ "-llibicucore", ); PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -231,6 +233,7 @@ 832C81961AAF6DF0007FA2F7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj index 0304ef851..3c4bcf5ba 100644 --- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj +++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj @@ -222,6 +222,7 @@ 58B511B01A9E6C1300147676 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -229,6 +230,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -236,6 +238,7 @@ 58B511B11A9E6C1300147676 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/Vibration/RCTVibration.xcodeproj/project.pbxproj b/Libraries/Vibration/RCTVibration.xcodeproj/project.pbxproj index bc8a47cbf..f8aec3fed 100644 --- a/Libraries/Vibration/RCTVibration.xcodeproj/project.pbxproj +++ b/Libraries/Vibration/RCTVibration.xcodeproj/project.pbxproj @@ -208,6 +208,7 @@ 832C81951AAF6DF0007FA2F7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, @@ -215,6 +216,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -222,6 +224,7 @@ 832C81961AAF6DF0007FA2F7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index 21dec570a..2b168e44a 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<24fa633b4dd81b7fb40c2b2b0b7c97d0>> * * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * !! This file is a check-in from github! !! @@ -642,19 +642,6 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { } } - float containerMainAxis = node->layout.dimensions[dim[mainAxis]]; - // If the user didn't specify a width or height, and it has not been set - // by the container, then we set it via the children. - if (isUndefined(node->layout.dimensions[dim[mainAxis]])) { - containerMainAxis = fmaxf( - // We're missing the last padding at this point to get the final - // dimension - mainDim + getPaddingAndBorder(node, trailing[mainAxis]), - // We can never assign a width smaller than the padding and borders - getPaddingAndBorderAxis(node, mainAxis) - ); - } - float containerCrossAxis = node->layout.dimensions[dim[crossAxis]]; if (isUndefined(node->layout.dimensions[dim[crossAxis]])) { containerCrossAxis = fmaxf( diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 8823e1565..3364cce76 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -625,6 +625,7 @@ 83CBBA401A601D0F00E9B192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -636,6 +637,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; }; name = Debug; @@ -643,6 +645,7 @@ 83CBBA411A601D0F00E9B192 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_STATIC_ANALYZER_MODE = deep; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -650,6 +653,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUN_CLANG_STATIC_ANALYZER = NO; SKIP_INSTALL = YES; }; name = Release; From a635a73abc352d2ae229d2b0b7569359164e4135 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Fri, 24 Apr 2015 09:12:04 -0700 Subject: [PATCH 90/92] [react_native] JS files from D2020585: [react_native] Remove focus for TextInputs when they unmount --- Libraries/Components/TextInput/TextInput.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index fb5f99949..dfd3ab1a1 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -350,6 +350,9 @@ var TextInput = React.createClass({ componentWillUnmount: function() { this._focusSubscription && this._focusSubscription.remove(); + if (this.isFocused()) { + this.blur(); + } }, _bufferTimeout: (undefined: ?number), From 861c66e587ab233a701bc567dd7d1f210a43f2ef Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 24 Apr 2015 10:31:28 -0700 Subject: [PATCH 91/92] [ReactNative] Fix launchEditor script --- packager/launchEditor.js | 43 +++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/packager/launchEditor.js b/packager/launchEditor.js index cf89ed4f0..b572b5cbd 100644 --- a/packager/launchEditor.js +++ b/packager/launchEditor.js @@ -8,23 +8,21 @@ */ 'use strict'; +var chalk = require('chalk'); var fs = require('fs'); -var spawn = require('child_process').spawn; +var exec = require('child_process').exec; -var firstLaunch = true; - -function guessEditor() { - if (firstLaunch) { - console.log('When you see Red Box with stack trace, you can click any ' + - 'stack frame to jump to the source file. The packager will launch your ' + - 'editor of choice. It will first look at REACT_EDITOR environment ' + - 'variable, then at EDITOR. To set it up, you can add something like ' + - 'REACT_EDITOR=atom to your .bashrc.'); - firstLaunch = false; - } - - var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl'; - return editor; +function printInstructions(title) { + console.log([ + '', + chalk.bgBlue.white.bold(' ' + title + ' '), + ' When you see Red Box with stack trace, you can click any ', + ' stack frame to jump to the source file. The packager will launch your ', + ' editor of choice. It will first look at REACT_EDITOR environment ', + ' variable, then at EDITOR. To set it up, you can add something like ', + ' REACT_EDITOR=atom to your .bashrc.', + '' + ].join('\n')); } function launchEditor(fileName, lineNumber) { @@ -37,9 +35,18 @@ function launchEditor(fileName, lineNumber) { argument += ':' + lineNumber; } - var editor = guessEditor(); - console.log('Opening ' + fileName + ' with ' + editor); - spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] }); + var editor = process.env.REACT_EDITOR || process.env.EDITOR; + if (editor) { + console.log('Opening ' + chalk.underline(fileName) + ' with ' + chalk.bold(editor)); + exec(editor + ' ' + argument, function(error) { + if (error) { + console.log(chalk.red(error.message)); + printInstructions('How to fix'); + } + }); + } else { + printInstructions('PRO TIP'); + } } module.exports = launchEditor; From d094952725b86acd955b8a7f7821c77f3b5877e3 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Fri, 24 Apr 2015 10:23:51 -0700 Subject: [PATCH 92/92] [ReactNative] Re-applied D2011598 --- Libraries/Utilities/MessageQueue.js | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index c047d06de..576aaa991 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -10,7 +10,9 @@ * @flow */ 'use strict'; + var ErrorUtils = require('ErrorUtils'); +var ReactUpdates = require('ReactUpdates'); var invariant = require('invariant'); var warning = require('warning'); @@ -307,21 +309,23 @@ var MessageQueueMixin = { ); }, - processBatch: function (batch) { + processBatch: function(batch) { var self = this; - batch.forEach(function (call) { - invariant( - call.module === 'BatchedBridge', - 'All the calls should pass through the BatchedBridge module' - ); - if (call.method === 'callFunctionReturnFlushedQueue') { - self.callFunction.apply(self, call.args); - } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { - self.invokeCallback.apply(self, call.args); - } else { - throw new Error( - 'Unrecognized method called on BatchedBridge: ' + call.method); - } + ReactUpdates.batchedUpdates(function() { + batch.forEach(function(call) { + invariant( + call.module === 'BatchedBridge', + 'All the calls should pass through the BatchedBridge module' + ); + if (call.method === 'callFunctionReturnFlushedQueue') { + self.callFunction.apply(self, call.args); + } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { + self.invokeCallback.apply(self, call.args); + } else { + throw new Error( + 'Unrecognized method called on BatchedBridge: ' + call.method); + } + }); }); return this.flushedQueue(); },