From 2975f26e805ff38af61f57136a0995b23db0e8dc Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 17 Apr 2015 09:10:45 -0700 Subject: [PATCH 01/62] [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 3c7be04355e558..e0bfeb2f339d63 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/62] [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 9af96c1442ce07..b629dfbf01f3c6 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/62] [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 9257d788b1baa9..dba6265a067e90 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/62] [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 b4bad9982ce931..9fb265f976839e 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 7bc78ee929390a..69d0d52a254870 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 20c43a94a45b2d..172819de2fa280 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 266c5df2414d02..fff116745bed98 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/62] [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 | 35 ++++++++++++------- 4 files changed, 41 insertions(+), 17 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 98ae7eb73f8f73..d3f6ce289eb590 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 dba6265a067e90..66534597750f01 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 0c9d4a84d6340f..333e0f5631e1df 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 74e2ff4c358709..2b1eb6b1658872 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,19 +177,26 @@ Packager.prototype.getGraphDebugInfo = function() { }; function generateAssetModule_DEPRECATED(module) { - var code = 'module.exports = ' + JSON.stringify({ - uri: module.id.replace(/^[^!]+!/, ''), - isStatic: true, - }) + ';'; - - return { - code: code, - sourceCode: code, - sourcePath: module.path, - }; -} + 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, + }; -var sizeOf = Promise.promisify(imageSize); + + var code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return { + code: code, + sourceCode: code, + sourcePath: module.path, + }; + }); +} function generateAssetModule(module, relPath) { return sizeOf(module.path).then(function(dimensions) { From aaaa9a98ef61e6128a808bec2470b1a076353e8a Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 17 Apr 2015 15:33:54 -0700 Subject: [PATCH 06/62] [ReactNative] Update stacktrace-parser --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5169828ef6b04..ce743e3bf6d1d9 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/62] [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 0308688b5768d0..e66612b22886c0 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 81815ba34c97c5..0c2d02da04f4ac 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 fed358e1337ead..d519c1ac5308e1 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/62] [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 00000000000000..745b83ab093053 --- /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 66dc7fa95cf585..4dec6bd7bb892d 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/62] [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 312da45e21c1f5..cd910054f4d817 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 00000000000000..2981f979d90dd5 --- /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 05285d0fc98c8a..8e593f9ffe7d09 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/62] 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 e168daae66201d..9ecf2543b94d44 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/62] [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 bb22c6b26511bf..73afe99bdb70c0 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 ec72a0b4f4696b..9d413e5c7ca7a1 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 b9abd5965c5f07..7f27ae0ea7dae3 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 00000000000000..c3a1ca8e5cb1ed --- /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 00000000000000..7f3d17c461b70a --- /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/62] [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 b6a350dcd4964e..ae04f9a1d4c21c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -398,13 +398,15 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block { 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/62] 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 | 128 +++++++-------- Libraries/LinkingIOS/RCTLinkingManager.m | 6 +- Libraries/RCTTest/RCTTestModule.h | 22 ++- Libraries/RCTTest/RCTTestModule.m | 41 ++--- Libraries/RCTTest/RCTTestRunner.m | 37 ++--- 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 | 53 +++--- 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, 383 insertions(+), 402 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 zcmeHwcT`i`w)dteSP&5v6cCi7s31j@UMvWDKv0n)B_L6dPUxXVvCvUbKxryRQ0X;D z2mvXPE+91!kPupcKqw(3B;Uq!z1MfgJ?9YXZ{O5F7)FG!S~oa>oWL1?9S{T9fSn+l+-CrQQw(HzCjhW<|HsFFF#JDG^(QR+$y)2D z;(YZSSkM0dvG_WrwqB~rN`E>rzJOdo&w1;Lp&g;S^_urK8no1>rv}@4Gqg!Oz4OVz z=O;}-jDd7$?R_Sq^@&Ta(z&Yg%# z19d>hiGexfeF%XA zr6x}#WP36=|4Jy%k>zH;O+Iveo|V+UD;Ecn9`Bp%q(c3 z8uYq!sOdq&QnQZ(P}*dKOhK5Ah?%n4N|m|mDDnoa?NRTRLJgwRS4R=%#n#BT%& zR0z6zAu$&t9TZmvM5bmBKsEguqiXMe{l1NdotMO57an_VA9|AxL>!&+{y6;kJcXXl zuVXgtaCvy-F(?kDHsXU`-o35)hHu3ivn>xp3vn7Tp;JYX{honjUC^IZlca{Y zUwV!@G{wYMFu&n44QxaC&kk?z)&GsOC8N+jwH)ez3ue8blMfUDNq${Dhnj|+@V-HF z#oXUc1rKH#Xm*|iRC}|9Iwop%iDzM*mmPnfR}#pG(aa2e<3Yv4lVu~%kd~BdMjG<9 z^opZO1om$K6^I_V8DV4gQoy$LgZg=-B~ZtG6E!xspzbq@3}iLG{e7EOK%EQD=6HPd zl1zmLX(bappMBNY+W9u|lS#PrQ#lKA%9`)RE0#Ww2!-?Iap#uN>pD-_jKE~sXUq7R z=8Pl)6^CAu(=c1|ZUnMkiswmbU+oA$Bu`0i1{LlV*O0RR^!5AApt&#)ws_;ql#bc- zDQ6hrsii#Kjd7TYTghz|rCJs$Rlj_C@BLer9N)b4We)y$XWhn95oA_*u%3HzoM3I{ zw#rgrNcey;mi6%vH>d|U_aXSs+lLKQ5y|Q&Z(oB2SPOT@rNis1^Yc;5VfM(~)F{VI zCdvIzu-C;GY@QMq(4bch$X*tT1d`mwgB;aUDbhpBgRt!clT711$Quha&|( zh$+2n(fmt#!zEM!G^|@p`wChV^xoSWf$jGAiiD<*4%9nS>_Nd3LXH>oHFT_@RM)1o z?#X<@QwQ>iKy+wU=iDMZi1;|+nKDz=)Q&Bt*E^Q+Tb$>~i?qlg`Vsxl64}vL(UjeC za%MMnRF+CXYMd>MbKu8c_$DbEW@LFOoCd8m=&Oy%#09SDKuBjK6e#>`z>&;y);D>aQ#`(H+T^5yK+eemHSt$nMhWJ{Ze}g&yKck;7 zzXE?1k^KaR{%wPQhU5OPsQ#DH&p&}E0w_B4-y+hVcJkvy{{oKvH>#b84LH1}J%0d) zhjea@q?7E%riu-{M_q8Yu`aW^B+i|PFD>KQ3g3gA6Mn$wmTSfGxsv7g*uvh}+}0fB znTsU5gwQQSMky<3MweGJ&)4N( zkQM49>!UxpylXYJf+|hUkXG{=D+)lPK|H&77Z|}#OhRKm8wV}iG)AsP(5aDZ#gd$d zXMuF$n-J#Bz zvxHEeRD~8<20`>(jK%Z7rMazaM*%(rBPkMu#Wox#K{$KdRo;d$xJP``Hc|LzKYm2@ zuUU_ZYF20;HxGfRwUs55Mw2j^YH(R%RP*j_qqY?wH@yP10E&k@u9pzb^LYa0Way95 zaZR}%xmTDqHt4y~Gw17Lby5RG_~a3un~ZGDi5l>Xo#)rHAsAJx&Ix75$H>-6kDH9D zzYu*KG;c3qXE#-LxmD8+*-D&JXU1LDWHkkZEH{*AP@+ggmhw^6Z!(zN0ERDqZg~x3 zGFUMnM3@?dr4@dZPghjfVPI!405h-1A=pjbN;?2be7svgEm5@EbM-TnQfQ{SrP8Gv zsz@4rHm%S+gJujnRb{=PJzOM8jNO{wuSwuZV|KT-cKte4h8|rOpXWU!qoJUx$ru9D zkfz|JCyAgM;jlR^zWDr`2D-u>d#zA~3x!`l!WQOe&VHwf~Yu& zWKRnPR>eLViC;8)818*?rDvM{ArXkg(#Df-(^%2LX#+0vo;_Hij&T1~z?;6Zy^d^| zoF9xk$?`eBf1f3ooXiz}7))M%l^A5Vmabp|g_kI^QQciBZk@u2xp{mLeqSwlHR-)! zL48JagTb`|^jY9!R*mR8QyYORsHod=4F&Wi5%s0#tkv$9gsqy5ES88sQgR?`m0C-% zP#qpG)-J)DE!a$7I$F42=p`hm#U(}8iP2G)52r1oiO&!25vo~GYsL8>r@%BNyPSoK zN}c;EqL3R9o&O|)$7*1goh^3Dwruu18B%_S7eAXh5@K^BpOGA(^-(6b6-+E2pYSIV z`>@QRY{mIPSR)labC>SmDvB-e_87i6zf0532!OcvFr#FBE0#2}KV{00GhNtanbcJi z$V(PkY2tO~>M%ULTS~*UXj^4PxkkvGzlj7S!?2@A?>JHSs z=C%r0xkL;kkgICacnh0BX$1V{1}D9J;N1F5uW^r#aJ!dh?dP-b^B))YOlo(7#E5>=Mv+y8ejTL6a$1%Zb z`fB)WyU6 zc0G@?v&F{-PuHua8*jD_exW|V;@m`KXS zP>qwg1=1c62R|+ZBy!Bg)~ycv$OPrs+2Io}t5O%78hZHW7h~VZrboO8XgG=!;S{SL zSxy*BHAkOyoN91XJ~vMl3D>;GcX&X{#7S8E;&6=9)DHWU^h3$_cRg|co>IbwJN)F> z3vC4)tvK>JJ-%l#xSGuiYv3JTK0?#P3hT9?S+KX3`Lu+@7^ELAcH6z2Y z5o3EAAGu;aw&aXfscn{x3k~to5EChIoj&TgrkotI6>&D2`uGoJ2KR58UAxk`$vQW; z^$ZKYh?PU+M<_-;jP4zkAq8j##TyPO#t5eak;F`yiqC_(Mrjneb1w}#I+Ws8?q3da z#f1R3#?fe7pPNnd-4wyHWTZ6ni`U9Ah1EHO)F5bk?Y89e%sT*-&NwmJ?WQWq=gj9MSfss3u>ZSgI@ZMQP@pLfn3AO6}&7$-#-+ zYua|LyzXJ?%ty4WzcKUdp%&@s1+OsbH8U4=!m~bHEzH*bg$6SVERGn9KEnrl*asyF z@=fJZjjX`5HicsC7#U#Hyk+_Gvrg=M9oNH!;xb@radF@HAc_g5@^n63;V}!UAZ+=9 zhTovzYSy+&l(a@JlNPTb)|4QNFX3YQ{6!a4bjZ3dS?ft$&tiwOXXgX+6^<46M840b zKJ8Ae(Q&RgYrtl@Xi+y^dK+ukoz5*8v+x@bN$wnwiC)#!74~>maX_e6zuQ^Z8nUS7 zenJDi=y^5toF7HKa{tAJN4D~&Sq*aQ=P#N6tKtJH#J zw{8X#VEg>|QmPX{>76&%M8;w5g0iR_rIhqe(ORM|L^8Vd{Vwrm_F>S0;R=|Pxp1Lj zhRZK+CO_7&^$~fDGRG*@0gi=TTPjDD?D5a9dkNoGOC6T#26AA91AAWlVoyEknRc9wqGcy$ zj|5kZO6d?n9p%S*iw%#0qHyW*7Elu#no{Anlf2eO;2~5@Is)mrimh7&&+_ZepAIIm z|6pGdfC_ljxT}PHD6jc!tD==A4dABFZh^I5G1GP_tC(Kn+X!FVG3gC+^OoD{J_NDk zvL2AGE;5&h*7?=LvzR~a39V`@3f?{3lcqu>O!cY9Q!Z_%T6x{jX%%m|KNSP$VV#Jr z*|y;cl-?*Kww^}6sS&T^5rl?AvNq8r3BMi|5lg+7HhE@Q1vV5|anm_zsC}knBK%ad zVfpqk&;|lKy8$U3%a<>Y=(`;qWjZ!xcVGHRuV|cEN{NczL~OTBP|ZS8$Fs9uY(4cZ z0~D=+wK51!lvC`yoe%!z6-tXJnU zrku5+3}QzjM-7x&pbvd3j6i2$^}$f6t*D(~Q*>H8s6ELo8LxP`s(o@Ke&nrkOjb(4 zJJ@i0%wfPk&}wlPqS=Ez{mJMls+9y=4~ZoI1>=L>Y^~Rmk}ex=+300AUb3xH@Sqb_ z&E_1Hq4lAz{#=PV_nC(lFYg`1w2(iqkQj9m{iouMgCAbdgFj7GYg4Mo>dYAKxxkMS zC2VC6{5Y(I8ry}s)&B6+K$xJMkC|FLg>4DVY0Y_k1&I^MIF;+!8SV}&6&%22CzhT& z7ddlZN#n!79e1XZh(&l-fVM;Ap%N8;lqHtzzIAg36IbPGZ^_La6;O|M&ax^HM>#G)Ng zr+r-Hmily3hl7uD-Ea9cHR~vYVGcR=gw$H)z(p`*C})>FK~SVKDR(So*i7+{YaS|$ zT4a?4*XYm?3*VVny`gB&q2y0lEY`G&WKXAk`H(L9ewT^QMbu&QY}9TOzEd!@$upa* zI~h^n-gC*B5cWJZ3G1fRZnd8zKfr0NhBdYWEwJ8*8dLwZqn+_BO&9^|tu5otAQD&> ze4K2b^Z2lx&oY*eioO!Y(%*8tBDiZC34Mn6oJha=u;+nb8b8qzsXqXV!rk3k zsf3qkiEe65cfNluY3_yh^V)r}u+6uVs^UcjnSGM0CEJuPpMIiHlp0Kd5oiY>TGySf z53>}lY(K#JA}eyR`dydHT-CgV4Z8xIyAs5dTFPf`9e-&HD+aTGOIwN1TAN$%1D=PD z@+E+7pW^bF`(A1E)G?e(pDSoN^bepZh+4fA&0Okd#ND;>I{X*{NxjQNlSXGFl?)Xj zm}PanV8U>qd5tBZCczz1$BM>8O&mXJWOqKELpCt)IEITbwOl%^w?FJeE1H}A@j(I9 z_Qv?F5t_cR{wo2841!B-6fax6v=@s$5pg9fU~Rzp zC4U}#2BXiB7ysSes_;p~f3@7Eq+#*)*t7P-q+!82Q_%@mD=kaCUCD6?r7D3t_1Oe5 zTzuX3VVUVQ&-5lrdpxCVBfsKC#ejXrHq6YP%Y11Dj+e=8%N%=y`^`i|JFFxGYH}J& z+qGyhw)^*tFHk&I=PYbYu5b=~crMu;c296i1m7E`+LqsFQiFnLCAls+h1x)cu#Vb8 zMTtsC++xo}Ss3|(nz7}qMv7~a`VB9Ue*IdPiqHm%wr0>nU%VHpm@W<&24IU5I{e6F zV2bqnIV$dNgQU>GKrlu89_RHnt+n3O9<}kCA?lXZ!ct+B>neNm88=5O!uTm|pM9;K zXM|p|BV_=QykGKNppd4~_qT>&qu|>UQLa1YAkZ<5&xZ)eOz(f&P9(L)a%||5*FG^ei9nZgecCL=9?>c zZ&DyTFy9M)pmxT9tc5S~7mC%W#m%MUsO~!+B&y?Dd=+Q^)MmBj)X4$ilN>+2`1^}5 zGJh%ni2>Q1fB3@lr{Dc3Fa0R9{j}&hDq`!W`V$uZq|&v1D(*9q?CX6W46gF;V)+xE zew+>4ox}eu^8Ljs<=QGg$k(^E^;7*g;J>hwpIBKx)fvES3$Tv;|Io6Z7F~z%*H5*+ z{r_NP{Z#8ep}(gM8;1V-eUA-8|A!M^zsauMi&;O_hN1s{n`pz(f4^+CepkGvbL*$t zF!T*W2Y$ZFxBl3?hB?+xwPENRhW=lW?f*N4_{&#We{!cA5&Z`+1lAd4{@lucSQ3;%hn&8H*)lUeZ#mB(KjOcKb-JJMBj+$Kb-1DME}V#{x=xr4<7%) z@LKR$lkR_b{0GB7pUM9S)rO&O82ZmjwSEx(_uRtQ*uNwMi7SdftZZ{5!*jU+D+? zBmMpG@YY^xGTd&IMmUyji-!*ewKB^GR_Ukz%gwmI%Id#_$$f^G^S?~-A7bUdi*PM> zTz`ue_Aep8AI0$hw8B5}^&dw5|52(xne0zC`!}fA#?NkicKx?5bb=bOA#btoE^HkR z?;n*O1h4>_?fT0;f6*WO+ak@HgtNe@Ai$IRBQT{Nu)0KS}xD zE#ZGJu5HNsKLvpQpXI#~(!X2yznjQkGvU8MwGq-c^&dR`gW(_f<$rj*6Y&Qj-O~ifS&s9O006xMC19cs=O#5doR3d$JgzAYm}WG^Mx= zCfbi27T*<2A4%yhBvBH;E?uv5PnhEq)x1XxsmPhb&J<(sWZ1v~D5#vCVbki3u~^>V zg640EYMcaxlEtjq1n~1VYpKh<3*YG2g~MrwTqIpOpuF5?5LdlD>*niKcLdpd{givH z-&-nVj2#4pY1XW%-g=zBro8Q3e8lb0+jB{v7Qd>Vqm~eYRY&LI{YRFC4uRbkf#m*a zT+WGL)RoRoPR%d^p?&M2}wK<*LT%V3D?ZEeF`s?Gs4>Jq=TEK&!1%`J!B`Z~9 z@7^d>*@ei@(ih?acfjRF?ytDS#qMVNh+X#50o4`z);0NQ+pNB67E#5Jt8U1v>_y}^ zgGJ8jj0uHI5#cC<3AbvR_>20XaIx%WC* zl)g^Oa8o@=;1J=d+)1tS*a8k8LHji7sP4C;y~nA*g`chmQx{Koch|^FW%EdJH>9x!L$HH-smFUe+cz$PecfO)ZP3)|J4qsmOuZij9`jDY zd}hx#w=60Ks&iO+gI`r!v-`dydnEHz+8e4IK&&n1iHpOi<+v<}=<3TPJP8XP)5Mhf z<3e-CR>JTl76zG(Apqx*Q14VavVG3pz()-rX!uG`9HF^%ac4CQT9Qd8yfM*8(pi9= zpBVD~JpY+7eLepV1U5l%dzTdJkslUiQq-q>G6Y(rf(99jO%5H4YCRNCrffYHz+R*{ z*b9jL3cw!OXjv)Fdl`ku-{xC`axpl7+-3@n4v0N=*?uPE%=qY8F+S7P3F8tj5zcm= zZjdQGj*ioM9MxJWo6~VQTx{HWKynJV->C%dmoE5SN>0N>J|}xp$r@t>iwuKJiu;qc z?f}){L{F((bt!LUDR0VJm%*Z;ZxxDIXGU#-{|>N;+?O{JKEY1?n}=fkAynZt(=Z|l zIL|EK|C0O?Q2EC}^7&4E&Zx5lG`tr&QdGf7BEJ?8(F7;SA@kmo{xjdT>wqSs9>x#q zRf;_L1GQJ68JTG)&YFha?(Zu6l1J5;H;L+eD zUu<>k)KbgQRC!B*;C#J&r|*jIS;_1zj75sDa5-TZooLLH6R~4p$3Q?Ny3(a*mtdn9 z*fxS&HXId#r6g3AI->%?UCHiE3+Z@*OjEv~*B{qb0qU0^6z3l|m@2iB9i8%xFjpOX zGer~R(=%3SlX=o3B>Lbo=mYf0YivrF-29e40_wR0FBPpkJR5PpL_jA- zlBg1PCekkBP2mqNu?lGQ6Sal06*pQ>dkw&P8RW#(L>GaW^YSq!^7_rGOO=fstWW(_ zSl>E%d;sA~#SM>GBi*H25J-_TXRv0*GQBwB5o=X)-b4VSsRZ>ikAo9F@znbx15Uw* zE3vUUF@XwjP=Bd0`~ItwYgHVO?lO9w+8HRX!|e6*`_>fhHNhTJ2u$r5>&m$Z@t-b^ z8>sL;{IT55-`%F}*~{+0#qrm}6uWlcByNML*bgykl-=^2mp+ya?liL!l4$Lj` zSSTqv4NSoHA_yvghoXL}p7`uOu;tZ@5w_)uX3Ogry2;DM!`=$ithxS^H`!(B0hawMBO~!t@-76sTL9-!wq$%F z6D9d*i3ffaY=q-mF<42As2zkTmzn@u&y>jmIuRfvn}(V_>WN8uo#yBc&~yQc`{UL09;VN2Q+K?OVjyQ*zHj?2BZN_^&B_ zx2sl0(7B9=5lcEt30_1ktj9&KURHTU&^&BaNFV&A&}87^juv5N-^300`_dU1WrkHa zae34kbA_QMZAen^&rpvaU>y_Ec!qDmw75_kd70ReR2Mq!R^3b0`cayr5T0eH$hhdO zD&N(ABB(?&dH-05Ot1l^Wi|L|z)f3LkDz@-f{PE zb5>%Z7qN@yeB8uvbaO8v&QvDJ5?z$pmCWu2>Z8+QBddgQ&QSpnccTm{zLbz1y>`4r zAw}ity!)Ij8qU-kXhX_Lu|}rGuwZ21+2>l2uD#tA_f-k#2zi#9A$nyoM`D=5Xq54b z-OqVV>7}+QoMd?-)n~YQq*h=D4-MzqGndK3ni2A5{Mt#3e)7p+isifN!AV^Wq}=ry zOcYLC@oc8EHlbPas43JE>n2|T-%}z9?y}0emlZ9R1Jz&)Mk}Y zEai0JOz7gx`~#+}YaML9$5-z`l%4h7_Pp`6ACvTQWi3SsQFUYn=>_~8J0!VWQ?iv@ zg0s584GV9wW1K)jCk6s6j6*%XEXKayxc`xwV9e!WBJ?DebwC{Q zq{L8Vroc?D5LbbH`^znr%K@0g9Nos$Z}}L>5>E&SHQ+& znv2}8xT=DU5Ti{VmcK4Iw)uG6VFhG7vcMSE42iCw+$p}9IN8Dbnj)9j%T?KM7@rlZ z1|S!*jt2>}6~n4#Ptdq*TYgRHU-W~0bnTzZJuogCM<=8EH`R(dY zo_=Mz)Yla(6N?#Dg;lL;AadYy@3FD=JAn>_7(0}|VzX7MGm%l*IB0IsjdWT4SQazE zcrFm$s8RS$2dysw2bP-sNOYEpw#hGdQ7N|e>pT&0 zUi(M}%0US15na*T6=7dviAT!gYK4T{Y@WF*W~*)*66CU8iXsd@m~7|6_#CgeP@@LJU?AGr}8J z322tI>W;jw+g4@;uAhx-zr9uxM9}tr4)z>sZSNT5!K@Ax3ha&#%f7FZ-i;5R8_9{; zgG4!}oIi5JAFw4AE^PCa_h5%8%a%&36b76%WHU`Mp0@Q%gb7<0c6`0QtyT?y`pi9! z+TIz5)LnBMksNtO`Ci8rL}I*vl*tzg%jaB&6ED%4eW!b#wDDGY(hiNfSfToSAJP^I zd|U?0FcH45T!6a8(`Dqj%O&NKub*Fa_S67GuV;d?=_X?64QPy&kFr0c?3swJz=!E* z=OcjlP`G97#LTFJxv=E(82QMm!^P~fyyJcxmJN-9G5*DkHNew=ed1Tp+MBEuOl1Z- zI`8OLaw8e@i{ADzW@sQ1(TJR^^~-v}&p9F zSmQEeW+M+{+U(nP&;I^`k+MktolQd@Pd9QQ5!3j-ud6L%vXP29X}AN8_SQBYt3z`4 z7CC0yqnl5};Ju}+eKuL&*O}VvQk#ZmL2KAb1A3nvWZQSQ*uUjd^kt61I~RgsFF;$z zUAA=HKKTwAQ4Y4@*?tpE3O_4FyFG}W@6LqS&ghy%G&Wh$K6JkWgc-wlYyHakw~O|0 zS3NW!6Loi6Dio6wGfqHoPtf{wyr6ICxmWVCl7TI00|8nOC=?M;hybN=~yv;vW2 zW9Z-dq~8sj&}!flobQdg06cnkEj4XMfQD)-DEQifp^Km@k$5N-c%MwWnU4rzzjQ^_SLM`rU(RzLFU> zYyS4Jo)tZL;{8av#ucE#jxG~*htwA@Oluq_LtbFYYyp>4WcfEQbnpOmPkbtBvhVTW z^o!G&&}LU0MIMsMo;NDj`y6x&XQI^_h6TxHD1AweymDq)9?Yww5l){ZzQq0#$1%I` zsn2St%zn*7-T;gb>^7rK-!5X8efU}Pb42NLMN1UBYl0DmnY{@>jzt~M?oaM%0j6Y}i$83yOprq}?nROuE@%h` zePx$LqkD!;0>pm9#@5U-9Sp1PgA-!vo9E7J47KyhyPC3@I)5ck)}ji|9)j6MyK~mv z=|3Ie6L}P1^^OKZENuJw$PN?@jnlXK5)!o2&49Y2L|f59A2K6qc%>mk+eX)pF*SES zb)j!J!iZz`9LV>4D-%T>V}e+uG3_B?%CuS94sIOXvtsJC+pWyx{mk5u<(ladWC1AT zwOQJ|zI}+XF6mtG|2$C0dGD|NwC$@N5XZYBa#6;LHY&2DjnO=7mj*Uu2g(RM6Q<=)oD1zq|tB(JwrUIT?v- zK2mtIb(1zE!2iAAgTAAHYh1~_)8VgrM9d@sLRrM%+iF5$_^XB5lvML&EumJ|9+2^| zVN{Pb*xek4?u7!-Pp#=Gh3y0gX}5=7G>}xXget5OglP|-Ch6Vkqoj6fD zR2ps%0%a?6d~-;Ra?CvLMoCsT=^D@l0?4EN6gyA7%ytlSIad_jn)T37cOidDKYf}* zO7#9yk#pAxTI`p(tOfo7Gs}hrE0Ukxi64IHd}|{PRL+! zF+oQX_hLbDDQvBzY>ssBU={B|^xDCR52F_f)i>ML*!gCO4lgy3?wMkZqM0I>0Pn^! zLm(OkL~8{Ih1Ex79*}4g10*G?vJR(T2s5o86VBR$n7bz>8gaEXo8GOwH{?u#qQ8nw zS=-4vt_#5{rA8XsyeCsC$LQ_VEah$0W>nT$b~jpk+Ibh!1drzE>0?0R*2<2z z%50LF@j75hl547c#`*kcS2j{V%cs;JldXqmO@DW=%R;X3?KI(?M*A?#l0CQ-_z^Vj z!5*(0(+|9rMGg%r&i78aN3rJx1op-?m?IzR80md1xNV88K!fH7n=I!>QH(!UWqWiN zVx?1|$2HU%1{p5TW`dQ$wwJC`Cu2f9B$4g588noqoY0aLS&N|8a#a0zp=6X|$=;lNPCBi99z9{-ZF6p%yO0~~gDcZg-qys~$UFMpNuxqZ z5$1GBINLC<_UK3`&R;x|Rq2Sxad%)AjX0ILj2pBTbRJ#l3&L4efcyZFroui6uB#Ej z{(5VLwkos-D|I)&Ubkwi99UON07kRQAWp0Pw$(9y-+wR{?HZs!n^b2h!2Jj@w^_|x z?`yg+2|KV*PO!MPMXB;_ANSbXMg!k~wdwe3`$Oc4_4MTCX;_vtts#)5aRc4c>$djn on+TW;Uz>jB{`1AOS0gMADej>e^Be~o0Q`69g1%PHIqQ4>2ZWg22mk;8 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 fd321546aab27c..2c2359b4400339 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -59,11 +59,10 @@ - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 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 @@ - (void)test__RootViewLoadsAndRenders 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 @@ - (void)testTabBarExampleSnapshot [_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 c6d6e404cb3ad8..ac672249f9f3b7 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -34,92 +34,88 @@ - (instancetype)init 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 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger #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 eb2ddd1cd81266..798c1456b6945f 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -68,6 +68,11 @@ - (instancetype)init 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 6bb95acb0ff248..c5303df1511ac3 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -33,7 +33,9 @@ typedef NS_ENUM(NSInteger, RCTPositionErrorCode) { 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 @@ - (instancetype)init - (void)dealloc { [_locationManager stopUpdatingLocation]; + _locationManager.delegate = nil; } #pragma mark - Private API @@ -153,41 +158,33 @@ - (void)timeout:(NSTimer *)timer #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 = RCTLocationOptionsWithJSON(optionsJSON); - for (RCTLocationRequest *request in _pendingRequests) { - _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); - } - - _locationManager.desiredAccuracy = _observerOptions.accuracy; - [self beginLocationUpdates]; - _observingLocation = YES; + // Select best options + _observerOptions = options; + for (RCTLocationRequest *request in _pendingRequests) { + _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); + } - }); + _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 @@ - (void)timeout:(NSTimer *)timer return; } - dispatch_async(dispatch_get_main_queue(), ^{ - - if (![CLLocationManager locationServicesEnabled]) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") - ]); - return; - } + if (![CLLocationManager locationServicesEnabled]) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") + ]); + return; } + } - if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorDenied, nil) - ]); - 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) { + // 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; - } + // 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]; - - }); + // 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 990c5900fee20d..4be8bfb8e45d5b 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -62,8 +62,10 @@ - (void)handleOpenURLNotification:(NSNotification *)notification 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 21ed60c6b91f05..a8a2da16ee9aa2 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 58b6572f8bfa32..33f562515d629b 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 @@ - (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controlle 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 9b3a7d3c89881b..75a8118318ee10 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 @@ - (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDi 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 @@ - (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDi - (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,26 +59,21 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName - (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]; + 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 - RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil]; + NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); + RCTTestModule *testModule = rootView.bridge.modules[testModuleName]; + testModule.controller = _testController; 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; + + 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 - rootView.initialProperties = initialProps; - rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index ab853851ca31e6..544e5e1a2bbbca 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 5c6bcf7cb35a5b..7b1f95b69e88e5 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -792,6 +792,7 @@ - (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink @implementation RCTBridge { RCTSparseArray *_modulesByID; + RCTSparseArray *_queuesByID; NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; @@ -824,13 +825,13 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL 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 @@ - (void)setUp [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 @@ - (void)setUp } } + // 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 @@ - (void)invalidate // Release modules (breaks retain cycle if module has strong bridge reference) _modulesByID = nil; + _queuesByID = nil; _modulesByName = nil; } @@ -1203,6 +1221,8 @@ - (void)_handleBuffer:(id)buffer 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 @@ - (void)_handleBuffer:(id)buffer } } - // 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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 70d5c76d092138..c8fa41c4438d53 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 e3f5e85980a0c5..22cc7ec816b3ee 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 25de29656dc98a..2aefe89405e96e 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -58,6 +58,10 @@ + (NSData *)NSData:(id)json + (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 @@ + (NSDate *)NSDate:(id)json // 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 ae11ce52be14df..b97364e3809aad 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -64,37 +64,34 @@ - (instancetype)init return; } - dispatch_async(dispatch_get_main_queue(), ^{ - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:message - delegate:self - cancelButtonTitle:nil - otherButtonTitles:nil]; - - 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 ++; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:self + cancelButtonTitle:nil + otherButtonTitles:nil]; + + 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 ++; + } - [_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 8e6d414cf083ef..2c01161d48fa93 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 @@ @implementation RCTAsyncLocalStorage 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 @@ - (id)_writeEntry:(NSArray *)entry 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; - } - 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]]); + 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]; } - }); + 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]; - } - if (callback) { - callback(@[errorOut ?: [NSNull null]]); + 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]; + } + 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 5be80133bcf96e..9e919f3a364bcf 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -48,13 +48,11 @@ - (instancetype)init } #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 76e9190bc279ed..e29a05637de173 100644 --- a/React/Modules/RCTSourceCode.m +++ b/React/Modules/RCTSourceCode.m @@ -24,7 +24,6 @@ @implementation RCTSourceCode } else { failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]); } - } @end diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index ad8ee1df6db4f0..149ad568e133ec 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -29,31 +29,25 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() 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 aaab5fae006c61..1f6e84d6afe98f 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -187,21 +187,17 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update 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 ae04f9a1d4c21c..961b5a0b599436 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -178,7 +178,7 @@ @interface RCTUIManager () @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 @@ @implementation RCTUIManager }; } -/** - * 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 @@ - (void)setBridge:(RCTBridge *)bridge 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 @@ - (void)setBridge:(RCTBridge *)bridge _viewConfigs = [viewConfigs copy]; } +- (dispatch_queue_t)methodQueue +{ + return _shadowQueue; +} + - (void)registerRootView:(UIView *)rootView; { RCTAssertMainThread(); @@ -366,7 +358,7 @@ - (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView 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 @@ - (void)_purgeChildren:(NSArray *)children fromRegistry:(RCTSparseArray *)regist - (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 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block - (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 @@ - (void)_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 @@ - (void)batchDidComplete - (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 74c7bbd8ce7a17..ed97cf3b2bf3f0 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 3c848537454872..8388b83cfb489c 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 @@ @implementation RCTViewManager 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/62] 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 73afe99bdb70c0..bb22c6b26511bf 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 9d413e5c7ca7a1..ec72a0b4f4696b 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 7f27ae0ea7dae3..b9abd5965c5f07 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 c3a1ca8e5cb1ed..00000000000000 --- 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 7f3d17c461b70a..00000000000000 --- 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/62] Improved logging and dev menu --- .../RCTAnimationExperimentalManager.m | 4 +- React/Base/RCTDevMenu.h | 45 +++- React/Base/RCTDevMenu.m | 228 +++++++++++++----- 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, 300 insertions(+), 133 deletions(-) diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index 798c1456b6945f..64ee577fef30b6 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -264,7 +264,9 @@ static void RCTInvalidAnimationProp(RCTSparseArray *callbacks, NSNumber *tag, NS 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 a49e076e621743..8057e570869383 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 + +/** + * 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; -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; +/** + * 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 7621b1955f3db8..f29201f9af3260 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 @@ - (void)stopProfiling; @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 +{ + // 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]) { - _bridge = bridge; + 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; + + switch (buttonIndex) { + case 0: { + [_bridge reload]; + break; + } + 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)_pollAndReload +- (void)setProfilingEnabled:(BOOL)enabled { - 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]; + if (_profilingEnabled == enabled) { + return; + } + + _profilingEnabled = enabled; + if (_bridge.profile) { + [_bridge stopProfiling]; + } else { + [_bridge startProfiling]; } } -- (void)_checkForUpdates:(NSURL *)URL +- (void)setLiveReloadEnabled:(BOOL)enabled { - 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]; - [_bridge reload]; - } - [self _pollAndReload]; - }); + 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 bdc551b4d22194..8d52529e67acfb 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 baf2ca34455476..02785fb416e861 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 1770a20a2a6fb0..4b9653650041a3 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -31,8 +31,8 @@ + (void)logMessage:(NSString *)message level:(NSString *)level; 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 1227eba9473ba5..ee5a35d7f0be96 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 c9a97dfd134e6a..24cfe8417101bd 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 @@ - (NSNumber *)allocateRootTag; @implementation RCTRootView { - RCTDevMenu *_devMenu; RCTBridge *_bridge; RCTTouchHandler *_touchHandler; NSString *_moduleName; @@ -60,12 +58,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge self.backgroundColor = [UIColor whiteColor]; -#ifdef DEBUG - - _enableDevMenu = YES; - -#endif - _bridge = bridge; _moduleName = moduleName; @@ -120,18 +112,6 @@ - (BOOL)canBecomeFirstResponder 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 8475e2afa311f2..39616bf3506db1 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 JSValueRef RCTNoop(JSContextRef context, JSObjectRef object, JSObjectRef + (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 @@ - (void)executeJSCall:(NSString *)name } - (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 @@ - (void)executeApplicationScript:(NSString *)script } 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 @@ - (void)injectJSONText:(NSString *)script 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 @@ - (void)injectJSONText:(NSString *)script 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 9e919f3a364bcf..ed30d874185465 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -19,10 +19,6 @@ @implementation RCTExceptionsManager NSUInteger _reloadRetries; } -#ifndef DEBUG -static NSUInteger RCTReloadRetries = 0; -#endif - RCT_EXPORT_MODULE() - (instancetype)initWithDelegate:(id)delegate @@ -47,27 +43,32 @@ - (instancetype)init 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 @@ - (instancetype)init 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 961b5a0b599436..2830ce6b029582 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -999,7 +999,7 @@ static void RCTMeasureLayout(RCTShadowView *view, * 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 @@ static void RCTMeasureLayout(RCTShadowView *view, } 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 @@ static void RCTMeasureLayout(RCTShadowView *view, 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/62] [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 784c91e12768fa..7fd817d53cfda1 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -82,7 +82,7 @@ - (BOOL)prepareJSRuntime { __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 @@ - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error 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 @@ - (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)call }]; callback(error, nil); return; + } else if (executorID && ![RCTGetExecutorID(self) isEqualToNumber:executorID]) { + return; } NSNumber *expectedID = @(lastID++); @@ -135,12 +137,12 @@ - (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)call - (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 @@ - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSAr @"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 7b1f95b69e88e5..a2ff0146bb30ce 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -234,12 +234,13 @@ @interface RCTBridge () - (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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName 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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName __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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName - (void)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments + context:(NSNumber *)context { #if DEBUG @@ -503,8 +506,8 @@ - (void)invokeWithBridge:(RCTBridge *)bridge 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 @@ - (NSString *)description 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 @@ @implementation RCTBridge RCTDisplayLink *_displayLink; NSMutableSet *_frameUpdateObservers; NSMutableArray *_scheduledCalls; - NSMutableArray *_scheduledCallbacks; + RCTSparseArray *_scheduledCallbacks; BOOL _loading; NSUInteger _startingTime; @@ -829,13 +831,13 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL - (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 @@ - (void)bindKeys } - - (NSDictionary *)modules { RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. " @@ -1072,7 +1073,8 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args if (!_loading) { [self _invokeAndProcessModule:@"BatchedBridge" method:@"callFunctionReturnFlushedQueue" - arguments:@[moduleID, methodID, args ?: @[]]]; + arguments:@[moduleID, methodID, args ?: @[]] + context:RCTGetExecutorID(_javaScriptExecutor)]; } } @@ -1093,13 +1095,15 @@ - (void)_immediatelyCallTimer:(NSNumber *)timer #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 @@ - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete: { 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 @@ - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete: [_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 @@ - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete: #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 @@ - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arg @"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 @@ - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arg 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 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)me 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 @@ - (void)_handleBuffer:(id)buffer [self _handleRequestNumber:i moduleID:[moduleIDs[i] integerValue] methodID:[methodIDs[i] integerValue] - params:paramsArrays[i]]; + params:paramsArrays[i] + context:context]; } } @@ -1247,6 +1256,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i } @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 @@ - (void)_runScheduledCalls { #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 @@ + (void)logMessage:(NSString *)message level:(NSString *)level [_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 57dff78e756700..2816c7a7a81f3d 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 02785fb416e861..4eaaf278dd480e 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -140,9 +140,9 @@ - (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onCom 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 39616bf3506db1..71e4f45c30f3cc 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -229,13 +229,14 @@ - (void)dealloc - (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 55de44ab969479..0bc6fdfc81f6d4 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -76,10 +76,15 @@ - (UIWebView *)invalidateAndReclaimWebView - (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/62] [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 c981e41294b486..0fdfa1fc8b76c4 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/62] [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 a2ff0146bb30ce..46dcc1dfe321c1 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 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { @interface RCTBridge () -@property (nonatomic, copy, readonly) NSArray *profile; - - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args @@ -250,6 +216,7 @@ @interface RCTModuleMethod : NSObject @property (nonatomic, copy, readonly) NSString *moduleClassName; @property (nonatomic, copy, readonly) NSString *JSMethodName; +@property (nonatomic, assign, readonly) SEL selector; @end @@ -805,10 +772,6 @@ @implementation RCTBridge NSMutableArray *_scheduledCalls; RCTSparseArray *_scheduledCallbacks; BOOL _loading; - - NSUInteger _startingTime; - NSMutableArray *_profile; - NSLock *_profileLock; } static id _latestJSExecutor; @@ -1111,25 +1074,28 @@ - (void)_immediatelyCallTimer:(NSNumber *)timer - (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 @@ - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete: - (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 @@ - (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arg [_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 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)me #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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i } 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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i @throw; } } + + RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ + @"module": method.moduleClassName, + @"method": method.JSMethodName, + @"selector": NSStringFromSelector(method.selector), + }); }); return YES; @@ -1305,7 +1272,8 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i - (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 @@ - (void)_update:(CADisplayLink *)displayLink [self _runScheduledCalls]; - RCT_PROFILE_END(display_link, nil, @"main_thread"); + RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); } - (void)_runScheduledCalls @@ -1382,23 +1350,12 @@ - (void)startProfiling 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 f29201f9af3260..4af9d4e62c118a 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 @@ - (void)show 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 @@ - (void)setProfilingEnabled:(BOOL)enabled } _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 7ffd860068d74e..3a8a70d5b6af34 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 4b9653650041a3..449980fc3b9620 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 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix) [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 00000000000000..b3a1683d1d30cc --- /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 00000000000000..4ba99725b176b1 --- /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 71e4f45c30f3cc..3de6182d144c30 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 @@ - (void)executeJSCall:(NSString *)name { 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 @@ - (void)executeJSCall:(NSString *)name } onComplete(objcValue, nil); - }]; + }), @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSString *)script @@ -285,7 +286,7 @@ - (void)executeApplicationScript:(NSString *)script 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 @@ - (void)executeApplicationScript:(NSString *)script } onComplete(error); } - }]; + }), @"js_call", (@{ @"url": sourceURL }))]; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block @@ -327,7 +328,7 @@ - (void)injectJSONText:(NSString *)script #endif __weak RCTContextExecutor *weakSelf = self; - [self executeBlockOnJavaScriptQueue:^{ + [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{ RCTContextExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; @@ -354,7 +355,7 @@ - (void)injectJSONText:(NSString *)script if (onComplete) { onComplete(nil); } - }]; + }), @"js_call,json_call", (@{@"objectName": objectName}))]; } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 2830ce6b029582..e2dc8d560d089b 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 @@ - (void)flushUIBlocks // 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 294bf414595641..48d7aee0459b40 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/62] 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 0376d2a9c0e960..1b31700649b31d 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -175,7 +175,7 @@ - (void)dockClosestSectionHeader 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 @@ - (void)dockClosestSectionHeader 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 @@ - (void)dockClosestSectionHeader 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/62] [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 3de6182d144c30..d2aac3fbe6df7d 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -47,9 +47,11 @@ - (BOOL)isValid - (void)invalidate { - JSGlobalContextRelease(_ctx); - _ctx = NULL; - _self = nil; + if (self.isValid) { + JSGlobalContextRelease(_ctx); + _ctx = NULL; + _self = nil; + } } @end @@ -216,10 +218,7 @@ - (BOOL)isValid - (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/62] [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 4ba99725b176b1..71d34551e9c37a 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/62] Fixed reachability --- Examples/2048/2048/AppDelegate.m | 41 ++++++++++++-------- Examples/Movies/Movies/AppDelegate.m | 39 ++++++++++++------- Examples/SampleApp/iOS/AppDelegate.m | 41 ++++++++++++-------- Examples/TicTacToe/TicTacToe/AppDelegate.m | 41 ++++++++++++-------- Examples/UIExplorer/UIExplorer/AppDelegate.m | 41 ++++++++++++-------- Libraries/Network/RCTReachability.m | 6 +-- 6 files changed, 127 insertions(+), 82 deletions(-) diff --git a/Examples/2048/2048/AppDelegate.m b/Examples/2048/2048/AppDelegate.m index bc089038c21d34..004e854a7558ae 100644 --- a/Examples/2048/2048/AppDelegate.m +++ b/Examples/2048/2048/AppDelegate.m @@ -22,24 +22,33 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. - - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/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 b5672271252c87..74aed2cc4724d9 100644 --- a/Examples/Movies/Movies/AppDelegate.m +++ b/Examples/Movies/Movies/AppDelegate.m @@ -23,24 +23,33 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { 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 5b6cc2d58e30b6..777072c6c56964 100644 --- a/Examples/SampleApp/iOS/AppDelegate.m +++ b/Examples/SampleApp/iOS/AppDelegate.m @@ -17,24 +17,33 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. - - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"]; - // 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 a118b94dc47051..9c328a3a83a57e 100644 --- a/Examples/TicTacToe/TicTacToe/AppDelegate.m +++ b/Examples/TicTacToe/TicTacToe/AppDelegate.m @@ -22,24 +22,33 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. - - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/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 84734f12f9d642..d72262e78f0cd5 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -22,24 +22,33 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { NSURL *jsCodeLocation; - // Loading JavaScript code - uncomment the one you want. - - // OPTION 1 - // Load from development server. Start the server from the repository root: - // - // $ npm start - // - // To run on device, change `localhost` to the IP address of your computer, and make sure your computer and - // iOS device are on the same Wi-Fi network. + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/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 b5f30de30e7865..e0711571b9a3c3 100644 --- a/Libraries/Network/RCTReachability.m +++ b/Libraries/Network/RCTReachability.m @@ -25,6 +25,8 @@ @implementation RCTReachability @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 @@ - (instancetype)initWithHost:(NSString *)host - (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/62] [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 2816c7a7a81f3d..8ff5a16585d8bc 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/62] [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 cd910054f4d817..059f0e319b6b75 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/62] 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 ac672249f9f3b7..75798efaf37ada 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -30,6 +30,11 @@ - (instancetype)init 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 c5303df1511ac3..3e864657b82c5d 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -128,6 +128,11 @@ - (void)dealloc _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 4be8bfb8e45d5b..eec17a012f9eeb 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -36,6 +36,11 @@ - (void)dealloc [[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 @@ - (void)handleOpenURLNotification:(NSNotification *)notification 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 544e5e1a2bbbca..c0f3a1a916b0dc 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 46dcc1dfe321c1..31a8bea7736921 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -228,6 +228,7 @@ @implementation RCTModuleMethod NSMethodSignature *_methodSignature; NSArray *_argumentBlocks; NSString *_methodName; + dispatch_block_t _methodQueue; } static Class _globalExecutorClass; @@ -762,6 +763,7 @@ @implementation RCTBridge { RCTSparseArray *_modulesByID; RCTSparseArray *_queuesByID; + dispatch_queue_t _methodQueue; NSDictionary *_modulesByName; id _javaScriptExecutor; Class _executorClass; @@ -787,7 +789,6 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL [self setUp]; [self bindKeys]; } - return self; } @@ -797,6 +798,7 @@ - (void)setUp _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 @@ - (void)setUp } } - // 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 @@ - (void)setUp } 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 @@ - (void)_handleBuffer:(id)buffer context:(NSNumber *)context [_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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i __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 @@ - (void)removeFrameUpdateObserver:(id)observer - (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 c8fa41c4438d53..12f7803ea73108 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 b97364e3809aad..2690de1dfea65e 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -35,6 +35,11 @@ - (instancetype)init 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 149ad568e133ec..04bb390387c0fb 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 1f6e84d6afe98f..62d42a7bbf532d 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -108,6 +108,11 @@ - (void)dealloc [[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/62] [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 d0d4aba54bfaf9..6e002a2255a7cf 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -47,7 +47,7 @@ } 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/62] [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 c0f3a1a916b0dc..0a82b05e2c440e 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 e29a05637de173..1b6eb842ece49d 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 @@ @implementation RCTSourceCode } } +- (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/62] [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 7fd817d53cfda1..4bdfab1bd53e04 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -136,7 +136,7 @@ - (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitF - (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 @@ - (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSAr { 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 6e002a2255a7cf..d72e40ead23ba4 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -41,13 +41,13 @@ 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/62] [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 0f55c8edcc9ebf..67e31e47efef45 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 5a7438d27f4293..db596a7bc4beb4 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 333e0f5631e1df..c177321651bc67 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 2b1eb6b1658872..aab55c0842a062 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/62] 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 83c90a1fdbe915..6257c12b71a8c2 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 3fd675103367ce..589ab75ddb3af9 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 bb9bb2acfb6b44..56cda0c88b6971 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -31,6 +31,7 @@ @implementation RCTWebView - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super initWithFrame:CGRectZero])) { + super.backgroundColor = [UIColor clearColor]; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; _eventDispatcher = eventDispatcher; @@ -95,6 +96,18 @@ - (void)setContentInset:(UIEdgeInsets)contentInset 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 e25a7da68b98b2..015285871ed4e0 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -25,6 +25,8 @@ - (UIView *)view 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/62] [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 f783c34eaf147d..7a030b45580ea4 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 9fb265f976839e..fa62b3f455baf6 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/62] Added RCT_DEBUG --- React/Base/RCTAssert.h | 31 +++--------- React/Base/RCTBridge.h | 3 +- React/Base/RCTBridge.m | 75 ++++++++++++++-------------- 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, 185 insertions(+), 190 deletions(-) create mode 100644 React/Base/RCTDefines.h diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 7e73aed7d6ab33..b0a3c5c52f553f 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 0a82b05e2c440e..a37b9ac7db946f 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 31a8bea7736921..40dfceec8f6ea4 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -143,7 +143,8 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { // 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 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { } } -#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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName _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 @@ - (void)invokeWithBridge:(RCTBridge *)bridge 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]); -#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; + // Sanity check + RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ + %@ on a module of class %@", _methodName, [module class]); + + // 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 22cc7ec816b3ee..e664f06e0863e1 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 00000000000000..71550a30d1c6c4 --- /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 3a8a70d5b6af34..7fa25e6a88ce42 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 449980fc3b9620..6ca2d4eb8ecb31 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 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix) { 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 - - // 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"]; + if (RCT_DEBUG) { -#endif + // 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"]; + } } } diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index b3a1683d1d30cc..0c254c80a15c0d 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 71d34551e9c37a..929a026a9587a2 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 3bed3150578c6a..882a87bfe8d736 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -282,23 +282,14 @@ - (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack - (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 d20ba8a5faf47e..812a651222a85c 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 d2aac3fbe6df7d..86444dd2a7f903 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 @@ - (void)injectJSONText:(NSString *)script 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 ed30d874185465..ece5d06687f10a 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 @@ - (instancetype)init 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 @@ - (instancetype)init 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 e2dc8d560d089b..3f3ea75960dcb9 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/62] [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 7beeabbeac5f78..2f02b1b9dc5f51 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/62] [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 f8e23398a9c802..e6c96b7fa6b30e 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/62] 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 acb5daa3e736b2..4a9b599165984e 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 3fc062a37eb639..9993cbc5a3060d 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 4bdfab1bd53e04..eb6428fc27fe80 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 @@ - (void)dealloc } @end + +#endif diff --git a/Libraries/RCTWebSocketDebugger/SRWebSocket.m b/Libraries/RCTWebSocketDebugger/SRWebSocket.m index 589ab75ddb3af9..e98e9e45637167 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 40dfceec8f6ea4..1c676640eba501 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -314,7 +314,8 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName 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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName #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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName #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 @@ - (void)setUp [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 @@ - (void)setUp [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]]; } + +#endif + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self]; } @@ -936,6 +944,9 @@ - (void)bindKeys 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 @@ - (void)bindKeys [strongSelf reload]; }]; #endif +#endif } @@ -1156,14 +1168,18 @@ - (void)_handleBuffer:(id)buffer context:(NSNumber *)context 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 @@ - (void)_handleBuffer:(id)buffer context:(NSNumber *)context } } +#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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 e664f06e0863e1..59ba79d271795b 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 2aefe89405e96e..f1ed77298dafdb 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 @@ + (NSDate *)NSDate:(id)json }), 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 @@ + (UIColor *)UIColor:(id)json } 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 @@ + (UIColor *)UIColor:(id)json 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 @@ + (UIImage *)UIImage:(id)json // 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 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family 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 7fa25e6a88ce42..75cbe722e09b43 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 6ca2d4eb8ecb31..fb70fe6d3053df 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 9a3a9b49a872ee..058759da7a0a5f 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 882a87bfe8d736..6268402526315e 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)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack - (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 @@ - (void)dismiss } @end + +#endif diff --git a/React/Executors/RCTWebViewExecutor.h b/React/Executors/RCTWebViewExecutor.h index 77d8a8310715c5..db8710c7d579bf 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 0bc6fdfc81f6d4..09628850fb3aa8 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 @@ - (void)injectJSONText:(NSString *)script 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 ece5d06687f10a..878138282f9d1f 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -44,10 +44,11 @@ - (instancetype)init 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 @@ - (instancetype)init 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 3f3ea75960dcb9..fc688e491cf55d 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -222,6 +222,7 @@ @implementation RCTUIManager 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 @@ @implementation RCTUIManager 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 48d7aee0459b40..f32e86ff1dc8bc 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 ed97cf3b2bf3f0..3788832ef5857d 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/62] [catlyst|madman] make map fire onRegionChange for user zoom in/out again --- React/Views/RCTMapManager.m | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 8de35141586a29..f9a6b9175ccbf0 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -67,15 +67,13 @@ - (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(BOOL)animated { [self _regionChanged:mapView]; - if (animated) { - mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval - target:self - selector:@selector(_onTick:) - userInfo:@{ RCTMapViewKey: mapView } - repeats:YES]; - - [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes]; - } + mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval + target:self + selector:@selector(_onTick:) + userInfo:@{ RCTMapViewKey: mapView } + repeats:YES]; + + [[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/62] [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 3cdfa1c6a6e2bb..90db1c4ade5f54 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 d3f6ce289eb590..cda717e875ac98 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 66534597750f01..6afb5f25c879e9 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 c177321651bc67..763e6dd6d17b9a 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 aab55c0842a062..c651dde78b8576 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 e0bfeb2f339d63..525636cb532de4 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 f1351a4040776e60c927cfa23a2a965a312b5ba4 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Tue, 21 Apr 2015 11:05:37 -0700 Subject: [PATCH 38/62] [react_native] JS files from D2009158: [react_native] Update other stacktrace-parser From c46c4a0ad4261ab3bd94568bafa43bda0475281a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 21 Apr 2015 10:59:29 -0700 Subject: [PATCH 39/62] [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 b629dfbf01f3c6..38ad19bf952675 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/62] [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 23da3a78c62ca7..212e17f715e547 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/62] [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 00000000000000..916419a29d3c90 --- /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 00000000000000..04e8d841e75f6f --- /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 58b763b9244a4f..a103da98d7f46a 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 @@ @implementation RCTSliderManager - (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)sliderTouchEnd:(UISlider *)sender +- (void)sliderValueChanged:(UISlider *)sender { - NSDictionary *event = @{ - @"target": sender.reactTag, - @"value": @(sender.value), - @"continuous": @NO, - }; + RCTSendSliderEvent(self, sender, YES); +} - [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; +- (void)sliderTouchEnd:(UISlider *)sender +{ + 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/62] [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 7a030b45580ea4..2d7a4a68f89fa7 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/62] [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 9a7a2f9541f9ff..b947b95258f38c 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/62] [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 d519c1ac5308e1..be95a3f3ff91f0 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 00000000000000..69bcb116a917cf --- /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 00000000000000..137f92f1c17c7d --- /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/62] [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 2d7a4a68f89fa7..f4fcdd443776fd 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 + + // Contextual nav actions + pop: this.requestPop, + popToRoute: this.requestPopTo, - // Legacy, imperitive nav actions. Use request when possible. + // 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/62] 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 f32e86ff1dc8bc..38c207459e3ac0 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/62] 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 4a9b599165984e..d9e3b9f5b695ae 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 59ba79d271795b..46bbbf8a4634c9 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 38c207459e3ac0..8823e15655c0d3 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/62] 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 de72d7b9f07306..0d1612bc9e0d9e 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/62] 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 18a79ae4a571f4..c322a743228306 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 d98b997ca82a72..2b65671a9c30d8 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/62] 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 d7619d07546f07..cfd1e35cc9b673 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/62] 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 a8a2da16ee9aa2..f248cbfca40818 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 75a8118318ee10..4d5963743f4a9c 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 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictiona 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 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictiona } 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/62] [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 84f6b85e1f0882..e51686ac7bcc18 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -25,6 +25,7 @@ - (instancetype)initWithFrame:(CGRect)frame 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/62] [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 f2bcbfd516c650..3a44020a663590 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 9bd0a2ac473080..41fc9b8779f50d 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 2f02b1b9dc5f51..e38dd9564bbc3c 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 70222794758e96..5a56e36b753d4f 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 05ac37c74f5bc7..4163b2d7861b69 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 86b38a8cda836d..32945c4343474e 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 0fdfa1fc8b76c4..0da57f554257e5 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 6257c12b71a8c2..c4e4fbcd3299c8 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 032e572ece118d..ab1d61728895fd 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 eb0d626e62e161..c60d83e81ebb2f 100644 --- a/React/Views/RCTSwitchManager.m +++ b/React/Views/RCTSwitchManager.m @@ -42,7 +42,14 @@ - (void)onChange:(RCTSwitch *)sender 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 8bbe782b7346ce..cdfa8669ce98d5 100644 --- a/React/Views/RCTTabBarItemManager.m +++ b/React/Views/RCTTabBarItemManager.m @@ -24,7 +24,7 @@ - (UIView *)view 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/62] [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 9ecf2543b94d44..4d4d21e25d6942 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/62] 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 55ad8a6b989e9e..4d9241853626a4 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 fc688e491cf55d..b7ae182bad84f9 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -225,29 +225,23 @@ @implementation RCTUIManager // 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 @@ - (NSDictionary *)constantsToExport } 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 @@ - (NSDictionary *)constantsToExport 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/62] [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 | 90 +++++++++++-------- React/Modules/RCTTiming.m | 2 - 5 files changed, 92 insertions(+), 51 deletions(-) diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index eb6428fc27fe80..753c8d76d38324 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -175,6 +175,11 @@ - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)object }); } +- (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 1c676640eba501..a4a4362b0323b2 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -694,7 +694,7 @@ - (NSString *)description @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 @@ @implementation RCTDisplayLink { __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 @@ - (void)invalidate - (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 @@ @implementation RCTBridge NSURL *_bundleURL; RCTBridgeModuleProviderBlock _moduleProvider; RCTDisplayLink *_displayLink; + RCTDisplayLink *_vsyncDisplayLink; NSMutableSet *_frameUpdateObservers; NSMutableArray *_scheduledCalls; RCTSparseArray *_scheduledCallbacks; @@ -799,11 +805,15 @@ - (void)setUp _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 @@ - (void)invalidate _javaScriptExecutor = nil; [_displayLink invalidate]; + [_vsyncDisplayLink invalidate]; _frameUpdateObservers = nil; // Invalidate modules @@ -1294,9 +1305,9 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i 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 @@ - (void)_update:(CADisplayLink *)displayLink } } - [self _runScheduledCalls]; - - RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); -} - -- (void)_runScheduledCalls -{ #if BATCHED_BRIDGE NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; @@ -1330,6 +1334,13 @@ - (void)_runScheduledCalls } #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 8ff5a16585d8bc..eb7fd7d31947cd 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 929a026a9587a2..19c6900c73abbc 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -36,6 +36,7 @@ NSUInteger RCTProfileEventID = 0; NSMutableDictionary *RCTProfileOngoingEvents; NSTimeInterval RCTProfileStartTime; +NSLock *_RCTProfileLock; #pragma mark - Macros @@ -51,6 +52,11 @@ return __VA_ARGS__; \ } +#define RCTProfileLock(...) \ +[_RCTProfileLock lock]; \ +__VA_ARGS__ \ +[_RCTProfileLock unlock] + #pragma mark - Private Helpers NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) @@ -66,7 +72,6 @@ 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 @@ 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; - } - - NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); - - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"cat": categories, - @"ph": @"X", - @"ts": startTimestamp, - @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), - @"args": args ?: @[], + RCTProfileLock( + NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; + if (startTimestamp) { + NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); + + 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 62d42a7bbf532d..1d99c1a2d42663 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -142,8 +142,6 @@ - (void)startTimers - (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/62] [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 cfd1e35cc9b673..4a4f16ac685178 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/62] [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 f4fcdd443776fd..ea8977ff87ca38 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/62] [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 d9118b7484f0e7..c5476eaab5cfe3 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/62] [react-packager] Additional data to asset modules --- .../AssetServer/__tests__/AssetServer-test.js | 141 ++++++++++++------ .../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, 225 insertions(+), 93 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 eede72c0ca3cbb..94dba8cff78ae4 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'], - assetExts: ['png'], - }); + pit('should support multiple project roots', function() { + var server = new AssetServer({ + projectRoots: ['/root', '/root2'], + 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'); + }); }); + }); + + 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'], + }); - return server.get('newImages/imgs/b.png').then(function(data) { - expect(data).toBe('b1 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.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 bdabafff4a177a..6f07dd01d38f89 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 cda717e875ac98..9cb08122c23fcb 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 6afb5f25c879e9..08a4b513bf0023 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 763e6dd6d17b9a..8e1420a3a6c0dd 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 c651dde78b8576..8563e27280f3c3 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 525636cb532de4..79022b21181166 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 0ea13d15d04688..d0e08a2f4baacf 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 ad5ac3fbfe5edd..d0309ca6a30335 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 8fb91afc4ebcff..c4848fd179b020 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/62] 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 3e864657b82c5d..5d56caccbe314b 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -163,12 +163,12 @@ - (void)timeout:(NSTimer *)timer #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 @@ - (void)timeout:(NSTimer *)timer } } -RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options +RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON withSuccessCallback:(RCTResponseSenderBlock)successBlock errorCallback:(RCTResponseSenderBlock)errorBlock) { @@ -219,6 +219,7 @@ - (void)timeout:(NSTimer *)timer } // 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 a4a4362b0323b2..48fd672a3926d4 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -375,10 +375,14 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName 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 @@ - (instancetype)initWithReactMethodName:(NSString *)reactMethodName default: defaultCase(argumentType); - break; } } } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index b7ae182bad84f9..451a343d04960d 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1001,11 +1001,12 @@ static void RCTMeasureLayout(RCTShadowView *view, * 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 @@ static void RCTMeasureLayout(RCTShadowView *view, } 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/62] [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 be95a3f3ff91f0..e917b6b637355b 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 69bcb116a917cf..b385e29aa8d692 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 137f92f1c17c7d..2325e2c8c3b13a 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;