From 1d9ba1ceb667b52e26511240f291a1e81bdbdef8 Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Wed, 23 Sep 2015 17:47:31 +0100 Subject: [PATCH 0001/1411] change native-modules link to work on gh-pages --- docs/NativeComponentsAndroid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md index b7d41551c6dc..81686e3703f9 100644 --- a/docs/NativeComponentsAndroid.md +++ b/docs/NativeComponentsAndroid.md @@ -96,7 +96,7 @@ Setting properties on a view is not handled by automatically calling setter meth ## 5. Register the `ViewManager` -The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](NativeModulesAndroid.md), via the applications package member function `createViewManagers.` +The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](native-modules-android.html), via the applications package member function `createViewManagers.` ```java @Override From 1c61a8a510f3ace3b1b1f9c13fd084340ce359c4 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 9 Oct 2015 22:34:00 +0100 Subject: [PATCH 0002/1411] Create .editorconfig --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..3b281f2f7d5a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 From 27a0ce3ead8235b53c2081f820063af36401831d Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 16 Nov 2015 15:01:48 +0800 Subject: [PATCH 0003/1411] update the way of starting packager on windows update the way of starting packager on windows --- docs/LinuxWindowsSupport.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/LinuxWindowsSupport.md b/docs/LinuxWindowsSupport.md index caff0bc027b9..219cd2bf89f7 100644 --- a/docs/LinuxWindowsSupport.md +++ b/docs/LinuxWindowsSupport.md @@ -19,5 +19,15 @@ As of **version 0.14** Android development with React native is mostly possible On Windows the packager won't be started automatically when you run `react-native run-android`. You can start it manually using: + #For React Native version < 0.14 cd MyAwesomeApp node node_modules/react-native/packager/packager.js + + + #For React Native version >= 0.14 (which had removed packager.js) + cd MyAwesomeApp + react-native start + +If you hit a `ERROR Watcher took too long to load` on Windows, try increasing the [timeout](https://github.com/facebook/react-native/blob/master/packager/react-packager/src/FileWatcher/index.js#L17) in this file (under your node_modules/react-native). + + From 7e22202061a3bd63c6ab40a4b878976e12036dc1 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Wed, 18 Nov 2015 09:01:17 +0800 Subject: [PATCH 0004/1411] add `ERROR Watcher took too long to load` hint add `ERROR Watcher took too long to load` hint --- docs/LinuxWindowsSupport.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/LinuxWindowsSupport.md b/docs/LinuxWindowsSupport.md index 219cd2bf89f7..2f6d59bcdbce 100644 --- a/docs/LinuxWindowsSupport.md +++ b/docs/LinuxWindowsSupport.md @@ -28,6 +28,6 @@ On Windows the packager won't be started automatically when you run `react-nativ cd MyAwesomeApp react-native start -If you hit a `ERROR Watcher took too long to load` on Windows, try increasing the [timeout](https://github.com/facebook/react-native/blob/master/packager/react-packager/src/FileWatcher/index.js#L17) in this file (under your node_modules/react-native). +If you hit a `ERROR Watcher took too long to load` on Windows, try increasing the timeout in [this file](https://github.com/facebook/react-native/blob/master/packager/react-packager/src/FileWatcher/index.js#L17) (under your node_modules/react-native). From 3686facdda377516ad28bc97284ed920556ce42e Mon Sep 17 00:00:00 2001 From: Emilio Rodriguez Date: Wed, 18 Nov 2015 12:04:19 +0100 Subject: [PATCH 0005/1411] [Docs] Document Platform in the modules section of the docs #3701 --- docs/PlatformSpecificInformation.md | 67 +++++++++++++++++++++++++++++ docs/Upgrading.md | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/PlatformSpecificInformation.md diff --git a/docs/PlatformSpecificInformation.md b/docs/PlatformSpecificInformation.md new file mode 100644 index 000000000000..7e2c5d583d13 --- /dev/null +++ b/docs/PlatformSpecificInformation.md @@ -0,0 +1,67 @@ +--- +id: platform-specific-code +title: Platform Specific Code +layout: docs +category: Guides +permalink: docs/platform-specific-code.html +next: native-modules-ios +--- + +When building a cross platform app, the need to write different code for different platforms may arise. This can always be achieved by organizing the various components in different folders: + +```sh +/common/components/ +/android/components/ +/ios/components/ +``` + +Another option may be naming the components differently depending on the platform they are going to be used in: + +```sh +BigButtonIOS.js +BigButtonAndroid.js +``` + +But React Native provides two alternatives to easily organize your code separating it by platform: + +## Platform specific extensions +React Native will detect when a file has a `.ios.` or `.android.` extension and load the right file for each platform when requiring them from other components. + +For example, you can have these files in your project: + +```sh +BigButton.ios.js +BigButton.android.js +``` + +With this setup, you can just require the files from a different component without paying attention to the platform in which the app will run. + +```javascript +var BigButton = require('./components/BigButton'); +``` + +React Native will import the correct component for the running platform. + +## Platform module +A module is provided by React Native to detect what is the platform in which the app is running. This piece of functionality can be useful when only small parts of a component are platform specific. + +```javascript +var {Platform} = React; + +var styles = StyleSheet.create({ + height: (Platform.OS === 'ios') ? 200 : 100 +}); +``` + +`Platform.OS` will be `ios` when running in iOS and `android` when running in an Android device or simulator. + +###Detecting android version +On Android, the Platform module can be also used to detect which is the version of the Android Platform in which the app is running + +```javascript +var {Platform} = React; + +if(Platform.Version === '5.0'){ + console.log('Running on Lollipop!'); +} +``` \ No newline at end of file diff --git a/docs/Upgrading.md b/docs/Upgrading.md index e427796f6bac..aca07b075806 100644 --- a/docs/Upgrading.md +++ b/docs/Upgrading.md @@ -4,7 +4,7 @@ title: Upgrading layout: docs category: Guides permalink: docs/upgrading.html -next: native-modules-ios +next: platform-specific-code --- Upgrading to new versions of React Native will give you access to more APIs, views, developer tools From a41afb7a1db325c9455945abffa5927ed10d3e93 Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Wed, 18 Nov 2015 16:58:21 +0100 Subject: [PATCH 0006/1411] Add Switch docs --- website/server/extractDocs.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 1d8abc600257..2875b15c37b2 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -192,8 +192,8 @@ var components = [ '../Libraries/Image/Image.ios.js', '../Libraries/CustomComponents/ListView/ListView.js', '../Libraries/Components/MapView/MapView.js', - '../Libraries/CustomComponents/Navigator/Navigator.js', '../Libraries/Modal/Modal.js', + '../Libraries/CustomComponents/Navigator/Navigator.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js', '../Libraries/Picker/PickerIOS.ios.js', '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js', @@ -201,8 +201,7 @@ var components = [ '../Libraries/Components/ScrollView/ScrollView.js', '../Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js', '../Libraries/Components/SliderIOS/SliderIOS.ios.js', - '../Libraries/Components/SwitchAndroid/SwitchAndroid.android.js', - '../Libraries/Components/SwitchIOS/SwitchIOS.ios.js', + '../Libraries/Components/Switch/Switch.js', '../Libraries/Components/TabBarIOS/TabBarIOS.ios.js', '../Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js', '../Libraries/Text/Text.js', From 0e4bd91db7ee95054f6f7c2f4c240ec6ef8e4eee Mon Sep 17 00:00:00 2001 From: Andreas Amsenius Date: Thu, 19 Nov 2015 01:26:58 -0800 Subject: [PATCH 0007/1411] Add RCTCameraRoll cocoapod subspec Summary: Struggled for some time to figure out why CameraRoll could not (anymore, upgraded from RN 0.11 iirc) be used in a cocoapods setup. This was the cleanest way to make it work, is it a bad idea for some reason? Closes https://github.com/facebook/react-native/pull/4230 Reviewed By: svcscm Differential Revision: D2674063 Pulled By: nicklockwood fb-gh-sync-id: 3d9e6a8ac1834d05ad807ff1fea1b89a724b9cc8 --- React.podspec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/React.podspec b/React.podspec index d8991c8ed29a..2f52c2ab8f8b 100644 --- a/React.podspec +++ b/React.podspec @@ -49,6 +49,12 @@ Pod::Spec.new do |s| ss.preserve_paths = "Libraries/AdSupport/*.js" end + s.subspec 'RCTCameraRoll' do |ss| + ss.dependency 'React/Core' + ss.source_files = "Libraries/CameraRoll/*.{h,m}" + ss.preserve_paths = "Libraries/CameraRoll/*.js" + end + s.subspec 'RCTGeolocation' do |ss| ss.dependency 'React/Core' ss.source_files = "Libraries/Geolocation/*.{h,m}" From a3d9f5ba8406a837f59652ca2bbc5d6cf31de019 Mon Sep 17 00:00:00 2001 From: gilchenzion Date: Thu, 19 Nov 2015 02:20:23 -0800 Subject: [PATCH 0008/1411] Add showsCompass to MapView props Summary: Was trying to remove the compass from the map and thought I would contribute to the project for everyone. Closes https://github.com/facebook/react-native/pull/4225 Reviewed By: svcscm Differential Revision: D2674060 Pulled By: nicklockwood fb-gh-sync-id: 66f069dfc53fdeae8aaab76980146296cea1140f --- Libraries/Components/MapView/MapView.js | 7 +++++++ React/Views/RCTMapManager.m | 1 + 2 files changed, 8 insertions(+) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index de8aaa9f729a..58950c490714 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -87,6 +87,13 @@ var MapView = React.createClass({ */ showsPointsOfInterest: React.PropTypes.bool, + /** + * If `false` compass won't be displayed on the map. + * Default value is `true`. + * @platform ios + */ + showsCompass: React.PropTypes.bool, + /** * If `false` the user won't be able to pinch/zoom the map. * Default value is `true`. diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 44dd0743ec4f..5372793a8166 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -38,6 +38,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL) RCT_EXPORT_VIEW_PROPERTY(showsPointsOfInterest, BOOL) +RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL) RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL) From c8fd9f7588ad02d2293cac7224715f4af7b0f352 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Thu, 19 Nov 2015 03:30:45 -0800 Subject: [PATCH 0009/1411] Split immediate into multiple passes Reviewed By: tadeuzagallo Differential Revision: D2663957 fb-gh-sync-id: d7f0041fc98edb46e518f684527effe2f5201240 --- .../System/JSTimers/JSTimersExecution.js | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js index 6f56d835796a..bc2b74d220f0 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js @@ -14,6 +14,7 @@ var invariant = require('invariant'); var keyMirror = require('keyMirror'); var performanceNow = require('performanceNow'); var warning = require('warning'); +var BridgeProfiling = require('BridgeProfiling'); /** * JS implementation of timer functions. Must be completely driven by an @@ -107,15 +108,36 @@ var JSTimersExecution = { } }, + /** + * Performs a single pass over the enqueued immediates. Returns whether + * more immediates are queued up (can be used as a condition a while loop). + */ + callImmediatesPass: function() { + BridgeProfiling.profile('JSTimersExecution.callImmediatesPass()'); + + // The main reason to extract a single pass is so that we can track + // in the system trace + if (JSTimersExecution.immediates.length > 0) { + var passImmediates = JSTimersExecution.immediates.slice(); + JSTimersExecution.immediates = []; + + passImmediates.forEach((timerID) => { + JSTimersExecution.callTimer(timerID); + }); + } + + BridgeProfiling.profileEnd(); + + return JSTimersExecution.immediates.length > 0; + }, + /** * This is called after we execute any command we receive from native but * before we hand control back to native. */ callImmediates: function() { JSTimersExecution.errors = null; - while (JSTimersExecution.immediates.length !== 0) { - JSTimersExecution.callTimer(JSTimersExecution.immediates.shift()); - } + while (JSTimersExecution.callImmediatesPass()) {} if (JSTimersExecution.errors) { JSTimersExecution.errors.forEach((error) => require('JSTimers').setTimeout(() => { throw error; }, 0) From 2a9a5fc9e871c651bafe42f97a0df97124f2e96f Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 19 Nov 2015 05:52:58 -0800 Subject: [PATCH 0010/1411] css layout systrace markers Reviewed By: astreet Differential Revision: D2668741 fb-gh-sync-id: 043ff740f8cc7c687a79c50c933db99ed67b60e4 --- .../com/facebook/react/uimanager/UIManagerModule.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 81a3bd5f6123..b0d61f61f563 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -768,7 +768,15 @@ private void dispatchViewUpdates(final int batchId) { int tag = mShadowNodeRegistry.getRootTag(i); ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); notifyOnBeforeLayoutRecursive(cssRoot); - cssRoot.calculateLayout(mLayoutContext); + + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") + .arg("rootTag", tag) + .flush(); + try { + cssRoot.calculateLayout(mLayoutContext); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } applyUpdatesRecursive(cssRoot, 0f, 0f); } From 7da42e3950169b12c78a450ac83cbee8c9141dff Mon Sep 17 00:00:00 2001 From: Quentin Valmori Date: Thu, 19 Nov 2015 07:09:09 -0800 Subject: [PATCH 0011/1411] add allowsInlineMediaPlayback prop to play inline html5 video Summary: Allow an html5 video to be played inline. (see #3112) Closes https://github.com/facebook/react-native/pull/3137 Reviewed By: svcscm Differential Revision: D2674318 Pulled By: nicklockwood fb-gh-sync-id: cf71e4039c7027f1468370ae3ddef6eb3e2d2d4f --- Libraries/Components/WebView/WebView.ios.js | 20 +++++++++++++-- React/Views/RCTWebViewManager.m | 28 +++++++++++---------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 5e303ff00b01..0e5452c9dd08 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -100,26 +100,41 @@ var WebView = React.createClass({ onNavigationStateChange: PropTypes.func, startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load style: View.propTypes.style, + /** * Used for android only, JS is enabled by default for WebView on iOS + * @platform android */ javaScriptEnabledAndroid: PropTypes.bool, + /** * Sets the JS to be injected when the webpage loads. */ injectedJavaScript: PropTypes.string, /** - * Used for iOS only, sets whether the webpage scales to fit the view and the - * user can change the scale + * Sets whether the webpage scales to fit the view and the user can change the scale. + * @platform ios */ scalesPageToFit: PropTypes.bool, /** * Allows custom handling of any webview requests by a JS handler. Return true * or false from this method to continue loading the request. + * @platform ios */ onShouldStartLoadWithRequest: PropTypes.func, + + /** + * Determines whether HTML5 videos play inline or use the native full-screen + * controller. + * default value `false` + * **NOTE** : "In order for video to play inline, not only does this + * property need to be set to true, but the video element in the HTML + * document must also include the webkit-playsinline attribute." + * @platform ios + */ + allowsInlineMediaPlayback: PropTypes.bool, }, getInitialState: function() { @@ -188,6 +203,7 @@ var WebView = React.createClass({ onLoadingError={this.onLoadingError} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} scalesPageToFit={this.props.scalesPageToFit} + allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback} />; return ( diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 6c765e8d4382..58d663cad6da 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -18,7 +18,8 @@ @interface RCTWebViewManager () @end -@implementation RCTWebViewManager { +@implementation RCTWebViewManager +{ NSConditionLock *_shouldStartLoadLock; BOOL _shouldStartLoad; } @@ -32,18 +33,19 @@ - (UIView *)view return webView; } -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_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); -RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString); -RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); -RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock); +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_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL) +RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) +RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) +RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) +RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) +RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) - (NSDictionary *)constantsToExport { From 3e7db5644514c144f78c5e71d4f8a6d825fd4cd1 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 19 Nov 2015 07:11:39 -0800 Subject: [PATCH 0012/1411] Fix multiple calls to createReactContextInBackground Reviewed By: astreet Differential Revision: D2674440 fb-gh-sync-id: f842826516d2b03291ad0c4bf5c8dcbf8ec0f3a9 --- .../java/com/facebook/react/ReactInstanceManager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 4678fd9f4944..b8cf21b23417 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -438,12 +438,8 @@ public String getSourceUrl() { // If react context is being created in the background, JS application will be started // automatically when creation completes, as root view is part of the attached root view list. - if (!mIsContextInitAsyncTaskRunning) { - if (mCurrentReactContext == null) { - createReactContextInBackground(); - } else { - attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance()); - } + if (!mIsContextInitAsyncTaskRunning && mCurrentReactContext != null) { + attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance()); } } From 0bf7928ebe2c27e6fb22f860a14d5db087391c15 Mon Sep 17 00:00:00 2001 From: dajomu Date: Thu, 19 Nov 2015 08:40:47 -0800 Subject: [PATCH 0013/1411] Added Subject to ActionSheetIOS share options and updated example Summary: I've added a subject property to the ActionSheetIOS.showShareActionSheetWithOptions options object. This will allow users to set a subject for things like emails when they are sharing to them through the ActionSheetIOS. Options are now as follows: ``` { url: 'https://code.facebook.com', message: 'message to go with the shared url', subject: 'a subject to go in the email heading', } ``` Closes https://github.com/facebook/react-native/pull/4238 Reviewed By: svcscm Differential Revision: D2674536 Pulled By: nicklockwood fb-gh-sync-id: 3dfad39f94f19999233bf777253ef71b6e692a6d --- Examples/UIExplorer/ActionSheetIOSExample.js | 2 ++ Libraries/ActionSheetIOS/RCTActionSheetManager.m | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 31aedca627c6..548fe3bd1583 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -88,6 +88,8 @@ var ShareActionSheetExample = React.createClass({ showShareActionSheet() { ActionSheetIOS.showShareActionSheetWithOptions({ url: 'https://code.facebook.com', + message: 'message to go with the shared url', + subject: 'a subject to go in the email heading', }, (error) => { console.error(error); diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index 626da966ceac..1d4e93ba741b 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -166,6 +166,12 @@ - (CGRect)sourceRectInView:(UIView *)sourceView } UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; + + NSString *subject = [RCTConvert NSString:options[@"subject"]]; + if (subject) { + [shareController setValue:subject forKey:@"subject"]; + } + UIViewController *controller = RCTKeyWindow().rootViewController; #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 From 5950f8cf1564905daa88d1b5e0aacd8e96fbaf71 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 19 Nov 2015 18:10:40 +0000 Subject: [PATCH 0014/1411] Update KnownIssues.md --- docs/KnownIssues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 6ce63307e609..9fc543299707 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -53,11 +53,11 @@ There are properties that work on one platform only, either because they can inh There are known cases where the APIs could be made more consistent across iOS and Android: -- `` (to be open sourced soon) and `` on iOS do a similar thing. We might want to unify them to ``. +- `` and `` on iOS do a similar thing. We might want to unify them to ``. - `alert()` needs Android support (once the Dialogs module is open sourced) - It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together. - `ActivityIndicator` could render a native spinning indicator on both platforms (currently this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android). -- `ProgressBar` could render a horizontal progress bar on both platforms (currently only supported on iOS via `ProgressViewIOS`). +- `ProgressBar` could render a horizontal progress bar on both platforms (on iOS this is `ProgressViewIOS`, on Android it's `ProgressBarAndroid`). ### Publishing modules on Android From 2faf8632d350c1ecb85f20d99eabf8d48202fc82 Mon Sep 17 00:00:00 2001 From: EwanThomas Date: Thu, 19 Nov 2015 11:13:42 -0800 Subject: [PATCH 0015/1411] UIRefreshControl added to scroll view Summary: **What:** adds `onRefreshStart` property to `ScrollView.js` for displaying and activating pull to refresh. **Why:** Javascript implementations seemed a little flakey and inconsistent. As you can see in the issues below: https://github.com/facebook/react-native/issues/2356 https://github.com/facebook/react-native/issues/745 So this is an attempt a completely native implementation. What do you think? ![Image of dog](http://i.imgur.com/HcTQnzJ.gif) Closes https://github.com/facebook/react-native/pull/4205 Reviewed By: svcscm Differential Revision: D2674945 Pulled By: nicklockwood fb-gh-sync-id: 65113a5db9785df5a95c68323c2cdf19f3b217b1 --- Libraries/Components/ScrollView/ScrollView.js | 29 ++++++++++++++ React/Views/RCTScrollView.h | 3 ++ React/Views/RCTScrollView.m | 38 +++++++++++++++++++ React/Views/RCTScrollViewManager.m | 18 +++++++++ 4 files changed, 88 insertions(+) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index c373b52c5a3f..7a2dc58e7b86 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -15,6 +15,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var Platform = require('Platform'); var PointPropType = require('PointPropType'); var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; +var RCTScrollViewManager = require('NativeModules').ScrollViewManager; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTUIManager = require('NativeModules').UIManager; @@ -279,6 +280,21 @@ var ScrollView = React.createClass({ * @platform ios */ zoomScale: PropTypes.number, + + /** + * When defined, displays a UIRefreshControl. + * Invoked with a function to stop refreshing when the UIRefreshControl is animating. + * + * ``` + * (endRefreshing) => { + * endRefreshing(); + * } + * ``` + * + * @platform ios + */ + onRefreshStart: PropTypes.func, + }, mixins: [ScrollResponder.Mixin], @@ -291,6 +307,12 @@ var ScrollView = React.createClass({ this.refs[SCROLLVIEW].setNativeProps(props); }, + endRefreshing: function() { + RCTScrollViewManager.endRefreshing( + React.findNodeHandle(this) + ); + }, + /** * Returns a reference to the underlying scroll responder, which supports * operations like `scrollTo`. All ScrollView-like components should @@ -396,6 +418,13 @@ var ScrollView = React.createClass({ onResponderReject: this.scrollResponderHandleResponderReject, }; + var onRefreshStart = this.props.onRefreshStart; + // this is necessary because if we set it on props, even when empty, + // it'll trigger the default pull-to-refresh behaviour on native. + props.onRefreshStart = onRefreshStart + ? function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this) + : null; + var ScrollViewClass; if (Platform.OS === 'ios') { ScrollViewClass = RCTScrollView; diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h index d44be6fafae1..47fb1753b7ad 100644 --- a/React/Views/RCTScrollView.h +++ b/React/Views/RCTScrollView.h @@ -47,6 +47,9 @@ @property (nonatomic, assign) int snapToInterval; @property (nonatomic, copy) NSString *snapToAlignment; @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; +@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart; + +- (void)endRefreshing; @end diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index e125061f4b78..2dc6f3886807 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -144,6 +144,7 @@ @interface RCTCustomScrollView : UIScrollView @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; @property (nonatomic, assign) BOOL centerContent; +@property (nonatomic, strong) UIRefreshControl *refreshControl; @end @@ -352,6 +353,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return hitView ?: [super hitTest:point withEvent:event]; } +- (void)setRefreshControl:(UIRefreshControl *)refreshControl +{ + if (_refreshControl) { + [_refreshControl removeFromSuperview]; + } + _refreshControl = refreshControl; + [self addSubview:_refreshControl]; +} + @end @implementation RCTScrollView @@ -844,6 +854,34 @@ - (id)valueForUndefinedKey:(NSString *)key return [_scrollView valueForKey:key]; } +- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart +{ + if (!onRefreshStart) { + _onRefreshStart = nil; + _scrollView.refreshControl = nil; + return; + } + _onRefreshStart = [onRefreshStart copy]; + + if (!_scrollView.refreshControl) { + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; + [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; + _scrollView.refreshControl = refreshControl; + } +} + +- (void)refreshControlValueChanged +{ + if (self.onRefreshStart) { + self.onRefreshStart(nil); + } +} + +- (void)endRefreshing +{ + [_scrollView.refreshControl endRefreshing]; +} + @end @implementation RCTEventDispatcher (RCTScrollView) diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index e90bda75ecf1..fdefe32cc463 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -65,6 +65,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) +RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock) - (NSDictionary *)constantsToExport { @@ -114,6 +115,22 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + + RCTScrollView *view = viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[RCTScrollView class]]) { + RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); + return; + } + + [view endRefreshing]; + + }]; +} + + - (NSArray *)customDirectEventTypes { return @[ @@ -127,3 +144,4 @@ - (UIView *)view } @end + From 0d17d6a8c0adee8cb3b91d735e0774e3aa381b13 Mon Sep 17 00:00:00 2001 From: Pawel Sienkowski Date: Thu, 19 Nov 2015 13:32:37 -0800 Subject: [PATCH 0016/1411] RCTRootView integration tests Reviewed By: javache Differential Revision: D2631527 fb-gh-sync-id: 377471d9e8546d7c05a045286a6ef7c5277ded16 --- .../UIExplorer.xcodeproj/project.pbxproj | 8 +- .../RCTRootViewIntegrationTests.m | 172 ++++++++++++++++++ IntegrationTests/PropertiesUpdateTest.js | 32 ++++ .../RCTRootViewIntegrationTestApp.js | 94 ++++++++++ .../ReactContentSizeUpdateTest.js | 73 ++++++++ IntegrationTests/SizeFlexibilityUpdateTest.js | 82 +++++++++ 6 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m create mode 100644 IntegrationTests/PropertiesUpdateTest.js create mode 100644 IntegrationTests/RCTRootViewIntegrationTestApp.js create mode 100644 IntegrationTests/ReactContentSizeUpdateTest.js create mode 100644 IntegrationTests/SizeFlexibilityUpdateTest.js diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 05416d4245ef..49a27dc61e74 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -50,8 +50,9 @@ 14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; - 272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; }; - 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; }; + 272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; settings = {ASSET_TAGS = (); }; }; + 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; settings = {ASSET_TAGS = (); }; }; + 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */; settings = {ASSET_TAGS = (); }; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; }; 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; }; @@ -228,6 +229,7 @@ 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UpdatePropertiesExampleView.m; path = UIExplorer/NativeExampleViews/UpdatePropertiesExampleView.m; sourceTree = ""; }; 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FlexibleSizeExampleView.m; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m; sourceTree = ""; }; 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = ""; }; + 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootViewIntegrationTests.m; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; }; 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; }; @@ -434,6 +436,7 @@ 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */ = { isa = PBXGroup; children = ( + 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */, 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */, 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */, 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */, @@ -886,6 +889,7 @@ 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */, 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */, 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */, + 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m new file mode 100644 index 000000000000..9e665b4e12d3 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m @@ -0,0 +1,172 @@ +/** + * 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. + */ + +//vs + +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#import "RCTAssert.h" + +#import "RCTEventDispatcher.h" +#import "RCTRootView.h" +#import "RCTRootViewDelegate.h" + +#import + +#define RCT_TEST_DATA_CONFIGURATION_BLOCK(appName, testType, input, block) \ +- (void)test##appName##_##testType##_##input \ +{ \ + [_runner runTest:_cmd \ + module:@#appName \ + initialProps:@{@#input:@YES} \ +configurationBlock:block]; \ +} + +#define RCT_TEST_CONFIGURATION_BLOCK(appName, block) \ +- (void)test##appName \ +{ \ + [_runner runTest:_cmd \ + module:@#appName \ + initialProps:nil \ +configurationBlock:block]; \ +} + +#define RCTNone RCTRootViewSizeFlexibilityNone +#define RCTHeight RCTRootViewSizeFlexibilityHeight +#define RCTWidth RCTRootViewSizeFlexibilityWidth +#define RCTBoth RCTRootViewSizeFlexibilityWidthAndHeight + +typedef void (^ControlBlock)(RCTRootView*); + +@interface SizeFlexibilityTestDelegate : NSObject +@end + +@implementation SizeFlexibilityTestDelegate + +- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView +{ + [rootView.bridge.eventDispatcher sendAppEventWithName:@"rootViewDidChangeIntrinsicSize" + body:@{@"width": @(rootView.intrinsicSize.width), + @"height": @(rootView.intrinsicSize.height)}]; +} + +@end + +static SizeFlexibilityTestDelegate *sizeFlexibilityDelegate() +{ + static SizeFlexibilityTestDelegate *delegate; + if (delegate == nil) { + delegate = [SizeFlexibilityTestDelegate new]; + } + + return delegate; +} + +static ControlBlock simpleSizeFlexibilityBlock(RCTRootViewSizeFlexibility sizeFlexibility) +{ + return ^(RCTRootView *rootView){ + rootView.delegate = sizeFlexibilityDelegate(); + rootView.sizeFlexibility = sizeFlexibility; + }; +} + +static ControlBlock multipleSizeFlexibilityUpdatesBlock(RCTRootViewSizeFlexibility finalSizeFlexibility) +{ + return ^(RCTRootView *rootView){ + + NSInteger arr[4] = {RCTNone, + RCTHeight, + RCTWidth, + RCTBoth}; + + rootView.delegate = sizeFlexibilityDelegate(); + + for (int i = 0; i < 4; ++i) { + if (arr[i] != finalSizeFlexibility) { + rootView.sizeFlexibility = arr[i]; + } + } + + rootView.sizeFlexibility = finalSizeFlexibility; + }; +} + +static ControlBlock reactContentSizeUpdateBlock(RCTRootViewSizeFlexibility sizeFlexibility) +{ + return ^(RCTRootView *rootView){ + rootView.delegate = sizeFlexibilityDelegate(); + rootView.sizeFlexibility = sizeFlexibility; + }; +} + +static ControlBlock propertiesUpdateBlock() +{ + return ^(RCTRootView *rootView){ + rootView.appProperties = @{@"markTestPassed":@YES}; + }; +} + +@interface RCTRootViewIntegrationTests : XCTestCase + +@end + +@implementation RCTRootViewIntegrationTests +{ + RCTTestRunner *_runner; +} + +- (void)setUp +{ +#if __LP64__ + RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); +#endif + + NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion; + RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); + _runner = RCTInitRunnerForApp(@"IntegrationTests/RCTRootViewIntegrationTestApp", nil); +} + +#pragma mark Logic Tests + +// This list should be kept in sync with RCTRootViewIntegrationTestApp.js + +// Simple size flexibility tests - test if the content is measured properly +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, none, simpleSizeFlexibilityBlock(RCTNone)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, height, simpleSizeFlexibilityBlock(RCTHeight)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, width, simpleSizeFlexibilityBlock(RCTWidth)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, both, simpleSizeFlexibilityBlock(RCTBoth)); + +// Consider multiple size flexibility updates in a row. Test if the view's flexibility mode eventually is set to the expected value +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, none, multipleSizeFlexibilityUpdatesBlock(RCTNone)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, height, multipleSizeFlexibilityUpdatesBlock(RCTHeight)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, width, multipleSizeFlexibilityUpdatesBlock(RCTWidth)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, both, multipleSizeFlexibilityUpdatesBlock(RCTBoth)); + +// Test if the 'rootViewDidChangeIntrinsicSize' delegate method is called after the RN app decides internally to resize +RCT_TEST_CONFIGURATION_BLOCK(ReactContentSizeUpdateTest, reactContentSizeUpdateBlock(RCTBoth)) + +// Test if setting 'appProperties' property updates the RN app +RCT_TEST_CONFIGURATION_BLOCK(PropertiesUpdateTest, propertiesUpdateBlock()) + +@end diff --git a/IntegrationTests/PropertiesUpdateTest.js b/IntegrationTests/PropertiesUpdateTest.js new file mode 100644 index 000000000000..da563ec94a55 --- /dev/null +++ b/IntegrationTests/PropertiesUpdateTest.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +var React = require('react-native'); +var { + View, +} = React; + +var { TestModule } = React.addons; + +var PropertiesUpdateTest = React.createClass({ + + render() { + if (this.props.markTestPassed) { + TestModule.markTestPassed(true); + } + return ( + + ); + } +}); + +PropertiesUpdateTest.displayName = 'PropertiesUpdateTest'; + +module.exports = PropertiesUpdateTest; diff --git a/IntegrationTests/RCTRootViewIntegrationTestApp.js b/IntegrationTests/RCTRootViewIntegrationTestApp.js new file mode 100644 index 000000000000..5a126a601920 --- /dev/null +++ b/IntegrationTests/RCTRootViewIntegrationTestApp.js @@ -0,0 +1,94 @@ +/** + * 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 RCTRootViewIntegrationTestsApp + */ +'use strict'; + +require('regenerator/runtime'); + +var React = require('react-native'); + +var { + AppRegistry, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} = React; + +/* Keep this list in sync with RCTRootViewIntegrationTests.m */ +var TESTS = [ + require('./PropertiesUpdateTest'), + require('./ReactContentSizeUpdateTest'), + require('./SizeFlexibilityUpdateTest'), +]; + +TESTS.forEach( + (test) => AppRegistry.registerComponent(test.displayName, () => test) +); + +var RCTRootViewIntegrationTestsApp = React.createClass({ + getInitialState: function() { + return { + test: null, + }; + }, + render: function() { + if (this.state.test) { + return ( + + + + ); + } + return ( + + + Click on a test to run it in this shell for easier debugging and + development. Run all tests in the testing environment with cmd+U in + Xcode. + + + + {TESTS.map((test) => [ + this.setState({test})} + style={styles.row}> + + {test.displayName} + + , + + ])} + + + ); + } +}); + +var styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + marginTop: 40, + margin: 15, + }, + row: { + padding: 10, + }, + testName: { + fontWeight: '500', + }, + separator: { + height: 1, + backgroundColor: '#bbbbbb', + }, +}); + +AppRegistry.registerComponent('RCTRootViewIntegrationTestsApp', () => RCTRootViewIntegrationTestsApp); diff --git a/IntegrationTests/ReactContentSizeUpdateTest.js b/IntegrationTests/ReactContentSizeUpdateTest.js new file mode 100644 index 000000000000..73e1c5f5d3d3 --- /dev/null +++ b/IntegrationTests/ReactContentSizeUpdateTest.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +var React = require('react-native'); +var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter'); +var Subscribable = require('Subscribable'); +var TimerMixin = require('react-timer-mixin'); + +var { View } = React; + +var { TestModule } = React.addons; + +var reactViewWidth = 101; +var reactViewHeight = 102; +var newReactViewWidth = 201; +var newReactViewHeight = 202; + +var ReactContentSizeUpdateTest = React.createClass({ + mixins: [Subscribable.Mixin, + TimerMixin], + + componentWillMount: function() { + this.addListenerOn( + RCTNativeAppEventEmitter, + 'rootViewDidChangeIntrinsicSize', + this.rootViewDidChangeIntrinsicSize + ); + }, + + getInitialState: function() { + return { + height: reactViewHeight, + width: reactViewWidth, + }; + }, + + updateViewSize: function() { + this.setState({ + height: newReactViewHeight, + width: newReactViewWidth, + }); + }, + + componentDidMount: function() { + this.setTimeout( + () => { this.updateViewSize(); }, + 1000 + ); + }, + + rootViewDidChangeIntrinsicSize: function(intrinsicSize) { + if (intrinsicSize.height === newReactViewHeight && intrinsicSize.width === newReactViewWidth) { + TestModule.markTestPassed(true); + } + }, + + render() { + return ( + + ); + } +}); + +ReactContentSizeUpdateTest.displayName = 'ReactContentSizeUpdateTest'; + +module.exports = ReactContentSizeUpdateTest; diff --git a/IntegrationTests/SizeFlexibilityUpdateTest.js b/IntegrationTests/SizeFlexibilityUpdateTest.js new file mode 100644 index 000000000000..ddc016b70ac6 --- /dev/null +++ b/IntegrationTests/SizeFlexibilityUpdateTest.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +var React = require('react-native'); +var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter'); +var Subscribable = require('Subscribable'); +var { View } = React; + +var { TestModule } = React.addons; + +var reactViewWidth = 111; +var reactViewHeight = 222; + +var finalState = false; + +var SizeFlexibilityUpdateTest = React.createClass({ + mixins: [Subscribable.Mixin], + + componentWillMount: function() { + this.addListenerOn( + RCTNativeAppEventEmitter, + 'rootViewDidChangeIntrinsicSize', + this.rootViewDidChangeIntrinsicSize + ); + }, + + markPassed: function() { + TestModule.markTestPassed(true); + finalState = true; + }, + + rootViewDidChangeIntrinsicSize: function(intrinsicSize) { + + if (finalState) { + // If a test reaches its final state, it is not expected to do anything more + TestModule.markTestPassed(false); + return; + } + + if (this.props.both) { + if (intrinsicSize.width === reactViewWidth && intrinsicSize.height === reactViewHeight) { + this.markPassed(); + return; + } + } + if (this.props.height) { + if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height === reactViewHeight) { + this.markPassed(); + return; + } + } + if (this.props.width) { + if (intrinsicSize.width === reactViewWidth && intrinsicSize.height !== reactViewHeight) { + this.markPassed(); + return; + } + } + if (this.props.none) { + if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height !== reactViewHeight) { + this.markPassed(); + return; + } + } + }, + + render() { + return ( + + ); + } +}); + +SizeFlexibilityUpdateTest.displayName = 'SizeFlexibilityUpdateTest'; + +module.exports = SizeFlexibilityUpdateTest; From 7cd7591f0466bc8aca617f75f5fd2dee53712846 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 20 Nov 2015 00:47:24 -0800 Subject: [PATCH 0017/1411] Use requireNativeComponent in View.js. Summary: public In a previous diff I've removed a need for using View attributes in requireNativeComponent. Thanks to that we can now use requireNativeComponent in View module. In a follow up diff I'll be getting rid of ReactNativeViewAttributes. Reviewed By: sahrens Differential Revision: D2676088 fb-gh-sync-id: d5d6e50f4629bd7982b90f3496e1fd22078e96a8 --- Libraries/Components/View/View.js | 11 ++++++----- Libraries/ReactIOS/verifyPropTypes.js | 2 -- .../facebook/react/views/view/ReactViewManager.java | 6 ++++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 3f62f027c66b..ff2def0afabe 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -20,7 +20,7 @@ var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); var ViewStylePropTypes = require('ViewStylePropTypes'); -var createReactNativeComponentClass = require('createReactNativeComponentClass'); +var requireNativeComponent = require('requireNativeComponent'); var stylePropType = StyleSheetPropType(ViewStylePropTypes); @@ -316,11 +316,12 @@ var View = React.createClass({ }, }); -var RCTView = createReactNativeComponentClass({ - validAttributes: ReactNativeViewAttributes.RCTView, - uiViewClassName: 'RCTView', +var RCTView = requireNativeComponent('RCTView', View, { + nativeOnly: { + nativeBackgroundAndroid: true, + } }); -RCTView.propTypes = View.propTypes; + if (__DEV__) { var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {}; for (var prop in viewConfig.nativeProps) { diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js index 02715c080772..a7ccd210b6b1 100644 --- a/Libraries/ReactIOS/verifyPropTypes.js +++ b/Libraries/ReactIOS/verifyPropTypes.js @@ -12,7 +12,6 @@ 'use strict'; var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); -var View = require('View'); export type ComponentInterface = ReactClass | { name?: string; @@ -40,7 +39,6 @@ function verifyPropTypes( var nativeProps = viewConfig.NativeProps; for (var prop in nativeProps) { if (!componentInterface.propTypes[prop] && - !View.propTypes[prop] && !ReactNativeStyleAttributes[prop] && (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { throw new Error( diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 12c691a616c0..ac0dfee0f0e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -122,6 +122,12 @@ public void setBorderColor(ReactViewGroup view, int index, Integer color) { color == null ? CSSConstants.UNDEFINED : (float) color); } + @ReactProp(name = ViewProps.COLLAPSABLE) + public void setCollapsable(ReactViewGroup view, boolean collapsable) { + // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually + // handled in NativeViewHierarchyOptimizer + } + @Override public String getName() { return REACT_CLASS; From 1a1c3f76a25d8042c606fba3ade80d170df61215 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 20 Nov 2015 03:33:07 -0800 Subject: [PATCH 0018/1411] Port CatalystLoggingTestCase to iOS Reviewed By: nicklockwood Differential Revision: D2658485 fb-gh-sync-id: e6803aefee69ee058651fc4c8c202619543f0cd2 --- .../RCTLoggingTests.m | 109 ++++++++++++++++++ IntegrationTests/IntegrationTestsApp.js | 3 + IntegrationTests/LoggingTestModule.js | 32 +++++ .../src/Resolver/polyfills/console.js | 20 ++-- 4 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m create mode 100644 IntegrationTests/LoggingTestModule.js diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m new file mode 100644 index 000000000000..0011b0524cf4 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m @@ -0,0 +1,109 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "RCTAssert.h" +#import "RCTLog.h" +#import "RCTBridge.h" + +@interface RCTLoggingTests : XCTestCase + +@end + +@implementation RCTLoggingTests +{ + RCTBridge *_bridge; + + dispatch_semaphore_t _logSem; + RCTLogLevel _lastLogLevel; + RCTLogSource _lastLogSource; + NSString *_lastLogMessage; +} + +- (void)setUp +{ +#if RUNNING_ON_CI + NSURL *scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; + RCTAssert(scriptURL != nil, @"Could not locate main.jsBundle"); +#else + NSString *app = @"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp"; + NSURL *scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]]; +#endif + + _bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5]; + while (date.timeIntervalSinceNow > 0 && _bridge.loading) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + XCTAssertFalse(_bridge.loading); + + _logSem = dispatch_semaphore_create(0); + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (source == RCTLogSourceJavaScript) { + _lastLogLevel = level; + _lastLogSource = source; + _lastLogMessage = message; + dispatch_semaphore_signal(_logSem); + } + }); +} + +- (void)tearDown +{ + [_bridge invalidate]; + _bridge = nil; + + RCTSetLogFunction(RCTDefaultLogFunction); +} + +- (void)testLogging +{ + [_bridge enqueueJSCall:@"LoggingTestModule.logToConsole" args:@[@"Invoking console.log"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelInfo); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.log"); + + [_bridge enqueueJSCall:@"LoggingTestModule.warning" args:@[@"Generating warning"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelWarning); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Warning: Generating warning"); + + [_bridge enqueueJSCall:@"LoggingTestModule.invariant" args:@[@"Invariant failed"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invariant Violation: Invariant failed"); + + [_bridge enqueueJSCall:@"LoggingTestModule.logErrorToConsole" args:@[@"Invoking console.error"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.error"); + + [_bridge enqueueJSCall:@"LoggingTestModule.throwError" args:@[@"Throwing an error"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Throwing an error"); +} + +@end diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index 95c2697c17ab..ce136f7fd6fa 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -36,6 +36,9 @@ TESTS.forEach( (test) => AppRegistry.registerComponent(test.displayName, () => test) ); +// Modules required for integration tests +require('LoggingTestModule'); + var IntegrationTestsApp = React.createClass({ getInitialState: function() { return { diff --git a/IntegrationTests/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js new file mode 100644 index 000000000000..96e59298d8be --- /dev/null +++ b/IntegrationTests/LoggingTestModule.js @@ -0,0 +1,32 @@ +/** + * 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 LoggingTestModule + */ +'use strict'; + +var warning = require('warning'); +var invariant = require('invariant'); + +module.exports = { + logToConsole: function(str) { + console.log(str); + }, + warning: function(str) { + warning(false, str); + }, + invariant: function(str) { + invariant(false, str); + }, + logErrorToConsole: function(str) { + console.error(str); + }, + throwError: function(str) { + throw new Error(str); + } +}; diff --git a/packager/react-packager/src/Resolver/polyfills/console.js b/packager/react-packager/src/Resolver/polyfills/console.js index e08e8ef7ce71..5461602b0ed4 100644 --- a/packager/react-packager/src/Resolver/polyfills/console.js +++ b/packager/react-packager/src/Resolver/polyfills/console.js @@ -366,7 +366,6 @@ }; function setupConsole(global) { - var originalConsole = global.console; if (!global.nativeLoggingHook) { @@ -375,16 +374,23 @@ function getNativeLogFunction(level) { return function() { - var str = Array.prototype.map.call(arguments, function(arg) { - return inspect(arg, {depth: 10}); - }).join(', '); - if (str.slice(0, 10) === "'Warning: " && level >= LOG_LEVELS.error) { + var str; + if (arguments.length === 1 && typeof arguments[0] === 'string') { + str = arguments[0]; + } else { + str = Array.prototype.map.call(arguments, function(arg) { + return inspect(arg, {depth: 10}); + }).join(', '); + } + + var logLevel = level; + if (str.slice(0, 9) === 'Warning: ' && logLevel >= LOG_LEVELS.error) { // React warnings use console.error so that a stack trace is shown, // but we don't (currently) want these to show a redbox // (Note: Logic duplicated in ExceptionsManager.js.) - level = LOG_LEVELS.warn; + logLevel = LOG_LEVELS.warn; } - global.nativeLoggingHook(str, level); + global.nativeLoggingHook(str, logLevel); }; } From 0fe074acbd460fc34aa8f43e51d30ed7e149cebb Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 20 Nov 2015 05:15:49 -0800 Subject: [PATCH 0019/1411] Removed all calls to [UIImage imageWithData:] on a background thread Summary: public I had previously assumed (based on past experience and common wisdom) that `[UIImage imageWithData:]` was safe to call concurrently and/or off the main thread, but it seems that may not be the case (see https://github.com/AFNetworking/AFNetworking/pull/2815). This diff replaces `[UIImage imageWithData:]` with ImageIO-based decoding wherever possible, and ensures that it is called on the main thread wherever that's not possible/convenient. I've also serialized access to the `NSURLCache` inside `RCTImageLoader`, which was causing a separate-but-similar crash when loading images. Reviewed By: fkgozali Differential Revision: D2678369 fb-gh-sync-id: 74d033dafcf6c412556e4c96f5ac5d3432298b18 --- Libraries/Image/RCTImageLoader.m | 210 ++++++++++++++----------- Libraries/Image/RCTImageStoreManager.m | 3 +- Libraries/Image/RCTImageUtils.m | 3 +- React/Base/RCTConvert.m | 14 +- 4 files changed, 131 insertions(+), 99 deletions(-) diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index b345591a395e..8c77828b058b 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -37,7 +37,8 @@ @implementation RCTImageLoader { NSArray> *_loaders; NSArray> *_decoders; - NSURLCache *_cache; + dispatch_queue_t _URLCacheQueue; + NSURLCache *_URLCache; } @synthesize bridge = _bridge; @@ -87,9 +88,10 @@ - (void)setBridge:(RCTBridge *)bridge _bridge = bridge; _loaders = loaders; _decoders = decoders; - _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB - diskCapacity:200 * 1024 * 1024 // 200MB - diskPath:@"React/RCTImageDownloader"]; + _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL); + _URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB + diskCapacity:200 * 1024 * 1024 // 200MB + diskPath:@"React/RCTImageDownloader"]; } - (id)imageURLLoaderForURL:(NSURL *)URL @@ -185,12 +187,10 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag progressBlock:(RCTImageLoaderProgressBlock)progressHandler completionBlock:(RCTImageLoaderCompletionBlock)completionBlock { - if (imageTag.length == 0) { - RCTLogWarn(@"source.uri should not be an empty string "); - return ^{}; - } - __block volatile uint32_t cancelled = 0; + __block void(^cancelLoad)(void) = nil; + __weak RCTImageLoader *weakSelf = self; + RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) { if ([NSThread isMainThread]) { @@ -206,111 +206,133 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag } }; - // Find suitable image URL loader - NSURLRequest *request = [RCTConvert NSURLRequest:imageTag]; - id loadHandler = [self imageURLLoaderForURL:request.URL]; - if (loadHandler) { - return [loadHandler loadImageForURL:request.URL - size:size - scale:scale - resizeMode:resizeMode - progressHandler:progressHandler - completionHandler:completionHandler] ?: ^{}; - } - - // Check if networking module is available - if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { - RCTLogError(@"No suitable image URL loader found for %@. You may need to " - " import the RCTNetworking library in order to load images.", - imageTag); + if (imageTag.length == 0) { + completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil); return ^{}; } - // Check if networking module can load image - if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) { - RCTLogError(@"No suitable image URL loader found for %@", imageTag); - return ^{}; - } + // All access to URL cache must be serialized + dispatch_async(_URLCacheQueue, ^{ + RCTImageLoader *strongSelf = weakSelf; + if (cancelled || !strongSelf) { + return; + } - // Use networking module to load image - __weak RCTImageLoader *weakSelf = self; - __block RCTImageLoaderCancellationBlock decodeCancel = nil; - RCTURLRequestCompletionBlock processResponse = - ^(NSURLResponse *response, NSData *data, NSError *error) { + // Find suitable image URL loader + NSURLRequest *request = [RCTConvert NSURLRequest:imageTag]; + id loadHandler = [strongSelf imageURLLoaderForURL:request.URL]; + if (loadHandler) { + cancelLoad = [loadHandler loadImageForURL:request.URL + size:size + scale:scale + resizeMode:resizeMode + progressHandler:progressHandler + completionHandler:completionHandler] ?: ^{}; + return; + } - // Check for system errors - if (error) { - completionHandler(error, nil); + // Check if networking module is available + if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { + RCTLogError(@"No suitable image URL loader found for %@. You may need to " + " import the RCTNetworking library in order to load images.", + imageTag); return; - } else if (!data) { - completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil); + } + + // Check if networking module can load image + if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) { + RCTLogError(@"No suitable image URL loader found for %@", imageTag); return; } - // Check for http errors - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; - if (statusCode != 200) { - completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain - code:statusCode - userInfo:nil], nil); + // Use networking module to load image + __block RCTImageLoaderCancellationBlock cancelDecode = nil; + RCTURLRequestCompletionBlock processResponse = + ^(NSURLResponse *response, NSData *data, NSError *error) { + + // Check for system errors + if (error) { + completionHandler(error, nil); + return; + } else if (!data) { + completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil); return; } - } - // Decode image - decodeCancel = [weakSelf decodeImageData:data - size:size - scale:scale - resizeMode:resizeMode - completionBlock:completionHandler]; - }; - - // Check for cached response before reloading - // TODO: move URL cache out of RCTImageLoader into its own module - NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request]; - if (cachedResponse) { - processResponse(cachedResponse.response, cachedResponse.data, nil); - return ^{}; - } + // Check for http errors + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (statusCode != 200) { + completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain + code:statusCode + userInfo:nil], nil); + return; + } + } - // Add missing png extension - if (request.URL.fileURL && request.URL.pathExtension.length == 0) { - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; - request = mutableRequest; - } + // Decode image + cancelDecode = [strongSelf decodeImageData:data + size:size + scale:scale + resizeMode:resizeMode + completionBlock:completionHandler]; + }; - // Download image - RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock: - ^(NSURLResponse *response, NSData *data, NSError *error) { - if (error) { - completionHandler(error, nil); - return; + // Add missing png extension + if (request.URL.fileURL && request.URL.pathExtension.length == 0) { + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; + request = mutableRequest; } - // Cache the response + // Check for cached response before reloading // TODO: move URL cache out of RCTImageLoader into its own module - RCTImageLoader *strongSelf = weakSelf; - BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"]; - [strongSelf->_cache storeCachedResponse: - [[NSCachedURLResponse alloc] initWithResponse:response - data:data - userInfo:nil - storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly] - forRequest:request]; + NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest:request]; + if (cachedResponse) { + processResponse(cachedResponse.response, cachedResponse.data, nil); + } - // Process image data - processResponse(response, data, nil); + // Download image + RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock: + ^(NSURLResponse *response, NSData *data, NSError *error) { + if (error) { + completionHandler(error, nil); + return; + } - }]; - task.downloadProgressBlock = progressHandler; - [task start]; + dispatch_async(_URLCacheQueue, ^{ + + // Cache the response + // TODO: move URL cache out of RCTImageLoader into its own module + BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"]; + [strongSelf->_URLCache storeCachedResponse: + [[NSCachedURLResponse alloc] initWithResponse:response + data:data + userInfo:nil + storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly] + forRequest:request]; + + // Process image data + processResponse(response, data, nil); + + }); + + }]; + task.downloadProgressBlock = progressHandler; + [task start]; + + cancelLoad = ^{ + [task cancel]; + if (cancelDecode) { + cancelDecode(); + } + }; + + }); return ^{ - [task cancel]; - if (decodeCancel) { - decodeCancel(); + if (cancelLoad) { + cancelLoad(); } OSAtomicOr32Barrier(1, &cancelled); }; @@ -342,7 +364,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data if (cancelled) { return; } - UIImage *image = [UIImage imageWithData:data scale:scale]; + UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode); if (image) { completionHandler(nil, image); } else { diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index f34a13e8d653..fe080101e7da 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -213,9 +213,8 @@ - (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image)) RCTAssertParam(block); dispatch_async(_methodQueue, ^{ NSData *imageData = _store[imageTag]; - UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ - block(image); + block([UIImage imageWithData:imageData]); }); }); } diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index c05bf526b96c..3146a5b5611d 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -255,7 +255,8 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale) // adjust scale size_t actualWidth = CGImageGetWidth(imageRef); - CGFloat scale = actualWidth / targetSize.width; + CGFloat scale = actualWidth / targetSize.width * destScale; + // return image UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 2531b69b9c38..53c6b9cad226 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -429,7 +429,18 @@ + (UIImage *)UIImage:(id)json return nil; } - UIImage *image; + __block UIImage *image; + if (![NSThread isMainThread]) { + // It seems that none of the UIImage loading methods can be guaranteed + // thread safe, so we'll pick the lesser of two evils here and block rather + // than run the risk of crashing + RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended"); + dispatch_sync(dispatch_get_main_queue(), ^{ + image = [self UIImage:json]; + }); + return image; + } + NSString *path; CGFloat scale = 0.0; BOOL isPackagerAsset = NO; @@ -452,7 +463,6 @@ + (UIImage *)UIImage:(id)json if (RCTIsXCAssetURL(URL)) { // Image may reside inside a .car file, in which case we have no choice // but to use +[UIImage imageNamed] - but this method isn't thread safe - RCTAssertMainThread(); NSString *assetName = RCTBundlePathForURL(URL); image = [UIImage imageNamed:assetName]; } else { From 848a151ff815a242ba61e21c9fc384e0a6678934 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 20 Nov 2015 14:26:54 +0000 Subject: [PATCH 0020/1411] Add a bookmarklet to comment on questions For higher accuraccy (and less clowniness) it might be better to have the question bot only label issues that are potentially questions and then have people quickly go through those issues and use this bookmarklet to comment on them. --- bots/question-bookmarklet.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 bots/question-bookmarklet.js diff --git a/bots/question-bookmarklet.js b/bots/question-bookmarklet.js new file mode 100644 index 000000000000..f8d0ce52e96d --- /dev/null +++ b/bots/question-bookmarklet.js @@ -0,0 +1 @@ +javascript:(function(){$('#new_comment_field')[0].value='Hey and thanks for reporting this!\n\nThis issue looks like a question - please use [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) for questions. It\'s the best system for Q&A and the best way to get questions answered.\n\nMany people from the community hang out on Stack Overflow and will be able to see your question and be more likely to answer because of the reputation system. If after reading this you think your question is better suited for Stack Overflow, please consider closing this issue.';$('button.btn-primary:contains("Comment")').click()})() \ No newline at end of file From 1195f9c8e8e47c25d90993218f9ea63123d71cc3 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 20 Nov 2015 07:36:23 -0800 Subject: [PATCH 0021/1411] Further improvements in RecyclerViewBackedScrollView. Summary: public Changed ListView to use onLayout and onContentSizeChange (new) events instead of measure. Updated ScrollView implementation to support contentSizeChange event with an implementation based on onLayout attached to the content view. For RecyclerViewBackedScrollView we need to generate that event directly as it doesn't have a concept of content view. This greatly improves performance of ListView that uses RecyclerViewBackedScrollView Reviewed By: mkonicek Differential Revision: D2679460 fb-gh-sync-id: ba26462d9d3b071965cbe46314f89f0dcfd9db9f --- .../RecyclerViewBackedScrollView.android.js | 9 +++++ Libraries/Components/ScrollView/ScrollView.js | 19 +++++++++ .../CustomComponents/ListView/ListView.js | 25 ++++++------ .../recyclerview/ContentSizeChangeEvent.java | 40 +++++++++++++++++++ .../RecyclerViewBackedScrollView.java | 36 ++++++++++++++--- .../RecyclerViewBackedScrollViewManager.java | 21 ++++++++++ 6 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js index c7b244a6323e..fa019c6be332 100644 --- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js +++ b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js @@ -71,6 +71,11 @@ var RecyclerViewBackedScrollView = React.createClass({ this.refs[INNERVIEW].setNativeProps(props); }, + _handleContentSizeChange: function(event) { + var {width, height} = event.nativeEvent; + this.props.onContentSizeChange(width, height); + }, + render: function() { var props = { ...this.props, @@ -92,6 +97,10 @@ var RecyclerViewBackedScrollView = React.createClass({ ref: INNERVIEW, }; + if (this.props.onContentSizeChange) { + props.onContentSizeChange = this._handleContentSizeChange; + } + var wrappedChildren = React.Children.map(this.props.children, (child) => { if (!child) { return null; diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 7a2dc58e7b86..4d90f19d6c88 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -193,6 +193,12 @@ var ScrollView = React.createClass({ * @platform ios */ onScrollAnimationEnd: PropTypes.func, + /** + * Called when scrollable content view of the ScrollView changes. It's + * implemented using onLayout handler attached to the content container + * which this ScrollView renders. + */ + onContentSizeChange: PropTypes.func, /** * When true, the scroll view stops on multiples of the scroll view's size * when scrolling. This can be used for horizontal pagination. The default @@ -360,6 +366,11 @@ var ScrollView = React.createClass({ this.scrollResponderHandleScroll(e); }, + _handleContentOnLayout: function(event) { + var {width, height} = event.nativeEvent.layout; + this.props.onContentSizeChange && this.props.onContentSizeChange(width, height); + }, + render: function() { var contentContainerStyle = [ this.props.horizontal && styles.contentContainerHorizontal, @@ -376,8 +387,16 @@ var ScrollView = React.createClass({ ); } + var contentSizeChangeProps = {}; + if (this.props.onContentSizeChange) { + contentSizeChangeProps = { + onLayout: this._handleContentOnLayout, + }; + } + var contentContainer = { + + public static final String EVENT_NAME = "topContentSizeChange"; + + private final int mWidth; + private final int mHeight; + + public ContentSizeChangeEvent(int viewTag, long timestampMs, int width, int height) { + super(viewTag, timestampMs); + mWidth = width; + mHeight = height; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + WritableMap data = Arguments.createMap(); + data.putDouble("width", PixelUtil.toDIPFromPixel(mWidth)); + data.putDouble("height", PixelUtil.toDIPFromPixel(mHeight)); + rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java index a419b58c38e8..054a9522220b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java @@ -148,6 +148,7 @@ public int getTopOffsetForItem(int index) { private final List mViews = new ArrayList<>(); private final ScrollOffsetTracker mScrollOffsetTracker; + private final RecyclerViewBackedScrollView mScrollView; private int mTotalChildrenHeight = 0; // The following `OnLayoutChangeListsner` is attached to the views stored in the adapter @@ -173,7 +174,7 @@ public void onLayoutChange( int newHeight = (bottom - top); if (oldHeight != newHeight) { - mTotalChildrenHeight = mTotalChildrenHeight - oldHeight + newHeight; + updateTotalChildrenHeight(newHeight - oldHeight); mScrollOffsetTracker.onHeightChange(mViews.indexOf(v), oldHeight, newHeight); // Since "wrapper" view position +dimensions are not managed by NativeViewHierarchyManager @@ -200,7 +201,8 @@ public void onLayoutChange( } }; - public ReactListAdapter() { + public ReactListAdapter(RecyclerViewBackedScrollView scrollView) { + mScrollView = scrollView; mScrollOffsetTracker = new ScrollOffsetTracker(this); setHasStableIds(true); } @@ -208,7 +210,7 @@ public ReactListAdapter() { public void addView(View child, int index) { mViews.add(index, child); - mTotalChildrenHeight += child.getMeasuredHeight(); + updateTotalChildrenHeight(child.getMeasuredHeight()); child.addOnLayoutChangeListener(mChildLayoutChangeListener); notifyItemInserted(index); @@ -219,12 +221,19 @@ public void removeViewAt(int index) { if (child != null) { mViews.remove(index); child.removeOnLayoutChangeListener(mChildLayoutChangeListener); - mTotalChildrenHeight -= child.getMeasuredHeight(); + updateTotalChildrenHeight(-child.getMeasuredHeight()); notifyItemRemoved(index); } } + private void updateTotalChildrenHeight(int delta) { + if (delta != 0) { + mTotalChildrenHeight += delta; + mScrollView.onTotalChildrenHeightChange(mTotalChildrenHeight); + } + } + @Override public ConcreteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ConcreteViewHolder(new RecyclableWrapperViewGroup(parent.getContext())); @@ -268,6 +277,12 @@ public int getTopOffsetForItem(int index) { } } + private boolean mSendContentSizeChangeEvents; + + public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) { + mSendContentSizeChangeEvents = sendContentSizeChangeEvents; + } + private int calculateAbsoluteOffset() { int offsetY = 0; if (getChildCount() > 0) { @@ -304,12 +319,23 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { getHeight())); } + private void onTotalChildrenHeightChange(int newTotalChildrenHeight) { + if (mSendContentSizeChangeEvents) { + ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() + .dispatchEvent(new ContentSizeChangeEvent( + getId(), + SystemClock.uptimeMillis(), + getWidth(), + newTotalChildrenHeight)); + } + } + public RecyclerViewBackedScrollView(Context context) { super(context); setHasFixedSize(true); setItemAnimator(new NotAnimatedItemAnimator()); setLayoutManager(new LinearLayoutManager(context)); - setAdapter(new ReactListAdapter()); + setAdapter(new ReactListAdapter(this)); } /*package*/ void addViewToAdapter(View child, int index) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java index 8c0134dad921..c49c26742ad1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java @@ -4,12 +4,17 @@ import javax.annotation.Nullable; +import java.util.Map; + import android.view.View; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.ReactProp; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.views.scroll.ReactScrollViewCommandHelper; +import com.facebook.react.views.scroll.ScrollEvent; /** * View manager for {@link RecyclerViewBackedScrollView}. @@ -27,6 +32,11 @@ public String getName() { // TODO(8624925): Implement removeClippedSubviews support for native ListView + @ReactProp(name = "onContentSizeChange") + public void setOnContentSizeChange(RecyclerViewBackedScrollView view, boolean value) { + view.setSendContentSizeChangeEvents(value); + } + @Override protected RecyclerViewBackedScrollView createViewInstance(ThemedReactContext reactContext) { return new RecyclerViewBackedScrollView(reactContext); @@ -76,4 +86,15 @@ public void scrollWithoutAnimationTo( ReactScrollViewCommandHelper.ScrollToCommandData data) { view.scrollTo(data.mDestX, data.mDestY, false); } + + @Override + public @Nullable + Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll")) + .put( + ContentSizeChangeEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onContentSizeChange")) + .build(); + } } From 06e514076b9f4b54a2211c9e104ec8e2dea71b14 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 20 Nov 2015 07:52:36 -0800 Subject: [PATCH 0022/1411] Ensure correct Babel plugin locations in packager transform Summary: Manually resolve all default Babel plugins. `babel.transform` will attempt to resolve all base plugins relative to the file it's compiling. This makes sure that we're using the plugins installed in the react-native package. **NOTE:** Haven't tested this. Please don't merge until Travis has done CI. Closes https://github.com/facebook/react-native/pull/4248 Reviewed By: svcscm Differential Revision: D2679651 Pulled By: davidaurelio fb-gh-sync-id: 0cdef1e738ba28c09c811432a71047f80540ed7b --- packager/transformer.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packager/transformer.js b/packager/transformer.js index ce984be9edb7..4700407c9368 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -37,6 +37,21 @@ function transform(src, filename, options) { } config.plugins = extraPlugins.concat(config.plugins); + // Manually resolve all default Babel plugins. babel.transform will attempt to resolve + // all base plugins relative to the file it's compiling. This makes sure that we're + // using the plugins installed in the react-native package. + config.plugins = config.plugins.map(function(plugin) { + // Normalise plugin to an array. + if (!Array.isArray(plugin)) { + plugin = [plugin]; + } + // Only resolve the plugin if it's a string reference. + if (typeof plugin[0] === 'string') { + plugin[0] = require(`babel-plugin-${plugin[0]}`); + } + return plugin; + }); + const result = babel.transform(src, Object.assign({}, babelRC, config)); return { From 0c8850f3a76f2c6d3979fb6aa0403cab3f05a79d Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 20 Nov 2015 07:54:43 -0800 Subject: [PATCH 0023/1411] Only send layout update operation to nativehierarchymanager when layout actually changes. Reviewed By: andreicoman11 Differential Revision: D2679408 fb-gh-sync-id: 7f0a972e9e12f70402e2d285edef458a61ca1c39 --- .../react/uimanager/ReactShadowNode.java | 38 +++++++++++++------ .../react/uimanager/UIManagerModule.java | 14 +------ 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 73134194fb1b..bbf33b7b2ed2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -18,6 +18,7 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.uimanager.events.EventDispatcher; /** * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily @@ -202,18 +203,37 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { float absoluteX, float absoluteY, UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer, + EventDispatcher eventDispatcher) { if (mNodeUpdated) { onCollectExtraUpdates(uiViewOperationQueue); } if (hasNewLayout()) { - mAbsoluteLeft = Math.round(absoluteX + getLayoutX()); - mAbsoluteTop = Math.round(absoluteY + getLayoutY()); - mAbsoluteRight = Math.round(absoluteX + getLayoutX() + getLayoutWidth()); - mAbsoluteBottom = Math.round(absoluteY + getLayoutY() + getLayoutHeight()); - - nativeViewHierarchyOptimizer.handleUpdateLayout(this); + float absoluteLeft = Math.round(absoluteX + getLayoutX()); + float absoluteTop = Math.round(absoluteY + getLayoutY()); + float absoluteRight = Math.round(absoluteX + getLayoutX() + getLayoutWidth()); + float absoluteBottom = Math.round(absoluteY + getLayoutY() + getLayoutHeight()); + // If the layout didn't change this should calculate exactly same values, it's fine to compare + // floats with "==" in this case + if (absoluteLeft != mAbsoluteLeft || absoluteTop != mAbsoluteTop || + absoluteRight != mAbsoluteRight || absoluteBottom != mAbsoluteBottom) { + mAbsoluteLeft = absoluteLeft; + mAbsoluteTop = absoluteTop; + mAbsoluteRight = absoluteRight; + mAbsoluteBottom = absoluteBottom; + + nativeViewHierarchyOptimizer.handleUpdateLayout(this); + if (mShouldNotifyOnLayout) { + eventDispatcher.dispatchEvent( + OnLayoutEvent.obtain( + getReactTag(), + getScreenX(), + getScreenY(), + getScreenWidth(), + getScreenHeight())); + } + } } } @@ -264,10 +284,6 @@ public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { mShouldNotifyOnLayout = shouldNotifyOnLayout; } - /* package */ boolean shouldNotifyOnLayout() { - return mShouldNotifyOnLayout; - } - /** * Adds a child that the native view hierarchy will have at this index in the native view * corresponding to this node. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index b0d61f61f563..a1d498e1c7c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -814,18 +814,8 @@ private void applyUpdatesRecursive(ReactShadowNode cssNode, float absoluteX, flo absoluteX, absoluteY, mOperationsQueue, - mNativeViewHierarchyOptimizer); - - // notify JS about layout event if requested - if (cssNode.shouldNotifyOnLayout()) { - mEventDispatcher.dispatchEvent( - OnLayoutEvent.obtain( - tag, - cssNode.getScreenX(), - cssNode.getScreenY(), - cssNode.getScreenWidth(), - cssNode.getScreenHeight())); - } + mNativeViewHierarchyOptimizer, + mEventDispatcher); } cssNode.markUpdateSeen(); } From 205a35ad37ac99b6a1dbf8c0758ff2d063c54c3e Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 20 Nov 2015 08:02:35 -0800 Subject: [PATCH 0024/1411] View recycling in JS. Summary: public Native view recycling implementation based on limited pools of views. In this diff I introduced new UIManager method: dropViews. Instead of removing views from tag->view maps when they are detached we keep them there until we get a call to dropViews with the appropriate tag. JS may keep a pool of object and selectively decide not to enqueue drop for certain views. Then instead of removing those views it may decide to reuse tag that has been previously allocated for a view that is no longer in use. Special handling is required for layout-only nodes as they only can transition from layout-only to non-layout-only (reverse transition hasn't been implemented). Because of that we'd loose benefits of view flattening if we decide to recycle existing non-layout-only view as a layout-only one. This diff provides only a simple and manual method for configuring pools by calling `ReactNativeViewPool.configure` with a dict from native view name to the view count. Note that we may not want recycle all the views (e.g. when we render mapview we don't want to keep it in memory after it's detached) Reviewed By: davidaurelio Differential Revision: D2677289 fb-gh-sync-id: 29f44ce5b01db3ec353522af051b6a50924614a2 --- .../ReactNative/ReactNativeBaseComponent.js | 21 +- Libraries/ReactNative/ReactNativeMount.js | 2 + .../ReactNativeReconcileTransaction.js | 9 +- Libraries/ReactNative/ReactNativeViewPool.js | 265 ++++++++++++++++++ .../uimanager/NativeViewHierarchyManager.java | 15 +- .../NativeViewHierarchyOptimizer.java | 4 + .../react/uimanager/UIManagerModule.java | 18 +- .../uimanager/UIManagerModuleConstants.java | 8 + .../UIManagerModuleConstantsHelper.java | 2 + .../react/uimanager/UIViewOperationQueue.java | 23 ++ .../facebook/react/uimanager/ViewProps.java | 2 +- 11 files changed, 342 insertions(+), 27 deletions(-) create mode 100644 Libraries/ReactNative/ReactNativeViewPool.js diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index cfd3df3e832d..b187a39f0f97 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -16,6 +16,7 @@ var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactNativeViewPool = require('ReactNativeViewPool'); var ReactMultiChild = require('ReactMultiChild'); var RCTUIManager = require('NativeModules').UIManager; @@ -88,6 +89,7 @@ ReactNativeBaseComponent.Mixin = { unmountComponent: function() { deleteAllListeners(this._rootNodeID); this.unmountChildren(); + ReactNativeViewPool.release(this); this._rootNodeID = null; }, @@ -204,24 +206,7 @@ ReactNativeBaseComponent.Mixin = { mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; - var tag = ReactNativeTagHandles.allocateTag(); - - if (__DEV__) { - deepFreezeAndThrowOnMutationInDev(this._currentElement.props); - } - - var updatePayload = ReactNativeAttributePayload.create( - this._currentElement.props, - this.viewConfig.validAttributes - ); - - var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); - RCTUIManager.createView( - tag, - this.viewConfig.uiViewClassName, - nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, - updatePayload - ); + var tag = ReactNativeViewPool.acquire(this); this._registerListenersUponCreation(this._currentElement.props); this.initializeChildren( diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 02dbcb40fc7e..aeef4cba77af 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -15,6 +15,7 @@ var RCTUIManager = require('NativeModules').UIManager; var ReactElement = require('ReactElement'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactNativeViewPool = require('ReactNativeViewPool'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); @@ -216,6 +217,7 @@ var ReactNativeMount = { ReactNativeMount.unmountComponentAtNode(containerTag); // call back into native to remove all of the subviews from this container RCTUIManager.removeRootView(containerTag); + ReactNativeViewPool.clearPoolForRootView(containerTag); }, /** diff --git a/Libraries/ReactNative/ReactNativeReconcileTransaction.js b/Libraries/ReactNative/ReactNativeReconcileTransaction.js index 309630e3cc36..aff41081d359 100644 --- a/Libraries/ReactNative/ReactNativeReconcileTransaction.js +++ b/Libraries/ReactNative/ReactNativeReconcileTransaction.js @@ -14,6 +14,7 @@ var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var Transaction = require('Transaction'); +var ReactNativeViewPool = require('ReactNativeViewPool'); /** * Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during @@ -35,12 +36,18 @@ var ON_DOM_READY_QUEUEING = { } }; +var RN_VIEW_POOL_WRAPPER = { + close: function() { + ReactNativeViewPool.onReconcileTransactionClose(); + }, +}; + /** * Executed within the scope of the `Transaction` instance. Consider these as * being member methods, but with an implied ordering while being isolated from * each other. */ -var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING]; +var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING, RN_VIEW_POOL_WRAPPER]; /** * Currently: diff --git a/Libraries/ReactNative/ReactNativeViewPool.js b/Libraries/ReactNative/ReactNativeViewPool.js new file mode 100644 index 000000000000..968449d8ba50 --- /dev/null +++ b/Libraries/ReactNative/ReactNativeViewPool.js @@ -0,0 +1,265 @@ +/** + * 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 ReactNativeViewPool + * @flow + */ +'use strict'; + +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var RCTUIManager = require('NativeModules').UIManager; +var Platform = require('Platform'); + +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +var emptyFunction = require('emptyFunction'); +var flattenStyle = require('flattenStyle'); + +var EMPTY_POOL = [[]]; + +var ENABLED = !!RCTUIManager.dropViews; + +/* indicies used for _addToPool arrays */ +var TAGS_IDX = 0; +var KEYS_IDX = 1; +var PROPS_IDX = 2; + +var _pools = {}; +var _poolSize = {}; + +var layoutOnlyProps = RCTUIManager.layoutOnlyProps; + +function isCollapsableForStyle(style) { + var flatStyle = flattenStyle(style); + for (var styleKey in flatStyle) { + if (layoutOnlyProps[styleKey] !== true) { + return false; + } + } + return true; +} + +function isCollapsable(viewRef) { + var props = viewRef._currentElement.props; + if (props.collapsable !== undefined && !props.collapsable) { + return false; + } + var validAttributes = viewRef.viewConfig.validAttributes; + for (var propKey in props) { + if (!!validAttributes[propKey] && propKey !== 'style' && propKey !== 'collapsable') { + return false; + } + } + return !props.style || isCollapsableForStyle(viewRef._currentElement.props.style); +} + +function enqueueCreate(viewRef, rootTag) { + var tag = ReactNativeTagHandles.allocateTag(); + + if (__DEV__) { + deepFreezeAndThrowOnMutationInDev(viewRef._currentElement.props); + } + + var updatePayload = ReactNativeAttributePayload.create( + viewRef._currentElement.props, + viewRef.viewConfig.validAttributes + ); + + RCTUIManager.createView( + tag, + viewRef.viewConfig.uiViewClassName, + rootTag, + updatePayload + ); + + return tag; +} + +function getViewTag(viewRef) { + return ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(viewRef._rootNodeID); +} + +function getViewProps(viewRef) { + return viewRef._currentElement.props; +} + +function getViewValidAttributes(viewRef) { + return viewRef.viewConfig.validAttributes; +} + +function getRootViewTag(viewRef) { + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(viewRef._rootNodeID); + return ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID]; +} + +function poolKey(viewRef) { + var viewClass = viewRef.viewConfig.uiViewClassName; + if (Platform.OS === 'android' && viewClass === 'RCTView') { + return isCollapsable(viewRef) ? 'CollapsedRCTView' : 'RCTView'; + } + return viewClass; +} + +class ReactNativeViewPool { + constructor() { + this._pool = {}; + this._poolQueue = {}; + this._addToPool = [[],[],[]]; + this._viewsToDelete = []; + if (__DEV__) { + this._recycleStats = {}; + this._deleteStats = {}; + } + } + + onReconcileTransactionClose() { + // flush all deletes, move object from pool_queue to the actual pool + if (this._viewsToDelete.length > 0) { + RCTUIManager.dropViews(this._viewsToDelete); + } + var addToPoolTags = this._addToPool[TAGS_IDX]; + var addToPoolKeys = this._addToPool[KEYS_IDX]; + var addToPoolProps = this._addToPool[PROPS_IDX]; + for (var i = addToPoolTags.length - 1; i >= 0; i--) { + var nativeTag = addToPoolTags[i]; + var key = addToPoolKeys[i]; + var props = addToPoolProps[i]; + var views = this._pool[key] || [[],[]]; + views[0].push(nativeTag); + views[1].push(props); + this._pool[key] = views; + } + this._viewsToDelete = []; + this._addToPool = [[],[],[]]; + this._poolQueue = {}; + } + + acquire(viewRef, rootTag) { + var key = poolKey(viewRef); + if ((this._pool[key] || EMPTY_POOL)[0].length) { + var views = this._pool[key]; + var nativeTag = views[0].pop(); + var oldProps = views[1].pop(); + var updatePayload = ReactNativeAttributePayload.diff( + oldProps, + getViewProps(viewRef), + getViewValidAttributes(viewRef) + ); + if (__DEV__) { + this._recycleStats[key] = (this._recycleStats[key] || 0) + 1; + } + + if (updatePayload) { + RCTUIManager.updateView( + nativeTag, + viewRef.viewConfig.uiViewClassName, + updatePayload + ); + } + return nativeTag; + } else { + // If there is no view available for the given pool key, we just enqueue call to create one + return enqueueCreate(viewRef, rootTag); + } + } + + release(viewRef) { + var key = poolKey(viewRef); + var nativeTag = getViewTag(viewRef); + var pooledCount = (this._pool[key] || EMPTY_POOL)[0].length + (this._poolQueue[key] || 0); + if (pooledCount < (_poolSize[key] || 0)) { + // we have room in the pool for this view + // we can add it to the queue so that it will be added to the actual pull in + // onReconcileTransactionClose + this._addToPool[TAGS_IDX].push(nativeTag); + this._addToPool[KEYS_IDX].push(key); + this._addToPool[PROPS_IDX].push(getViewProps(viewRef)); + this._poolQueue[key] = (this._poolQueue[key] || 0) + 1; + } else { + if (__DEV__) { + if (_poolSize[key]) { + this._deleteStats[key] = (this._deleteStats[key] || 0) + 1; + } + } + this._viewsToDelete.push(nativeTag); + } + } + + clear() { + for (var key in this._pool) { + var poolTags = this._pool[key][0]; + for (var i = poolTags.length - 1; i >= 0; i--) { + this._viewsToDelete.push(poolTags[i]); + } + } + var addToPoolTags = this._addToPool[0]; + for (var i = addToPoolTags.length - 1; i >= 0; i--) { + this._viewsToDelete.push(addToPoolTags[i]); + } + this._addToPool = [[],[],[]]; + this.onReconcileTransactionClose(); + } + + printStats() { + if (__DEV__) { + console.log('Stats', this._recycleStats, this._deleteStats); + } + } +} + +module.exports = { + + onReconcileTransactionClose: function() { + if (ENABLED) { + for (var pool in _pools) { + _pools[pool].onReconcileTransactionClose(); + } + } + }, + + acquire: function(viewRef) { + var rootTag = getRootViewTag(viewRef); + if (ENABLED) { + var pool = _pools[rootTag]; + if (!pool) { + pool = _pools[rootTag] = new ReactNativeViewPool(); + } + return pool.acquire(viewRef, rootTag); + } else { + return enqueueCreate(viewRef, rootTag); + } + }, + + release: ENABLED ? function(viewRef) { + var pool = _pools[getRootViewTag(viewRef)]; + if (pool) { + pool.release(viewRef); + } + } : emptyFunction, + + clearPoolForRootView: ENABLED ? function(rootID) { + var pool = _pools[rootID]; + if (pool) { + pool.clear(); + delete _pools[rootID]; + } + } : emptyFunction, + + configure: function(pool_size) { + _poolSize = pool_size; + }, + + printStats: function() { + if (__DEV__) { + console.log('Pool size', _poolSize); + for (var pool in _pools) { + _pools[pool].onReconcileTransactionClose(); + } + } + }, +}; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 68a1e567f038..0b7b7e1f08ba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -349,7 +349,7 @@ public void manageChildren( viewsToAdd, tagsToDelete)); } - dropView(viewToDestroy); + detachView(viewToDestroy); } } } @@ -378,10 +378,15 @@ public void addRootView( view.setId(tag); } + public void dropView(int tag) { + mTagsToViews.remove(tag); + mTagsToViewManagers.remove(tag); + } + /** * Releases all references to given native View. */ - private void dropView(View view) { + private void detachView(View view) { UiThreadUtil.assertOnUiThread(); if (!mRootTags.get(view.getId())) { // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} @@ -396,13 +401,11 @@ private void dropView(View view) { for (int i = viewGroupManager.getChildCount(viewGroup) - 1; i >= 0; i--) { View child = viewGroupManager.getChildAt(viewGroup, i); if (mTagsToViews.get(child.getId()) != null) { - dropView(child); + detachView(child); } } viewGroupManager.removeAllViews(viewGroup); } - mTagsToViews.remove(view.getId()); - mTagsToViewManagers.remove(view.getId()); } public void removeRootView(int rootViewTag) { @@ -412,7 +415,7 @@ public void removeRootView(int rootViewTag) { "View with tag " + rootViewTag + " is not registered as a root view"); } View rootView = mTagsToViews.get(rootViewTag); - dropView(rootView); + detachView(rootView); mRootTags.delete(rootViewTag); mRootViewsContext.remove(rootViewTag); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java index fd3946eeb7c9..e01fab28dcce 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java @@ -90,6 +90,10 @@ public void handleCreateView( } } + public void handleDropViews(int[] viewTagsToDrop, int length) { + mUIViewOperationQueue.enqueueDropViews(viewTagsToDrop, length); + } + /** * Handles native children cleanup when css node is removed from hierarchy */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index a1d498e1c7c6..6482a209b332 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -261,6 +261,23 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p } } + @ReactMethod + public void dropViews(ReadableArray viewTags) { + int size = viewTags.size(), realViewsCount = 0; + int realViewTags[] = new int[size]; + for (int i = 0; i < size; i++) { + int tag = viewTags.getInt(i); + ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); + if (!cssNode.isVirtual()) { + realViewTags[realViewsCount++] = tag; + } + mShadowNodeRegistry.removeNode(tag); + } + if (realViewsCount > 0) { + mNativeViewHierarchyOptimizer.handleDropViews(realViewTags, realViewsCount); + } + } + @ReactMethod public void updateView(int tag, String className, ReadableMap props) { ViewManager viewManager = mViewManagers.get(className); @@ -405,7 +422,6 @@ public void manageChildren( private void removeShadowNode(ReactShadowNode nodeToRemove) { mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); - mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { removeShadowNode(nodeToRemove.getChildAt(i)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java index 3287ebf7854c..eab1c3489170 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java @@ -154,4 +154,12 @@ public static Map getConstants(DisplayMetrics displayMetrics) { return constants; } + + public static Map getLayoutOnlyPropsConstants() { + HashMap constants = new HashMap<>(); + for (String propName : ViewProps.LAYOUT_ONLY_PROPS) { + constants.put(propName, Boolean.TRUE); + } + return constants; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java index bce7c37361b2..d5b566529729 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java @@ -25,6 +25,7 @@ private static final String CUSTOM_BUBBLING_EVENT_TYPES_KEY = "customBubblingEventTypes"; private static final String CUSTOM_DIRECT_EVENT_TYPES_KEY = "customDirectEventTypes"; + private static final String LAYOUT_ONLY_PROPS = "layoutOnlyProps"; /** * Generates map of constants that is then exposed by {@link UIManagerModule}. The constants map @@ -75,6 +76,7 @@ constants.put(CUSTOM_BUBBLING_EVENT_TYPES_KEY, bubblingEventTypesConstants); constants.put(CUSTOM_DIRECT_EVENT_TYPES_KEY, directEventTypesConstants); + constants.put(LAYOUT_ONLY_PROPS, UIManagerModuleConstants.getLayoutOnlyPropsConstants()); return constants; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 739558938334..bb9cea705cb3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -142,6 +142,25 @@ public void execute() { } } + private final class DropViewsOperation extends ViewOperation { + + private final int[] mViewTagsToDrop; + private final int mArrayLength; + + public DropViewsOperation(int[] viewTagsToDrop, int length) { + super(-1); + mViewTagsToDrop = viewTagsToDrop; + mArrayLength = length; + } + + @Override + public void execute() { + for (int i = 0; i < mArrayLength; i++) { + mNativeViewHierarchyManager.dropView(mViewTagsToDrop[i]); + } + } + } + private final class ManageChildrenOperation extends ViewOperation { private final @Nullable int[] mIndicesToRemove; @@ -502,6 +521,10 @@ public void enqueueCreateView( initialProps)); } + public void enqueueDropViews(int[] viewTagsToDrop, int length) { + mOperations.add(new DropViewsOperation(viewTagsToDrop, length)); + } + public void enqueueUpdateProperties(int reactTag, String className, CatalystStylesDiffMap props) { mOperations.add(new UpdatePropertiesOperation(reactTag, props)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index 7e87a8419d85..d19ea165e904 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -84,7 +84,7 @@ public class ViewProps { Spacing.BOTTOM }; - private static final HashSet LAYOUT_ONLY_PROPS = new HashSet<>( + /*package*/ static final HashSet LAYOUT_ONLY_PROPS = new HashSet<>( Arrays.asList( ALIGN_SELF, ALIGN_ITEMS, From a7c4ed106b28c609aa10f016e84e4bf22298d65c Mon Sep 17 00:00:00 2001 From: Saurabh Aggarwal Date: Fri, 20 Nov 2015 08:56:15 -0800 Subject: [PATCH 0025/1411] Adds stetho interceptor to the OSS react native networking module Reviewed By: mkonicek Differential Revision: D2669416 fb-gh-sync-id: d061711a412348b16ffb877e0178a05460fd95f2 --- ReactAndroid/build.gradle | 2 ++ .../com/facebook/react/modules/network/NetworkingModule.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 13d95719b767..b9dc2f04610e 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -248,6 +248,8 @@ dependencies { compile 'com.android.support:recyclerview-v7:23.0.1' compile 'com.facebook.fresco:fresco:0.8.1' compile 'com.facebook.fresco:imagepipeline-okhttp:0.8.1' + compile 'com.facebook.stetho:stetho:1.2.0' + compile 'com.facebook.stetho:stetho-okhttp:1.2.0' compile 'com.fasterxml.jackson.core:jackson-core:2.2.3' compile 'com.google.code.findbugs:jsr305:3.0.0' compile 'com.squareup.okhttp:okhttp:2.5.0' diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 39d2cb8109ce..4ddbf1f60587 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -25,6 +25,7 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.stetho.okhttp.StethoInterceptor; import com.squareup.okhttp.Callback; import com.squareup.okhttp.Headers; @@ -65,6 +66,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { OkHttpClient client) { super(reactContext); mClient = client; + mClient.networkInterceptors().add(new StethoInterceptor()); mShuttingDown = false; mDefaultUserAgent = defaultUserAgent; } From 155a609781870db8293f37799efeee442f5f3152 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 20 Nov 2015 09:51:58 -0800 Subject: [PATCH 0026/1411] Unbreak tests Summary: The recent commits for RecyclerViewBackedScrollView introduced a bunch of Flow errors. Disable Flow for now in `ReactNativeViewPool` as it looks like that file doesn't use Flow :) public Reviewed By: gabelevi Differential Revision: D2679974 fb-gh-sync-id: 71512f403e3fa271f6c0fa1a64e877ca1750f675 --- Libraries/Components/ScrollView/ScrollView.js | 6 +++--- Libraries/ReactNative/ReactNativeViewPool.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 4d90f19d6c88..a23ac8c5d505 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -346,7 +346,7 @@ var ScrollView = React.createClass({ ); }, - handleScroll: function(e: Event) { + handleScroll: function(e: Object) { if (__DEV__) { if (this.props.onScroll && !this.props.scrollEventThrottle) { console.log( @@ -366,8 +366,8 @@ var ScrollView = React.createClass({ this.scrollResponderHandleScroll(e); }, - _handleContentOnLayout: function(event) { - var {width, height} = event.nativeEvent.layout; + _handleContentOnLayout: function(e: Object) { + var {width, height} = e.nativeEvent.layout; this.props.onContentSizeChange && this.props.onContentSizeChange(width, height); }, diff --git a/Libraries/ReactNative/ReactNativeViewPool.js b/Libraries/ReactNative/ReactNativeViewPool.js index 968449d8ba50..dd0a596165a0 100644 --- a/Libraries/ReactNative/ReactNativeViewPool.js +++ b/Libraries/ReactNative/ReactNativeViewPool.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactNativeViewPool - * @flow */ 'use strict'; From 7febd1367656aa1c337f62763624078f9e3062f8 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Fri, 20 Nov 2015 10:03:21 -0800 Subject: [PATCH 0027/1411] Attach to all RelayProfiler events Summary: public Dynamically profile events from RelayProfiler if available. This will expose time spent in Relay in the systraces. Reviewed By: tadeuzagallo Differential Revision: D2674215 fb-gh-sync-id: d5f9d529b86d267a80b0cda2223f6a28a08ac385 --- .../InitializeJavaScriptAppEngine.js | 4 +++- Libraries/Utilities/BridgeProfiling.js | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 501d4c9cc329..8735e1ab004b 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -160,7 +160,9 @@ function setUpWebSockets() { function setUpProfile() { if (__DEV__) { - require('BridgeProfiling').swizzleReactPerf(); + var BridgeProfiling = require('BridgeProfiling'); + BridgeProfiling.swizzleReactPerf(); + BridgeProfiling.attachToRelayProfiler(); } } diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 16b6a7159339..e62c8b76dcec 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -61,6 +61,24 @@ var BridgeProfiling = { swizzleReactPerf() { ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure); }, + + attachToRelayProfiler() { + // We don't want to create a dependency on `RelayProfiler`, so that's why + // we require it indirectly (rather than using a literal string). Since + // there's no guarantee that the module will be present, we must wrap + // everything in a try-catch block as requiring a non-existing module + // will just throw. + try { + var rpName = 'RelayProfiler'; + var RelayProfiler = require(rpName); + RelayProfiler.attachProfileHandler('*', (name) => { + BridgeProfiling.profile(name); + return () => { + BridgeProfiling.profileEnd(); + }; + }); + } catch(err) {} + } }; BridgeProfiling.setEnabled(global.__RCTProfileIsProfiling || false); From 67209e6396516358ec47d68eeb14b84f8d0d7991 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Fri, 20 Nov 2015 09:09:36 -0800 Subject: [PATCH 0028/1411] If fbsystrace is running when catalyst starts, it asserts Summary: registering with systrace checks if the bridge is initialized, which it's not yet in the ctor. Defer registration until after the bridge is created, and only unregister in that case. public Reviewed By: astreet, dreiss Differential Revision: D2651244 fb-gh-sync-id: 8da1108e9d15fddde48d06f4ed61ee0f787016ad --- .../java/com/facebook/react/bridge/CatalystInstance.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index c96ddb8e469d..f8d7bff457e1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -82,7 +82,6 @@ private CatalystInstance( mJSBundleLoader = jsBundleLoader; mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; mTraceListener = new JSProfilerTraceListener(); - Systrace.registerListener(mTraceListener); final CountDownLatch initLatch = new CountDownLatch(1); mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( @@ -116,6 +115,8 @@ private void initializeBridge( mBridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); + + Systrace.registerListener(mTraceListener); } public void runJSBundle() { @@ -239,7 +240,7 @@ public void run() { } } - if (mTraceListener != null) { + if (mBridge != null) { Systrace.unregisterListener(mTraceListener); } From 60b67a447dab883bad8065cf719eb4acc6db6e55 Mon Sep 17 00:00:00 2001 From: Mike Fowler Date: Fri, 20 Nov 2015 11:38:04 -0800 Subject: [PATCH 0029/1411] Update Animations.md Add a better explanation of the extrapolation options. --- docs/Animations.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Animations.md b/docs/Animations.md index a18f0f78ce69..22a5f1c0f514 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -168,9 +168,11 @@ Input | Output already implemented in the [`Easing`](https://github.com/facebook/react-native/blob/master/Libraries/Animation/Animated/Easing.js) class including quadratic, exponential, and bezier curves as well as functions -like step and bounce. `interpolation` also has configurable behavior for -extrapolation, the default being `'extend'`, but `'clamp'` is also very useful -to prevent the output value from exceeding `outputRange`. +like step and bounce. `interpolation` also has configurable behavior for +extrapolating the `outputRange`. You can set the extrapolation by setting the `extrapolate`, +`extrapolateLeft` or `extrapolateRight` options. The default value is +`extend` but you can use `clamp` to prevent the output value from exceeding +`outputRange`. #### Tracking Dynamic Values From f2a10d1f169f419fbae16c4186f1cc1cbcd0d6ce Mon Sep 17 00:00:00 2001 From: Ricky Reusser Date: Fri, 20 Nov 2015 14:59:33 -0500 Subject: [PATCH 0030/1411] Additional note for Android borderRadius clipping #3198 --- docs/KnownIssues.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 9fc543299707..27f677da218a 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -65,7 +65,9 @@ There is currently no easy way of publishing custom native modules on Android. S ### The `overflow` style property defaults to `hidden` and cannot be changed on Android -This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks. +This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks. + +Another issue with `overflow: 'hidden'` on Android: a view is not clipped by the parent's `borderRadius` even if the parent has `overflow: 'hidden'` enabled – the corners of the inner view will be visible outside of the rounded corners. This is only on Android; it works as expected on iOS. See a [demo of the bug](https://rnplay.org/apps/BlGjdQ) and the [corresponding issue](https://github.com/facebook/react-native/issues/3198). ### No support for shadows on Android From 5b6b5a64d195d03bf407f5e460915d50268370ce Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Fri, 20 Nov 2015 12:16:19 -0800 Subject: [PATCH 0031/1411] Split JavaSJExecutor and ProxyExecutorException into their own file Summary: This makes the exception a nested class of the interface, which eliminates the dependency of DevSupportManager on ProxyJavaScriptExecutor. public Reviewed By: astreet Differential Revision: D2651252 fb-gh-sync-id: 99de1c308b9bce717ab749c4e239d2773a920e1f --- .../facebook/react/ReactInstanceManager.java | 9 ++-- .../facebook/react/bridge/JavaJSExecutor.java | 54 +++++++++++++++++++ .../react/bridge/ProxyJavaScriptExecutor.java | 42 --------------- .../bridge/WebsocketJavaScriptExecutor.java | 10 ++-- .../react/devsupport/DevSupportManager.java | 2 +- .../ReactInstanceDevCommandsHandler.java | 4 +- .../src/main/jni/react/jni/OnLoad.cpp | 2 +- .../src/main/jni/react/jni/ProxyExecutor.cpp | 2 +- 8 files changed, 69 insertions(+), 56 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index b8cf21b23417..ba4cd8d12abb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -28,6 +28,7 @@ import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSCJavaScriptExecutor; +import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutor; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.JavaScriptModulesConfig; @@ -97,8 +98,8 @@ public class ReactInstanceManager { new ReactInstanceDevCommandsHandler() { @Override - public void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) { - ReactInstanceManager.this.onReloadWithJSDebugger(proxyExecutor); + public void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { + ReactInstanceManager.this.onReloadWithJSDebugger(jsExecutor); } @Override @@ -474,9 +475,9 @@ public String getSourceUrl() { return mCurrentReactContext; } - private void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) { + private void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { recreateReactContextInBackground( - proxyExecutor, + new ProxyJavaScriptExecutor(jsExecutor), JSBundleLoader.createRemoteDebuggerBundleLoader( mDevSupportManager.getJSBundleURLForRemoteDebugging())); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java new file mode 100644 index 000000000000..76b8a1cb5275 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * This is class represents java version of native js executor interface. When set through + * {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will + * delegate js calls to the given implementation of this interface. + */ +@DoNotStrip +public interface JavaJSExecutor { + public static class ProxyExecutorException extends Exception { + public ProxyExecutorException(Throwable cause) { + super(cause); + } + } + + /** + * Close this executor and cleanup any resources that it was using. No further calls are + * expected after this. + */ + void close(); + + /** + * Load javascript into the js context + * @param script script contet to be executed + * @param sourceURL url or file location from which script content was loaded + */ + @DoNotStrip + void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException; + + /** + * Execute javascript method within js context + * @param modulename name of the common-js like module to execute the method from + * @param methodName name of the method to be executed + * @param jsonArgsArray json encoded array of arguments provided for the method call + * @return json encoded value returned from the method call + */ + @DoNotStrip + String executeJSCall(String modulename, String methodName, String jsonArgsArray) + throws ProxyExecutorException; + + @DoNotStrip + void setGlobalVariable(String propertyName, String jsonEncodedValue); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java index 08447802bf0b..f9e58869b3f5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java @@ -29,48 +29,6 @@ public class ProxyJavaScriptExecutor extends JavaScriptExecutor { SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB); } - public static class ProxyExecutorException extends Exception { - public ProxyExecutorException(Throwable cause) { - super(cause); - } - } - - /** - * This is class represents java version of native js executor interface. When set through - * {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will - * delegate js calls to the given implementation of this interface. - */ - @DoNotStrip - public interface JavaJSExecutor { - /** - * Close this executor and cleanup any resources that it was using. No further calls are - * expected after this. - */ - void close(); - - /** - * Load javascript into the js context - * @param script script contet to be executed - * @param sourceURL url or file location from which script content was loaded - */ - @DoNotStrip - void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException; - - /** - * Execute javascript method within js context - * @param modulename name of the common-js like module to execute the method from - * @param methodName name of the method to be executed - * @param jsonArgsArray json encoded array of arguments provided for the method call - * @return json encoded value returned from the method call - */ - @DoNotStrip - String executeJSCall(String modulename, String methodName, String jsonArgsArray) - throws ProxyExecutorException; - - @DoNotStrip - void setGlobalVariable(String propertyName, String jsonEncodedValue); - } - private @Nullable JavaJSExecutor mJavaJSExecutor; /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java index 4557d9fe6747..e044ca8cd65f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java @@ -23,7 +23,7 @@ /** * Executes JS remotely via the react nodejs server as a proxy to a browser on the host machine. */ -public class WebsocketJavaScriptExecutor implements ProxyJavaScriptExecutor.JavaJSExecutor { +public class WebsocketJavaScriptExecutor implements JavaJSExecutor { private static final long CONNECT_TIMEOUT_MS = 5000; private static final int CONNECT_RETRY_COUNT = 3; @@ -146,7 +146,7 @@ public void close() { @Override public void executeApplicationScript(String script, String sourceURL) - throws ProxyJavaScriptExecutor.ProxyExecutorException { + throws ProxyExecutorException { JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture(); Assertions.assertNotNull(mWebSocketClient).executeApplicationScript( sourceURL, @@ -155,13 +155,13 @@ public void executeApplicationScript(String script, String sourceURL) try { callback.get(); } catch (Throwable cause) { - throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause); + throw new ProxyExecutorException(cause); } } @Override public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray) - throws ProxyJavaScriptExecutor.ProxyExecutorException { + throws ProxyExecutorException { JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture(); Assertions.assertNotNull(mWebSocketClient).executeJSCall( moduleName, @@ -171,7 +171,7 @@ public void executeApplicationScript(String script, String sourceURL) try { return callback.get(); } catch (Throwable cause) { - throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause); + throw new ProxyExecutorException(cause); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index 69ef7fa96211..bd8add591b80 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -541,7 +541,7 @@ public void onSuccess() { @Override public void run() { mReactInstanceCommandsHandler.onReloadWithJSDebugger( - new ProxyJavaScriptExecutor(webSocketJSExecutor)); + webSocketJSExecutor); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java index 5489853b085f..e97b16f7d70f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java @@ -9,7 +9,7 @@ package com.facebook.react.devsupport; -import com.facebook.react.bridge.ProxyJavaScriptExecutor; +import com.facebook.react.bridge.JavaJSExecutor; /** * Interface used by {@link DevSupportManager} for requesting React instance recreation @@ -20,7 +20,7 @@ public interface ReactInstanceDevCommandsHandler { /** * Request react instance recreation with JS debugging enabled. */ - void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor); + void onReloadWithJSDebugger(JavaJSExecutor proxyExecutor); /** * Notify react instance manager about new JS bundle version downloaded from the server. diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index bc35feeb2bd2..6d3a1c2d8bdf 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -808,7 +808,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { registerNatives("com/facebook/react/bridge/ProxyJavaScriptExecutor", { makeNativeMethod( - "initialize", "(Lcom/facebook/react/bridge/ProxyJavaScriptExecutor$JavaJSExecutor;)V", + "initialize", "(Lcom/facebook/react/bridge/JavaJSExecutor;)V", executors::createProxyExecutor), }); diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp index 129d737fc55e..ab86a669854f 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp @@ -10,7 +10,7 @@ namespace facebook { namespace react { -const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/ProxyJavaScriptExecutor$JavaJSExecutor"; +const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/JavaJSExecutor"; std::unique_ptr ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) { FBASSERTMSGF( From c8f3b43984d8be9598e0c523472996dcfcefe87a Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Fri, 20 Nov 2015 12:16:39 -0800 Subject: [PATCH 0032/1411] Don't use the Bridge from CatalystInstance. Summary: getBridge() is annotated VisibleForTesting, but still used in DevSupportManager. Instead, add the necessary methods to the CatalystInstance interface. public Reviewed By: astreet Differential Revision: D2651265 fb-gh-sync-id: 395893a961c32843871de4451eeccb33135b7ede --- .../react/bridge/CatalystInstance.java | 21 +++++++++++++++++++ .../react/devsupport/DevSupportManager.java | 8 +++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index f8d7bff457e1..ee913802237c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -306,6 +306,27 @@ public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener l mBridgeIdleListeners.remove(listener); } + public boolean supportsProfiling() { + if (mBridge == null) { + return false; + } + return mBridge.supportsProfiling(); + } + + public void startProfiler(String title) { + if (mBridge == null) { + return; + } + mBridge.startProfiler(title); + } + + public void stopProfiler(String title, String filename) { + if (mBridge == null) { + return; + } + mBridge.stopProfiler(title, filename); + } + private String buildModulesConfigJSONProperty( NativeModuleRegistry nativeModuleRegistry, JavaScriptModulesConfig jsModulesConfig) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index bd8add591b80..0a5d1da14651 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -285,8 +285,7 @@ public void onOptionSelected() { if (mCurrentContext != null && mCurrentContext.getCatalystInstance() != null && !mCurrentContext.getCatalystInstance().isDestroyed() && - mCurrentContext.getCatalystInstance().getBridge() != null && - mCurrentContext.getCatalystInstance().getBridge().supportsProfiling()) { + mCurrentContext.getCatalystInstance().supportsProfiling()) { options.put( mApplicationContext.getString( mIsCurrentlyProfiling ? R.string.catalyst_stop_profile : @@ -302,7 +301,6 @@ public void onOptionSelected() { mProfileIndex++; Debug.stopMethodTracing(); mCurrentContext.getCatalystInstance() - .getBridge() .stopProfiler("profile", profileName); Toast.makeText( mCurrentContext, @@ -310,7 +308,7 @@ public void onOptionSelected() { Toast.LENGTH_LONG).show(); } else { mIsCurrentlyProfiling = true; - mCurrentContext.getCatalystInstance().getBridge().startProfiler("profile"); + mCurrentContext.getCatalystInstance().startProfiler("profile"); Debug.startMethodTracingSampling( profileName, JAVA_SAMPLING_PROFILE_MEMORY_BYTES, @@ -476,7 +474,7 @@ private void resetCurrentContext(@Nullable ReactContext reactContext) { "/profile_" + mProfileIndex + ".json"); mProfileIndex++; Debug.stopMethodTracing(); - mCurrentContext.getCatalystInstance().getBridge().stopProfiler("profile", profileName); + mCurrentContext.getCatalystInstance().stopProfiler("profile", profileName); } mCurrentContext = reactContext; From facf8a56d2b2c685e63b2f5c7d8f1ada497c9895 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Fri, 20 Nov 2015 12:17:00 -0800 Subject: [PATCH 0033/1411] minor build cleanups Summary: make DevSupportManager PUBLIC visibility, instead of adding to it more; put private parts of OnLoad into an anonymous namespace, and move NativeRunnable into it; don't make NativeArray depend on MethodCall which it does not use. public Reviewed By: astreet Differential Revision: D2651270 fb-gh-sync-id: d5262da79cbd60c4eb490d43d13cc625fa163cdf --- ReactAndroid/src/main/jni/react/jni/NativeArray.h | 2 +- ReactAndroid/src/main/jni/react/jni/OnLoad.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/jni/NativeArray.h b/ReactAndroid/src/main/jni/react/jni/NativeArray.h index 452f6b1d054e..f30ef107da2d 100644 --- a/ReactAndroid/src/main/jni/react/jni/NativeArray.h +++ b/ReactAndroid/src/main/jni/react/jni/NativeArray.h @@ -3,7 +3,7 @@ #pragma once #include -#include +#include namespace facebook { namespace react { diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 6d3a1c2d8bdf..e3ceddd5fdb1 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -60,10 +60,6 @@ struct ReadableNativeMapKeySetIterator : public Countable { , mapRef(mapRef_) {} }; -struct NativeRunnable : public Countable { - std::function callable; -}; - static jobject createReadableNativeMapWithContents(JNIEnv* env, folly::dynamic map) { if (map.isNull()) { return nullptr; @@ -515,8 +511,14 @@ static jstring getNextKey(JNIEnv* env, jobject obj) { } // namespace iterator } // namespace map +namespace { + namespace runnable { +struct NativeRunnable : public Countable { + std::function callable; +}; + static jclass gNativeRunnableClass; static jmethodID gNativeRunnableCtor; @@ -743,6 +745,8 @@ static void createProxyExecutor(JNIEnv *env, jobject obj, jobject executorInstan } // namespace executors +} + extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { return initialize(vm, [] { // get the current env From 9a61628f133a4d3fb67190e38de6c61c017a83e3 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Fri, 20 Nov 2015 12:17:23 -0800 Subject: [PATCH 0034/1411] Clean up the interface to the bridge Summary: Change the following classes into interfaces, with a separate Impl file: CatalystInstance, ReactInstanceManager, CatalystQueueConfiguration, MessageQueueThread. This is done to help isolate the interface between React Native and applications which use it. This will also help some intrusive development work on a branch such as porting parts of the bridge to common C++ code, without affecting app reliability while this work is ongoing. public Reviewed By: astreet Differential Revision: D2651277 fb-gh-sync-id: f04dc04a6e68df7acbc2bbf8b2529287d7b5b2ae --- .../facebook/react/ReactInstanceManager.java | 571 +--------------- .../react/ReactInstanceManagerImpl.java | 635 ++++++++++++++++++ .../react/bridge/CatalystInstance.java | 458 +------------ .../react/bridge/CatalystInstanceImpl.java | 510 ++++++++++++++ .../bridge/JavaScriptModuleRegistry.java | 6 +- .../queue/CatalystQueueConfiguration.java | 73 +- .../queue/CatalystQueueConfigurationImpl.java | 85 +++ .../bridge/queue/MessageQueueThread.java | 118 +--- .../bridge/queue/MessageQueueThreadImpl.java | 144 ++++ 9 files changed, 1435 insertions(+), 1165 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationImpl.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index ba4cd8d12abb..609b320fc9e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -16,44 +16,16 @@ import android.app.Activity; import android.app.Application; -import android.content.Context; import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.View; -import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.CatalystInstance; -import com.facebook.react.bridge.JSBundleLoader; -import com.facebook.react.bridge.JSCJavaScriptExecutor; -import com.facebook.react.bridge.JavaJSExecutor; -import com.facebook.react.bridge.JavaScriptExecutor; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.JavaScriptModulesConfig; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.NativeModuleRegistry; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; -import com.facebook.react.bridge.ProxyJavaScriptExecutor; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; -import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; -import com.facebook.react.devsupport.DevServerHelper; import com.facebook.react.devsupport.DevSupportManager; -import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.uimanager.AppRegistry; -import com.facebook.react.uimanager.ReactNative; -import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; -import com.facebook.soloader.SoLoader; /** * This class is managing instances of {@link CatalystInstance}. It expose a way to configure @@ -70,169 +42,11 @@ * owning activity's lifecycle events to the instance manager (see {@link #onPause}, * {@link #onDestroy} and {@link #onResume}). * - * To instantiate an instance of this class use {@link #builder}. + * Ideally, this would be an interface, but because of the API used by earlier versions, it has to + * have a static method, and so cannot (in Java < 8), be one. */ -public class ReactInstanceManager { - - /* should only be accessed from main thread (UI thread) */ - private final List mAttachedRootViews = new ArrayList<>(); - private LifecycleState mLifecycleState; - private boolean mIsContextInitAsyncTaskRunning; - private @Nullable ReactContextInitParams mPendingReactContextInitParams; - - /* accessed from any thread */ - private @Nullable String mJSBundleFile; /* path to JS bundle on file system */ - private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */ - private final List mPackages; - private final DevSupportManager mDevSupportManager; - private final boolean mUseDeveloperSupport; - private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; - private @Nullable volatile ReactContext mCurrentReactContext; - private final Context mApplicationContext; - private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl; - private String mSourceUrl; - private @Nullable Activity mCurrentActivity; - private volatile boolean mHasStartedCreatingInitialContext = false; - - private final ReactInstanceDevCommandsHandler mDevInterface = - new ReactInstanceDevCommandsHandler() { - - @Override - public void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { - ReactInstanceManager.this.onReloadWithJSDebugger(jsExecutor); - } - - @Override - public void onJSBundleLoadedFromServer() { - ReactInstanceManager.this.onJSBundleLoadedFromServer(); - } - - @Override - public void toggleElementInspector() { - ReactInstanceManager.this.toggleElementInspector(); - } - }; - - private final DefaultHardwareBackBtnHandler mBackBtnHandler = - new DefaultHardwareBackBtnHandler() { - @Override - public void invokeDefaultOnBackPressed() { - ReactInstanceManager.this.invokeDefaultOnBackPressed(); - } - }; - - private class ReactContextInitParams { - private final JavaScriptExecutor mJsExecutor; - private final JSBundleLoader mJsBundleLoader; - - public ReactContextInitParams( - JavaScriptExecutor jsExecutor, - JSBundleLoader jsBundleLoader) { - mJsExecutor = Assertions.assertNotNull(jsExecutor); - mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader); - } - - public JavaScriptExecutor getJsExecutor() { - return mJsExecutor; - } - - public JSBundleLoader getJsBundleLoader() { - return mJsBundleLoader; - } - } - - /* - * Task class responsible for (re)creating react context in the background. These tasks can only - * be executing one at time, see {@link #recreateReactContextInBackground()}. - */ - private final class ReactContextInitAsyncTask extends - AsyncTask { - - @Override - protected void onPreExecute() { - if (mCurrentReactContext != null) { - tearDownReactContext(mCurrentReactContext); - mCurrentReactContext = null; - } - } - - @Override - protected ReactApplicationContext doInBackground(ReactContextInitParams... params) { - Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); - return createReactContext(params[0].getJsExecutor(), params[0].getJsBundleLoader()); - } - - @Override - protected void onPostExecute(ReactApplicationContext reactContext) { - try { - setupReactContext(reactContext); - } finally { - mIsContextInitAsyncTaskRunning = false; - } - - // Handle enqueued request to re-initialize react context. - if (mPendingReactContextInitParams != null) { - recreateReactContextInBackground( - mPendingReactContextInitParams.getJsExecutor(), - mPendingReactContextInitParams.getJsBundleLoader()); - mPendingReactContextInitParams = null; - } - } - } - - private ReactInstanceManager( - Context applicationContext, - @Nullable String jsBundleFile, - @Nullable String jsMainModuleName, - List packages, - boolean useDeveloperSupport, - @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, - LifecycleState initialLifecycleState) { - initializeSoLoaderIfNecessary(applicationContext); - - mApplicationContext = applicationContext; - mJSBundleFile = jsBundleFile; - mJSMainModuleName = jsMainModuleName; - mPackages = packages; - mUseDeveloperSupport = useDeveloperSupport; - // We need to instantiate DevSupportManager regardless to the useDeveloperSupport option, - // although will prevent dev support manager from displaying any options or dialogs by - // checking useDeveloperSupport option before calling setDevSupportEnabled on this manager - // TODO(6803830): Don't instantiate devsupport manager when useDeveloperSupport is false - mDevSupportManager = new DevSupportManager( - applicationContext, - mDevInterface, - mJSMainModuleName, - useDeveloperSupport); - mBridgeIdleDebugListener = bridgeIdleDebugListener; - mLifecycleState = initialLifecycleState; - } - - public DevSupportManager getDevSupportManager() { - return mDevSupportManager; - } - - /** - * Creates a builder that is capable of creating an instance of {@link ReactInstanceManager}. - */ - public static Builder builder() { - return new Builder(); - } - - private static void initializeSoLoaderIfNecessary(Context applicationContext) { - // Call SoLoader.initialize here, this is required for apps that does not use exopackage and - // does not use SoLoader for loading other native code except from the one used by React Native - // This way we don't need to require others to have additional initialization code and to - // subclass android.app.Application. - - // Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call - // SoLoader.init with appropriate args before initializing ReactInstanceManager - SoLoader.init(applicationContext, /* native exopackage */ false); - } - - public void setJSBundleFile(String jsBundleFile) { - mJSBundleFile = jsBundleFile; - } +public abstract class ReactInstanceManager { + public abstract DevSupportManager getDevSupportManager(); /** * Trigger react context initialization asynchronously in a background async task. This enables @@ -243,129 +57,20 @@ public void setJSBundleFile(String jsBundleFile) { * * Called from UI thread. */ - public void createReactContextInBackground() { - Assertions.assertCondition( - !mHasStartedCreatingInitialContext, - "createReactContextInBackground should only be called when creating the react " + - "application for the first time. When reloading JS, e.g. from a new file, explicitly" + - "use recreateReactContextInBackground"); - - mHasStartedCreatingInitialContext = true; - recreateReactContextInBackgroundInner(); - } - - /** - * Recreate the react application and context. This should be called if configuration has - * changed or the developer has requested the app to be reloaded. It should only be called after - * an initial call to createReactContextInBackground. - * - * Called from UI thread. - */ - public void recreateReactContextInBackground() { - Assertions.assertCondition( - mHasStartedCreatingInitialContext, - "recreateReactContextInBackground should only be called after the initial " + - "createReactContextInBackground call."); - recreateReactContextInBackgroundInner(); - } - - private void recreateReactContextInBackgroundInner() { - UiThreadUtil.assertOnUiThread(); - - if (mUseDeveloperSupport && mJSMainModuleName != null) { - if (mDevSupportManager.hasUpToDateJSBundleInCache()) { - // If there is a up-to-date bundle downloaded from server, always use that - onJSBundleLoadedFromServer(); - } else if (mJSBundleFile == null) { - mDevSupportManager.handleReloadJS(); - } else { - mDevSupportManager.isPackagerRunning( - new DevServerHelper.PackagerStatusCallback() { - @Override - public void onPackagerStatusFetched(final boolean packagerIsRunning) { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - if (packagerIsRunning) { - mDevSupportManager.handleReloadJS(); - } else { - recreateReactContextInBackgroundFromBundleFile(); - } - } - }); - } - }); - } - return; - } - - recreateReactContextInBackgroundFromBundleFile(); - } - - private void recreateReactContextInBackgroundFromBundleFile() { - recreateReactContextInBackground( - new JSCJavaScriptExecutor(), - JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); - } + public abstract void createReactContextInBackground(); /** * @return whether createReactContextInBackground has been called. Will return false after * onDestroy until a new initial context has been created. */ - public boolean hasStartedCreatingInitialContext() { - return mHasStartedCreatingInitialContext; - } + public abstract boolean hasStartedCreatingInitialContext(); /** * This method will give JS the opportunity to consume the back button event. If JS does not * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS. */ - public void onBackPressed() { - UiThreadUtil.assertOnUiThread(); - ReactContext reactContext = mCurrentReactContext; - if (mCurrentReactContext == null) { - // Invoke without round trip to JS. - FLog.w(ReactConstants.TAG, "Instance detached from instance manager"); - invokeDefaultOnBackPressed(); - } else { - DeviceEventManagerModule deviceEventManagerModule = - Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class); - deviceEventManagerModule.emitHardwareBackPressed(); - } - } - - private void invokeDefaultOnBackPressed() { - UiThreadUtil.assertOnUiThread(); - if (mDefaultBackButtonImpl != null) { - mDefaultBackButtonImpl.invokeDefaultOnBackPressed(); - } - } - - private void toggleElementInspector() { - if (mCurrentReactContext != null) { - mCurrentReactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("toggleElementInspector", null); - } - } - - public void onPause() { - UiThreadUtil.assertOnUiThread(); - - mLifecycleState = LifecycleState.BEFORE_RESUME; - - mDefaultBackButtonImpl = null; - if (mUseDeveloperSupport) { - mDevSupportManager.setDevSupportEnabled(false); - } - - mCurrentActivity = null; - if (mCurrentReactContext != null) { - mCurrentReactContext.onPause(); - } - } - + public abstract void onBackPressed(); + public abstract void onPause(); /** * Use this method when the activity resumes to enable invoking the back button directly from JS. * @@ -377,53 +82,17 @@ public void onPause() { * @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns * this instance of {@link ReactInstanceManager}. */ - public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) { - UiThreadUtil.assertOnUiThread(); - - mLifecycleState = LifecycleState.RESUMED; - - mDefaultBackButtonImpl = defaultBackButtonImpl; - if (mUseDeveloperSupport) { - mDevSupportManager.setDevSupportEnabled(true); - } - - mCurrentActivity = activity; - if (mCurrentReactContext != null) { - mCurrentReactContext.onResume(activity); - } - } - - public void onDestroy() { - UiThreadUtil.assertOnUiThread(); - - if (mUseDeveloperSupport) { - mDevSupportManager.setDevSupportEnabled(false); - } - - if (mCurrentReactContext != null) { - mCurrentReactContext.onDestroy(); - mCurrentReactContext = null; - mHasStartedCreatingInitialContext = false; - } - } - - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mCurrentReactContext != null) { - mCurrentReactContext.onActivityResult(requestCode, resultCode, data); - } - } - - public void showDevOptionsDialog() { - UiThreadUtil.assertOnUiThread(); - mDevSupportManager.showDevOptionsDialog(); - } + public abstract void onResume( + Activity activity, + DefaultHardwareBackBtnHandler defaultBackButtonImpl); + public abstract void onDestroy(); + public abstract void onActivityResult(int requestCode, int resultCode, Intent data); + public abstract void showDevOptionsDialog(); /** * Get the URL where the last bundle was loaded from. */ - public String getSourceUrl() { - return Assertions.assertNotNull(mSourceUrl); - } + public abstract String getSourceUrl(); /** * Attach given {@param rootView} to a catalyst instance manager and start JS application using @@ -433,218 +102,46 @@ public String getSourceUrl() { * This view will then be tracked by this manager and in case of catalyst instance restart it will * be re-attached. */ - /* package */ void attachMeasuredRootView(ReactRootView rootView) { - UiThreadUtil.assertOnUiThread(); - mAttachedRootViews.add(rootView); - - // If react context is being created in the background, JS application will be started - // automatically when creation completes, as root view is part of the attached root view list. - if (!mIsContextInitAsyncTaskRunning && mCurrentReactContext != null) { - attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance()); - } - } + public abstract void attachMeasuredRootView(ReactRootView rootView); /** * Detach given {@param rootView} from current catalyst instance. It's safe to call this method * multiple times on the same {@param rootView} - in that case view will be detached with the * first call. */ - /* package */ void detachRootView(ReactRootView rootView) { - UiThreadUtil.assertOnUiThread(); - if (mAttachedRootViews.remove(rootView)) { - if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) { - detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance()); - } - } - } + public abstract void detachRootView(ReactRootView rootView); /** * Uses configured {@link ReactPackage} instances to create all view managers */ - /* package */ List createAllViewManagers( - ReactApplicationContext catalystApplicationContext) { - List allViewManagers = new ArrayList<>(); - for (ReactPackage reactPackage : mPackages) { - allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext)); - } - return allViewManagers; - } + public abstract List createAllViewManagers( + ReactApplicationContext catalystApplicationContext); @VisibleForTesting - public @Nullable ReactContext getCurrentReactContext() { - return mCurrentReactContext; - } - - private void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { - recreateReactContextInBackground( - new ProxyJavaScriptExecutor(jsExecutor), - JSBundleLoader.createRemoteDebuggerBundleLoader( - mDevSupportManager.getJSBundleURLForRemoteDebugging())); - } - - private void onJSBundleLoadedFromServer() { - recreateReactContextInBackground( - new JSCJavaScriptExecutor(), - JSBundleLoader.createCachedBundleFromNetworkLoader( - mDevSupportManager.getSourceUrl(), - mDevSupportManager.getDownloadedJSBundleFile())); - } - - private void recreateReactContextInBackground( - JavaScriptExecutor jsExecutor, - JSBundleLoader jsBundleLoader) { - UiThreadUtil.assertOnUiThread(); - - ReactContextInitParams initParams = new ReactContextInitParams(jsExecutor, jsBundleLoader); - if (!mIsContextInitAsyncTaskRunning) { - // No background task to create react context is currently running, create and execute one. - ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask(); - initTask.execute(initParams); - mIsContextInitAsyncTaskRunning = true; - } else { - // Background task is currently running, queue up most recent init params to recreate context - // once task completes. - mPendingReactContextInitParams = initParams; - } - } - - private void setupReactContext(ReactApplicationContext reactContext) { - UiThreadUtil.assertOnUiThread(); - Assertions.assertCondition(mCurrentReactContext == null); - mCurrentReactContext = Assertions.assertNotNull(reactContext); - CatalystInstance catalystInstance = - Assertions.assertNotNull(reactContext.getCatalystInstance()); - - catalystInstance.initialize(); - mDevSupportManager.onNewReactContextCreated(reactContext); - moveReactContextToCurrentLifecycleState(reactContext); - - for (ReactRootView rootView : mAttachedRootViews) { - attachMeasuredRootViewToInstance(rootView, catalystInstance); - } - } - - private void attachMeasuredRootViewToInstance( - ReactRootView rootView, - CatalystInstance catalystInstance) { - UiThreadUtil.assertOnUiThread(); - - // Reset view content as it's going to be populated by the application content from JS - rootView.removeAllViews(); - rootView.setId(View.NO_ID); - - UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class); - int rootTag = uiManagerModule.addMeasuredRootView(rootView); - @Nullable Bundle launchOptions = rootView.getLaunchOptions(); - WritableMap initialProps = launchOptions != null - ? Arguments.fromBundle(launchOptions) - : Arguments.createMap(); - String jsAppModuleName = rootView.getJSModuleName(); - - WritableNativeMap appParams = new WritableNativeMap(); - appParams.putDouble("rootTag", rootTag); - appParams.putMap("initialProps", initialProps); - catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); - } - - private void detachViewFromInstance( - ReactRootView rootView, - CatalystInstance catalystInstance) { - UiThreadUtil.assertOnUiThread(); - catalystInstance.getJSModule(ReactNative.class) - .unmountComponentAtNodeAndRemoveContainer(rootView.getId()); - } - - private void tearDownReactContext(ReactContext reactContext) { - UiThreadUtil.assertOnUiThread(); - if (mLifecycleState == LifecycleState.RESUMED) { - reactContext.onPause(); - } - for (ReactRootView rootView : mAttachedRootViews) { - detachViewFromInstance(rootView, reactContext.getCatalystInstance()); - } - reactContext.onDestroy(); - mDevSupportManager.onReactInstanceDestroyed(reactContext); - } + public abstract @Nullable ReactContext getCurrentReactContext(); /** - * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set + * Creates a builder that is capable of creating an instance of {@link ReactInstanceManagerImpl}. */ - private ReactApplicationContext createReactContext( - JavaScriptExecutor jsExecutor, - JSBundleLoader jsBundleLoader) { - FLog.i(ReactConstants.TAG, "Creating react context."); - mSourceUrl = jsBundleLoader.getSourceUrl(); - NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); - JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); - - ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); - if (mUseDeveloperSupport) { - reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); - } - - CoreModulesPackage coreModulesPackage = - new CoreModulesPackage(this, mBackBtnHandler); - processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); - - // TODO(6818138): Solve use-case of native/js modules overriding - for (ReactPackage reactPackage : mPackages) { - processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); - } - - CatalystInstance.Builder catalystInstanceBuilder = new CatalystInstance.Builder() - .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) - .setJSExecutor(jsExecutor) - .setRegistry(nativeRegistryBuilder.build()) - .setJSModulesConfig(jsModulesBuilder.build()) - .setJSBundleLoader(jsBundleLoader) - .setNativeModuleCallExceptionHandler(mDevSupportManager); - - CatalystInstance catalystInstance = catalystInstanceBuilder.build(); - if (mBridgeIdleDebugListener != null) { - catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); - } - - reactContext.initializeWithInstance(catalystInstance); - catalystInstance.runJSBundle(); - - return reactContext; - } - - private void processPackage( - ReactPackage reactPackage, - ReactApplicationContext reactContext, - NativeModuleRegistry.Builder nativeRegistryBuilder, - JavaScriptModulesConfig.Builder jsModulesBuilder) { - for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) { - nativeRegistryBuilder.add(nativeModule); - } - for (Class jsModuleClass : reactPackage.createJSModules()) { - jsModulesBuilder.add(jsModuleClass); - } - } - - private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) { - if (mLifecycleState == LifecycleState.RESUMED) { - reactContext.onResume(mCurrentActivity); - } + public static Builder builder() { + return new Builder(); } /** - * Builder class for {@link ReactInstanceManager} + * Builder class for {@link ReactInstanceManagerImpl} */ public static class Builder { - private final List mPackages = new ArrayList<>(); + protected final List mPackages = new ArrayList<>(); - private @Nullable String mJSBundleFile; - private @Nullable String mJSMainModuleName; - private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; - private @Nullable Application mApplication; - private boolean mUseDeveloperSupport; - private @Nullable LifecycleState mInitialLifecycleState; + protected @Nullable String mJSBundleFile; + protected @Nullable String mJSMainModuleName; + protected @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; + protected @Nullable Application mApplication; + protected boolean mUseDeveloperSupport; + protected @Nullable LifecycleState mInitialLifecycleState; - private Builder() { + protected Builder() { } /** @@ -718,7 +215,7 @@ public Builder setInitialLifecycleState(LifecycleState initialLifecycleState) { } /** - * Instantiates a new {@link ReactInstanceManager}. + * Instantiates a new {@link ReactInstanceManagerImpl}. * Before calling {@code build}, the following must be called: *
    *
  • {@link #setApplication} @@ -734,7 +231,7 @@ public ReactInstanceManager build() { mJSMainModuleName != null || mJSBundleFile != null, "Either MainModuleName or JS Bundle File needs to be provided"); - return new ReactInstanceManager( + return new ReactInstanceManagerImpl( Assertions.assertNotNull( mApplication, "Application property has not been set with this builder"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java new file mode 100644 index 000000000000..f8e0cdc0448a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -0,0 +1,635 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react; + +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.View; + +import com.facebook.common.logging.FLog; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.CatalystInstanceImpl; +import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.JSCJavaScriptExecutor; +import com.facebook.react.bridge.JavaJSExecutor; +import com.facebook.react.bridge.JavaScriptExecutor; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.JavaScriptModulesConfig; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.NativeModuleRegistry; +import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; +import com.facebook.react.bridge.ProxyJavaScriptExecutor; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.devsupport.DevServerHelper; +import com.facebook.react.devsupport.DevSupportManager; +import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.uimanager.AppRegistry; +import com.facebook.react.uimanager.ReactNative; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.soloader.SoLoader; + +/** + * This class is managing instances of {@link CatalystInstance}. It expose a way to configure + * catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that + * instance. It also sets up connection between the instance and developers support functionality + * of the framework. + * + * An instance of this manager is required to start JS application in {@link ReactRootView} (see + * {@link ReactRootView#startReactApplication} for more info). + * + * The lifecycle of the instance of {@link ReactInstanceManagerImpl} should be bound to the activity + * that owns the {@link ReactRootView} that is used to render react application using this + * instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass + * owning activity's lifecycle events to the instance manager (see {@link #onPause}, + * {@link #onDestroy} and {@link #onResume}). + * + * To instantiate an instance of this class use {@link #builder}. + */ +/* package */ class ReactInstanceManagerImpl extends ReactInstanceManager { + + /* should only be accessed from main thread (UI thread) */ + private final List mAttachedRootViews = new ArrayList<>(); + private LifecycleState mLifecycleState; + private boolean mIsContextInitAsyncTaskRunning; + private @Nullable ReactContextInitParams mPendingReactContextInitParams; + + /* accessed from any thread */ + private @Nullable String mJSBundleFile; /* path to JS bundle on file system */ + private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */ + private final List mPackages; + private final DevSupportManager mDevSupportManager; + private final boolean mUseDeveloperSupport; + private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; + private @Nullable volatile ReactContext mCurrentReactContext; + private final Context mApplicationContext; + private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl; + private String mSourceUrl; + private @Nullable Activity mCurrentActivity; + private volatile boolean mHasStartedCreatingInitialContext = false; + + private final ReactInstanceDevCommandsHandler mDevInterface = + new ReactInstanceDevCommandsHandler() { + + @Override + public void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { + ReactInstanceManagerImpl.this.onReloadWithJSDebugger(jsExecutor); + } + + @Override + public void onJSBundleLoadedFromServer() { + ReactInstanceManagerImpl.this.onJSBundleLoadedFromServer(); + } + + @Override + public void toggleElementInspector() { + ReactInstanceManagerImpl.this.toggleElementInspector(); + } + }; + + private final DefaultHardwareBackBtnHandler mBackBtnHandler = + new DefaultHardwareBackBtnHandler() { + @Override + public void invokeDefaultOnBackPressed() { + ReactInstanceManagerImpl.this.invokeDefaultOnBackPressed(); + } + }; + + private class ReactContextInitParams { + private final JavaScriptExecutor mJsExecutor; + private final JSBundleLoader mJsBundleLoader; + + public ReactContextInitParams( + JavaScriptExecutor jsExecutor, + JSBundleLoader jsBundleLoader) { + mJsExecutor = Assertions.assertNotNull(jsExecutor); + mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader); + } + + public JavaScriptExecutor getJsExecutor() { + return mJsExecutor; + } + + public JSBundleLoader getJsBundleLoader() { + return mJsBundleLoader; + } + } + + /* + * Task class responsible for (re)creating react context in the background. These tasks can only + * be executing one at time, see {@link #recreateReactContextInBackground()}. + */ + private final class ReactContextInitAsyncTask extends + AsyncTask { + + @Override + protected void onPreExecute() { + if (mCurrentReactContext != null) { + tearDownReactContext(mCurrentReactContext); + mCurrentReactContext = null; + } + } + + @Override + protected ReactApplicationContext doInBackground(ReactContextInitParams... params) { + Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); + return createReactContext(params[0].getJsExecutor(), params[0].getJsBundleLoader()); + } + + @Override + protected void onPostExecute(ReactApplicationContext reactContext) { + try { + setupReactContext(reactContext); + } finally { + mIsContextInitAsyncTaskRunning = false; + } + + // Handle enqueued request to re-initialize react context. + if (mPendingReactContextInitParams != null) { + recreateReactContextInBackground( + mPendingReactContextInitParams.getJsExecutor(), + mPendingReactContextInitParams.getJsBundleLoader()); + mPendingReactContextInitParams = null; + } + } + } + + /* package */ ReactInstanceManagerImpl( + Context applicationContext, + @Nullable String jsBundleFile, + @Nullable String jsMainModuleName, + List packages, + boolean useDeveloperSupport, + @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, + LifecycleState initialLifecycleState) { + initializeSoLoaderIfNecessary(applicationContext); + + mApplicationContext = applicationContext; + mJSBundleFile = jsBundleFile; + mJSMainModuleName = jsMainModuleName; + mPackages = packages; + mUseDeveloperSupport = useDeveloperSupport; + // We need to instantiate DevSupportManager regardless to the useDeveloperSupport option, + // although will prevent dev support manager from displaying any options or dialogs by + // checking useDeveloperSupport option before calling setDevSupportEnabled on this manager + // TODO(6803830): Don't instantiate devsupport manager when useDeveloperSupport is false + mDevSupportManager = new DevSupportManager( + applicationContext, + mDevInterface, + mJSMainModuleName, + useDeveloperSupport); + mBridgeIdleDebugListener = bridgeIdleDebugListener; + mLifecycleState = initialLifecycleState; + } + + @Override + public DevSupportManager getDevSupportManager() { + return mDevSupportManager; + } + + private static void initializeSoLoaderIfNecessary(Context applicationContext) { + // Call SoLoader.initialize here, this is required for apps that does not use exopackage and + // does not use SoLoader for loading other native code except from the one used by React Native + // This way we don't need to require others to have additional initialization code and to + // subclass android.app.Application. + + // Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call + // SoLoader.init with appropriate args before initializing ReactInstanceManagerImpl + SoLoader.init(applicationContext, /* native exopackage */ false); + } + + /** + * Trigger react context initialization asynchronously in a background async task. This enables + * applications to pre-load the application JS, and execute global code before + * {@link ReactRootView} is available and measured. This should only be called the first time the + * application is set up, which is enforced to keep developers from accidentally creating their + * application multiple times without realizing it. + * + * Called from UI thread. + */ + @Override + public void createReactContextInBackground() { + Assertions.assertCondition( + !mHasStartedCreatingInitialContext, + "createReactContextInBackground should only be called when creating the react " + + "application for the first time. When reloading JS, e.g. from a new file, explicitly" + + "use recreateReactContextInBackground"); + + mHasStartedCreatingInitialContext = true; + recreateReactContextInBackgroundInner(); + } + + /** + * Recreate the react application and context. This should be called if configuration has + * changed or the developer has requested the app to be reloaded. It should only be called after + * an initial call to createReactContextInBackground. + * + * Called from UI thread. + */ + public void recreateReactContextInBackground() { + Assertions.assertCondition( + mHasStartedCreatingInitialContext, + "recreateReactContextInBackground should only be called after the initial " + + "createReactContextInBackground call."); + recreateReactContextInBackgroundInner(); + } + + private void recreateReactContextInBackgroundInner() { + UiThreadUtil.assertOnUiThread(); + + if (mUseDeveloperSupport && mJSMainModuleName != null) { + if (mDevSupportManager.hasUpToDateJSBundleInCache()) { + // If there is a up-to-date bundle downloaded from server, always use that + onJSBundleLoadedFromServer(); + } else if (mJSBundleFile == null) { + mDevSupportManager.handleReloadJS(); + } else { + mDevSupportManager.isPackagerRunning( + new DevServerHelper.PackagerStatusCallback() { + @Override + public void onPackagerStatusFetched(final boolean packagerIsRunning) { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (packagerIsRunning) { + mDevSupportManager.handleReloadJS(); + } else { + recreateReactContextInBackgroundFromBundleFile(); + } + } + }); + } + }); + } + return; + } + + recreateReactContextInBackgroundFromBundleFile(); + } + + private void recreateReactContextInBackgroundFromBundleFile() { + recreateReactContextInBackground( + new JSCJavaScriptExecutor(), + JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); + } + + /** + * @return whether createReactContextInBackground has been called. Will return false after + * onDestroy until a new initial context has been created. + */ + public boolean hasStartedCreatingInitialContext() { + return mHasStartedCreatingInitialContext; + } + + /** + * This method will give JS the opportunity to consume the back button event. If JS does not + * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS. + */ + @Override + public void onBackPressed() { + UiThreadUtil.assertOnUiThread(); + ReactContext reactContext = mCurrentReactContext; + if (mCurrentReactContext == null) { + // Invoke without round trip to JS. + FLog.w(ReactConstants.TAG, "Instance detached from instance manager"); + invokeDefaultOnBackPressed(); + } else { + DeviceEventManagerModule deviceEventManagerModule = + Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class); + deviceEventManagerModule.emitHardwareBackPressed(); + } + } + + private void invokeDefaultOnBackPressed() { + UiThreadUtil.assertOnUiThread(); + if (mDefaultBackButtonImpl != null) { + mDefaultBackButtonImpl.invokeDefaultOnBackPressed(); + } + } + + private void toggleElementInspector() { + if (mCurrentReactContext != null) { + mCurrentReactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("toggleElementInspector", null); + } + } + + @Override + public void onPause() { + UiThreadUtil.assertOnUiThread(); + + mLifecycleState = LifecycleState.BEFORE_RESUME; + + mDefaultBackButtonImpl = null; + if (mUseDeveloperSupport) { + mDevSupportManager.setDevSupportEnabled(false); + } + + mCurrentActivity = null; + if (mCurrentReactContext != null) { + mCurrentReactContext.onPause(); + } + } + + /** + * Use this method when the activity resumes to enable invoking the back button directly from JS. + * + * This method retains an instance to provided mDefaultBackButtonImpl. Thus it's + * important to pass from the activity instance that owns this particular instance of {@link + * ReactInstanceManagerImpl}, so that once this instance receive {@link #onDestroy} event it will + * clear the reference to that defaultBackButtonImpl. + * + * @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns + * this instance of {@link ReactInstanceManagerImpl}. + */ + @Override + public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) { + UiThreadUtil.assertOnUiThread(); + + mLifecycleState = LifecycleState.RESUMED; + + mDefaultBackButtonImpl = defaultBackButtonImpl; + if (mUseDeveloperSupport) { + mDevSupportManager.setDevSupportEnabled(true); + } + + mCurrentActivity = activity; + if (mCurrentReactContext != null) { + mCurrentReactContext.onResume(activity); + } + } + + @Override + public void onDestroy() { + UiThreadUtil.assertOnUiThread(); + + if (mUseDeveloperSupport) { + mDevSupportManager.setDevSupportEnabled(false); + } + + if (mCurrentReactContext != null) { + mCurrentReactContext.onDestroy(); + mCurrentReactContext = null; + mHasStartedCreatingInitialContext = false; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mCurrentReactContext != null) { + mCurrentReactContext.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void showDevOptionsDialog() { + UiThreadUtil.assertOnUiThread(); + mDevSupportManager.showDevOptionsDialog(); + } + + /** + * Get the URL where the last bundle was loaded from. + */ + @Override + public String getSourceUrl() { + return Assertions.assertNotNull(mSourceUrl); + } + + /** + * Attach given {@param rootView} to a catalyst instance manager and start JS application using + * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently + * being (re)-created, or if react context has not been created yet, the JS application associated + * with the provided root view will be started asynchronously, i.e this method won't block. + * This view will then be tracked by this manager and in case of catalyst instance restart it will + * be re-attached. + */ + @Override + public void attachMeasuredRootView(ReactRootView rootView) { + UiThreadUtil.assertOnUiThread(); + mAttachedRootViews.add(rootView); + + // If react context is being created in the background, JS application will be started + // automatically when creation completes, as root view is part of the attached root view list. + if (!mIsContextInitAsyncTaskRunning && mCurrentReactContext != null) { + attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance()); + } + } + + /** + * Detach given {@param rootView} from current catalyst instance. It's safe to call this method + * multiple times on the same {@param rootView} - in that case view will be detached with the + * first call. + */ + @Override + public void detachRootView(ReactRootView rootView) { + UiThreadUtil.assertOnUiThread(); + if (mAttachedRootViews.remove(rootView)) { + if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) { + detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance()); + } + } + } + + /** + * Uses configured {@link ReactPackage} instances to create all view managers + */ + @Override + public List createAllViewManagers( + ReactApplicationContext catalystApplicationContext) { + List allViewManagers = new ArrayList<>(); + for (ReactPackage reactPackage : mPackages) { + allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext)); + } + return allViewManagers; + } + + @VisibleForTesting + @Override + public @Nullable ReactContext getCurrentReactContext() { + return mCurrentReactContext; + } + + private void onReloadWithJSDebugger(JavaJSExecutor jsExecutor) { + recreateReactContextInBackground( + new ProxyJavaScriptExecutor(jsExecutor), + JSBundleLoader.createRemoteDebuggerBundleLoader( + mDevSupportManager.getJSBundleURLForRemoteDebugging())); + } + + private void onJSBundleLoadedFromServer() { + recreateReactContextInBackground( + new JSCJavaScriptExecutor(), + JSBundleLoader.createCachedBundleFromNetworkLoader( + mDevSupportManager.getSourceUrl(), + mDevSupportManager.getDownloadedJSBundleFile())); + } + + private void recreateReactContextInBackground( + JavaScriptExecutor jsExecutor, + JSBundleLoader jsBundleLoader) { + UiThreadUtil.assertOnUiThread(); + + ReactContextInitParams initParams = new ReactContextInitParams(jsExecutor, jsBundleLoader); + if (!mIsContextInitAsyncTaskRunning) { + // No background task to create react context is currently running, create and execute one. + ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask(); + initTask.execute(initParams); + mIsContextInitAsyncTaskRunning = true; + } else { + // Background task is currently running, queue up most recent init params to recreate context + // once task completes. + mPendingReactContextInitParams = initParams; + } + } + + private void setupReactContext(ReactApplicationContext reactContext) { + UiThreadUtil.assertOnUiThread(); + Assertions.assertCondition(mCurrentReactContext == null); + mCurrentReactContext = Assertions.assertNotNull(reactContext); + CatalystInstance catalystInstance = + Assertions.assertNotNull(reactContext.getCatalystInstance()); + + catalystInstance.initialize(); + mDevSupportManager.onNewReactContextCreated(reactContext); + moveReactContextToCurrentLifecycleState(reactContext); + + for (ReactRootView rootView : mAttachedRootViews) { + attachMeasuredRootViewToInstance(rootView, catalystInstance); + } + } + + private void attachMeasuredRootViewToInstance( + ReactRootView rootView, + CatalystInstance catalystInstance) { + UiThreadUtil.assertOnUiThread(); + + // Reset view content as it's going to be populated by the application content from JS + rootView.removeAllViews(); + rootView.setId(View.NO_ID); + + UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class); + int rootTag = uiManagerModule.addMeasuredRootView(rootView); + @Nullable Bundle launchOptions = rootView.getLaunchOptions(); + WritableMap initialProps = launchOptions != null + ? Arguments.fromBundle(launchOptions) + : Arguments.createMap(); + String jsAppModuleName = rootView.getJSModuleName(); + + WritableNativeMap appParams = new WritableNativeMap(); + appParams.putDouble("rootTag", rootTag); + appParams.putMap("initialProps", initialProps); + catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); + } + + private void detachViewFromInstance( + ReactRootView rootView, + CatalystInstance catalystInstance) { + UiThreadUtil.assertOnUiThread(); + catalystInstance.getJSModule(ReactNative.class) + .unmountComponentAtNodeAndRemoveContainer(rootView.getId()); + } + + private void tearDownReactContext(ReactContext reactContext) { + UiThreadUtil.assertOnUiThread(); + if (mLifecycleState == LifecycleState.RESUMED) { + reactContext.onPause(); + } + for (ReactRootView rootView : mAttachedRootViews) { + detachViewFromInstance(rootView, reactContext.getCatalystInstance()); + } + reactContext.onDestroy(); + mDevSupportManager.onReactInstanceDestroyed(reactContext); + } + + /** + * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set + */ + private ReactApplicationContext createReactContext( + JavaScriptExecutor jsExecutor, + JSBundleLoader jsBundleLoader) { + FLog.i(ReactConstants.TAG, "Creating react context."); + mSourceUrl = jsBundleLoader.getSourceUrl(); + NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); + JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); + + ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); + if (mUseDeveloperSupport) { + reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); + } + + CoreModulesPackage coreModulesPackage = + new CoreModulesPackage(this, mBackBtnHandler); + processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + + // TODO(6818138): Solve use-case of native/js modules overriding + for (ReactPackage reactPackage : mPackages) { + processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + } + + CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() + .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) + .setJSExecutor(jsExecutor) + .setRegistry(nativeRegistryBuilder.build()) + .setJSModulesConfig(jsModulesBuilder.build()) + .setJSBundleLoader(jsBundleLoader) + .setNativeModuleCallExceptionHandler(mDevSupportManager); + + CatalystInstance catalystInstance = catalystInstanceBuilder.build(); + if (mBridgeIdleDebugListener != null) { + catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); + } + + reactContext.initializeWithInstance(catalystInstance); + catalystInstance.runJSBundle(); + + return reactContext; + } + + private void processPackage( + ReactPackage reactPackage, + ReactApplicationContext reactContext, + NativeModuleRegistry.Builder nativeRegistryBuilder, + JavaScriptModulesConfig.Builder jsModulesBuilder) { + for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) { + nativeRegistryBuilder.add(nativeModule); + } + for (Class jsModuleClass : reactPackage.createJSModules()) { + jsModulesBuilder.add(jsModuleClass); + } + } + + private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) { + if (mLifecycleState == LifecycleState.RESUMED) { + reactContext.onResume(mCurrentActivity); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index ee913802237c..7e5afc3a47ff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -11,27 +11,11 @@ import javax.annotation.Nullable; -import java.io.IOException; -import java.io.StringWriter; import java.util.Collection; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import com.facebook.common.logging.FLog; import com.facebook.react.bridge.queue.CatalystQueueConfiguration; -import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; -import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; -import com.facebook.infer.annotation.Assertions; -import com.facebook.systrace.Systrace; -import com.facebook.systrace.TraceListener; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; /** * A higher level API on top of the asynchronous JSC bridge. This provides an @@ -39,254 +23,31 @@ * Java APIs be invokable from JavaScript as well. */ @DoNotStrip -public class CatalystInstance { - - private static final int BRIDGE_SETUP_TIMEOUT_MS = 30000; - private static final int LOAD_JS_BUNDLE_TIMEOUT_MS = 30000; - - private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1); - - // Access from any thread - private final CatalystQueueConfiguration mCatalystQueueConfiguration; - private final CopyOnWriteArrayList mBridgeIdleListeners; - private final AtomicInteger mPendingJSCalls = new AtomicInteger(0); - private final String mJsPendingCallsTitleForTrace = - "pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement(); - private volatile boolean mDestroyed = false; - private final TraceListener mTraceListener; - private final JavaScriptModuleRegistry mJSModuleRegistry; - private final JSBundleLoader mJSBundleLoader; - - // Access from native modules thread - private final NativeModuleRegistry mJavaRegistry; - private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; - private boolean mInitialized = false; - - // Access from JS thread - private @Nullable ReactBridge mBridge; - private boolean mJSBundleHasLoaded; - - private CatalystInstance( - final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec, - final JavaScriptExecutor jsExecutor, - final NativeModuleRegistry registry, - final JavaScriptModulesConfig jsModulesConfig, - final JSBundleLoader jsBundleLoader, - NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { - mCatalystQueueConfiguration = CatalystQueueConfiguration.create( - catalystQueueConfigurationSpec, - new NativeExceptionHandler()); - mBridgeIdleListeners = new CopyOnWriteArrayList<>(); - mJavaRegistry = registry; - mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig); - mJSBundleLoader = jsBundleLoader; - mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; - mTraceListener = new JSProfilerTraceListener(); - - final CountDownLatch initLatch = new CountDownLatch(1); - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - initializeBridge(jsExecutor, jsModulesConfig); - initLatch.countDown(); - } - }); - - try { - Assertions.assertCondition( - initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS), - "Timed out waiting for bridge to initialize!"); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - private void initializeBridge( - JavaScriptExecutor jsExecutor, - JavaScriptModulesConfig jsModulesConfig) { - mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); - Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); - - mBridge = new ReactBridge( - jsExecutor, - new NativeModulesReactCallback(), - mCatalystQueueConfiguration.getNativeModulesQueueThread()); - mBridge.setGlobalVariable( - "__fbBatchedBridgeConfig", - buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); - - Systrace.registerListener(mTraceListener); - } - - public void runJSBundle() { - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "CatalystInstance_runJSBundle"); - - try { - final CountDownLatch initLatch = new CountDownLatch(1); - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); - mJSBundleHasLoaded = true; - - incrementPendingJSCalls(); - - try { - mJSBundleLoader.loadScript(mBridge); - } catch (JSExecutionException e) { - mNativeModuleCallExceptionHandler.handleException(e); - } - - initLatch.countDown(); - } - }); - Assertions.assertCondition( - initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS), - "Timed out loading JS!"); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - /* package */ void callFunction( - final int moduleId, - final int methodId, - final NativeArray arguments, - final String tracingName) { - if (mDestroyed) { - FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed."); - return; - } - - incrementPendingJSCalls(); - - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); - - if (mDestroyed) { - return; - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName); - try { - Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }); - } - +public interface CatalystInstance { + void runJSBundle(); // This is called from java code, so it won't be stripped anyway, but proguard will rename it, // which this prevents. @DoNotStrip - /* package */ void invokeCallback(final int callbackID, final NativeArray arguments) { - if (mDestroyed) { - FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed."); - return; - } - - incrementPendingJSCalls(); - - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); - - if (mDestroyed) { - return; - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, ""); - try { - Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }); - } - + void invokeCallback(final int callbackID, final NativeArray arguments); /** * Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration * (besides the UI thread) to finish running. Must be called from the UI thread so that we can * fully shut down other threads. */ - /* package */ void destroy() { - UiThreadUtil.assertOnUiThread(); - - if (mDestroyed) { - return; - } - - // TODO: tell all APIs to shut down - mDestroyed = true; - mJavaRegistry.notifyCatalystInstanceDestroy(); - mCatalystQueueConfiguration.destroy(); - boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); - if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { - for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { - listener.onTransitionToBridgeIdle(); - } - } - - if (mBridge != null) { - Systrace.unregisterListener(mTraceListener); - } - - // We can access the Bridge from any thread now because we know either we are on the JS thread - // or the JS thread has finished via CatalystQueueConfiguration#destroy() - Assertions.assertNotNull(mBridge).dispose(); - } - - public boolean isDestroyed() { - return mDestroyed; - } + void destroy(); + boolean isDestroyed(); /** * Initialize all the native modules */ @VisibleForTesting - public void initialize() { - UiThreadUtil.assertOnUiThread(); - Assertions.assertCondition( - !mInitialized, - "This catalyst instance has already been initialized"); - mInitialized = true; - mJavaRegistry.notifyCatalystInstanceInitialized(); - } - - public CatalystQueueConfiguration getCatalystQueueConfiguration() { - return mCatalystQueueConfiguration; - } - - @VisibleForTesting - public @Nullable - ReactBridge getBridge() { - return mBridge; - } + void initialize(); - public T getJSModule(Class jsInterface) { - return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface); - } + CatalystQueueConfiguration getCatalystQueueConfiguration(); - public T getNativeModule(Class nativeModuleInterface) { - return mJavaRegistry.getModule(nativeModuleInterface); - } - - public Collection getNativeModules() { - return mJavaRegistry.getAllModules(); - } + T getJSModule(Class jsInterface); + T getNativeModule(Class nativeModuleInterface); + Collection getNativeModules(); /** * Adds a idle listener for this Catalyst instance. The listener will receive notifications @@ -294,204 +55,15 @@ public Collection getNativeModules() { * defined as there being some non-zero number of calls to JS that haven't resolved via a * onBatchCompleted call. The listener should be purely passive and not affect application logic. */ - public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) { - mBridgeIdleListeners.add(listener); - } + void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener); /** * Removes a NotThreadSafeBridgeIdleDebugListener previously added with * {@link #addBridgeIdleDebugListener} */ - public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) { - mBridgeIdleListeners.remove(listener); - } - - public boolean supportsProfiling() { - if (mBridge == null) { - return false; - } - return mBridge.supportsProfiling(); - } - - public void startProfiler(String title) { - if (mBridge == null) { - return; - } - mBridge.startProfiler(title); - } - - public void stopProfiler(String title, String filename) { - if (mBridge == null) { - return; - } - mBridge.stopProfiler(title, filename); - } - - private String buildModulesConfigJSONProperty( - NativeModuleRegistry nativeModuleRegistry, - JavaScriptModulesConfig jsModulesConfig) { - // TODO(5300733): Serialize config using single json generator - JsonFactory jsonFactory = new JsonFactory(); - StringWriter writer = new StringWriter(); - try { - JsonGenerator jg = jsonFactory.createGenerator(writer); - jg.writeStartObject(); - jg.writeFieldName("remoteModuleConfig"); - jg.writeRawValue(nativeModuleRegistry.moduleDescriptions()); - jg.writeFieldName("localModulesConfig"); - jg.writeRawValue(jsModulesConfig.moduleDescriptions()); - jg.writeEndObject(); - jg.close(); - } catch (IOException ioe) { - throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe); - } - return writer.getBuffer().toString(); - } - - private void incrementPendingJSCalls() { - int oldPendingCalls = mPendingJSCalls.getAndIncrement(); - boolean wasIdle = oldPendingCalls == 0; - Systrace.traceCounter( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - mJsPendingCallsTitleForTrace, - oldPendingCalls + 1); - if (wasIdle && !mBridgeIdleListeners.isEmpty()) { - for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { - listener.onTransitionToBridgeBusy(); - } - } - } - - private void decrementPendingJSCalls() { - int newPendingCalls = mPendingJSCalls.decrementAndGet(); - Assertions.assertCondition(newPendingCalls >= 0); - boolean isNowIdle = newPendingCalls == 0; - Systrace.traceCounter( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - mJsPendingCallsTitleForTrace, - newPendingCalls); - - if (isNowIdle && !mBridgeIdleListeners.isEmpty()) { - for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { - listener.onTransitionToBridgeIdle(); - } - } - } - - private class NativeModulesReactCallback implements ReactCallback { - - @Override - public void call(int moduleId, int methodId, ReadableNativeArray parameters) { - mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); - - // Suppress any callbacks if destroyed - will only lead to sadness. - if (mDestroyed) { - return; - } - - mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters); - } - - @Override - public void onBatchComplete() { - mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); - - // The bridge may have been destroyed due to an exception during the batch. In that case - // native modules could be in a bad state so we don't want to call anything on them. We - // still want to trigger the debug listener since it allows instrumentation tests to end and - // check their assertions without waiting for a timeout. - if (!mDestroyed) { - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete"); - try { - mJavaRegistry.onBatchComplete(); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - decrementPendingJSCalls(); - } - } - - private class NativeExceptionHandler implements QueueThreadExceptionHandler { - - @Override - public void handleException(Exception e) { - // Any Exception caught here is because of something in JS. Even if it's a bug in the - // framework/native code, it was triggered by JS and theoretically since we were able - // to set up the bridge, JS could change its logic, reload, and not trigger that crash. - mNativeModuleCallExceptionHandler.handleException(e); - mCatalystQueueConfiguration.getUIQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - destroy(); - } - }); - } - } - - private class JSProfilerTraceListener implements TraceListener { - @Override - public void onTraceStarted() { - getJSModule(BridgeProfiling.class).setEnabled(true); - } - - @Override - public void onTraceStopped() { - getJSModule(BridgeProfiling.class).setEnabled(false); - } - } - - public static class Builder { - - private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec; - private @Nullable JSBundleLoader mJSBundleLoader; - private @Nullable NativeModuleRegistry mRegistry; - private @Nullable JavaScriptModulesConfig mJSModulesConfig; - private @Nullable JavaScriptExecutor mJSExecutor; - private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; - - public Builder setCatalystQueueConfigurationSpec( - CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) { - mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec; - return this; - } - - public Builder setRegistry(NativeModuleRegistry registry) { - mRegistry = registry; - return this; - } - - public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) { - mJSModulesConfig = jsModulesConfig; - return this; - } - - public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) { - mJSBundleLoader = jsBundleLoader; - return this; - } - - public Builder setJSExecutor(JavaScriptExecutor jsExecutor) { - mJSExecutor = jsExecutor; - return this; - } - - public Builder setNativeModuleCallExceptionHandler( - NativeModuleCallExceptionHandler handler) { - mNativeModuleCallExceptionHandler = handler; - return this; - } + void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener); - public CatalystInstance build() { - return new CatalystInstance( - Assertions.assertNotNull(mCatalystQueueConfigurationSpec), - Assertions.assertNotNull(mJSExecutor), - Assertions.assertNotNull(mRegistry), - Assertions.assertNotNull(mJSModulesConfig), - Assertions.assertNotNull(mJSBundleLoader), - Assertions.assertNotNull(mNativeModuleCallExceptionHandler)); - } - } + boolean supportsProfiling(); + void startProfiler(String title); + void stopProfiler(String title, String filename); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java new file mode 100644 index 000000000000..4b987e78d256 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -0,0 +1,510 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +import javax.annotation.Nullable; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.queue.CatalystQueueConfiguration; +import com.facebook.react.bridge.queue.CatalystQueueConfigurationImpl; +import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; +import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.infer.annotation.Assertions; +import com.facebook.systrace.Systrace; +import com.facebook.systrace.TraceListener; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +/** + * This provides an implementation of the public CatalystInstance instance. It is public because + * it is built by ReactInstanceManager which is in a different package. + */ +@DoNotStrip +public class CatalystInstanceImpl implements CatalystInstance { + + private static final int BRIDGE_SETUP_TIMEOUT_MS = 30000; + private static final int LOAD_JS_BUNDLE_TIMEOUT_MS = 30000; + + private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1); + + // Access from any thread + private final CatalystQueueConfigurationImpl mCatalystQueueConfiguration; + private final CopyOnWriteArrayList mBridgeIdleListeners; + private final AtomicInteger mPendingJSCalls = new AtomicInteger(0); + private final String mJsPendingCallsTitleForTrace = + "pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement(); + private volatile boolean mDestroyed = false; + private final TraceListener mTraceListener; + private final JavaScriptModuleRegistry mJSModuleRegistry; + private final JSBundleLoader mJSBundleLoader; + + // Access from native modules thread + private final NativeModuleRegistry mJavaRegistry; + private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; + private boolean mInitialized = false; + + // Access from JS thread + private @Nullable ReactBridge mBridge; + private boolean mJSBundleHasLoaded; + + private CatalystInstanceImpl( + final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec, + final JavaScriptExecutor jsExecutor, + final NativeModuleRegistry registry, + final JavaScriptModulesConfig jsModulesConfig, + final JSBundleLoader jsBundleLoader, + NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { + mCatalystQueueConfiguration = CatalystQueueConfigurationImpl.create( + catalystQueueConfigurationSpec, + new NativeExceptionHandler()); + mBridgeIdleListeners = new CopyOnWriteArrayList<>(); + mJavaRegistry = registry; + mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig); + mJSBundleLoader = jsBundleLoader; + mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; + mTraceListener = new JSProfilerTraceListener(); + + final CountDownLatch initLatch = new CountDownLatch(1); + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + initializeBridge(jsExecutor, jsModulesConfig); + initLatch.countDown(); + } + }); + + try { + Assertions.assertCondition( + initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS), + "Timed out waiting for bridge to initialize!"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void initializeBridge( + JavaScriptExecutor jsExecutor, + JavaScriptModulesConfig jsModulesConfig) { + mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); + Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); + + mBridge = new ReactBridge( + jsExecutor, + new NativeModulesReactCallback(), + mCatalystQueueConfiguration.getNativeModulesQueueThread()); + mBridge.setGlobalVariable( + "__fbBatchedBridgeConfig", + buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); + Systrace.registerListener(mTraceListener); + } + + @Override + public void runJSBundle() { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "CatalystInstance_runJSBundle"); + + try { + final CountDownLatch initLatch = new CountDownLatch(1); + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); + mJSBundleHasLoaded = true; + + incrementPendingJSCalls(); + + try { + mJSBundleLoader.loadScript(mBridge); + } catch (JSExecutionException e) { + mNativeModuleCallExceptionHandler.handleException(e); + } + + initLatch.countDown(); + } + }); + Assertions.assertCondition( + initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS), + "Timed out loading JS!"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + /* package */ void callFunction( + final int moduleId, + final int methodId, + final NativeArray arguments, + final String tracingName) { + if (mDestroyed) { + FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed."); + return; + } + + incrementPendingJSCalls(); + + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); + + if (mDestroyed) { + return; + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName); + try { + Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + }); + } + + // This is called from java code, so it won't be stripped anyway, but proguard will rename it, + // which this prevents. + @DoNotStrip + @Override + public void invokeCallback(final int callbackID, final NativeArray arguments) { + if (mDestroyed) { + FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed."); + return; + } + + incrementPendingJSCalls(); + + mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); + + if (mDestroyed) { + return; + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, ""); + try { + Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + }); + } + + /** + * Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration + * (besides the UI thread) to finish running. Must be called from the UI thread so that we can + * fully shut down other threads. + */ + @Override + public void destroy() { + UiThreadUtil.assertOnUiThread(); + + if (mDestroyed) { + return; + } + + // TODO: tell all APIs to shut down + mDestroyed = true; + mJavaRegistry.notifyCatalystInstanceDestroy(); + mCatalystQueueConfiguration.destroy(); + boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); + if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { + for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { + listener.onTransitionToBridgeIdle(); + } + } + + if (mBridge != null) { + Systrace.unregisterListener(mTraceListener); + } + + // We can access the Bridge from any thread now because we know either we are on the JS thread + // or the JS thread has finished via CatalystQueueConfiguration#destroy() + Assertions.assertNotNull(mBridge).dispose(); + } + + @Override + public boolean isDestroyed() { + return mDestroyed; + } + + /** + * Initialize all the native modules + */ + @VisibleForTesting + @Override + public void initialize() { + UiThreadUtil.assertOnUiThread(); + Assertions.assertCondition( + !mInitialized, + "This catalyst instance has already been initialized"); + mInitialized = true; + mJavaRegistry.notifyCatalystInstanceInitialized(); + } + + @Override + public CatalystQueueConfiguration getCatalystQueueConfiguration() { + return mCatalystQueueConfiguration; + } + + @VisibleForTesting + public @Nullable + ReactBridge getBridge() { + return mBridge; + } + + @Override + public T getJSModule(Class jsInterface) { + return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface); + } + + @Override + public T getNativeModule(Class nativeModuleInterface) { + return mJavaRegistry.getModule(nativeModuleInterface); + } + + @Override + public Collection getNativeModules() { + return mJavaRegistry.getAllModules(); + } + + /** + * Adds a idle listener for this Catalyst instance. The listener will receive notifications + * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is + * defined as there being some non-zero number of calls to JS that haven't resolved via a + * onBatchCompleted call. The listener should be purely passive and not affect application logic. + */ + @Override + public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) { + mBridgeIdleListeners.add(listener); + } + + /** + * Removes a NotThreadSafeBridgeIdleDebugListener previously added with + * {@link #addBridgeIdleDebugListener} + */ + @Override + public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) { + mBridgeIdleListeners.remove(listener); + } + + @Override + public boolean supportsProfiling() { + if (mBridge == null) { + return false; + } + return mBridge.supportsProfiling(); + } + + @Override + public void startProfiler(String title) { + if (mBridge == null) { + return; + } + mBridge.startProfiler(title); + } + + @Override + public void stopProfiler(String title, String filename) { + if (mBridge == null) { + return; + } + mBridge.stopProfiler(title, filename); + } + + private String buildModulesConfigJSONProperty( + NativeModuleRegistry nativeModuleRegistry, + JavaScriptModulesConfig jsModulesConfig) { + // TODO(5300733): Serialize config using single json generator + JsonFactory jsonFactory = new JsonFactory(); + StringWriter writer = new StringWriter(); + try { + JsonGenerator jg = jsonFactory.createGenerator(writer); + jg.writeStartObject(); + jg.writeFieldName("remoteModuleConfig"); + jg.writeRawValue(nativeModuleRegistry.moduleDescriptions()); + jg.writeFieldName("localModulesConfig"); + jg.writeRawValue(jsModulesConfig.moduleDescriptions()); + jg.writeEndObject(); + jg.close(); + } catch (IOException ioe) { + throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe); + } + return writer.getBuffer().toString(); + } + + private void incrementPendingJSCalls() { + int oldPendingCalls = mPendingJSCalls.getAndIncrement(); + boolean wasIdle = oldPendingCalls == 0; + Systrace.traceCounter( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + mJsPendingCallsTitleForTrace, + oldPendingCalls + 1); + if (wasIdle && !mBridgeIdleListeners.isEmpty()) { + for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { + listener.onTransitionToBridgeBusy(); + } + } + } + + private void decrementPendingJSCalls() { + int newPendingCalls = mPendingJSCalls.decrementAndGet(); + Assertions.assertCondition(newPendingCalls >= 0); + boolean isNowIdle = newPendingCalls == 0; + Systrace.traceCounter( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + mJsPendingCallsTitleForTrace, + newPendingCalls); + + if (isNowIdle && !mBridgeIdleListeners.isEmpty()) { + for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { + listener.onTransitionToBridgeIdle(); + } + } + } + + private class NativeModulesReactCallback implements ReactCallback { + + @Override + public void call(int moduleId, int methodId, ReadableNativeArray parameters) { + mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); + + // Suppress any callbacks if destroyed - will only lead to sadness. + if (mDestroyed) { + return; + } + + mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters); + } + + @Override + public void onBatchComplete() { + mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); + + // The bridge may have been destroyed due to an exception during the batch. In that case + // native modules could be in a bad state so we don't want to call anything on them. We + // still want to trigger the debug listener since it allows instrumentation tests to end and + // check their assertions without waiting for a timeout. + if (!mDestroyed) { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete"); + try { + mJavaRegistry.onBatchComplete(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + decrementPendingJSCalls(); + } + } + + private class NativeExceptionHandler implements QueueThreadExceptionHandler { + + @Override + public void handleException(Exception e) { + // Any Exception caught here is because of something in JS. Even if it's a bug in the + // framework/native code, it was triggered by JS and theoretically since we were able + // to set up the bridge, JS could change its logic, reload, and not trigger that crash. + mNativeModuleCallExceptionHandler.handleException(e); + mCatalystQueueConfiguration.getUIQueueThread().runOnQueue( + new Runnable() { + @Override + public void run() { + destroy(); + } + }); + } + } + + private class JSProfilerTraceListener implements TraceListener { + @Override + public void onTraceStarted() { + getJSModule(BridgeProfiling.class).setEnabled(true); + } + + @Override + public void onTraceStopped() { + getJSModule(BridgeProfiling.class).setEnabled(false); + } + } + + public static class Builder { + + private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec; + private @Nullable JSBundleLoader mJSBundleLoader; + private @Nullable NativeModuleRegistry mRegistry; + private @Nullable JavaScriptModulesConfig mJSModulesConfig; + private @Nullable JavaScriptExecutor mJSExecutor; + private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; + + public Builder setCatalystQueueConfigurationSpec( + CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) { + mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec; + return this; + } + + public Builder setRegistry(NativeModuleRegistry registry) { + mRegistry = registry; + return this; + } + + public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) { + mJSModulesConfig = jsModulesConfig; + return this; + } + + public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) { + mJSBundleLoader = jsBundleLoader; + return this; + } + + public Builder setJSExecutor(JavaScriptExecutor jsExecutor) { + mJSExecutor = jsExecutor; + return this; + } + + public Builder setNativeModuleCallExceptionHandler( + NativeModuleCallExceptionHandler handler) { + mNativeModuleCallExceptionHandler = handler; + return this; + } + + public CatalystInstanceImpl build() { + return new CatalystInstanceImpl( + Assertions.assertNotNull(mCatalystQueueConfigurationSpec), + Assertions.assertNotNull(mJSExecutor), + Assertions.assertNotNull(mRegistry), + Assertions.assertNotNull(mJSModulesConfig), + Assertions.assertNotNull(mJSBundleLoader), + Assertions.assertNotNull(mNativeModuleCallExceptionHandler)); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java index fab0f231e339..093770fe05fd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java @@ -30,7 +30,7 @@ private final HashMap, JavaScriptModule> mModuleInstances; public JavaScriptModuleRegistry( - CatalystInstance instance, + CatalystInstanceImpl instance, JavaScriptModulesConfig config) { mModuleInstances = new HashMap<>(); for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) { @@ -52,11 +52,11 @@ public T getJavaScriptModule(Class moduleInterfa private static class JavaScriptModuleInvocationHandler implements InvocationHandler { - private final CatalystInstance mCatalystInstance; + private final CatalystInstanceImpl mCatalystInstance; private final JavaScriptModuleRegistration mModuleRegistration; public JavaScriptModuleInvocationHandler( - CatalystInstance catalystInstance, + CatalystInstanceImpl catalystInstance, JavaScriptModuleRegistration moduleRegistration) { mCatalystInstance = catalystInstance; mModuleRegistration = moduleRegistration; diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java index 10be2a44df10..99fec74857d3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java @@ -9,12 +9,6 @@ package com.facebook.react.bridge.queue; -import java.util.Map; - -import android.os.Looper; - -import com.facebook.react.common.MapBuilder; - /** * Specifies which {@link MessageQueueThread}s must be used to run the various contexts of * execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be @@ -24,67 +18,8 @@ * Native Modules Queue Thread: The thread and Looper that native modules are invoked on. * JS Queue Thread: The thread and Looper that JS is executed on. */ -public class CatalystQueueConfiguration { - - private final MessageQueueThread mUIQueueThread; - private final MessageQueueThread mNativeModulesQueueThread; - private final MessageQueueThread mJSQueueThread; - - private CatalystQueueConfiguration( - MessageQueueThread uiQueueThread, - MessageQueueThread nativeModulesQueueThread, - MessageQueueThread jsQueueThread) { - mUIQueueThread = uiQueueThread; - mNativeModulesQueueThread = nativeModulesQueueThread; - mJSQueueThread = jsQueueThread; - } - - public MessageQueueThread getUIQueueThread() { - return mUIQueueThread; - } - - public MessageQueueThread getNativeModulesQueueThread() { - return mNativeModulesQueueThread; - } - - public MessageQueueThread getJSQueueThread() { - return mJSQueueThread; - } - - /** - * Should be called when the corresponding {@link com.facebook.react.bridge.CatalystInstance} - * is destroyed so that we shut down the proper queue threads. - */ - public void destroy() { - if (mNativeModulesQueueThread.getLooper() != Looper.getMainLooper()) { - mNativeModulesQueueThread.quitSynchronous(); - } - if (mJSQueueThread.getLooper() != Looper.getMainLooper()) { - mJSQueueThread.quitSynchronous(); - } - } - - public static CatalystQueueConfiguration create( - CatalystQueueConfigurationSpec spec, - QueueThreadExceptionHandler exceptionHandler) { - Map specsToThreads = MapBuilder.newHashMap(); - - MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec(); - MessageQueueThread uiThread = MessageQueueThread.create( uiThreadSpec, exceptionHandler); - specsToThreads.put(uiThreadSpec, uiThread); - - MessageQueueThread jsThread = specsToThreads.get(spec.getJSQueueThreadSpec()); - if (jsThread == null) { - jsThread = MessageQueueThread.create(spec.getJSQueueThreadSpec(), exceptionHandler); - } - - MessageQueueThread nativeModulesThread = - specsToThreads.get(spec.getNativeModulesQueueThreadSpec()); - if (nativeModulesThread == null) { - nativeModulesThread = - MessageQueueThread.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler); - } - - return new CatalystQueueConfiguration(uiThread, nativeModulesThread, jsThread); - } +public interface CatalystQueueConfiguration { + MessageQueueThread getUIQueueThread(); + MessageQueueThread getNativeModulesQueueThread(); + MessageQueueThread getJSQueueThread(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationImpl.java new file mode 100644 index 000000000000..c6b8be58b0ea --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationImpl.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge.queue; + +import java.util.Map; + +import android.os.Looper; + +import com.facebook.react.common.MapBuilder; + +public class CatalystQueueConfigurationImpl implements CatalystQueueConfiguration { + + private final MessageQueueThreadImpl mUIQueueThread; + private final MessageQueueThreadImpl mNativeModulesQueueThread; + private final MessageQueueThreadImpl mJSQueueThread; + + private CatalystQueueConfigurationImpl( + MessageQueueThreadImpl uiQueueThread, + MessageQueueThreadImpl nativeModulesQueueThread, + MessageQueueThreadImpl jsQueueThread) { + mUIQueueThread = uiQueueThread; + mNativeModulesQueueThread = nativeModulesQueueThread; + mJSQueueThread = jsQueueThread; + } + + @Override + public MessageQueueThread getUIQueueThread() { + return mUIQueueThread; + } + + @Override + public MessageQueueThread getNativeModulesQueueThread() { + return mNativeModulesQueueThread; + } + + @Override + public MessageQueueThread getJSQueueThread() { + return mJSQueueThread; + } + + /** + * Should be called when the corresponding {@link com.facebook.react.bridge.CatalystInstance} + * is destroyed so that we shut down the proper queue threads. + */ + public void destroy() { + if (mNativeModulesQueueThread.getLooper() != Looper.getMainLooper()) { + mNativeModulesQueueThread.quitSynchronous(); + } + if (mJSQueueThread.getLooper() != Looper.getMainLooper()) { + mJSQueueThread.quitSynchronous(); + } + } + + public static CatalystQueueConfigurationImpl create( + CatalystQueueConfigurationSpec spec, + QueueThreadExceptionHandler exceptionHandler) { + Map specsToThreads = MapBuilder.newHashMap(); + + MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec(); + MessageQueueThreadImpl uiThread = + MessageQueueThreadImpl.create( uiThreadSpec, exceptionHandler); + specsToThreads.put(uiThreadSpec, uiThread); + + MessageQueueThreadImpl jsThread = specsToThreads.get(spec.getJSQueueThreadSpec()); + if (jsThread == null) { + jsThread = MessageQueueThreadImpl.create(spec.getJSQueueThreadSpec(), exceptionHandler); + } + + MessageQueueThreadImpl nativeModulesThread = + specsToThreads.get(spec.getNativeModulesQueueThreadSpec()); + if (nativeModulesThread == null) { + nativeModulesThread = + MessageQueueThreadImpl.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler); + } + + return new CatalystQueueConfigurationImpl(uiThread, nativeModulesThread, jsThread); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java index 0090b82ad0bf..b04285a6f601 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java @@ -9,136 +9,28 @@ package com.facebook.react.bridge.queue; -import android.os.Looper; - -import com.facebook.common.logging.FLog; import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.bridge.AssertionException; -import com.facebook.react.bridge.SoftAssertions; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.futures.SimpleSettableFuture; /** - * Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables. + * Encapsulates a Thread that can accept Runnables. */ @DoNotStrip -public class MessageQueueThread { - - private final String mName; - private final Looper mLooper; - private final MessageQueueThreadHandler mHandler; - private final String mAssertionErrorMessage; - private volatile boolean mIsFinished = false; - - private MessageQueueThread( - String name, - Looper looper, - QueueThreadExceptionHandler exceptionHandler) { - mName = name; - mLooper = looper; - mHandler = new MessageQueueThreadHandler(looper, exceptionHandler); - mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!"; - } - +public interface MessageQueueThread { /** * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even * if it is being submitted from the same queue Thread. */ @DoNotStrip - public void runOnQueue(Runnable runnable) { - if (mIsFinished) { - FLog.w( - ReactConstants.TAG, - "Tried to enqueue runnable on already finished thread: '" + getName() + - "... dropping Runnable."); - } - mHandler.post(runnable); - } + void runOnQueue(Runnable runnable); /** * @return whether the current Thread is also the Thread associated with this MessageQueueThread. */ - public boolean isOnThread() { - return mLooper.getThread() == Thread.currentThread(); - } + boolean isOnThread(); /** * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an * {@link AssertionError}) if the assertion fails. */ - public void assertIsOnThread() { - SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage); - } - - /** - * Quits this queue's Looper. If that Looper was running on a different Thread than the current - * Thread, also waits for the last message being processed to finish and the Thread to die. - */ - public void quitSynchronous() { - mIsFinished = true; - mLooper.quit(); - if (mLooper.getThread() != Thread.currentThread()) { - try { - mLooper.getThread().join(); - } catch (InterruptedException e) { - throw new RuntimeException("Got interrupted waiting to join thread " + mName); - } - } - } - - public Looper getLooper() { - return mLooper; - } - - public String getName() { - return mName; - } - - public static MessageQueueThread create( - MessageQueueThreadSpec spec, - QueueThreadExceptionHandler exceptionHandler) { - switch (spec.getThreadType()) { - case MAIN_UI: - return createForMainThread(spec.getName(), exceptionHandler); - case NEW_BACKGROUND: - return startNewBackgroundThread(spec.getName(), exceptionHandler); - default: - throw new RuntimeException("Unknown thread type: " + spec.getThreadType()); - } - } - - /** - * @return a MessageQueueThread corresponding to Android's main UI thread. - */ - private static MessageQueueThread createForMainThread( - String name, - QueueThreadExceptionHandler exceptionHandler) { - Looper mainLooper = Looper.getMainLooper(); - return new MessageQueueThread(name, mainLooper, exceptionHandler); - } - - /** - * Creates and starts a new MessageQueueThread encapsulating a new Thread with a new Looper - * running on it. Give it a name for easier debugging. When this method exits, the new - * MessageQueueThread is ready to receive events. - */ - private static MessageQueueThread startNewBackgroundThread( - String name, - QueueThreadExceptionHandler exceptionHandler) { - final SimpleSettableFuture simpleSettableFuture = new SimpleSettableFuture<>(); - Thread bgThread = new Thread( - new Runnable() { - @Override - public void run() { - Looper.prepare(); - - simpleSettableFuture.set(Looper.myLooper()); - - Looper.loop(); - } - }, "mqt_" + name); - bgThread.start(); - - return new MessageQueueThread(name, simpleSettableFuture.get(5000), exceptionHandler); - } + void assertIsOnThread(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java new file mode 100644 index 000000000000..5453e5355638 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge.queue; + +import android.os.Looper; + +import com.facebook.common.logging.FLog; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.AssertionException; +import com.facebook.react.bridge.SoftAssertions; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.common.futures.SimpleSettableFuture; + +/** + * Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables. + */ +@DoNotStrip +/* package */ class MessageQueueThreadImpl implements MessageQueueThread { + + private final String mName; + private final Looper mLooper; + private final MessageQueueThreadHandler mHandler; + private final String mAssertionErrorMessage; + private volatile boolean mIsFinished = false; + + private MessageQueueThreadImpl( + String name, + Looper looper, + QueueThreadExceptionHandler exceptionHandler) { + mName = name; + mLooper = looper; + mHandler = new MessageQueueThreadHandler(looper, exceptionHandler); + mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!"; + } + + /** + * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even + * if it is being submitted from the same queue Thread. + */ + @DoNotStrip + public void runOnQueue(Runnable runnable) { + if (mIsFinished) { + FLog.w( + ReactConstants.TAG, + "Tried to enqueue runnable on already finished thread: '" + getName() + + "... dropping Runnable."); + } + mHandler.post(runnable); + } + + /** + * @return whether the current Thread is also the Thread associated with this MessageQueueThread. + */ + public boolean isOnThread() { + return mLooper.getThread() == Thread.currentThread(); + } + + /** + * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an + * {@link AssertionError}) if the assertion fails. + */ + public void assertIsOnThread() { + SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage); + } + + /** + * Quits this queue's Looper. If that Looper was running on a different Thread than the current + * Thread, also waits for the last message being processed to finish and the Thread to die. + */ + public void quitSynchronous() { + mIsFinished = true; + mLooper.quit(); + if (mLooper.getThread() != Thread.currentThread()) { + try { + mLooper.getThread().join(); + } catch (InterruptedException e) { + throw new RuntimeException("Got interrupted waiting to join thread " + mName); + } + } + } + + public Looper getLooper() { + return mLooper; + } + + public String getName() { + return mName; + } + + public static MessageQueueThreadImpl create( + MessageQueueThreadSpec spec, + QueueThreadExceptionHandler exceptionHandler) { + switch (spec.getThreadType()) { + case MAIN_UI: + return createForMainThread(spec.getName(), exceptionHandler); + case NEW_BACKGROUND: + return startNewBackgroundThread(spec.getName(), exceptionHandler); + default: + throw new RuntimeException("Unknown thread type: " + spec.getThreadType()); + } + } + + /** + * @return a MessageQueueThreadImpl corresponding to Android's main UI thread. + */ + private static MessageQueueThreadImpl createForMainThread( + String name, + QueueThreadExceptionHandler exceptionHandler) { + Looper mainLooper = Looper.getMainLooper(); + return new MessageQueueThreadImpl(name, mainLooper, exceptionHandler); + } + + /** + * Creates and starts a new MessageQueueThreadImpl encapsulating a new Thread with a new Looper + * running on it. Give it a name for easier debugging. When this method exits, the new + * MessageQueueThreadImpl is ready to receive events. + */ + private static MessageQueueThreadImpl startNewBackgroundThread( + String name, + QueueThreadExceptionHandler exceptionHandler) { + final SimpleSettableFuture simpleSettableFuture = new SimpleSettableFuture<>(); + Thread bgThread = new Thread( + new Runnable() { + @Override + public void run() { + Looper.prepare(); + + simpleSettableFuture.set(Looper.myLooper()); + + Looper.loop(); + } + }, "mqt_" + name); + bgThread.start(); + + return new MessageQueueThreadImpl(name, simpleSettableFuture.get(5000), exceptionHandler); + } +} From 945a1c72dbc70efc492c68b4b959bdaff8bc1a09 Mon Sep 17 00:00:00 2001 From: Ricky Reusser Date: Fri, 20 Nov 2015 15:43:07 -0500 Subject: [PATCH 0035/1411] Note in docs that Android geolocation not yet open sourced --- Libraries/Geolocation/Geolocation.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js index b6752e3f08c2..681a5f77b895 100644 --- a/Libraries/Geolocation/Geolocation.js +++ b/Libraries/Geolocation/Geolocation.js @@ -42,6 +42,10 @@ type GeoOptions = { * app's `AndroidManifest.xml`: * * `` + * + * Geolocation support for Android is planned but not yet open sourced. See + * [Known Issues](http://facebook.github.io/react-native/docs/known-issues.html#missing-modules-and-native-views). + * */ var Geolocation = { From 8ab51828ff077ae0ad10c06f62f9f01d58b9bf85 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Fri, 20 Nov 2015 13:05:34 -0800 Subject: [PATCH 0036/1411] RN: Revamp YellowBox for Warnings Reviewed By: vjeux Differential Revision: D2667624 fb-gh-sync-id: f3c6ed63f3138edd13e7fe283cf877d598018813 --- Examples/UIExplorer/RCTRootViewIOSExample.js | 15 +- Libraries/ReactIOS/WarningBox.js | 393 ------------------ Libraries/ReactIOS/YellowBox.js | 335 +++++++++++++++ .../ReactIOS/renderApplication.android.js | 9 +- Libraries/ReactIOS/renderApplication.ios.js | 13 +- 5 files changed, 361 insertions(+), 404 deletions(-) delete mode 100644 Libraries/ReactIOS/WarningBox.js create mode 100644 Libraries/ReactIOS/YellowBox.js diff --git a/Examples/UIExplorer/RCTRootViewIOSExample.js b/Examples/UIExplorer/RCTRootViewIOSExample.js index 174dcf2bb947..69ecab0a7655 100644 --- a/Examples/UIExplorer/RCTRootViewIOSExample.js +++ b/Examples/UIExplorer/RCTRootViewIOSExample.js @@ -15,19 +15,20 @@ */ 'use strict'; -var React = require('react-native'); -var { + +const React = require('react-native'); +const { StyleSheet, Text, View, } = React; -var requireNativeComponent = require('requireNativeComponent'); -var UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView'); -var FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView'); +const requireNativeComponent = require('requireNativeComponent'); class AppPropertiesUpdateExample extends React.Component { render() { + // Do not require this unless we are actually rendering. + const UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView'); return ( @@ -45,6 +46,8 @@ class AppPropertiesUpdateExample extends React.Component { class RootViewSizeFlexibilityExample extends React.Component { render() { + // Do not require this unless we are actually rendering. + const FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView'); return ( @@ -60,7 +63,7 @@ class RootViewSizeFlexibilityExample extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5FCFF', diff --git a/Libraries/ReactIOS/WarningBox.js b/Libraries/ReactIOS/WarningBox.js deleted file mode 100644 index 09ac954c5502..000000000000 --- a/Libraries/ReactIOS/WarningBox.js +++ /dev/null @@ -1,393 +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 WarningBox - */ -'use strict'; - -var AsyncStorage = require('AsyncStorage'); -var EventEmitter = require('EventEmitter'); -var Map = require('Map'); -var PanResponder = require('PanResponder'); -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var TouchableOpacity = require('TouchableOpacity'); -var View = require('View'); - -var invariant = require('invariant'); -var rebound = require('rebound'); -var stringifySafe = require('stringifySafe'); - -var SCREEN_WIDTH = require('Dimensions').get('window').width; -var IGNORED_WARNINGS_KEY = '__DEV_WARNINGS_IGNORED'; - -var consoleWarn = console.warn.bind(console); - -var warningCounts = new Map(); -var ignoredWarnings: Array = []; -var totalWarningCount = 0; -var warningCountEvents = new EventEmitter(); - -/** - * WarningBox renders warnings on top of the app being developed. Warnings help - * guard against subtle yet significant issues that can impact the quality of - * your application, such as accessibility and memory leaks. This "in your - * face" style of warning allows developers to notice and correct these issues - * as quickly as possible. - * - * The warning box is currently opt-in. Set the following flag to enable it: - * - * `console.yellowBoxEnabled = true;` - * - * If "ignore" is tapped on a warning, the WarningBox will record that warning - * and will not display it again. This is useful for hiding errors that already - * exist or have been introduced elsewhere. To re-enable all of the errors, set - * the following: - * - * `console.yellowBoxResetIgnored = true;` - * - * This can also be set permanently, and ignore will only silence the warnings - * until the next refresh. - */ - -if (__DEV__) { - console.warn = function() { - consoleWarn.apply(null, arguments); - if (!console.yellowBoxEnabled) { - return; - } - var warning = Array.prototype.map.call(arguments, stringifySafe).join(' '); - if (!console.yellowBoxResetIgnored && - ignoredWarnings.indexOf(warning) !== -1) { - return; - } - var count = warningCounts.has(warning) ? warningCounts.get(warning) + 1 : 1; - warningCounts.set(warning, count); - totalWarningCount += 1; - warningCountEvents.emit('count', totalWarningCount); - }; -} - -function saveIgnoredWarnings() { - AsyncStorage.setItem( - IGNORED_WARNINGS_KEY, - JSON.stringify(ignoredWarnings), - function(err) { - if (err) { - console.warn('Could not save ignored warnings.', err); - } - } - ); -} - -AsyncStorage.getItem(IGNORED_WARNINGS_KEY, function(err, data) { - if (!err && data && !console.yellowBoxResetIgnored) { - ignoredWarnings = JSON.parse(data); - } -}); - -var WarningRow = React.createClass({ - componentWillMount: function() { - this.springSystem = new rebound.SpringSystem(); - this.dismissalSpring = this.springSystem.createSpring(); - this.dismissalSpring.setRestSpeedThreshold(0.05); - this.dismissalSpring.setCurrentValue(0); - this.dismissalSpring.addListener({ - onSpringUpdate: () => { - var val = this.dismissalSpring.getCurrentValue(); - this.text && this.text.setNativeProps({ - left: SCREEN_WIDTH * val, - }); - this.container && this.container.setNativeProps({ - opacity: 1 - val, - }); - this.closeButton && this.closeButton.setNativeProps({ - opacity: 1 - (val * 5), - }); - }, - onSpringAtRest: () => { - if (this.dismissalSpring.getCurrentValue()) { - this.collapseSpring.setEndValue(1); - } - }, - }); - this.collapseSpring = this.springSystem.createSpring(); - this.collapseSpring.setRestSpeedThreshold(0.05); - this.collapseSpring.setCurrentValue(0); - this.collapseSpring.getSpringConfig().friction = 20; - this.collapseSpring.getSpringConfig().tension = 200; - this.collapseSpring.addListener({ - onSpringUpdate: () => { - var val = this.collapseSpring.getCurrentValue(); - this.container && this.container.setNativeProps({ - height: Math.abs(46 - (val * 46)), - }); - }, - onSpringAtRest: () => { - this.props.onDismissed(); - }, - }); - this.panGesture = PanResponder.create({ - onStartShouldSetPanResponder: () => { - return !!this.dismissalSpring.getCurrentValue(); - }, - onMoveShouldSetPanResponder: () => true, - onPanResponderGrant: () => { - this.isResponderOnlyToBlockTouches = - !!this.dismissalSpring.getCurrentValue(); - }, - onPanResponderMove: (e, gestureState) => { - if (this.isResponderOnlyToBlockTouches) { - return; - } - this.dismissalSpring.setCurrentValue(gestureState.dx / SCREEN_WIDTH); - }, - onPanResponderRelease: (e, gestureState) => { - if (this.isResponderOnlyToBlockTouches) { - return; - } - var gestureCompletion = gestureState.dx / SCREEN_WIDTH; - var doesGestureRelease = (gestureState.vx + gestureCompletion) > 0.5; - this.dismissalSpring.setEndValue(doesGestureRelease ? 1 : 0); - } - }); - }, - render: function() { - var countText; - if (warningCounts.get(this.props.warning) > 1) { - countText = ( - - ({warningCounts.get(this.props.warning)}){" "} - - ); - } - return ( - { this.container = container; }} - {...this.panGesture.panHandlers}> - - { this.text = text; }}> - {countText} - {this.props.warning} - - - { this.closeButton = closeButton; }} - style={styles.closeButton}> - { - this.dismissalSpring.setEndValue(1); - }}> - - - - - ); - } -}); - -var WarningBoxOpened = React.createClass({ - render: function() { - var countText; - if (warningCounts.get(this.props.warning) > 1) { - countText = ( - - ({warningCounts.get(this.props.warning)}){" "} - - ); - } - return ( - - - {countText} - {this.props.warning} - - - - - Dismiss - - - - - Ignore - - - - - ); - }, -}); - -var canMountWarningBox = true; - -var WarningBox = React.createClass({ - getInitialState: function() { - return { - totalWarningCount, - openWarning: null, - }; - }, - componentWillMount: function() { - if (console.yellowBoxResetIgnored) { - AsyncStorage.setItem(IGNORED_WARNINGS_KEY, '[]', function(err) { - if (err) { - console.warn('Could not reset ignored warnings.', err); - } - }); - ignoredWarnings = []; - } - }, - componentDidMount: function() { - invariant( - canMountWarningBox, - 'There can only be one WarningBox' - ); - canMountWarningBox = false; - warningCountEvents.addListener( - 'count', - this._onWarningCount - ); - }, - componentWillUnmount: function() { - warningCountEvents.removeAllListeners(); - canMountWarningBox = true; - }, - _onWarningCount: function(totalWarningCount) { - // Must use setImmediate because warnings often happen during render and - // state cannot be set while rendering - setImmediate(() => { - this.setState({ totalWarningCount, }); - }); - }, - _onDismiss: function(warning) { - warningCounts.delete(warning); - this.setState({ - openWarning: null, - }); - }, - render: function() { - if (warningCounts.size === 0) { - return ; - } - if (this.state.openWarning) { - return ( - { - this.setState({ openWarning: null }); - }} - onDismissed={this._onDismiss.bind(this, this.state.openWarning)} - onIgnored={() => { - ignoredWarnings.push(this.state.openWarning); - saveIgnoredWarnings(); - this._onDismiss(this.state.openWarning); - }} - /> - ); - } - var warningRows = []; - warningCounts.forEach((count, warning) => { - warningRows.push( - { - this.setState({ openWarning: warning }); - }} - onDismissed={this._onDismiss.bind(this, warning)} - warning={warning} - /> - ); - }); - return ( - - {warningRows} - - ); - }, -}); - -var styles = StyleSheet.create({ - bold: { - fontWeight: 'bold', - }, - closeButton: { - position: 'absolute', - right: 0, - height: 46, - width: 46, - }, - closeButtonText: { - color: 'white', - fontSize: 32, - position: 'relative', - left: 8, - }, - warningContainer: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0 - }, - warningBox: { - position: 'relative', - backgroundColor: 'rgba(171, 124, 36, 0.9)', - flex: 1, - height: 46, - }, - warningText: { - color: 'white', - position: 'absolute', - left: 0, - marginLeft: 15, - marginRight: 46, - top: 7, - }, - yellowBox: { - backgroundColor: 'rgba(171, 124, 36, 0.9)', - position: 'absolute', - left: 0, - right: 0, - top: 0, - bottom: 0, - padding: 15, - paddingTop: 35, - }, - yellowBoxText: { - color: 'white', - fontSize: 20, - }, - yellowBoxButtons: { - flexDirection: 'row', - position: 'absolute', - bottom: 0, - }, - yellowBoxButton: { - flex: 1, - padding: 25, - }, - yellowBoxButtonText: { - color: 'white', - fontSize: 16, - } -}); - -module.exports = WarningBox; diff --git a/Libraries/ReactIOS/YellowBox.js b/Libraries/ReactIOS/YellowBox.js new file mode 100644 index 000000000000..c439b4e20cba --- /dev/null +++ b/Libraries/ReactIOS/YellowBox.js @@ -0,0 +1,335 @@ +/** + * 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 YellowBox + * @flow + */ + +'use strict'; + +const EventEmitter = require('EventEmitter'); +import type EmitterSubscription from 'EmitterSubscription'; +const Map = require('Map'); +const Platform = require('Platform'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); + +const _warningEmitter = new EventEmitter(); +const _warningMap = new Map(); + +/** + * YellowBox renders warnings at the bottom of the app being developed. + * + * Warnings help guard against subtle yet significant issues that can impact the + * quality of the app. This "in your face" style of warning allows developers to + * notice and correct these issues as quickly as possible. + * + * By default, the warning box is enabled in `__DEV__`. Set the following flag + * to disable it (and call `console.warn` to update any rendered ): + * + * console.disableYellowBox = true; + * console.warn('YellowBox is disabled.'); + * + * Warnings can be ignored programmatically by setting the array: + * + * console.ignoredYellowBox = ['Warning: ...']; + * + * Strings in `console.ignoredYellowBox` can be a prefix of the warning that + * should be ignored. + */ + +if (__DEV__) { + const {error, warn} = console; + console.error = function() { + error.apply(console, arguments); + // Show yellow box for the `warning` module. + if (typeof arguments[0] === 'string' && + arguments[0].startsWith('Warning: ')) { + updateWarningMap.apply(null, arguments); + } + }; + console.warn = function() { + warn.apply(console, arguments); + updateWarningMap.apply(null, arguments); + }; +} + +function updateWarningMap(format, ...args): void { + const sprintf = require('sprintf'); + const stringifySafe = require('stringifySafe'); + + format = String(format); + const argCount = (format.match(/%s/g) || []).length; + const warning = [ + sprintf(format, ...args.slice(0, argCount)), + ...args.slice(argCount).map(stringifySafe), + ].join(' '); + + const count = _warningMap.has(warning) ? _warningMap.get(warning) : 0; + _warningMap.set(warning, count + 2); + _warningEmitter.emit('warning', _warningMap); +} + +function isWarningIgnored(warning: string): boolean { + return ( + Array.isArray(console.ignoredYellowBox) && + console.ignoredYellowBox.some( + ignorePrefix => warning.startsWith(ignorePrefix) + ) + ); +} + +const WarningRow = ({count, warning, onPress}) => { + const Text = require('Text'); + const TouchableHighlight = require('TouchableHighlight'); + const View = require('View'); + + const countText = count > 1 ? + {'(' + count + ') '} : + null; + + return ( + + + + {countText} + {warning} + + + + ); +}; + +const WarningInspector = ({count, warning, onClose, onDismiss}) => { + const ScrollView = require('ScrollView'); + const Text = require('Text'); + const TouchableHighlight = require('TouchableHighlight'); + const View = require('View'); + + const countSentence = + 'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.'; + + return ( + + + + {countSentence} + + + {warning} + + + + + Dismiss Warning + + + + + + ); +}; + +class YellowBox extends React.Component { + state: { + inspecting: ?string; + warningMap: Map; + }; + _listener: ?EmitterSubscription; + + constructor(props: mixed, context: mixed) { + super(props, context); + this.state = { + inspecting: null, + warningMap: _warningMap, + }; + this.dismissWarning = warning => { + const {inspecting, warningMap} = this.state; + warningMap.delete(warning); + this.setState({ + inspecting: inspecting === warning ? null : inspecting, + warningMap, + }); + }; + } + + componentDidMount() { + let scheduled = null; + this._listener = _warningEmitter.addListener('warning', warningMap => { + // Use `setImmediate` because warnings often happen during render, but + // state cannot be set while rendering. + scheduled = scheduled || setImmediate(() => { + scheduled = null; + this.setState({ + warningMap, + }); + }); + }); + } + + componentWillUnmount() { + if (this._listener) { + this._listener.remove(); + } + } + + render() { + if (console.disableYellowBox || this.state.warningMap.size === 0) { + return null; + } + const ScrollView = require('ScrollView'); + const View = require('View'); + + const inspecting = this.state.inspecting; + const inspector = inspecting !== null ? + this.setState({inspecting: null})} + onDismiss={() => this.dismissWarning(inspecting)} + /> : + null; + + const rows = []; + this.state.warningMap.forEach((count, warning) => { + if (!isWarningIgnored(warning)) { + rows.push( + this.setState({inspecting: warning})} + onDismiss={() => this.dismissWarning(warning)} + /> + ); + } + }); + + const listStyle = [ + styles.list, + // Additional `0.4` so the 5th row can peek into view. + {height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)}, + ]; + return ( + + + {rows} + + {inspector} + + ); + } +} + +const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')'; +const textColor = 'white'; +const rowGutter = 1; +const rowHeight = 46; + +var styles = StyleSheet.create({ + fullScreen: { + backgroundColor: 'transparent', + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + inspector: { + backgroundColor: backgroundColor(0.95), + flex: 1, + }, + inspectorContainer: { + flex: 1, + }, + inspectorButtons: { + flexDirection: 'row', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, + inspectorButton: { + padding: 22, + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, + inspectorButtonText: { + color: textColor, + fontSize: 14, + opacity: 0.8, + textAlign: 'center', + }, + inspectorContent: { + flex: 1, + paddingTop: 5, + }, + inspectorCount: { + padding: 15, + paddingBottom: 0, + }, + inspectorCountText: { + color: textColor, + fontSize: 14, + }, + inspectorWarning: { + padding: 15, + position: 'absolute', + top: 39, + bottom: 60, + }, + inspectorWarningText: { + color: textColor, + fontSize: 16, + fontWeight: '600', + }, + list: { + backgroundColor: 'transparent', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, + listRow: { + position: 'relative', + backgroundColor: backgroundColor(0.95), + flex: 1, + height: rowHeight, + marginTop: rowGutter, + }, + listRowContent: { + flex: 1, + }, + listRowCount: { + color: 'rgba(255, 255, 255, 0.5)', + }, + listRowText: { + color: textColor, + position: 'absolute', + left: 0, + top: Platform.OS === 'android' ? 5 : 7, + marginLeft: 15, + marginRight: 15, + }, +}); + +module.exports = YellowBox; diff --git a/Libraries/ReactIOS/renderApplication.android.js b/Libraries/ReactIOS/renderApplication.android.js index d299c2eb3842..05b87178b6f5 100644 --- a/Libraries/ReactIOS/renderApplication.android.js +++ b/Libraries/ReactIOS/renderApplication.android.js @@ -8,6 +8,7 @@ * * @providesModule renderApplication */ + 'use strict'; var Inspector = require('Inspector'); @@ -20,6 +21,8 @@ var View = require('View'); var invariant = require('invariant'); +var YellowBox = __DEV__ ? require('YellowBox') : null; + // require BackAndroid so it sets the default handler that exits the app if no listeners respond require('BackAndroid'); @@ -89,10 +92,14 @@ var AppContainer = React.createClass({ ; - + let yellowBox = null; + if (__DEV__) { + yellowBox = ; + } return this.state.enabled ? {appView} + {yellowBox} {this.renderInspector()} : appView; diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js index 01eb1e5d53f9..0b0f306d6be3 100644 --- a/Libraries/ReactIOS/renderApplication.ios.js +++ b/Libraries/ReactIOS/renderApplication.ios.js @@ -8,6 +8,7 @@ * * @providesModule renderApplication */ + 'use strict'; var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); @@ -19,7 +20,7 @@ var View = require('View'); var invariant = require('invariant'); var Inspector = __DEV__ ? require('Inspector') : null; -var WarningBox = __DEV__ ? require('WarningBox') : null; +var YellowBox = __DEV__ ? require('YellowBox') : null; var AppContainer = React.createClass({ mixins: [Subscribable.Mixin], @@ -47,14 +48,16 @@ var AppContainer = React.createClass({ }, render: function() { - var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled; - var warningBox = shouldRenderWarningBox ? : null; + let yellowBox = null; + if (__DEV__) { + yellowBox = ; + } return ( {this.props.children} - {warningBox} + {yellowBox} {this.state.inspector} ); @@ -70,6 +73,7 @@ function renderApplication( rootTag, 'Expect to have a valid rootTag, instead got ', rootTag ); + /* eslint-disable jsx-no-undef-with-namespace */ React.render( ( , rootTag ); + /* eslint-enable jsx-no-undef-with-namespace */ } var styles = StyleSheet.create({ From 43f18ffd087280e6df1e4900deb6fe201b87741a Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 20 Nov 2015 20:17:17 -0800 Subject: [PATCH 0037/1411] Add infra for Prepack build option Summary: This adds a build option for using Prepack (an experimental packager) to build a bundle. It doesn't actually take on the npm package dependency because it's not published/open source (yet). This will be used while we experiment and should be maintained as the build system changes so that we can continue getting fresh builds. I found that saveBundleAndMap and processBundle were over abstracted and got in my way so I inlined it and removed the unit tests because the unit test was testing trivial code that is likely to change interface. I went with a separate build phase and a separate Bundle class even though there are a lot of commonalities. I imagine that the requirements for Prepack will continue to diverge. Especially for source maps but a larger refactor could try to unify these a bit more. The fact that modules are wrapped before the write phase seems to be an unfortunate architecture that makes this difficult. Closes https://github.com/facebook/react-native/pull/4226 Reviewed By: amasad Differential Revision: D2673760 Pulled By: sebmarkbage fb-gh-sync-id: 299ccc42e4be1d9dee19ade443ea3388db2e39a8 --- .../bundle/__tests__/saveBundleAndMap-test.js | 63 -------- local-cli/bundle/buildBundle.js | 108 +++++++++++-- local-cli/bundle/bundleCommandLineArgs.js | 8 + local-cli/bundle/processBundle.js | 29 ---- .../{saveBundleAndMap.js => saveAssets.js} | 22 +-- packager/react-packager/index.js | 9 ++ .../src/Bundler/PrepackBundle.js | 149 ++++++++++++++++++ packager/react-packager/src/Bundler/index.js | 104 +++++++++--- packager/react-packager/src/Server/index.js | 11 ++ .../src/SocketInterface/SocketClient.js | 8 + .../src/SocketInterface/SocketServer.js | 8 + 11 files changed, 372 insertions(+), 147 deletions(-) delete mode 100644 local-cli/bundle/__tests__/saveBundleAndMap-test.js delete mode 100644 local-cli/bundle/processBundle.js rename local-cli/bundle/{saveBundleAndMap.js => saveAssets.js} (80%) create mode 100644 packager/react-packager/src/Bundler/PrepackBundle.js diff --git a/local-cli/bundle/__tests__/saveBundleAndMap-test.js b/local-cli/bundle/__tests__/saveBundleAndMap-test.js deleted file mode 100644 index 4bd516a05d46..000000000000 --- a/local-cli/bundle/__tests__/saveBundleAndMap-test.js +++ /dev/null @@ -1,63 +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. - */ -'use strict'; - -jest.autoMockOff(); - -jest.mock('fs'); -jest.mock('../sign'); - -const saveBundleAndMap = require('../saveBundleAndMap'); -const fs = require('fs'); -const temp = require('temp'); - -const code = 'const foo = "bar";'; -const map = JSON.stringify({ - version: 3, - file: 'foo.js.map', - sources: ['foo.js'], - sourceRoot: '/', - names: ['bar'], - mappings: 'AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO' -}); - -describe('saveBundleAndMap', () => { - beforeEach(() => { - fs.writeFileSync = jest.genMockFn(); - }); - - it('should save bundle', () => { - const codeWithMap = {code: code}; - const bundleOutput = temp.path({suffix: '.bundle'}); - - saveBundleAndMap( - codeWithMap, - 'ios', - bundleOutput, - 'utf8', - ); - - expect(fs.writeFileSync.mock.calls[0]).toEqual([bundleOutput, code, 'utf8']); - }); - - it('should save sourcemaps if required so', () => { - const codeWithMap = {code: code, map: map}; - const bundleOutput = temp.path({suffix: '.bundle'}); - const sourceMapOutput = temp.path({suffix: '.map'}); - saveBundleAndMap( - codeWithMap, - 'ios', - bundleOutput, - 'utf8', - sourceMapOutput - ); - - expect(fs.writeFileSync.mock.calls[1]).toEqual([sourceMapOutput, map]); - }); -}); diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index f69eea5a8d78..5cb752a43b1c 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -8,11 +8,57 @@ */ 'use strict'; +const fs = require('fs'); const log = require('../util/log').out('bundle'); -const processBundle = require('./processBundle'); const Promise = require('promise'); const ReactPackager = require('../../packager/react-packager'); -const saveBundleAndMap = require('./saveBundleAndMap'); +const saveAssets = require('./saveAssets'); + +const sign = require('./sign'); + +function saveBundleAndMap( + bundle, + bundleOutput, + encoding, + sourcemapOutput, + dev +) { + log('start'); + let codeWithMap; + if (!dev) { + codeWithMap = bundle.getMinifiedSourceAndMap(dev); + } else { + codeWithMap = { + code: bundle.getSource({ dev }), + map: JSON.stringify(bundle.getSourceMap({ dev })), + }; + } + log('finish'); + + log('Writing bundle output to:', bundleOutput); + fs.writeFileSync(bundleOutput, sign(codeWithMap.code), encoding); + log('Done writing bundle output'); + + if (sourcemapOutput) { + log('Writing sourcemap output to:', sourcemapOutput); + fs.writeFileSync(sourcemapOutput, codeWithMap.map); + log('Done writing sourcemap output'); + } +} + +function savePrepackBundleAndMap( + bundle, + bundleOutput, + sourcemapOutput, + bridgeConfig +) { + log('Writing prepack bundle output to:', bundleOutput); + const result = bundle.build({ + batchedBridgeConfig: bridgeConfig + }); + fs.writeFileSync(bundleOutput, result, 'ucs-2'); + log('Done writing prepack bundle output'); +} function buildBundle(args, config) { return new Promise((resolve, reject) => { @@ -36,24 +82,56 @@ function buildBundle(args, config) { platform: args.platform, }; - resolve(ReactPackager.createClientFor(options).then(client => { - log('Created ReactPackager'); - return client.buildBundle(requestOpts) + const prepack = args.prepack; + + const client = ReactPackager.createClientFor(options); + + client.then(() => log('Created ReactPackager')); + + // Build and save the bundle + let bundle; + if (prepack) { + bundle = client.then(c => c.buildPrepackBundle(requestOpts)) .then(outputBundle => { - log('Closing client'); - client.close(); + savePrepackBundleAndMap( + outputBundle, + args['bundle-output'], + args['sourcemap-output'], + args['bridge-config'] + ); return outputBundle; - }) - .then(outputBundle => processBundle(outputBundle, args.dev)) - .then(outputBundle => saveBundleAndMap( - outputBundle, + }); + } else { + bundle = client.then(c => c.buildBundle(requestOpts)) + .then(outputBundle => { + saveBundleAndMap( + outputBundle, + args['bundle-output'], + args['bundle-encoding'], + args['sourcemap-output'], + args.dev + ); + return outputBundle; + }); + } + + // When we're done bundling, close the client + bundle.then(() => client.then(c => { + log('Closing client'); + c.close(); + })); + + // Save the assets of the bundle + const assets = bundle + .then(outputBundle => outputBundle.getAssets()) + .then(outputAssets => saveAssets( + outputAssets, args.platform, - args['bundle-output'], - args['bundle-encoding'], - args['sourcemap-output'], args['assets-dest'] )); - })); + + // When we're done saving the assets, we're done. + resolve(assets); }); } diff --git a/local-cli/bundle/bundleCommandLineArgs.js b/local-cli/bundle/bundleCommandLineArgs.js index 353c7ea5f28e..2597a7f13154 100644 --- a/local-cli/bundle/bundleCommandLineArgs.js +++ b/local-cli/bundle/bundleCommandLineArgs.js @@ -27,6 +27,14 @@ module.exports = [ command: 'dev', description: 'If false, warnings are disabled and the bundle is minified', default: true, + }, { + command: 'prepack', + description: 'If true, the output bundle will use the Prepack format.', + default: false + }, { + command: 'bridge-config', + description: 'File name of a a JSON export of __fbBatchedBridgeConfig. Used by Prepack. Ex. ./bridgeconfig.json', + type: 'string' }, { command: 'bundle-output', description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle', diff --git a/local-cli/bundle/processBundle.js b/local-cli/bundle/processBundle.js deleted file mode 100644 index 3a86a61f522e..000000000000 --- a/local-cli/bundle/processBundle.js +++ /dev/null @@ -1,29 +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. - */ -'use strict'; - -const log = require('../util/log').out('bundle'); - -function processBundle(input, dev) { - log('start'); - let bundle; - if (!dev) { - bundle = input.getMinifiedSourceAndMap(dev); - } else { - bundle = { - code: input.getSource({ dev }), - map: JSON.stringify(input.getSourceMap({ dev })), - }; - } - bundle.assets = input.getAssets(); - log('finish'); - return bundle; -} - -module.exports = processBundle; diff --git a/local-cli/bundle/saveBundleAndMap.js b/local-cli/bundle/saveAssets.js similarity index 80% rename from local-cli/bundle/saveBundleAndMap.js rename to local-cli/bundle/saveAssets.js index bb6bf2291f22..1dd88512b4f2 100644 --- a/local-cli/bundle/saveBundleAndMap.js +++ b/local-cli/bundle/saveAssets.js @@ -14,26 +14,12 @@ const getAssetDestPathAndroid = require('./getAssetDestPathAndroid'); const getAssetDestPathIOS = require('./getAssetDestPathIOS'); const log = require('../util/log').out('bundle'); const path = require('path'); -const sign = require('./sign'); -function saveBundleAndMap( - codeWithMap, +function saveAssets( + assets, platform, - bundleOutput, - encoding, - sourcemapOutput, assetsDest ) { - log('Writing bundle output to:', bundleOutput); - fs.writeFileSync(bundleOutput, sign(codeWithMap.code), encoding); - log('Done writing bundle output'); - - if (sourcemapOutput) { - log('Writing sourcemap output to:', sourcemapOutput); - fs.writeFileSync(sourcemapOutput, codeWithMap.map); - log('Done writing sourcemap output'); - } - if (!assetsDest) { console.warn('Assets destination folder is not set, skipping...'); return Promise.resolve(); @@ -44,7 +30,7 @@ function saveBundleAndMap( : getAssetDestPathIOS; const filesToCopy = Object.create(null); // Map src -> dest - codeWithMap.assets + assets .filter(asset => !asset.deprecated) .forEach(asset => asset.scales.forEach((scale, idx) => { @@ -94,4 +80,4 @@ function copy(src, dest, callback) { }); } -module.exports = saveBundleAndMap; +module.exports = saveAssets; diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index e0a29684d9bb..e1a294f4097a 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -35,6 +35,15 @@ exports.buildBundle = function(options, bundleOptions) { }); }; +exports.buildPrepackBundle = function(options, bundleOptions) { + var server = createNonPersistentServer(options); + return server.buildPrepackBundle(bundleOptions) + .then(function(p) { + server.end(); + return p; + }); +}; + exports.buildPackageFromUrl = exports.buildBundleFromUrl = function(options, reqUrl) { var server = createNonPersistentServer(options); diff --git a/packager/react-packager/src/Bundler/PrepackBundle.js b/packager/react-packager/src/Bundler/PrepackBundle.js new file mode 100644 index 000000000000..123d3297a5c0 --- /dev/null +++ b/packager/react-packager/src/Bundler/PrepackBundle.js @@ -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. + */ +'use strict'; + +const fs = require('fs'); + +class PrepackBundle { + constructor(sourceMapUrl) { + this._finalized = false; + this._moduleIds = Object.create(null); + this._modules = Object.create(null); + this._eagerModules = []; + this._mainModule = null; + this._assets = []; + this._sourceMapUrl = sourceMapUrl; + } + + addModule(id, module, deps, isPolyfill) { + this._modules[module.sourcePath] = { module, deps }; + this._moduleIds[id] = module.sourcePath; + if (isPolyfill) { + this._eagerModules.push(id); + } + } + + addAsset(asset) { + this._assets.push(asset); + } + + // Synchronously load a file path. + _loadFilename(path) { + const module = this._modules[path]; + if (!module) { + throw new Error('Could not find file "' + path + '" in preloaded files.'); + } + return module.module.code; + } + + // Synchronously resolve a relative require from a parent module. + _resolveFilename(parentPath, relativePath) { + if (!parentPath) { + const resolvedPath = this._moduleIds[relativePath]; + if (!resolvedPath) { + throw new Error('Could not resolve "' + relativePath + '".'); + } + return resolvedPath; + } + const deps = this._modules[parentPath].deps; + const resolvedPath = deps[relativePath]; + if (!resolvedPath) { + throw new Error( + 'Could not resolve "' + relativePath + '" from "' + parentPath + '".' + ); + } + return resolvedPath; + } + + build(options) { + var prepack = require('prepack'); + + var batchedBridgeConfig = (options && options.batchedBridgeConfig) || null; + if (typeof batchedBridgeConfig === 'string') { + batchedBridgeConfig = JSON.parse( + fs.readFileSync(batchedBridgeConfig, 'utf-8') + ); + } + + var options = { + batchedBridgeConfig: batchedBridgeConfig, + environment: 'react-native', + resolveFilename: this._resolveFilename.bind(this), + loadFilename: this._loadFilename.bind(this), + eagerModules: this._eagerModules + }; + + return prepack.compileModule(this._mainModule, options); + } + + finalize(options) { + options = options || {}; + if (options.runMainModule) { + options.runBeforeMainModule.forEach(this._addRequireCall, this); + this._mainModule = options.mainModuleId; + } + + Object.freeze(this._moduleIds); + Object.freeze(this._modules); + Object.freeze(this._assets); + Object.freeze(this._eagerModules); + this._finalized = true; + } + + _addRequireCall(moduleId) { + this._eagerModules.push(moduleId); + } + + _assertFinalized() { + if (!this._finalized) { + throw new Error('Bundle needs to be finalized before getting any source'); + } + } + + getAssets() { + return this._assets; + } + + toJSON() { + if (!this._finalized) { + throw new Error('Cannot serialize bundle unless finalized'); + } + + return { + modules: this._modules, + moduleIds: this._moduleIds, + assets: this._assets, + sourceMapUrl: this._sourceMapUrl, + mainModule: this._mainModule, + eagerModules: this._eagerModules, + }; + } + + static fromJSON(json) { + const bundle = new PrepackBundle(json.sourceMapUrl); + bundle._assets = json.assets; + bundle._moduleIds = json.moduleIds; + bundle._modules = json.modules; + bundle._sourceMapUrl = json.sourceMapUrl; + + bundle._eagerModules = json.eagerModules; + bundle._mainModule = json.mainModule; + + Object.freeze(bundle._moduleIds); + Object.freeze(bundle._modules); + Object.freeze(bundle._assets); + Object.freeze(bundle._eagerModules); + + bundle._finalized = true; + + return bundle; + } +} + +module.exports = PrepackBundle; diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 82a0282be22b..5f5d795905e5 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -18,6 +18,7 @@ const Cache = require('../Cache'); const Transformer = require('../JSTransformer'); const Resolver = require('../Resolver'); const Bundle = require('./Bundle'); +const PrepackBundle = require('./PrepackBundle'); const Activity = require('../Activity'); const ModuleTransport = require('../lib/ModuleTransport'); const declareOpts = require('../lib/declareOpts'); @@ -171,7 +172,7 @@ class Bundler { if (bar) { bar.tick(); } - return transformed; + return this._wrapTransformedModule(response, module, transformed); }) ) ); @@ -187,6 +188,68 @@ class Bundler { }); } + prepackBundle({ + entryFile, + runModule: runMainModule, + runBeforeMainModule, + sourceMapUrl, + dev: isDev, + platform, + }) { + const bundle = new PrepackBundle(sourceMapUrl); + const findEventId = Activity.startEvent('find dependencies'); + let transformEventId; + let mainModuleId; + + return this.getDependencies(entryFile, isDev, platform).then((response) => { + Activity.endEvent(findEventId); + transformEventId = Activity.startEvent('transform'); + + let bar; + if (process.stdout.isTTY) { + bar = new ProgressBar('transforming [:bar] :percent :current/:total', { + complete: '=', + incomplete: ' ', + width: 40, + total: response.dependencies.length, + }); + } + + mainModuleId = response.mainModuleId; + + return Promise.all( + response.dependencies.map( + module => this._transformModule( + bundle, + response, + module, + platform + ).then(transformed => { + if (bar) { + bar.tick(); + } + + var deps = Object.create(null); + var pairs = response.getResolvedDependencyPairs(module); + if (pairs) { + pairs.forEach(pair => { + deps[pair[0]] = pair[1].path; + }); + } + + return module.getName().then(name => { + bundle.addModule(name, transformed, deps, module.isPolyfill()); + }); + }) + ) + ); + }).then(() => { + Activity.endEvent(transformEventId); + bundle.finalize({runBeforeMainModule, runMainModule, mainModuleId }); + return bundle; + }); + } + invalidateFile(filePath) { this._transformer.invalidateFile(filePath); } @@ -228,35 +291,32 @@ class Bundler { } _transformModule(bundle, response, module, platform = null) { - let transform; - if (module.isAsset_DEPRECATED()) { - transform = this.generateAssetModule_DEPRECATED(bundle, module); + return this.generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - transform = this.generateAssetModule(bundle, module, platform); + return this.generateAssetModule(bundle, module, platform); } else if (module.isJSON()) { - transform = generateJSONModule(module); + return generateJSONModule(module); } else { - transform = this._transformer.loadFileAndTransform( + return this._transformer.loadFileAndTransform( path.resolve(module.path) ); } + } - const resolver = this._resolver; - return transform.then( - transformed => resolver.wrapModule( - response, - module, - transformed.code - ).then( - code => new ModuleTransport({ - code: code, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }) - ) + _wrapTransformedModule(response, module, transformed) { + return this._resolver.wrapModule( + response, + module, + transformed.code + ).then( + code => new ModuleTransport({ + code: code, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }) ); } diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 1abb78cf6ece..28c5b4d1bc80 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -190,6 +190,17 @@ class Server { }); } + buildPrepackBundle(options) { + return Promise.resolve().then(() => { + if (!options.platform) { + options.platform = getPlatformExtension(options.entryFile); + } + + const opts = bundleOpts(options); + return this._bundler.prepackBundle(opts); + }); + } + buildBundleFromUrl(reqUrl) { const options = this._getOptionsFromUrl(reqUrl); return this.buildBundle(options); diff --git a/packager/react-packager/src/SocketInterface/SocketClient.js b/packager/react-packager/src/SocketInterface/SocketClient.js index 41ce0df14ee2..a344f5d97aee 100644 --- a/packager/react-packager/src/SocketInterface/SocketClient.js +++ b/packager/react-packager/src/SocketInterface/SocketClient.js @@ -9,6 +9,7 @@ 'use strict'; const Bundle = require('../Bundler/Bundle'); +const PrepackBundle = require('../Bundler/PrepackBundle'); const Promise = require('promise'); const bser = require('bser'); const debug = require('debug')('ReactNativePackager:SocketClient'); @@ -100,6 +101,13 @@ class SocketClient { }).then(json => Bundle.fromJSON(json)); } + buildPrepackBundle(options) { + return this._send({ + type: 'buildPrepackBundle', + data: options, + }).then(json => PrepackBundle.fromJSON(json)); + } + _send(message) { message.id = uid(); this._sock.write(bser.dumpToBuffer(message)); diff --git a/packager/react-packager/src/SocketInterface/SocketServer.js b/packager/react-packager/src/SocketInterface/SocketServer.js index 8258873a4bd5..56562e273c99 100644 --- a/packager/react-packager/src/SocketInterface/SocketServer.js +++ b/packager/react-packager/src/SocketInterface/SocketServer.js @@ -121,6 +121,14 @@ class SocketServer { ); break; + case 'buildPrepackBundle': + this._jobs++; + this._packagerServer.buildPrepackBundle(m.data).then( + (result) => this._reply(sock, m.id, 'result', result), + handleError, + ); + break; + case 'getOrderedDependencyPaths': this._jobs++; this._packagerServer.getOrderedDependencyPaths(m.data).then( From 16dd5d664b4a269aee0989eb257790a38ca83766 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Sat, 21 Nov 2015 11:08:31 -0800 Subject: [PATCH 0038/1411] Add systrace markers to module require Summary: public Show modules' dependencies and time to load. Reviewed By: davidaurelio Differential Revision: D2603245 fb-gh-sync-id: a1d5067a8522b908b87fdfdd51ff4c4fdbc2edfc --- .../src/Resolver/polyfills/require.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packager/react-packager/src/Resolver/polyfills/require.js b/packager/react-packager/src/Resolver/polyfills/require.js index 6ebfee90a1b2..d7550ba1b9bb 100644 --- a/packager/react-packager/src/Resolver/polyfills/require.js +++ b/packager/react-packager/src/Resolver/polyfills/require.js @@ -54,9 +54,13 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; + __DEV__ && BridgeProfiling().profile(id); + // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); + + __DEV__ && BridgeProfiling().profileEnd(); } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -66,6 +70,16 @@ return mod.module.exports; } + const BridgeProfiling = __DEV__ && (() => { + var _BridgeProfiling; + try { + _BridgeProfiling = require('BridgeProfiling'); + } catch(e) {} + + return _BridgeProfiling && _BridgeProfiling.profile ? + _BridgeProfiling : { profile: () => {}, profileEnd: () => {} }; + }); + global.__d = define; global.require = require; })(this); From 156b5ad79e16fb93a22a7d5837003c6d08bfe33d Mon Sep 17 00:00:00 2001 From: Rishabh Mehan Date: Sun, 22 Nov 2015 00:14:20 -0800 Subject: [PATCH 0039/1411] Adding DareU to the Showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 5d485df69d2f..14c181fedf6c 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -43,6 +43,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/company-name-search/id1043824076', author: 'The Formations Factory Ltd', }, + { + name: 'DareU - dare your friends, dare the world', + icon: 'http://a3.mzstatic.com/us/r30/Purple6/v4/10/fb/6a/10fb6a50-57c8-061a-d865-503777bf7f00/icon175x175.png', + link: 'https://itunes.apple.com/us/app/dareu-dare-your-friends-dare/id1046434563?mt=8', + author: 'Rishabh Mehan', + }, { name: 'Discord', icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg', From 54470021ca418a021421dc9fd079726ae71a33e3 Mon Sep 17 00:00:00 2001 From: panjianjun Date: Mon, 23 Nov 2015 01:18:46 -0800 Subject: [PATCH 0040/1411] remove prepare_command in podspec file Summary: since 0.13.2 , prepare_command in podspec would download the dependencies of react-native again when you install react-native via cocoapods with local path. Closes https://github.com/facebook/react-native/pull/4291 Reviewed By: svcscm Differential Revision: D2685452 Pulled By: nicklockwood fb-gh-sync-id: b1c1a0f45897d3eb45be99db3633c899c35feb8e --- React.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/React.podspec b/React.podspec index 2f52c2ab8f8b..8083357a2b59 100644 --- a/React.podspec +++ b/React.podspec @@ -22,7 +22,6 @@ Pod::Spec.new do |s| s.default_subspec = 'Core' s.requires_arc = true s.platform = :ios, "7.0" - s.prepare_command = 'npm install --production' s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" s.subspec 'Core' do |ss| From f57c2a9140140bc54a83d4a87bde486e2f052ce5 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Mon, 23 Nov 2015 02:16:27 -0800 Subject: [PATCH 0041/1411] Add GuardedResultAsyncTask Reviewed By: andreicoman11 Differential Revision: D2679459 fb-gh-sync-id: 8a9ec170ce76bbc3340c9e8872e19b78ae5a5c2d --- .../react/bridge/GuardedAsyncTask.java | 5 +-- .../react/bridge/GuardedResultAsyncTask.java | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java index 917c1279c667..83a7fb014e34 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java @@ -16,9 +16,8 @@ * handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if * the app is in dev mode. * - * This class doesn't allow doInBackground to return a results. This is mostly because when this - * class was written that functionality wasn't used and it would require some extra code to make - * work correctly with caught exceptions. Don't let that stop you from adding it if you need it :) + * This class doesn't allow doInBackground to return a results. If you need this + * use GuardedResultAsyncTask instead. */ public abstract class GuardedAsyncTask extends AsyncTask { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java new file mode 100644 index 000000000000..db13a71464e4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java @@ -0,0 +1,43 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.bridge; + +import android.os.AsyncTask; + +/** + * Abstract base for a AsyncTask with result support that should have any RuntimeExceptions it + * throws handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} + * registered if the app is in dev mode. + */ +public abstract class GuardedResultAsyncTask + extends AsyncTask { + + private final ReactContext mReactContext; + + protected GuardedResultAsyncTask(ReactContext reactContext) { + mReactContext = reactContext; + } + + @Override + protected final Result doInBackground(Void... params) { + try { + return doInBackgroundGuarded(); + } catch (RuntimeException e) { + mReactContext.handleException(e); + throw e; + } + } + + @Override + protected final void onPostExecute(Result result) { + try { + onPostExecuteGuarded(result); + } catch (RuntimeException e) { + mReactContext.handleException(e); + } + } + + protected abstract Result doInBackgroundGuarded(); + protected abstract void onPostExecuteGuarded(Result result); + +} From 274c5c78c4933a09335fca0e2fdc266c80fe4017 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Mon, 23 Nov 2015 02:16:51 -0800 Subject: [PATCH 0042/1411] Support cookies on Android Summary: This adds a persistent cookie store that shares cookies with WebView. Add a `ForwardingCookieHandler` to OkHttp that uses the underlying Android webkit `CookieManager`. Use a `LazyCookieHandler` to defer initialization of `CookieManager` as this will in turn trigger initialization of the Chromium stack in KitKat+ which takes some time. This was we will incur this cost on a background network thread instead of during startup. Also add a `clearCookies()` method to the network module. Add a cookies example to the XHR example. This example should also work for iOS (except for the clear cookies part). They are for now just scoped to Android. Closes #2792. public Reviewed By: andreicoman11 Differential Revision: D2615550 fb-gh-sync-id: ff726a35f0fc3c7124d2f755448fe24c9d1caf21 --- Examples/UIExplorer/XHRExample.android.js | 7 + Examples/UIExplorer/XHRExampleCookies.js | 128 ++++++++++ Libraries/Network/RCTNetworking.android.js | 4 + .../react/modules/fresco/FrescoModule.java | 3 +- .../network/ForwardingCookieHandler.java | 230 ++++++++++++++++++ .../modules/network/NetworkingModule.java | 26 +- .../modules/network/OkHttpClientProvider.java | 36 ++- 7 files changed, 419 insertions(+), 15 deletions(-) create mode 100644 Examples/UIExplorer/XHRExampleCookies.js create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js index ab272d133da2..ad4bbf772a90 100644 --- a/Examples/UIExplorer/XHRExample.android.js +++ b/Examples/UIExplorer/XHRExample.android.js @@ -27,6 +27,8 @@ var { } = React; var XHRExampleHeaders = require('./XHRExampleHeaders'); +var XHRExampleCookies = require('./XHRExampleCookies'); + // TODO t7093728 This is a simlified XHRExample.ios.js. // Once we have Camera roll, Toast, Intent (for opening URLs) @@ -284,6 +286,11 @@ exports.examples = [{ render() { return ; } +}, { + title: 'Cookies', + render() { + return ; + } }]; var styles = StyleSheet.create({ diff --git a/Examples/UIExplorer/XHRExampleCookies.js b/Examples/UIExplorer/XHRExampleCookies.js new file mode 100644 index 000000000000..310accc19b60 --- /dev/null +++ b/Examples/UIExplorer/XHRExampleCookies.js @@ -0,0 +1,128 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; + +var RCTNetworking = require('RCTNetworking'); + +class XHRExampleCookies extends React.Component { + constructor(props: any) { + super(props); + this.cancelled = false; + this.state = { + status: '', + a: 1, + b: 2, + }; + } + + setCookie(domain: string) { + var {a, b} = this.state; + var url = `https://${domain}/cookies/set?a=${a}&b=${b}`; + fetch(url).then((response) => { + this.setStatus(`Cookies a=${a}, b=${b} set`); + }); + + this.setState({ + status: 'Setting cookies...', + a: a + 1, + b: b + 2, + }); + } + + getCookies(domain: string) { + fetch(`https://${domain}/cookies`).then((response) => { + return response.json(); + }).then((data) => { + this.setStatus(`Got cookies ${JSON.stringify(data.cookies)} from server`); + }); + + this.setStatus('Getting cookies...'); + } + + clearCookies() { + RCTNetworking.clearCookies((cleared) => { + this.setStatus('Cookies cleared, had cookies=' + cleared); + }); + } + + setStatus(status: string) { + this.setState({status}); + } + + render() { + return ( + + + + Set cookie + + + + + Set cookie (EU) + + + + + Get cookies + + + + + Get cookies (EU) + + + + + Clear cookies + + + {this.state.status} + + ); + } +} + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 8, + }, +}); + +module.exports = XHRExampleCookies; diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js index e8c4be0bd109..351524eb991a 100644 --- a/Libraries/Network/RCTNetworking.android.js +++ b/Libraries/Network/RCTNetworking.android.js @@ -40,6 +40,10 @@ class RCTNetworking { static abortRequest(requestId) { RCTNetworkingNative.abortRequest(requestId); } + + static clearCookies(callback) { + RCTNetworkingNative.clearCookies(callback); + } } module.exports = RCTNetworking; diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 14b95c0aebdc..8a980cb46e18 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -80,7 +80,8 @@ public void loadLibrary(String libraryName) { } Context context = this.getReactApplicationContext().getApplicationContext(); - OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); + OkHttpClient okHttpClient = + OkHttpClientProvider.getCookieAwareOkHttpClient(getReactApplicationContext()); ImagePipelineConfig.Builder builder = OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java new file mode 100644 index 000000000000..d24540ceaa4d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java @@ -0,0 +1,230 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.modules.network; + +import javax.annotation.Nullable; + +import java.io.IOException; +import java.net.CookieHandler; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.ValueCallback; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.GuardedAsyncTask; +import com.facebook.react.bridge.GuardedResultAsyncTask; +import com.facebook.react.bridge.ReactContext; + +/** + * Cookie handler that forwards all cookies to the WebView CookieManager. + * + * This class relies on CookieManager to persist cookies to disk so cookies may be lost if the + * application is terminated before it syncs. + */ +public class ForwardingCookieHandler extends CookieHandler { + private static final String VERSION_ZERO_HEADER = "Set-cookie"; + private static final String VERSION_ONE_HEADER = "Set-cookie2"; + private static final String COOKIE_HEADER = "Cookie"; + + // As CookieManager was synchronous before API 21 this class emulates the async behavior on <21. + private static final boolean USES_LEGACY_STORE = Build.VERSION.SDK_INT < 21; + + private final CookieSaver mCookieSaver; + private final ReactContext mContext; + private @Nullable CookieManager mCookieManager; + + public ForwardingCookieHandler(ReactContext context) { + mContext = context; + mCookieSaver = new CookieSaver(); + } + + @Override + public Map> get(URI uri, Map> headers) + throws IOException { + String cookies = getCookieManager().getCookie(uri.toString()); + if (TextUtils.isEmpty(cookies)) { + return Collections.emptyMap(); + } + + return Collections.singletonMap(COOKIE_HEADER, Collections.singletonList(cookies)); + } + + @Override + public void put(URI uri, Map> headers) throws IOException { + String url = uri.toString(); + for (Map.Entry> entry : headers.entrySet()) { + String key = entry.getKey(); + if (key != null && isCookieHeader(key)) { + addCookies(url, entry.getValue()); + } + } + } + + public void clearCookies(final Callback callback) { + if (USES_LEGACY_STORE) { + new GuardedResultAsyncTask(mContext) { + @Override + protected Boolean doInBackgroundGuarded() { + getCookieManager().removeAllCookie(); + mCookieSaver.onCookiesModified(); + return true; + } + + @Override + protected void onPostExecuteGuarded(Boolean result) { + callback.invoke(result); + } + }.execute(); + } else { + clearCookiesAsync(callback); + } + } + + private void clearCookiesAsync(final Callback callback) { + getCookieManager().removeAllCookies( + new ValueCallback() { + @Override + public void onReceiveValue(Boolean value) { + mCookieSaver.onCookiesModified(); + callback.invoke(value); + } + }); + } + + public void destroy() { + if (USES_LEGACY_STORE) { + getCookieManager().removeExpiredCookie(); + mCookieSaver.persistCookies(); + } + } + + private void addCookies(final String url, final List cookies) { + if (USES_LEGACY_STORE) { + runInBackground( + new Runnable() { + @Override + public void run() { + for (String cookie : cookies) { + getCookieManager().setCookie(url, cookie); + } + mCookieSaver.onCookiesModified(); + } + }); + } else { + for (String cookie : cookies) { + addCookieAsync(url, cookie); + } + mCookieSaver.onCookiesModified(); + } + } + + @TargetApi(21) + private void addCookieAsync(String url, String cookie) { + getCookieManager().setCookie(url, cookie, null); + } + + private static boolean isCookieHeader(String name) { + return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER); + } + + private void runInBackground(final Runnable runnable) { + new GuardedAsyncTask(mContext) { + @Override + protected void doInBackgroundGuarded(Void... params) { + runnable.run(); + } + }.execute(); + } + + /** + * Instantiating CookieManager in KitKat+ will load the Chromium task taking a 100ish ms so we + * do it lazily to make sure it's done on a background thread as needed. + */ + private CookieManager getCookieManager() { + if (mCookieManager == null) { + possiblyWorkaroundSyncManager(mContext); + mCookieManager = CookieManager.getInstance(); + + if (USES_LEGACY_STORE) { + mCookieManager.removeExpiredCookie(); + } + } + + return mCookieManager; + } + + private static void possiblyWorkaroundSyncManager(Context context) { + if (USES_LEGACY_STORE) { + // This is to work around a bug where CookieManager may fail to instantiate if + // CookieSyncManager has never been created. Note that the sync() may not be required but is + // here of legacy reasons. + CookieSyncManager syncManager = CookieSyncManager.createInstance(context); + syncManager.sync(); + } + } + + /** + * Responsible for flushing cookies to disk. Flushes to disk with a maximum delay of 30 seconds. + * This class is only active if we are on API < 21. + */ + private class CookieSaver { + private static final int MSG_PERSIST_COOKIES = 1; + + private static final int TIMEOUT = 30 * 1000; // 30 seconds + + private final Handler mHandler; + + public CookieSaver() { + mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_PERSIST_COOKIES) { + persistCookies(); + return true; + } else { + return false; + } + } + }); + } + + public void onCookiesModified() { + if (USES_LEGACY_STORE) { + mHandler.sendEmptyMessageDelayed(MSG_PERSIST_COOKIES, TIMEOUT); + } + } + + public void persistCookies() { + mHandler.removeMessages(MSG_PERSIST_COOKIES); + runInBackground( + new Runnable() { + @Override + public void run() { + if (USES_LEGACY_STORE) { + CookieSyncManager syncManager = CookieSyncManager.getInstance(); + syncManager.sync(); + } else { + flush(); + } + } + }); + } + + @TargetApi(21) + private void flush() { + getCookieManager().flush(); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 4ddbf1f60587..db316e3710e3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.net.CookieHandler; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.GuardedAsyncTask; @@ -72,19 +73,19 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { } /** - * @param reactContext the ReactContext of the application + * @param context the ReactContext of the application */ - public NetworkingModule(ReactApplicationContext reactContext) { - this(reactContext, null, OkHttpClientProvider.getOkHttpClient()); + public NetworkingModule(final ReactApplicationContext context) { + this(context, null, OkHttpClientProvider.getCookieAwareOkHttpClient(context)); } /** - * @param reactContext the ReactContext of the application + * @param context the ReactContext of the application * @param defaultUserAgent the User-Agent header that will be set for all requests where the * caller does not provide one explicitly */ - public NetworkingModule(ReactApplicationContext reactContext, String defaultUserAgent) { - this(reactContext, defaultUserAgent, OkHttpClientProvider.getOkHttpClient()); + public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) { + this(context, defaultUserAgent, OkHttpClientProvider.getCookieAwareOkHttpClient(context)); } public NetworkingModule(ReactApplicationContext reactContext, OkHttpClient client) { @@ -100,6 +101,11 @@ public String getName() { public void onCatalystInstanceDestroy() { mShuttingDown = true; mClient.cancel(null); + + CookieHandler cookieHandler = mClient.getCookieHandler(); + if (cookieHandler instanceof ForwardingCookieHandler) { + ((ForwardingCookieHandler) cookieHandler).destroy(); + } } @ReactMethod @@ -311,6 +317,14 @@ protected void doInBackgroundGuarded(Void... params) { }.execute(); } + @ReactMethod + public void clearCookies(com.facebook.react.bridge.Callback callback) { + CookieHandler cookieHandler = mClient.getCookieHandler(); + if (cookieHandler instanceof ForwardingCookieHandler) { + ((ForwardingCookieHandler) cookieHandler).clearCookies(callback); + } + } + private @Nullable MultipartBuilder constructMultipartBody( ReadableArray body, String contentType, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java index fb700201302c..699ffa5255c4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java @@ -9,7 +9,12 @@ package com.facebook.react.modules.network; +import javax.annotation.Nullable; + import java.util.concurrent.TimeUnit; + +import com.facebook.react.bridge.ReactContext; + import com.squareup.okhttp.OkHttpClient; /** @@ -19,18 +24,33 @@ public class OkHttpClientProvider { // Centralized OkHttpClient for all networking requests. - private static OkHttpClient sClient; + private static @Nullable OkHttpClient sClient; + private static ForwardingCookieHandler sCookieHandler; public static OkHttpClient getOkHttpClient() { if (sClient == null) { - // TODO: #7108751 plug in stetho - sClient = new OkHttpClient(); - - // No timeouts by default - sClient.setConnectTimeout(0, TimeUnit.MILLISECONDS); - sClient.setReadTimeout(0, TimeUnit.MILLISECONDS); - sClient.setWriteTimeout(0, TimeUnit.MILLISECONDS); + sClient = createClient(); } return sClient; } + + public static OkHttpClient getCookieAwareOkHttpClient(ReactContext context) { + if (sCookieHandler == null) { + sCookieHandler = new ForwardingCookieHandler(context); + getOkHttpClient().setCookieHandler(sCookieHandler); + } + return getOkHttpClient(); + } + + private static OkHttpClient createClient() { + // TODO: #7108751 plug in stetho + OkHttpClient client = new OkHttpClient(); + + // No timeouts by default + client.setConnectTimeout(0, TimeUnit.MILLISECONDS); + client.setReadTimeout(0, TimeUnit.MILLISECONDS); + client.setWriteTimeout(0, TimeUnit.MILLISECONDS); + + return client; + } } From f827a513a1a312a9d7b2f056cd025f9621426fff Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 23 Nov 2015 04:18:20 -0800 Subject: [PATCH 0043/1411] Fixed double-callback for cached images Summary: public A missing return statement in RCTImageLoader meant that cached images would be loaded twice - once from cache and again from the source. This was mostly innocuous, causing only a slight perf regression due to the image cache being effectively disabled, however in some cases (such as RCTImageEditingManager.cropImage) it caused the success callback to fire twice, resulting in a crash. Reviewed By: fkgozali Differential Revision: D2684956 fb-gh-sync-id: 7580a6fbfe00a30807951803e04bfcdbee3bb80a --- Libraries/Image/RCTImageLoader.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 8c77828b058b..7f67b53b7fda 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -290,6 +290,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest:request]; if (cachedResponse) { processResponse(cachedResponse.response, cachedResponse.data, nil); + return; } // Download image From d92b3b3e9b453231853f0440c37514d02252d70f Mon Sep 17 00:00:00 2001 From: Andrew Sardone Date: Mon, 23 Nov 2015 08:20:20 -0500 Subject: [PATCH 0044/1411] Clarify --dev flag for react-native-bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Given some [confusion around `react-native bundle`'s `--dev` flag][1], this hopes to clear somet things up int he docs by… - Removing mentions of the `__DEV__` environment variable - I think it confuses the user on how to work with the command-line flag, and frankly it seems like an internal implementation detail from the perspective of react-native-cli. We should focus on what the `--dev` flag does (e.g., toggles dev warnings, performance optimizations). - Adding a minimal note about native-land's build configurations and how that should be checked for production builds [1]: https://github.com/facebook/react-native/issues/4181 --- docs/RunningOnDeviceIOS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/RunningOnDeviceIOS.md b/docs/RunningOnDeviceIOS.md index f4bc780235ae..5dae7ccc4a05 100644 --- a/docs/RunningOnDeviceIOS.md +++ b/docs/RunningOnDeviceIOS.md @@ -32,7 +32,7 @@ You can also pack all the JavaScript code within the app itself. This way you ca The bundle script supports a couple of flags: -* `--dev` - sets the value of `__DEV__` variable to true. When `true` it turns on a bunch of useful development warnings. For production it is recommended to set `__DEV__=false`. +* `--dev` - a boolean with a default value of `true`. With the `--dev true` flag, the bundled JavaScript code turns on useful development warnings and limits performance optimizations. For production it is recommended to pass `--dev false`. Also for production, be sure to have your native build configuration set to `Release` (e.g., Xcode's Release configuration for iOS and gradle's `assembleRelease` task for Android) in order to disable things like the shake-to-show developer menu. * `--minify` - pipe the JS code through UglifyJS. Note that on 0.14 we'll change the API of `react-native bundle`. The major changes are: From 6b3a6e59580e71256a3a6b885b0fb8f88bd1cb6e Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 23 Nov 2015 06:35:18 -0800 Subject: [PATCH 0045/1411] Unbreak open source release due to import of internal module Reviewed By: majak Differential Revision: D2685592 fb-gh-sync-id: eb5e9baf15d08ff1ec2c29e0ca1fe94df079d79e --- Libraries/ReactIOS/YellowBox.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Libraries/ReactIOS/YellowBox.js b/Libraries/ReactIOS/YellowBox.js index c439b4e20cba..5c1e0e37284d 100644 --- a/Libraries/ReactIOS/YellowBox.js +++ b/Libraries/ReactIOS/YellowBox.js @@ -59,8 +59,21 @@ if (__DEV__) { }; } +/** + * Simple function for formatting strings. + * + * Replaces placeholders with values passed as extra arguments + * + * @param {string} format the base string + * @param ...args the values to insert + * @return {string} the replaced string + */ +function sprintf(format, ...args) { + var index = 0; + return format.replace(/%s/g, match => args[index++]); +} + function updateWarningMap(format, ...args): void { - const sprintf = require('sprintf'); const stringifySafe = require('stringifySafe'); format = String(format); From 02b67d9d403e4c7fb02bbfc2b21b74b8db1d1352 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Mon, 23 Nov 2015 07:01:06 -0800 Subject: [PATCH 0046/1411] BridgeProfiling measure methods Summary: public Add measure() family of methods which allow to easily swizzle methods for profiling Reviewed By: tadeuzagallo Differential Revision: D2679904 fb-gh-sync-id: 3724440e1bdaca9e854f4d4124a897a204966dc7 --- Libraries/Utilities/BridgeProfiling.js | 51 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index e62c8b76dcec..e707b357d3d1 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -78,7 +78,56 @@ var BridgeProfiling = { }; }); } catch(err) {} - } + }, + + /** + * Measures multiple methods of a class. For example, you can do: + * BridgeProfiling.measureMethods(JSON, 'JSON', ['parse', 'stringify']); + * + * @param object + * @param objectName + * @param methodNames Map from method names to method display names. + */ + measureMethods(object: any, objectName: string, methodNames: Array): void { + if (!__DEV__) { + return; + } + + methodNames.forEach(methodName => { + object[methodName] = BridgeProfiling.measure( + objectName, + methodName, + object[methodName] + ); + }); + }, + + /** + * Returns an profiled version of the input function. For example, you can: + * JSON.parse = BridgeProfiling.measure('JSON', 'parse', JSON.parse); + * + * @param objName + * @param fnName + * @param {function} func + * @return {function} replacement function + */ + measure(objName: string, fnName: string, func: any): any { + if (!__DEV__) { + return func; + } + + var profileName = `${objName}.${fnName}`; + return function() { + if (!_enabled) { + return func.apply(this, arguments); + } + + BridgeProfiling.profile(profileName); + var ret = func.apply(this, arguments); + BridgeProfiling.profileEnd(); + return ret; + }; + }, }; BridgeProfiling.setEnabled(global.__RCTProfileIsProfiling || false); From e4dca7a1fa29bf500849a0fea4f0f6ecc3581293 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Mon, 23 Nov 2015 07:01:11 -0800 Subject: [PATCH 0047/1411] Add JSON methods to systrace Summary: public Add JSON methods to systraces Reviewed By: jspahrsummers Differential Revision: D2679719 fb-gh-sync-id: d8bbdc9577264b1de01d7bb52656f4f1a86a5982 --- Libraries/Utilities/BridgeProfiling.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index e707b357d3d1..f71b6511c7ed 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -80,6 +80,15 @@ var BridgeProfiling = { } catch(err) {} }, + /* This is not called by default due to perf overhead but it's useful + if you want to find traces which spend too much time in JSON. */ + swizzleJSON() { + BridgeProfiling.measureMethods(JSON, 'JSON', [ + 'parse', + 'stringify' + ]); + }, + /** * Measures multiple methods of a class. For example, you can do: * BridgeProfiling.measureMethods(JSON, 'JSON', ['parse', 'stringify']); From e0d53e1c48d34419ea9a0622bd9b360c51ec3b78 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Mon, 23 Nov 2015 09:17:32 -0800 Subject: [PATCH 0048/1411] Fix move gesture handling. Summary: public The gesture that moves scene around should only be attached when the move starts at the moment that the first move is granted. No move would ever be granted if the move event is prevented by the decendent children (e.g. a slider component). For now, the move gesture is attached at `onPanResponderGrant` instead of `onPanResponderMove` thus we'd create "ghost-move-gesture" when no actual moves is received my the navigator. Reviewed By: fkgozali Differential Revision: D2683802 fb-gh-sync-id: 50ae877787167511df48378304bd2ad665c73300 --- .../CustomComponents/Navigator/Navigator.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 8bfd55457a3d..ff7ff2c632f7 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -329,7 +329,6 @@ var Navigator = React.createClass({ }); this.panGesture = PanResponder.create({ onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder, - onPanResponderGrant: this._handlePanResponderGrant, onPanResponderRelease: this._handlePanResponderRelease, onPanResponderMove: this._handlePanResponderMove, onPanResponderTerminate: this._handlePanResponderTerminate, @@ -609,7 +608,8 @@ var Navigator = React.createClass({ if (!sceneConfig) { return false; } - this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState); + this._expectingGestureGrant = + this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState); return !!this._expectingGestureGrant; }, @@ -621,16 +621,6 @@ var Navigator = React.createClass({ return wouldOverswipeForward || wouldOverswipeBack; }, - _handlePanResponderGrant: function(e, gestureState) { - invariant( - this._expectingGestureGrant, - 'Responder granted unexpectedly.' - ); - this._attachGesture(this._expectingGestureGrant); - this._onAnimationStart(); - this._expectingGestureGrant = null; - }, - _deltaForGestureAction: function(gestureAction) { switch (gestureAction) { case 'pop': @@ -735,6 +725,16 @@ var Navigator = React.createClass({ }, _handlePanResponderMove: function(e, gestureState) { + if (this._isMoveGestureAttached !== undefined) { + invariant( + this._expectingGestureGrant, + 'Responder granted unexpectedly.' + ); + this._attachGesture(this._expectingGestureGrant); + this._onAnimationStart(); + this._expectingGestureGrant = undefined; + } + var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex]; if (this.state.activeGesture) { var gesture = sceneConfig.gestures[this.state.activeGesture]; @@ -825,7 +825,7 @@ var Navigator = React.createClass({ this._eligibleGestures = this._eligibleGestures.slice().splice(gestureIndex, 1); } }); - return matchedGesture; + return matchedGesture || null; }, _transitionSceneStyle: function(fromIndex, toIndex, progress, index) { From 0c2ee5d480e696f8621252c936a8773e8de9f8b6 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Mon, 23 Nov 2015 08:00:46 -0800 Subject: [PATCH 0049/1411] Update Android Touch events Summary: public This moves Android touch events to parity with iOS. The locationX,Y value passed to js now is view relative to the view that is handling the touch. The pageX,Y is now relative to the root view. Reviewed By: andreicoman11 Differential Revision: D2670028 fb-gh-sync-id: 5438640d6c78633629b9a308a59cc306bb07815e --- .../com/facebook/react/ReactRootView.java | 53 ++++++++++++++++--- .../react/uimanager/TouchTargetHelper.java | 27 ++++++++-- .../react/uimanager/events/TouchEvent.java | 31 +++++++++-- .../react/uimanager/events/TouchesHelper.java | 49 ++++++++++------- 4 files changed, 125 insertions(+), 35 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 0a4eadc00862..0f51cd86d4bb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -61,6 +61,9 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView private @Nullable String mJSModuleName; private @Nullable Bundle mLaunchOptions; private int mTargetTag = -1; + // Note mTargetCoordinates are Y,X + // TODO: t9136625 tracks moving to X,Y + private final float[] mTargetCoordinates = new float[2]; private boolean mChildIsHandlingNativeGesture = false; private boolean mWasMeasured = false; private boolean mAttachScheduled = false; @@ -143,9 +146,19 @@ private void handleTouchEvent(MotionEvent ev) { // {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling // this gesture mChildIsHandlingNativeGesture = false; - mTargetTag = TouchTargetHelper.findTargetTagForTouch(ev.getY(), ev.getX(), this); + mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch( + ev.getY(), + ev.getX(), + this, + mTargetCoordinates); eventDispatcher.dispatchEvent( - TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev)); + TouchEvent.obtain( + mTargetTag, + SystemClock.uptimeMillis(), + TouchEventType.START, + ev, + mTargetCoordinates[1], + mTargetCoordinates[0])); } else if (mChildIsHandlingNativeGesture) { // If the touch was intercepted by a child, we've already sent a cancel event to JS for this // gesture, so we shouldn't send any more touches related to it. @@ -161,20 +174,44 @@ private void handleTouchEvent(MotionEvent ev) { // End of the gesture. We reset target tag to -1 and expect no further event associated with // this gesture. eventDispatcher.dispatchEvent( - TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev)); + TouchEvent.obtain( + mTargetTag, + SystemClock.uptimeMillis(), + TouchEventType.END, + ev, + mTargetCoordinates[1], + mTargetCoordinates[0])); mTargetTag = -1; } else if (action == MotionEvent.ACTION_MOVE) { // Update pointer position for current gesture eventDispatcher.dispatchEvent( - TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.MOVE, ev)); + TouchEvent.obtain( + mTargetTag, + SystemClock.uptimeMillis(), + TouchEventType.MOVE, + ev, + mTargetCoordinates[1], + mTargetCoordinates[0])); } else if (action == MotionEvent.ACTION_POINTER_DOWN) { // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer eventDispatcher.dispatchEvent( - TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev)); + TouchEvent.obtain( + mTargetTag, + SystemClock.uptimeMillis(), + TouchEventType.START, + ev, + mTargetCoordinates[1], + mTargetCoordinates[0])); } else if (action == MotionEvent.ACTION_POINTER_UP) { // Exactly onw of the pointers goes up eventDispatcher.dispatchEvent( - TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev)); + TouchEvent.obtain( + mTargetTag, + SystemClock.uptimeMillis(), + TouchEventType.END, + ev, + mTargetCoordinates[1], + mTargetCoordinates[0])); } else if (action == MotionEvent.ACTION_CANCEL) { dispatchCancelEvent(ev); mTargetTag = -1; @@ -223,7 +260,9 @@ private void dispatchCancelEvent(MotionEvent androidEvent) { mTargetTag, SystemClock.uptimeMillis(), TouchEventType.CANCEL, - androidEvent)); + androidEvent, + mTargetCoordinates[1], + mTargetCoordinates[0])); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 0658f7005ccd..409a9f91f402 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -39,17 +39,34 @@ public static int findTargetTagForTouch( float eventY, float eventX, ViewGroup viewGroup) { + return findTargetTagAndCoordinatesForTouch(eventY, eventX, viewGroup, mEventCoords); + } + + /** + * Find touch event target view within the provided container given the coordinates provided + * via {@link MotionEvent}. + * + * @param eventY the Y screen coordinate of the touch location + * @param eventX the X screen coordinate of the touch location + * @param viewGroup the container view to traverse + * @param viewCoords an out parameter that will return the Y,X value in the target view + * @return the react tag ID of the child view that should handle the event + */ + public static int findTargetTagAndCoordinatesForTouch( + float eventY, + float eventX, + ViewGroup viewGroup, + float[] viewCoords) { UiThreadUtil.assertOnUiThread(); int targetTag = viewGroup.getId(); // Store eventCoords in array so that they are modified to be relative to the targetView found. - float[] eventCoords = mEventCoords; - eventCoords[0] = eventY; - eventCoords[1] = eventX; - View nativeTargetView = findTouchTargetView(eventCoords, viewGroup); + viewCoords[0] = eventY; + viewCoords[1] = eventX; + View nativeTargetView = findTouchTargetView(viewCoords, viewGroup); if (nativeTargetView != null) { View reactTargetView = findClosestReactAncestor(nativeTargetView); if (reactTargetView != null) { - targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]); + targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]); } } return targetTag; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java index 407b8c69108e..1600b74783d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java @@ -35,12 +35,14 @@ public static TouchEvent obtain( int viewTag, long timestampMs, TouchEventType touchEventType, - MotionEvent motionEventToCopy) { + MotionEvent motionEventToCopy, + float viewX, + float viewY) { TouchEvent event = EVENTS_POOL.acquire(); if (event == null) { event = new TouchEvent(); } - event.init(viewTag, timestampMs, touchEventType, motionEventToCopy); + event.init(viewTag, timestampMs, touchEventType, motionEventToCopy, viewX, viewY); return event; } @@ -48,6 +50,10 @@ public static TouchEvent obtain( private @Nullable TouchEventType mTouchEventType; private short mCoalescingKey; + // Coordinates in the ViewTag coordinate space + private float mViewX; + private float mViewY; + private TouchEvent() { } @@ -55,7 +61,9 @@ private void init( int viewTag, long timestampMs, TouchEventType touchEventType, - MotionEvent motionEventToCopy) { + MotionEvent motionEventToCopy, + float viewX, + float viewY) { super.init(viewTag, timestampMs); short coalescingKey = 0; @@ -84,6 +92,8 @@ private void init( mTouchEventType = touchEventType; mMotionEvent = MotionEvent.obtain(motionEventToCopy); mCoalescingKey = coalescingKey; + mViewX = viewX; + mViewY = viewY; } @Override @@ -126,6 +136,19 @@ public void dispatch(RCTEventEmitter rctEventEmitter) { rctEventEmitter, Assertions.assertNotNull(mTouchEventType), getViewTag(), - Assertions.assertNotNull(mMotionEvent)); + this); + } + + public MotionEvent getMotionEvent() { + Assertions.assertNotNull(mMotionEvent); + return mMotionEvent; + } + + public float getViewX() { + return mViewX; + } + + public float getViewY() { + return mViewY; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index 56c6ff0ada2f..d312a402026b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -27,8 +27,6 @@ private static final String TIMESTAMP_KEY = "timeStamp"; private static final String POINTER_IDENTIFIER_KEY = "identifier"; - // TODO(7351435): remove when we standardize touchEvent payload, since iOS uses locationXYZ but - // Android uses pageXYZ. As a temporary solution, Android currently sends both. private static final String LOCATION_X_KEY = "locationX"; private static final String LOCATION_Y_KEY = "locationY"; @@ -37,23 +35,35 @@ * given {@param event} instance. This method use {@param reactTarget} parameter to set as a * target view id associated with current gesture. */ - private static WritableArray createsPointersArray(int reactTarget, MotionEvent event) { + private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) { WritableArray touches = Arguments.createArray(); + MotionEvent motionEvent = event.getMotionEvent(); - // Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the - // pointer at index 0. We use those value to calculate "raw" coordinates for other pointers - float offsetX = event.getRawX() - event.getX(); - float offsetY = event.getRawY() - event.getY(); + // Calculate the coordinates for the target view. + // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view + // The TouchEvent contains the X,Y of the touch in the coordinate space of the target view + // Subtracting them allows us to get the coordinates of the target view's top left corner + // We then use this when computing the view specific touches below + // Since only one view is actually handling even multiple touches, the values are all relative + // to this one target view. + float targetViewCoordinateX = motionEvent.getX() - event.getViewX(); + float targetViewCoordinateY = motionEvent.getY() - event.getViewY(); - for (int index = 0; index < event.getPointerCount(); index++) { + for (int index = 0; index < motionEvent.getPointerCount(); index++) { WritableMap touch = Arguments.createMap(); - touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX)); - touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY)); - touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index))); - touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index))); + // pageX,Y values are relative to the RootReactView + // the motionEvent already contains coordinates in that view + touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index))); + touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index))); + // locationX,Y values are relative to the target view + // To compute the values for the view, we subtract that views location from the event X,Y + float locationX = motionEvent.getX(index) - targetViewCoordinateX; + float locationY = motionEvent.getY(index) - targetViewCoordinateY; + touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX)); + touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY)); touch.putInt(TARGET_KEY, reactTarget); - touch.putDouble(TIMESTAMP_KEY, event.getEventTime()); - touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index)); + touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime()); + touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index)); touches.pushMap(touch); } @@ -67,25 +77,26 @@ private static WritableArray createsPointersArray(int reactTarget, MotionEvent e * @param rctEventEmitter Event emitter used to execute JS module call * @param type type of the touch event (see {@link TouchEventType}) * @param reactTarget target view react id associated with this gesture - * @param androidMotionEvent native touch event to read pointers count and coordinates from + * @param touchEvent native touch event to read pointers count and coordinates from */ public static void sendTouchEvent( RCTEventEmitter rctEventEmitter, TouchEventType type, int reactTarget, - MotionEvent androidMotionEvent) { + TouchEvent touchEvent) { - WritableArray pointers = createsPointersArray(reactTarget, androidMotionEvent); + WritableArray pointers = createsPointersArray(reactTarget, touchEvent); + MotionEvent motionEvent = touchEvent.getMotionEvent(); // For START and END events send only index of the pointer that is associated with that event // For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices WritableArray changedIndices = Arguments.createArray(); if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) { - for (int i = 0; i < androidMotionEvent.getPointerCount(); i++) { + for (int i = 0; i < motionEvent.getPointerCount(); i++) { changedIndices.pushInt(i); } } else if (type == TouchEventType.START || type == TouchEventType.END) { - changedIndices.pushInt(androidMotionEvent.getActionIndex()); + changedIndices.pushInt(motionEvent.getActionIndex()); } else { throw new RuntimeException("Unknown touch type: " + type); } From 3afa8b6c5f7df50a2f4ce000e042ae7b2633c10b Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 23 Nov 2015 17:32:59 +0000 Subject: [PATCH 0050/1411] Update breaking-changes.md --- breaking-changes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/breaking-changes.md b/breaking-changes.md index 5cb09da7966b..96f4b7e49e24 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -1,5 +1,9 @@ # Breaking Changes +## 0.16 + +- Touch events on Android now have coordinates consistent with iOS: https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6 + ## 0.15 - React Native now uses [React 0.14](http://facebook.github.io/react/blog/2015/10/07/react-v0.14.html); see the linked blog post for a changelog (at this time, the package split has not been made in RN (i.e., please continue to use `require('react-native')`) and refs to native components have not changed) From 659cc005ad74f760ceee1c529363bea94bca98a4 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 23 Nov 2015 09:48:43 -0800 Subject: [PATCH 0051/1411] Restore VSYNC markers on RCTProfile Summary: public The VSYNC markers got lost at some point when refactoring RCTBatchedBridge, restore it, but keep it in RCTProfile. Reviewed By: jspahrsummers Differential Revision: D2685805 fb-gh-sync-id: 1acad330de7baf004a83b41f90ba4b6532605de6 --- React/Profiler/RCTProfile.m | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index db23fb8c200e..681a464b7a77 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -46,6 +46,7 @@ static NSMutableDictionary *RCTProfileOngoingEvents; static NSTimeInterval RCTProfileStartTime; static NSUInteger RCTProfileEventID = 0; +static CADisplayLink *RCTProfileDisplayLink; #pragma mark - Macros @@ -266,6 +267,19 @@ void RCTProfileUnhookModules(RCTBridge *bridge) dispatch_group_leave(RCTProfileGetUnhookGroup()); } +#pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target + +@interface RCTProfile : NSObject +@end + +@implementation RCTProfile + ++ (void)vsync:(__unused CADisplayLink *)displayLink +{ + RCTProfileImmediateEvent(0, @"VSYNC", 'g'); +} + +@end #pragma mark - Public Functions @@ -312,6 +326,11 @@ void RCTProfileInit(RCTBridge *bridge) RCTProfileHookModules(bridge); + RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class] + selector:@selector(vsync:)]; + [RCTProfileDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] + forMode:NSRunLoopCommonModes]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling object:nil]; } @@ -329,6 +348,9 @@ void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *)) [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:nil]; + [RCTProfileDisplayLink invalidate]; + RCTProfileDisplayLink = nil; + RCTProfileUnhookModules(bridge); if (callbacks != NULL) { From 90de853cc1d06ec22b2a291ab76131a178aa17b5 Mon Sep 17 00:00:00 2001 From: dmmiller Date: Mon, 23 Nov 2015 18:19:58 +0000 Subject: [PATCH 0052/1411] Update GestureResponderSystem.md Update to reflect the reality of what pageX and pageY are on both platforms. --- docs/GestureResponderSystem.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/GestureResponderSystem.md b/docs/GestureResponderSystem.md index fac70d0d28c4..2c83274350c7 100644 --- a/docs/GestureResponderSystem.md +++ b/docs/GestureResponderSystem.md @@ -51,8 +51,8 @@ If the view is responding, the following handlers can be called: + `identifier` - The ID of the touch + `locationX` - The X position of the touch, relative to the element + `locationY` - The Y position of the touch, relative to the element - + `pageX` - The X position of the touch, relative to the screen - + `pageY` - The Y position of the touch, relative to the screen + + `pageX` - The X position of the touch, relative to the root element + + `pageY` - The Y position of the touch, relative to the root element + `target` - The node id of the element receiving the touch event + `timestamp` - A time identifier for the touch, useful for velocity calculation + `touches` - Array of all current touches on the screen From 56a56f33d144c96d330bd19eae7a84137f924efc Mon Sep 17 00:00:00 2001 From: Peter Lai Date: Mon, 23 Nov 2015 10:47:06 -0800 Subject: [PATCH 0053/1411] add method to determine if current activity is null Reviewed By: andreicoman11 Differential Revision: D2680281 fb-gh-sync-id: ac158fb55dd60d05b7e3444d68e06a010e04eef4 --- .../main/java/com/facebook/react/bridge/ReactContext.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index ee669462ad35..ca2d40c73434 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -225,15 +225,17 @@ public void handleException(RuntimeException e) { } } + public boolean hasCurrentActivity() { + return mCurrentActivity != null; + } + /** * Same as {@link Activity#startActivityForResult(Intent, int)}, this just redirects the call to * the current activity. Returns whether the activity was started, as this might fail if this * was called before the context is in the right state. */ public boolean startActivityForResult(Intent intent, int code, Bundle bundle) { - if (mCurrentActivity == null) { - return false; - } + Assertions.assertNotNull(mCurrentActivity); mCurrentActivity.startActivityForResult(intent, code, bundle); return true; } From b9580511a6e3d3096705b87c38bf7a76ddbdd7ff Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 23 Nov 2015 09:49:20 -0800 Subject: [PATCH 0054/1411] Codemod IntentAndroid.{openURI -> openURL} Reviewed By: olegbl Differential Revision: D2680128 fb-gh-sync-id: d9786b6c20d4d6f62cc79fb21ab6861afe30bb54 --- Libraries/Components/Intent/IntentAndroid.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/Intent/IntentAndroid.ios.js b/Libraries/Components/Intent/IntentAndroid.ios.js index 7ae19934d682..8b4af29ae6e3 100644 --- a/Libraries/Components/Intent/IntentAndroid.ios.js +++ b/Libraries/Components/Intent/IntentAndroid.ios.js @@ -11,7 +11,7 @@ 'use strict'; module.exports = { - openURI: function(url) { + openURL: function(url) { console.error('IntentAndroid is not supported on iOS'); }, }; From 5f226cf4c5176141ad9d67741acf1365d525b31e Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 23 Nov 2015 11:10:36 -0800 Subject: [PATCH 0055/1411] Update breaking-changes.md --- breaking-changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/breaking-changes.md b/breaking-changes.md index 96f4b7e49e24..77a6268fcb2b 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -3,6 +3,7 @@ ## 0.16 - Touch events on Android now have coordinates consistent with iOS: https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6 +- YellowBox enabled by default: https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85 ## 0.15 From cfd9f65cfef525ab17ae2181f6e64c3534b92cac Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Mon, 23 Nov 2015 12:29:57 -0800 Subject: [PATCH 0056/1411] Add space after url. Fixes #4292 Summary: Closes https://github.com/facebook/react-native/pull/4297 Reviewed By: svcscm Differential Revision: D2686725 Pulled By: foghina fb-gh-sync-id: 3679c7e8d9b3e4c12ef42628e348c6a6d5cc32d7 --- local-cli/generator-ios/templates/app/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-cli/generator-ios/templates/app/Info.plist b/local-cli/generator-ios/templates/app/Info.plist index cddf0766c980..91963b267072 100644 --- a/local-cli/generator-ios/templates/app/Info.plist +++ b/local-cli/generator-ios/templates/app/Info.plist @@ -40,7 +40,7 @@ NSAppTransportSecurity - + NSAllowsArbitraryLoads From fdca4224bc7e42a6169debe998613583f1c28afa Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 24 Nov 2015 02:51:51 +0530 Subject: [PATCH 0057/1411] Added docs on using Promisesin Native Modules --- docs/NativeModulesAndroid.md | 60 ++++++++++++++++++++++++++++++++++-- docs/NativeModulesIOS.md | 36 ++++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index c7dcf2eb9750..dbbcd0cd3072 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -124,7 +124,7 @@ mReactInstanceManager = ReactInstanceManager.builder() To make it simpler to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of `NativeModules` each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality. -```java +```js /** * @providesModule ToastAndroid */ @@ -203,6 +203,62 @@ A native module is supposed to invoke its callback only once. It can, however, s It is very important to highlight that the callback is not invoked immediately after the native function completes - remember that bridge communication is asynchronous, and this too is tied to the run loop. +### Promises + +Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's `async/await` syntax. When the last parameter of a bridged native method is a `Promise`, its corresponding JS method will return a JS Promise object. + +Refactoring the above code to use a promise instead of callbacks looks like this: + +```java +public class UIManagerModule extends ReactContextBaseJavaModule { + +... + + @ReactMethod + public void measureLayout( + int tag, + int ancestorTag, + Promise promise) { + try { + measureLayout(tag, ancestorTag, mMeasureBuffer); + + WritableMap map = Arguments.createMap(); + + map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0])); + map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1])); + map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2])); + map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3])); + + promise.resolve(map); + } catch (IllegalViewOperationException e) { + promise.reject(e.getMessage()); + } + } + +... +``` + +The JavaScript counterpart of this method returns a Promise. This means you can use the `await` keyword within an async function to call it and wait for its result: + +```js +async function measureLayout() { + try { + var { + relativeX, + relativeY, + width, + height, + } = await UIManager.measureLayout(100, 100); + + console.log(relativeX + ':' + relativeY + ':' + width + ':' + height); + } catch (e) { + console.error(e); + } +} + +measureLayout(); +``` + ### Threading Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there. @@ -259,4 +315,4 @@ componentWillMount: function() { }); } ... -``` \ No newline at end of file +``` diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md index f3b8510c4337..b50b82a27ae1 100644 --- a/docs/NativeModulesIOS.md +++ b/docs/NativeModulesIOS.md @@ -173,6 +173,42 @@ A native module is supposed to invoke its callback only once. It can, however, s If you want to pass error-like objects to JavaScript, use `RCTMakeError` from [`RCTUtils.h`](https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.h). Right now this just passes an Error-shaped dictionary to JavaScript, but we would like to automatically generate real JavaScript `Error` objects in the future. +## Promises + +Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's `async/await` syntax. When the last parameters of a bridged native method are an `RCTPromiseResolveBlock` and `RCTPromiseRejectBlock`, its corresponding JS method will return a JS Promise object. + +Refactoring the above code to use a promise instead of callbacks looks like this: + +```objective-c +RCT_REMAP_METHOD(findEvents, + resolver:(RCTPromiseResolveBlock)resolve, + rejecter:(RCTPromiseRejectBlock)reject)) +{ + NSArray *events = ... + if (events) { + resolve(events); + } else { + reject(error); + } +} +``` + +The JavaScript counterpart of this method returns a Promise. This means you can use the `await` keyword within an async function to call it and wait for its result: + +```js +async function updateEvents() { + try { + var events = await CalendarManager.findEvents(); + + this.setState({ events }); + } catch (e) { + console.error(e); + } +} + +updateEvents(); +``` + ## Threading The native module should not have any assumptions about what thread it is being called on. React Native invokes native modules methods on a separate serial GCD queue, but this is an implementation detail and might change. The `- (dispatch_queue_t)methodQueue` method allows the native module to specify which queue its methods should be run on. For example, if it needs to use a main-thread-only iOS API, it should specify this via: From db617e56a308de0db09c93e73a05d7627653ff11 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Mon, 23 Nov 2015 13:41:34 -0800 Subject: [PATCH 0058/1411] send fatal error for js exception in eventEmitter Summary: public Any uncaught exception inside an event emitter handler was reported as softError, which didn't crash the app, but left the app running in an unknown state. Since there's no way for the app to catch these softerror (to provide a fallback error view, etc), let's change it to report fatal error for uncaught exception for the time being. Reviewed By: javache Differential Revision: D2685322 fb-gh-sync-id: 52956d3db20809cc90448bd080795754b899435e --- Libraries/vendor/emitter/EventEmitter.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/Libraries/vendor/emitter/EventEmitter.js b/Libraries/vendor/emitter/EventEmitter.js index d8b679214e2a..cb3725ebb989 100644 --- a/Libraries/vendor/emitter/EventEmitter.js +++ b/Libraries/vendor/emitter/EventEmitter.js @@ -1,16 +1,10 @@ /** - * @generated SignedSource<<494e66dea72a3e90b763a5ec50b1e0ca>> + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. * - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * !! This file is a check-in of a static_upstream project! !! - * !! !! - * !! You should not modify this file directly. Instead: !! - * !! 1) Use `fjs use-upstream` to temporarily replace this with !! - * !! the latest version from upstream. !! - * !! 2) Make your changes, test them, etc. !! - * !! 3) Use `fjs push-upstream` to copy your changes back to !! - * !! static_upstream. !! - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * 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 EventEmitter * @typechecks @@ -164,13 +158,9 @@ class EventEmitter { // The subscription may have been removed during this event loop. if (subscription) { this._currentSubscription = subscription; - - ErrorUtils.applyWithGuard( - subscription.listener, + subscription.listener.apply( subscription.context, - Array.prototype.slice.call(arguments, 1), - null, - 'EventEmitter:' + eventType + Array.prototype.slice.call(arguments, 1) ); } } From 598d37f6d5eb7bc4faf2ff7e00a2f04b6c858e56 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 23 Nov 2015 13:02:05 -0800 Subject: [PATCH 0059/1411] Quick fix to require cycle in UnimplementedView Reviewed By: vjeux Differential Revision: D2686884 fb-gh-sync-id: 99afd8b389d5849d708654a4967dbd403ac24a9b --- Libraries/Components/UnimplementedViews/UnimplementedView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/UnimplementedViews/UnimplementedView.js b/Libraries/Components/UnimplementedViews/UnimplementedView.js index 6aebef38b16b..1870f495bcc7 100644 --- a/Libraries/Components/UnimplementedViews/UnimplementedView.js +++ b/Libraries/Components/UnimplementedViews/UnimplementedView.js @@ -9,7 +9,6 @@ var React = require('React'); var StyleSheet = require('StyleSheet'); -var View = require('View'); var UnimplementedView = React.createClass({ setNativeProps: function() { @@ -18,6 +17,8 @@ var UnimplementedView = React.createClass({ // See ensureComponentIsNative.js for more info }, render: function() { + // Workaround require cycle from requireNativeComponent + var View = require('View'); return ( {this.props.children} From f624d01cacf64ae8adc76231e2c8efa30fb71f33 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 23 Nov 2015 14:17:54 -0800 Subject: [PATCH 0060/1411] Make Style Interpolator Function Generation Lazy Summary: This code generation executes eagerly and these functions are fairly large and takes time to compile. However, I'm mostly doing this change because it significantly increases the Prepack binary file size. In theory, there might be a slight impact on the first use of these interpolators but I couldn't really tell. An alternative would be to create a factory that is called by the components at an appropriate time, or to just refactor the whole thing to use Animated. I didn't want to dig too deeply for a single component though. public Reviewed By: vjeux Differential Revision: D2687296 fb-gh-sync-id: 6fc8cdf54dfb6f0b50c11db973d67d114bbc7400 --- Libraries/Utilities/buildStyleInterpolator.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js index fdb9653e8f01..1ff93d106400 100644 --- a/Libraries/Utilities/buildStyleInterpolator.js +++ b/Libraries/Utilities/buildStyleInterpolator.js @@ -553,8 +553,15 @@ var createFunctionString = function(anims) { * object and returns a boolean describing if any update was actually applied. */ var buildStyleInterpolator = function(anims) { - return Function(createFunctionString(anims))(); + // Defer compiling this method until we really need it. + var interpolator = null; + function lazyStyleInterpolator(result, value) { + if (interpolator === null) { + interpolator = Function(createFunctionString(anims))(); + } + return interpolator(result, value); + } + return lazyStyleInterpolator; }; - module.exports = buildStyleInterpolator; From ac0134322ff25b0c527f75fd3da1b743caaaf148 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 23 Nov 2015 13:21:29 -0800 Subject: [PATCH 0061/1411] Remove UIManagerModule dependency in UIViewOperationQueue Reviewed By: astreet Differential Revision: D2463226 fb-gh-sync-id: eafc876ca750a08406917d8bbbfe87c27a4649fd --- .../DidJSUpdateUiDuringFrameDetector.java | 4 +-- .../modules/debug/FpsDebugFrameCallback.java | 4 +-- .../react/uimanager/UIManagerModule.java | 34 +++---------------- .../react/uimanager/UIViewOperationQueue.java | 28 +++++++++------ ...SafeViewHierarchyUpdateDebugListener.java} | 4 +-- 5 files changed, 28 insertions(+), 46 deletions(-) rename ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/{NotThreadSafeUiManagerDebugListener.java => NotThreadSafeViewHierarchyUpdateDebugListener.java} (85%) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java index 28144b9afaf2..f15a143df7c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java @@ -15,7 +15,7 @@ import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.common.LongArray; import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.debug.NotThreadSafeUiManagerDebugListener; +import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; /** * Debug object that listens to bridge busy/idle events and UiManagerModule dispatches and uses it @@ -25,7 +25,7 @@ * {@link Choreographer.FrameCallback}. */ public class DidJSUpdateUiDuringFrameDetector implements NotThreadSafeBridgeIdleDebugListener, - NotThreadSafeUiManagerDebugListener { + NotThreadSafeViewHierarchyUpdateDebugListener { private final LongArray mTransitionToIdleEvents = LongArray.createWithInitialCapacity(20); private final LongArray mTransitionToBusyEvents = LongArray.createWithInitialCapacity(20); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java index 71400867e640..1eac40fae1a4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java @@ -135,7 +135,7 @@ public void start() { mShouldStop = false; mReactContext.getCatalystInstance().addBridgeIdleDebugListener( mDidJSUpdateUiDuringFrameDetector); - mUIManagerModule.setUiManagerDebugListener(mDidJSUpdateUiDuringFrameDetector); + mUIManagerModule.setViewHierarchyUpdateDebugListener(mDidJSUpdateUiDuringFrameDetector); mChoreographer.postFrameCallback(this); } @@ -149,7 +149,7 @@ public void stop() { mShouldStop = true; mReactContext.getCatalystInstance().removeBridgeIdleDebugListener( mDidJSUpdateUiDuringFrameDetector); - mUIManagerModule.setUiManagerDebugListener(null); + mUIManagerModule.setViewHierarchyUpdateDebugListener(null); } public double getFPS() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 6482a209b332..67b171ee9a34 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -35,7 +35,7 @@ import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; -import com.facebook.react.uimanager.debug.NotThreadSafeUiManagerDebugListener; +import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -87,7 +87,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; private final int[] mMeasureBuffer = new int[4]; - private @Nullable NotThreadSafeUiManagerDebugListener mUiManagerDebugListener; private int mNextRootViewTag = 1; private int mBatchId = 0; @@ -100,7 +99,6 @@ public UIManagerModule(ReactApplicationContext reactContext, List v mViewManagers); mOperationsQueue = new UIViewOperationQueue( reactContext, - this, mNativeViewHierarchyManager, mAnimationRegistry); mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( @@ -771,8 +769,9 @@ public void onBatchComplete() { } } - public void setUiManagerDebugListener(@Nullable NotThreadSafeUiManagerDebugListener listener) { - mUiManagerDebugListener = listener; + public void setViewHierarchyUpdateDebugListener( + @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { + mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); } public EventDispatcher getEventDispatcher() { @@ -836,34 +835,9 @@ private void applyUpdatesRecursive(ReactShadowNode cssNode, float absoluteX, flo cssNode.markUpdateSeen(); } - /* package */ void notifyOnViewHierarchyUpdateEnqueued() { - if (mUiManagerDebugListener != null) { - mUiManagerDebugListener.onViewHierarchyUpdateEnqueued(); - } - } - - /* package */ void notifyOnViewHierarchyUpdateFinished() { - if (mUiManagerDebugListener != null) { - mUiManagerDebugListener.onViewHierarchyUpdateFinished(); - } - } - @ReactMethod public void sendAccessibilityEvent(int tag, int eventType) { mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); } - /** - * Get the first non-virtual (i.e. native) parent view tag of the react view with the passed tag. - * If the passed tag represents a non-virtual view, the same tag is returned. If the passed tag - * doesn't map to a react view, or a non-virtual parent cannot be found, -1 is returned. - */ - /* package */ int getNonVirtualParent(int reactTag) { - ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); - while (node != null && node.isVirtual()) { - node = node.getParent(); - } - return node == null ? -1 : node.getReactTag(); - } - } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index bb9cea705cb3..a931b5e0f976 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -405,11 +406,10 @@ public void execute() { final float containerX = (float) mMeasureBuffer[0]; final float containerY = (float) mMeasureBuffer[1]; - final int touchTargetReactTag = mUIManagerModule.getNonVirtualParent( - mNativeViewHierarchyManager.findTargetTagForTouch( - mReactTag, - mTargetX, - mTargetY)); + final int touchTargetReactTag = mNativeViewHierarchyManager.findTargetTagForTouch( + mReactTag, + mTargetX, + mTargetY); try { mNativeViewHierarchyManager.measure( @@ -443,7 +443,6 @@ public void execute() { } } - private final UIManagerModule mUIManagerModule; private final NativeViewHierarchyManager mNativeViewHierarchyManager; private final AnimationRegistry mAnimationRegistry; @@ -453,17 +452,22 @@ public void execute() { @GuardedBy("mDispatchRunnablesLock") private final ArrayList mDispatchUIRunnables = new ArrayList<>(); + private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener; + /* package */ UIViewOperationQueue( ReactApplicationContext reactContext, - UIManagerModule uiManagerModule, NativeViewHierarchyManager nativeViewHierarchyManager, AnimationRegistry animationRegistry) { - mUIManagerModule = uiManagerModule; mNativeViewHierarchyManager = nativeViewHierarchyManager; mAnimationRegistry = animationRegistry; mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext); } + public void setViewHierarchyUpdateDebugListener( + @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { + mViewHierarchyUpdateDebugListener = listener; + } + public boolean isEmpty() { return mOperations.isEmpty(); } @@ -592,7 +596,9 @@ public void enqueueSendAccessibilityEvent(int tag, int eventType) { mOperations = new ArrayList<>(); } - mUIManagerModule.notifyOnViewHierarchyUpdateEnqueued(); + if (mViewHierarchyUpdateDebugListener != null) { + mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued(); + } synchronized (mDispatchRunnablesLock) { mDispatchUIRunnables.add( @@ -608,7 +614,9 @@ public void run() { operations.get(i).execute(); } } - mUIManagerModule.notifyOnViewHierarchyUpdateFinished(); + if (mViewHierarchyUpdateDebugListener != null) { + mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished(); + } } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java similarity index 85% rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java index 1f4a5690b558..6f35ca06588e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java @@ -12,13 +12,13 @@ import com.facebook.react.uimanager.UIManagerModule; /** - * A listener that is notified about {@link UIManagerModule} events. This listener should only be + * A listener that is notified about view hierarchy update events. This listener should only be * used for debug purposes and should not affect application state. * * NB: while onViewHierarchyUpdateFinished will always be called from the UI thread, there are no * guarantees what thread onViewHierarchyUpdateEnqueued is called on. */ -public interface NotThreadSafeUiManagerDebugListener { +public interface NotThreadSafeViewHierarchyUpdateDebugListener { /** * Called when {@link UIManagerModule} enqueues a UI batch to be dispatched to the main thread. From 6b8f3da955249f7cc5208bcf87e0d3d04f26a37b Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 23 Nov 2015 23:57:54 +0000 Subject: [PATCH 0062/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index 71f3cad64664..76ac0bd42b39 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -1,4 +1,4 @@ -Hey {author}, thanks for reporting this issue! +Hey @{author}, thanks for reporting this issue! We've started using a new website called **[Product Pains](https://productpains.com/product/react-native/?tab=top)** to help the community **prioritize issues**. From 8ccd13b9b63fac6d1365017b4d4da43e7813ed19 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 23 Nov 2015 16:05:51 -0800 Subject: [PATCH 0063/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index 76ac0bd42b39..f170c5b2f5fd 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -1,7 +1,8 @@ -Hey @{author}, thanks for reporting this issue! +Hey {author}, thanks for reporting this issue! -We've started using a new website called **[Product Pains](https://productpains.com/product/react-native/?tab=top)** to help the community **prioritize issues**. +React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly. -We have a limited number of people working on React Native; it's therefore important to know which issues should be fixed first. The reason Product Pains works better than GitHub is that people can **vote on issues**. If you want to help the community prioritize, go ahead and give [Product Pains](https://productpains.com/product/react-native/?tab=top) a try. You can link to this issue from Product Pains. +- If this is a feature request or a bug that you would like to be fixed by the team, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing. + +- If you don't know how to do something, asking on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. -There are also other ways to get help. Chat with us on [Reactiflux](https://discord.gg/0ZcbPKXt5bWJVmUY) in #react-native or check out the [README](https://github.com/facebook/react-native#getting-help). From 5d9b8d9c15c681b5126ef504c056b730d2e207b8 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 00:07:40 +0000 Subject: [PATCH 0064/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index f170c5b2f5fd..ea708fafdb18 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -4,5 +4,5 @@ React Native, as you've probably heard, is getting really popular and truth is w - If this is a feature request or a bug that you would like to be fixed by the team, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing. -- If you don't know how to do something, asking on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. +- If you don't know how to do something, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. From e49c70026a93a0f006b7c637b97b3a09610d1768 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 23 Nov 2015 16:18:16 -0800 Subject: [PATCH 0065/1411] [Issues] Solicit high quality issues and PRs --- bots/NewIssueGreeting.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index ea708fafdb18..dc5d6629090c 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -6,3 +6,4 @@ React Native, as you've probably heard, is getting really popular and truth is w - If you don't know how to do something, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. +- We welcome well-written issues and PRs that are ready for in-depth discussion; thank you for your contributions! From b40631d0db88adfebcf0967014f682c1ac09dc9c Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 23 Nov 2015 14:47:36 -0800 Subject: [PATCH 0066/1411] Add exception description to exception name Reviewed By: fkgozali Differential Revision: D2687906 fb-gh-sync-id: f1bbc7c3deb9068877add37619faee730cdec743 --- React/Base/RCTAssert.m | 5 +++-- React/Base/RCTBatchedBridge.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index 03e40c9308fb..4742cb381d91 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -128,8 +128,9 @@ void RCTFatal(NSError *error) #if DEBUG @try { #endif + NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, [error localizedDescription]]; NSString *message = RCTFormatError([error localizedDescription], error.userInfo[RCTJSStackTraceKey], 75); - [NSException raise:RCTFatalExceptionName format:@"%@", message]; + [NSException raise:name format:@"%@", message]; #if DEBUG } @catch (NSException *e) {} #endif @@ -160,5 +161,5 @@ RCTFatalHandler RCTGetFatalHandler(void) } } - return [NSString stringWithFormat:@"Message: %@%@", message, prettyStack]; + return [NSString stringWithFormat:@"%@%@", message, prettyStack]; } diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 66f1f737d6a4..26dcb57721ac 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -780,7 +780,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i } @catch (NSException *exception) { // Pass on JS exceptions - if ([exception.name isEqualToString:RCTFatalExceptionName]) { + if ([exception.name hasPrefix:RCTFatalExceptionName]) { @throw exception; } From 26946e07f17702c42cbe3d123f3656ba3affb328 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 01:20:33 +0000 Subject: [PATCH 0067/1411] Update KnownIssues.md --- docs/KnownIssues.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 9fc543299707..5cfefec8bab4 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -33,12 +33,10 @@ Webview #### Modules ``` -Geo Location Net Info Camera Roll App State Dialog -Intent Media Pasteboard PushNotificationIOS From d5678e95e1d12704ee8c2101d722b84f8ce5732e Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 24 Nov 2015 10:29:38 +0800 Subject: [PATCH 0068/1411] Update removeClippedSubviews default value Update removeClippedSubviews default value --- docs/Performance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Performance.md b/docs/Performance.md index a69f6d17e80a..39a4ae254c56 100644 --- a/docs/Performance.md +++ b/docs/Performance.md @@ -205,7 +205,7 @@ should continue to render rows. "When true, offscreen child views (whose `overflow` value is `hidden`) are removed from their native backing superview when offscreen. This can improve scrolling performance on long lists. The default value is -false." +`true`."(note:before version 0.14-rc, the default value is `false`). This is an extremely important optimization to apply on large ListViews. On Android the `overflow` value is always `hidden` so you don't need to From bb3123353f642abf37671b4637e8687dd2b938ba Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 24 Nov 2015 10:31:35 +0800 Subject: [PATCH 0069/1411] Update Performance.md --- docs/Performance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Performance.md b/docs/Performance.md index 39a4ae254c56..1dfbb544445c 100644 --- a/docs/Performance.md +++ b/docs/Performance.md @@ -205,7 +205,7 @@ should continue to render rows. "When true, offscreen child views (whose `overflow` value is `hidden`) are removed from their native backing superview when offscreen. This can improve scrolling performance on long lists. The default value is -`true`."(note:before version 0.14-rc, the default value is `false`). +`true`."(The default value is `false` before version 0.14-rc). This is an extremely important optimization to apply on large ListViews. On Android the `overflow` value is always `hidden` so you don't need to From 64675dc078a7447fc7c92c441f0c04219b4db2d3 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 23 Nov 2015 19:22:34 -0800 Subject: [PATCH 0070/1411] Move RelayProfiler Decoupled Initialization to use Double Dispatch Reviewed By: josephsavona Differential Revision: D2689433 fb-gh-sync-id: 966b3d855a5a0a755fd55fb583e31ba648de2a7a --- .../InitializeJavaScriptAppEngine.js | 1 - Libraries/Utilities/BridgeProfiling.js | 32 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 8735e1ab004b..ec13c1615032 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -162,7 +162,6 @@ function setUpProfile() { if (__DEV__) { var BridgeProfiling = require('BridgeProfiling'); BridgeProfiling.swizzleReactPerf(); - BridgeProfiling.attachToRelayProfiler(); } } diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index f71b6511c7ed..526775b20793 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -11,6 +11,13 @@ */ 'use strict'; +type RelayProfiler = { + attachProfileHandler( + name: string, + handler: (name: string, state?: any) => () => void + ): void +}; + var GLOBAL = GLOBAL || this; var TRACE_TAG_REACT_APPS = 1 << 17; @@ -52,7 +59,7 @@ var BridgeProfiling = { var name = objName === 'ReactCompositeComponent' && this.getName() || ''; BridgeProfiling.profile(`${objName}.${fnName}(${name})`); - var ret = func.apply(this, arguments); + var ret = func.apply(this, arguments); BridgeProfiling.profileEnd(); return ret; }; @@ -62,22 +69,13 @@ var BridgeProfiling = { ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure); }, - attachToRelayProfiler() { - // We don't want to create a dependency on `RelayProfiler`, so that's why - // we require it indirectly (rather than using a literal string). Since - // there's no guarantee that the module will be present, we must wrap - // everything in a try-catch block as requiring a non-existing module - // will just throw. - try { - var rpName = 'RelayProfiler'; - var RelayProfiler = require(rpName); - RelayProfiler.attachProfileHandler('*', (name) => { - BridgeProfiling.profile(name); - return () => { - BridgeProfiling.profileEnd(); - }; - }); - } catch(err) {} + attachToRelayProfiler(relayProfiler: RelayProfiler) { + relayProfiler.attachProfileHandler('*', (name) => { + BridgeProfiling.profile(name); + return () => { + BridgeProfiling.profileEnd(); + }; + }); }, /* This is not called by default due to perf overhead but it's useful From 87302ac74b710fb39de216f6769b4c2adb8541e9 Mon Sep 17 00:00:00 2001 From: Emilio Rodriguez Date: Tue, 24 Nov 2015 08:44:34 +0100 Subject: [PATCH 0071/1411] style and typos --- docs/PlatformSpecificInformation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/PlatformSpecificInformation.md b/docs/PlatformSpecificInformation.md index 7e2c5d583d13..38345e541ca8 100644 --- a/docs/PlatformSpecificInformation.md +++ b/docs/PlatformSpecificInformation.md @@ -7,7 +7,7 @@ permalink: docs/platform-specific-code.html next: native-modules-ios --- -When building a cross platform app, the need to write different code for different platforms may arise. This can always be achieved by organizing the various components in different folders: +When building a cross-platform app, the need to write different code for different platforms may arise. This can always be achieved by organizing the various components in different folders: ```sh /common/components/ @@ -49,19 +49,19 @@ A module is provided by React Native to detect what is the platform in which the var {Platform} = React; var styles = StyleSheet.create({ - height: (Platform.OS === 'ios') ? 200 : 100 + height: (Platform.OS === 'ios') ? 200 : 100, }); ``` `Platform.OS` will be `ios` when running in iOS and `android` when running in an Android device or simulator. -###Detecting android version +###Detecting Android version On Android, the Platform module can be also used to detect which is the version of the Android Platform in which the app is running ```javascript var {Platform} = React; if(Platform.Version === '5.0'){ - console.log('Running on Lollipop!'); + console.log('Running on Lollipop!'); } ``` \ No newline at end of file From e919567323ce9e66ac625b9de786958911f41dab Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 24 Nov 2015 17:30:36 +0800 Subject: [PATCH 0072/1411] Give more details about js require. Native developers may get confused by require statement. --- docs/NativeModulesAndroid.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index dbbcd0cd3072..8ce2ef3d2986 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -142,10 +142,12 @@ var { NativeModules } = require('react-native'); module.exports = NativeModules.ToastAndroid; ``` -Now, from your JavaScript file you can call the method like this: +Save the above code into a file named "ToastAndroid.js". Now, from your other JavaScript file you can call the method like this: ```js -var ToastAndroid = require('ToastAndroid') +// Suppose this js file is under the same directory as ToastAndroid.js. +// Otherwise change the require path. +var ToastAndroid = require('./ToastAndroid'); ToastAndroid.show('Awesome', ToastAndroid.SHORT); // Note: We require ToastAndroid without any relative filepath because From 05c79059aefdd60e813223319abd9da564137253 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Tue, 24 Nov 2015 03:34:41 -0800 Subject: [PATCH 0073/1411] Reduce logcat spew when checking packager status Summary: public The common case is to hit an error when the server isn't running. We shouldn't spew a stack trace for it. Reviewed By: foghina Differential Revision: D2685706 fb-gh-sync-id: d230af170b92a05452f04a90f23172f15e62dce6 --- .../java/com/facebook/react/devsupport/DevServerHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 2db8f0b019db..1ba9b96e3727 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -217,7 +217,10 @@ public void isPackagerRunning(final PackagerStatusCallback callback) { new Callback() { @Override public void onFailure(Request request, IOException e) { - FLog.e(ReactConstants.TAG, "IOException requesting status from packager", e); + FLog.w( + ReactConstants.TAG, + "The packager does not seem to be running as we got an IOException requesting " + + "its status: " + e.getMessage()); callback.onPackagerStatusFetched(false); } From c324286fb78834d38b50b9caa2bfa0b1fa05bf3e Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Tue, 24 Nov 2015 06:28:08 -0800 Subject: [PATCH 0074/1411] truncate redbox error Summary: If a redbox error is too long it's not shown at all: {F24443416} This diff truncates it to its first 10000 chars, which should be good enough: {F24443417} The reason is a limitation of UILabel which backs text property on the used UITableViewCell. Ideally we would use a custom cell with UITextView, but I don't feel there is any value in displaying super long error messages in a redbox. public Reviewed By: jspahrsummers, nicklockwood Differential Revision: D2690638 fb-gh-sync-id: d9b3fcecd2602e8c2618afe1bb97221c2e506605 --- React/Modules/RCTRedBox.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index a334c753368b..9f603afd0c86 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -107,7 +107,8 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray { if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) { _lastStackTrace = stack; - _lastErrorMessage = message; + // message is displayed using UILabel, which is unable to render text of unlimited length, so we truncate it + _lastErrorMessage = [message substringToIndex:MIN(10000, message.length)]; [_stackTraceTableView reloadData]; From 789b5708ec8afa79b3fdc2eb50772908f23cd91c Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 24 Nov 2015 06:48:04 -0800 Subject: [PATCH 0075/1411] Fix arch macro on profiler Summary: public Bad typo in `RCTProfile.m`, was using `__x86__` instead of the right one `__i386__`. Reviewed By: jspahrsummers Differential Revision: D2690557 fb-gh-sync-id: 537eb0502f5df22cd93665cabfddeead12cad9db --- React/Profiler/RCTProfile.m | 4 ++-- ...ileTrampoline-x86.S => RCTProfileTrampoline-i386.S} | 0 React/Profiler/RCTProfileTrampoline-x86_64.S | 3 --- React/React.xcodeproj/project.pbxproj | 10 ++++++---- 4 files changed, 8 insertions(+), 9 deletions(-) rename React/Profiler/{RCTProfileTrampoline-x86.S => RCTProfileTrampoline-i386.S} (100%) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index 681a464b7a77..276ae2d4f17b 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -175,9 +175,9 @@ IMP RCTProfileGetImplementation(id obj, SEL cmd) * state, call the actual function we want to profile and stop the profiler. * * The implementation can be found in RCTProfileTrampoline-.s where arch - * is one of: x86, x86_64, arm, arm64. + * is one of: i386, x86_64, arm, arm64. */ -#if defined(__x86__) || \ +#if defined(__i386__) || \ defined(__x86_64__) || \ defined(__arm__) || \ defined(__arm64__) diff --git a/React/Profiler/RCTProfileTrampoline-x86.S b/React/Profiler/RCTProfileTrampoline-i386.S similarity index 100% rename from React/Profiler/RCTProfileTrampoline-x86.S rename to React/Profiler/RCTProfileTrampoline-i386.S diff --git a/React/Profiler/RCTProfileTrampoline-x86_64.S b/React/Profiler/RCTProfileTrampoline-x86_64.S index 2f9d17786290..9a7a81c8594a 100644 --- a/React/Profiler/RCTProfileTrampoline-x86_64.S +++ b/React/Profiler/RCTProfileTrampoline-x86_64.S @@ -3,9 +3,6 @@ #if RCT_DEV && defined(__x86_64__) -/** - * Define both symbols for compatibility with other assemblers - */ .globl SYMBOL_NAME(RCTProfileTrampoline) SYMBOL_NAME(RCTProfileTrampoline): diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 973e917618c7..b04420043bcd 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -56,8 +56,8 @@ 1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF811BCFF28A00208362 /* RCTProfile.m */; }; 1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */; }; 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; }; - 1450FF891BCFF28A00208362 /* RCTProfileTrampoline-x86.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */; }; 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */; }; + 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; }; 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; }; 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; }; @@ -210,9 +210,10 @@ 1450FF811BCFF28A00208362 /* RCTProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProfile.m; sourceTree = ""; }; 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm.S"; sourceTree = ""; }; 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm64.S"; sourceTree = ""; }; - 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86.S"; sourceTree = ""; }; 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86_64.S"; sourceTree = ""; }; 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeDelegate.h; sourceTree = ""; }; + 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-i386.S"; sourceTree = ""; }; + 14BF71811C04795500C97D0C /* RCTMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMacros.h; sourceTree = ""; }; 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = ""; }; 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = ""; }; 14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleData.h; sourceTree = ""; }; @@ -430,6 +431,8 @@ 1450FF7F1BCFF28A00208362 /* Profiler */ = { isa = PBXGroup; children = ( + 14BF71811C04795500C97D0C /* RCTMacros.h */, + 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */, 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */, 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */, 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */, @@ -437,7 +440,6 @@ 1450FF811BCFF28A00208362 /* RCTProfile.m */, 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */, 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */, - 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */, 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */, ); path = Profiler; @@ -680,8 +682,8 @@ 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */, 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */, 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, - 1450FF891BCFF28A00208362 /* RCTProfileTrampoline-x86.S in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, + 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */, 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */, From 3622e44426431988cd03ffeb6d1e4671207c9a2a Mon Sep 17 00:00:00 2001 From: Roman Telicak Date: Tue, 24 Nov 2015 16:04:35 +0100 Subject: [PATCH 0076/1411] Update removeClippedSubviews prop default value --- Libraries/Components/ScrollView/ScrollView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index a23ac8c5d505..62b619ca381a 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -278,7 +278,7 @@ var ScrollView = React.createClass({ * Experimental: When true, offscreen child views (whose `overflow` value is * `hidden`) are removed from their native backing superview when offscreen. * This can improve scrolling performance on long lists. The default value is - * false. + * true. */ removeClippedSubviews: PropTypes.bool, /** From 40e15e15bcd9516b8d3813273edab6799a805eff Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 15:19:25 +0000 Subject: [PATCH 0077/1411] Update KnownIssues.md --- docs/KnownIssues.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 5cfefec8bab4..b350cae6a62e 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -57,9 +57,11 @@ There are known cases where the APIs could be made more consistent across iOS an - `ActivityIndicator` could render a native spinning indicator on both platforms (currently this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android). - `ProgressBar` could render a horizontal progress bar on both platforms (on iOS this is `ProgressViewIOS`, on Android it's `ProgressBarAndroid`). -### Publishing modules on Android +### Using 3rd-party native modules -There is currently no easy way of publishing custom native modules on Android. Smooth work flow for contributors is important and this will be looked at very closely after the initial Open Source release. Of course the aim will be to streamline and optimize the process between iOS and Android as much as possible. +There are many awesome 3rd party modules: https://react.parts/native + +Adding these to your apps should be made simpler. Here's [an example](https://github.com/apptailor/react-native-google-signin) how this is done currently. ### The `overflow` style property defaults to `hidden` and cannot be changed on Android @@ -69,6 +71,10 @@ This is a result of how Android rendering works. This feature is not being worke We don't support shadows on Android currently. These are notoriously hard to implement as they require drawing outside of a view's bounds and Android's invalidation logic has a hard time with that. A possible solution is to use [elevation](https://developer.android.com/training/material/shadows-clipping.html), but more experimentation will be required. +### Android M permissions + +The open source version of React Native doesn't yet support the [Android M permission model](http://developer.android.com/training/permissions/requesting.html). + ### Layout-only nodes on Android An optimization feature of the Android version of React Native is for views which only contribute to the layout to not have a native view, only their layout properties are propagated to their children views. This optimization is to provide stability in deep view hierarchies for React Native and is therefore enabled by default. Should you depend on a view being present or internal tests incorrectly detect a view is layout only it will be necessary to turn off this behavior. To do this, set `collapsable` to false as in this example: From 9a484757dd4a80a5d5481740a47bd8dd6da48efc Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 15:29:14 +0000 Subject: [PATCH 0078/1411] Update breaking-changes.md --- breaking-changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/breaking-changes.md b/breaking-changes.md index 77a6268fcb2b..21cfe4b174aa 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -4,6 +4,7 @@ - Touch events on Android now have coordinates consistent with iOS: https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6 - YellowBox enabled by default: https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85 +- React Native now uses Babel 6 (props to tadeuzagallo for upgrading!). We've been using React Native with Babel 6 at Facebook for quite a while now. Nevertheless, please report any errors related to Babel, such as transforms for some JS features not working as expected and we'll fix them. ## 0.15 From bf269659686e862bf7b43b80bd18c6f721930e38 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 15:30:29 +0000 Subject: [PATCH 0079/1411] Update breaking-changes.md --- breaking-changes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/breaking-changes.md b/breaking-changes.md index 21cfe4b174aa..3501907b85d8 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -2,8 +2,8 @@ ## 0.16 -- Touch events on Android now have coordinates consistent with iOS: https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6 -- YellowBox enabled by default: https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85 +- Touch events on Android now have coordinates consistent with iOS: [0c2ee5](https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6) +- YellowBox enabled by default: [8ab518](https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85) - React Native now uses Babel 6 (props to tadeuzagallo for upgrading!). We've been using React Native with Babel 6 at Facebook for quite a while now. Nevertheless, please report any errors related to Babel, such as transforms for some JS features not working as expected and we'll fix them. ## 0.15 From 918ef31e105590dfc6b3ffcc2f55c38dc79decfc Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 15:47:29 +0000 Subject: [PATCH 0080/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index dc5d6629090c..6e3fe153cf26 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -4,6 +4,6 @@ React Native, as you've probably heard, is getting really popular and truth is w - If this is a feature request or a bug that you would like to be fixed by the team, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing. -- If you don't know how to do something, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. +- If you don't know how to do something or not sure whether some behavior is expected or a bug, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. - We welcome well-written issues and PRs that are ready for in-depth discussion; thank you for your contributions! From 7bfbc5dd00ce297d359677fa4556f9f5587504cf Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Tue, 24 Nov 2015 07:17:14 -0800 Subject: [PATCH 0081/1411] Add support for providing AbstractDraweeControllerBuilder to TextInlineImageSpan similar to ImageView Reviewed By: andreicoman11 Differential Revision: D2690762 fb-gh-sync-id: 80596575d3fa577f8eb05d9f44f72337a640a3db --- .../text/ReactTextInlineImageShadowNode.java | 18 +++++++++++++ .../text/ReactTextInlineImageViewManager.java | 25 ++++++++++++++++++- .../react/views/text/ReactTextShadowNode.java | 9 +++++-- .../react/views/text/TextInlineImageSpan.java | 19 +++++++++++--- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java index 9dc4132abc2d..dbe716020fd2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java @@ -17,6 +17,7 @@ import android.net.Uri; import com.facebook.common.util.UriUtil; +import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactProp; import com.facebook.react.uimanager.ReactShadowNode; @@ -28,6 +29,15 @@ public class ReactTextInlineImageShadowNode extends LayoutShadowNode { private @Nullable Uri mUri; + private final AbstractDraweeControllerBuilder mDraweeControllerBuilder; + private final @Nullable Object mCallerContext; + + public ReactTextInlineImageShadowNode( + AbstractDraweeControllerBuilder draweeControllerBuilder, + @Nullable Object callerContext) { + mDraweeControllerBuilder = draweeControllerBuilder; + mCallerContext = callerContext; + } @ReactProp(name = "src") public void setSource(@Nullable String source) { @@ -77,4 +87,12 @@ public boolean isVirtual() { return true; } + public AbstractDraweeControllerBuilder getDraweeControllerBuilder() { + return mDraweeControllerBuilder; + } + + public @Nullable Object getCallerContext() { + return mCallerContext; + } + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java index 8df8645da987..b16b42deaf94 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java @@ -9,8 +9,12 @@ package com.facebook.react.views.text; +import javax.annotation.Nullable; + import android.view.View; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewManager; @@ -23,6 +27,20 @@ public class ReactTextInlineImageViewManager static final String REACT_CLASS = "RCTTextInlineImage"; + private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder; + private final @Nullable Object mCallerContext; + + public ReactTextInlineImageViewManager() { + this(null, null); + } + + public ReactTextInlineImageViewManager( + @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder, + @Nullable Object callerContext) { + mDraweeControllerBuilder = draweeControllerBuilder; + mCallerContext = callerContext; + } + @Override public String getName() { return REACT_CLASS; @@ -35,7 +53,12 @@ public View createViewInstance(ThemedReactContext context) { @Override public ReactTextInlineImageShadowNode createShadowNodeInstance() { - return new ReactTextInlineImageShadowNode(); + return new ReactTextInlineImageShadowNode( + (mDraweeControllerBuilder != null) ? + mDraweeControllerBuilder : + Fresco.newDraweeControllerBuilder(), + mCallerContext + ); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 11c09a24a514..1e4286f78751 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -148,8 +148,13 @@ private static final void buildSpannedFromImageNode( Resources resources = node.getThemedContext().getResources(); int height = (int) PixelUtil.toDIPFromPixel(node.getStyleHeight()); int width = (int) PixelUtil.toDIPFromPixel(node.getStyleWidth()); - TextInlineImageSpan imageSpan = - new TextInlineImageSpan(resources, height, width, node.getUri()); + TextInlineImageSpan imageSpan = new TextInlineImageSpan( + resources, + height, + width, + node.getUri(), + node.getDraweeControllerBuilder(), + node.getCallerContext()); // We make the image take up 1 character in the span and put a corresponding character into the // text so that the image doesn't run over any following text. sb.append(INLINE_IMAGE_PLACEHOLDER); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java index e0a124109109..6079ce00b552 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java @@ -21,6 +21,7 @@ import android.widget.TextView; import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.interfaces.DraweeController; @@ -42,7 +43,9 @@ public class TextInlineImageSpan extends ReplacementSpan { private @Nullable Drawable mDrawable; - private DraweeHolder mDraweeHolder; + private final AbstractDraweeControllerBuilder mDraweeControllerBuilder; + private final DraweeHolder mDraweeHolder; + private final @Nullable Object mCallerContext; private int mHeight; private Uri mUri; @@ -50,11 +53,19 @@ public class TextInlineImageSpan extends ReplacementSpan { private @Nullable TextView mTextView; - public TextInlineImageSpan(Resources resources, int height, int width, @Nullable Uri uri) { + public TextInlineImageSpan( + Resources resources, + int height, + int width, + @Nullable Uri uri, + AbstractDraweeControllerBuilder draweeControllerBuilder, + @Nullable Object callerContext) { mDraweeHolder = new DraweeHolder( GenericDraweeHierarchyBuilder.newInstance(resources) .build() ); + mDraweeControllerBuilder = draweeControllerBuilder; + mCallerContext = callerContext; mHeight = height; mWidth = width; @@ -116,8 +127,10 @@ public void draw( ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri) .build(); - DraweeController draweeController = Fresco.newDraweeControllerBuilder() + DraweeController draweeController = mDraweeControllerBuilder + .reset() .setOldController(mDraweeHolder.getController()) + .setCallerContext(mCallerContext) .setImageRequest(imageRequest) .build(); mDraweeHolder.setController(draweeController); From 55f72c4c8f343f587a4ab522707e8805fdbe52ca Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 16:55:29 +0000 Subject: [PATCH 0082/1411] Update question-bookmarklet.js --- bots/question-bookmarklet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/question-bookmarklet.js b/bots/question-bookmarklet.js index f8d0ce52e96d..9fa4885de85c 100644 --- a/bots/question-bookmarklet.js +++ b/bots/question-bookmarklet.js @@ -1 +1 @@ -javascript:(function(){$('#new_comment_field')[0].value='Hey and thanks for reporting this!\n\nThis issue looks like a question - please use [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) for questions. It\'s the best system for Q&A and the best way to get questions answered.\n\nMany people from the community hang out on Stack Overflow and will be able to see your question and be more likely to answer because of the reputation system. If after reading this you think your question is better suited for Stack Overflow, please consider closing this issue.';$('button.btn-primary:contains("Comment")').click()})() \ No newline at end of file +javascript:(function(){$('#new_comment_field')[0].value='Hey and thanks for reporting this!\n\nThere\'s an awesome place to ask question like this one: [StackOverflow](http://stackoverflow.com/questions/tagged/react-native). It\'s the best system for Q&A. Many people from the community hang out there and will be able to see your question, you can vote on answers and mark question as answered etc. This lets us keep a list of bug reports and feature requests on github and especially [Product Pains](https://productpains.com/product/react-native/?tab=top) (again, with voting which is really nice).\n\nIf you think StackOverflow works for you please consider posting there instead and closing this issue.\n\nI\'m posting this here because github issues haven\'t been working very well for us and because StackOverflow is so much better. Thanks for reading! :)';$('button.btn-primary:contains("Comment")').click()})() From ffea7793aff0961ebc22007d650ce36f7c2e61cb Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 24 Nov 2015 09:14:44 -0800 Subject: [PATCH 0083/1411] Add babel helpers necessary for es2015 imports Summary: public Adding the babel helpers that are necessary to support ES2015 imports. Fixes: https://gist.github.com/ehd/49cb2465df9da6b39710 Reviewed By: mkonicek Differential Revision: D2690772 fb-gh-sync-id: b1b6c0c048bad809a5c58cdd0a2cbeaa11c72ea7 --- .../src/Resolver/polyfills/babelHelpers.js | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js index b1254a688e6a..a7fac6bcd6ab 100644 --- a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -11,8 +11,10 @@ /* eslint-disable strict */ // Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) // then replacing the `global` reference in the last line to also use `this`. +// +// actually, that's a lie, because babel6 omits _extends and createRawReactElement (function (global) { var babelHelpers = global.babelHelpers = {}; @@ -125,6 +127,29 @@ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; + babelHelpers.interopRequireDefault = function (obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; + }; + + babelHelpers.interopRequireWildcard = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } + } + + newObj.default = obj; + return newObj; + } + }; + babelHelpers.objectWithoutProperties = function (obj, keys) { var target = {}; From e55d70789c457be9cf42043f312a9918380fa3b4 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Tue, 24 Nov 2015 17:19:46 +0000 Subject: [PATCH 0084/1411] Update breaking-changes.md --- breaking-changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/breaking-changes.md b/breaking-changes.md index 3501907b85d8..b4448fb2aca8 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -5,6 +5,7 @@ - Touch events on Android now have coordinates consistent with iOS: [0c2ee5](https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6) - YellowBox enabled by default: [8ab518](https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85) - React Native now uses Babel 6 (props to tadeuzagallo for upgrading!). We've been using React Native with Babel 6 at Facebook for quite a while now. Nevertheless, please report any errors related to Babel, such as transforms for some JS features not working as expected and we'll fix them. +- Decorators won't work until [T2645](https://phabricator.babeljs.io/T2645) lands in Babel. ## 0.15 From 37f81341a006458d84d934882485c2a0a8838497 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 24 Nov 2015 08:50:39 -0800 Subject: [PATCH 0085/1411] Open source SwipeRefreshLayoutAndroid Reviewed By: mkonicek Differential Revision: D2679605 fb-gh-sync-id: 7f3e7384b37f29002ddd8cb7a4567fa96c76f047 --- ...llToRefreshLayoutAndroidExample.android.js | 127 ++++++++++++++++++ Examples/UIExplorer/UIExplorerList.android.js | 1 + .../PullToRefreshLayoutAndroid.android.js | 87 ++++++++++++ .../PullToRefreshLayoutAndroid.ios.js | 13 ++ Libraries/react-native/react-native.js | 1 + .../react/shell/MainReactPackage.java | 4 +- .../swiperefresh/ReactSwipeRefreshLayout.java | 35 +++++ .../views/swiperefresh/RefreshEvent.java | 30 +++++ .../SwipeRefreshLayoutManager.java | 106 +++++++++++++++ 9 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js create mode 100644 Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js create mode 100644 Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java diff --git a/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js b/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js new file mode 100644 index 000000000000..06a39c7ccd86 --- /dev/null +++ b/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js @@ -0,0 +1,127 @@ +/** +* The examples provided by Facebook are for non-commercial testing and +* evaluation purposes only. +* +* Facebook reserves all rights not expressly granted. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL +* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ +'use strict'; + +const React = require('react-native'); +const { + ScrollView, + StyleSheet, + PullToRefreshLayoutAndroid, + Text, + TouchableWithoutFeedback, + View, +} = React; + +const styles = StyleSheet.create({ + row: { + borderColor: 'grey', + borderWidth: 1, + padding: 20, + backgroundColor: '#3a5795', + margin: 5, + }, + text: { + alignSelf: 'center', + color: '#fff', + + }, + layout: { + flex: 1, + }, + scrollview: { + flex: 1, + }, +}); + +const Row = React.createClass({ + _onClick: function() { + this.props.onClick(this.props.data); + }, + render: function() { + return ( + + + + {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'} + + + + ); + }, +}); +const PullToRefreshLayoutAndroidExample = React.createClass({ + statics: { + title: '', + description: 'Container that adds pull-to-refresh support to its child view.' + }, + + getInitialState() { + return { + isRefreshing: false, + loaded: 0, + rowData: Array.from(new Array(20)).map( + (val, i) => {return {text: 'Initial row' + i, clicks: 0}}), + }; + }, + + _onClick(row) { + row.clicks++; + this.setState({ + rowData: this.state.rowData, + }); + }, + + render() { + const rows = this.state.rowData.map((row) => { + return ; + }); + return ( + + + {rows} + + + ); + }, + + _onRefresh() { + this.setState({isRefreshing: true}); + setTimeout(() => { + // prepend 10 items + const rowData = Array.from(new Array(10)) + .map((val, i) => {return { + text: 'Loaded row' + (+this.state.loaded + i), + clicks: 0, + }}) + .concat(this.state.rowData); + + this.setState({ + loaded: this.state.loaded + 10, + isRefreshing: false, + rowData: rowData, + }); + }, 5000); + }, + +}); + + +module.exports = PullToRefreshLayoutAndroidExample; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 5a370a01ce06..7d57332c2318 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -27,6 +27,7 @@ var COMPONENTS = [ require('./ProgressBarAndroidExample'), require('./ScrollViewSimpleExample'), require('./SwitchAndroidExample'), + require('./PullToRefreshLayoutAndroidExample.android'), require('./TextExample.android'), require('./TextInputExample.android'), require('./ToolbarAndroidExample'), diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js new file mode 100644 index 000000000000..a1d69c353e8d --- /dev/null +++ b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js @@ -0,0 +1,87 @@ +/** + * 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 PullToRefreshLayoutAndroid + */ +'use strict'; + +var React = require('React'); +var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants; +var View = require('View'); + +var onlyChild = require('onlyChild'); +var processColor = require('processColor'); +var requireNativeComponent = require('requireNativeComponent'); + +var NATIVE_REF = 'native_swiperefreshlayout'; + +/** + * React view that supports a single scrollable child view (e.g. `ScrollView`). When this child + * view is at `scrollY: 0`, swiping down triggers an `onRefresh` event. + */ +var PullToRefreshLayoutAndroid = React.createClass({ + statics: { + SIZE: RefreshLayoutConsts.SIZE, + }, + + propTypes: { + ...View.propTypes, + /** + * Whether the pull to refresh functionality is enabled + */ + enabled: React.PropTypes.bool, + /** + * The colors (at least one) that will be used to draw the refresh indicator + */ + colors: React.PropTypes.arrayOf(React.PropTypes.string), + /** + * The background color of the refresh indicator + */ + progressBackgroundColor: React.PropTypes.string, + /** + * Whether the view should be indicating an active refresh + */ + refreshing: React.PropTypes.bool, + /** + * Size of the refresh indicator, see PullToRefreshLayoutAndroid.SIZE + */ + size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE), + }, + + getInnerViewNode: function() { + return this.refs[NATIVE_REF]; + }, + + render: function() { + return ( + + {onlyChild(this.props.children)} + + ); + }, + + _onRefresh: function() { + this.props.onRefresh && this.props.onRefresh(); + this.refs[NATIVE_REF].setNativeProps({refreshing: !!this.props.refreshing}); + } +}); + +var NativePullToRefresh = requireNativeComponent( + 'AndroidSwipeRefreshLayout', + PullToRefreshLayoutAndroid +); + +module.exports = PullToRefreshLayoutAndroid; diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js new file mode 100644 index 000000000000..eea4dbae7695 --- /dev/null +++ b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.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. + * + * @providesModule PullToRefreshLayoutAndroid + */ + 'use strict'; + + module.exports = require('UnimplementedView'); diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 206b5f785f9f..29ff95205868 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -37,6 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { SliderIOS: require('SliderIOS'), SnapshotViewIOS: require('SnapshotViewIOS'), Switch: require('Switch'), + PullToRefreshLayoutAndroid: require('PullToRefreshLayoutAndroid'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 115c33ea63f6..a0e90db20613 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -39,6 +39,7 @@ import com.facebook.react.views.toolbar.ReactToolbarManager; import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; +import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; /** * Package defining basic modules and view managers. @@ -78,6 +79,7 @@ public List createViewManagers(ReactApplicationContext reactContext new ReactViewManager(), new ReactViewPagerManager(), new ReactTextInlineImageViewManager(), - new ReactVirtualTextViewManager()); + new ReactVirtualTextViewManager(), + new SwipeRefreshLayoutManager()); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java new file mode 100644 index 000000000000..f458c0ab06b9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java @@ -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. + */ + +package com.facebook.react.views.swiperefresh; + +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.MotionEvent; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.events.NativeGestureUtil; + +/** + * Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality. + */ +public class ReactSwipeRefreshLayout extends SwipeRefreshLayout { + + public ReactSwipeRefreshLayout(ReactContext reactContext) { + super(reactContext); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (super.onInterceptTouchEvent(ev)) { + NativeGestureUtil.notifyNativeGestureStarted(this, ev); + return true; + } + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java new file mode 100644 index 000000000000..d8325d2bce49 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.swiperefresh; + +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +public class RefreshEvent extends Event { + + protected RefreshEvent(int viewTag, long timestampMs) { + super(viewTag, timestampMs); + } + + @Override + public String getEventName() { + return "topRefresh"; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java new file mode 100644 index 000000000000..1520dc1f987c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.swiperefresh; + +import javax.annotation.Nullable; + +import java.util.Map; + +import android.graphics.Color; +import android.os.SystemClock; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.ReactProp; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.ViewProps; + +/** + * ViewManager for {@link ReactSwipeRefreshLayout} which allows the user to "pull to refresh" a + * child view. Emits an {@code onRefresh} event when this happens. + */ +public class SwipeRefreshLayoutManager extends ViewGroupManager { + + @Override + protected ReactSwipeRefreshLayout createViewInstance(ThemedReactContext reactContext) { + return new ReactSwipeRefreshLayout(reactContext); + } + + @Override + public String getName() { + return "AndroidSwipeRefreshLayout"; + } + + @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) + public void setEnabled(ReactSwipeRefreshLayout view, boolean enabled) { + view.setEnabled(enabled); + } + + @ReactProp(name = "colors") + public void setColors(ReactSwipeRefreshLayout view, @Nullable ReadableArray colors) { + if (colors != null) { + int[] colorValues = new int[colors.size()]; + for (int i = 0; i < colors.size(); i++) { + colorValues[i] = colors.getInt(i); + } + view.setColorSchemeColors(colorValues); + } else { + view.setColorSchemeColors(); + } + } + + @ReactProp(name = "progressBackgroundColor", defaultInt = Color.TRANSPARENT, customType = "Color") + public void setProgressBackgroundColor(ReactSwipeRefreshLayout view, int color) { + view.setProgressBackgroundColorSchemeColor(color); + } + + @ReactProp(name = "size", defaultInt = SwipeRefreshLayout.DEFAULT) + public void setSize(ReactSwipeRefreshLayout view, int size) { + view.setSize(size); + } + + @ReactProp(name = "refreshing") + public void setRefreshing(ReactSwipeRefreshLayout view, boolean refreshing) { + view.setRefreshing(refreshing); + } + + @Override + protected void addEventEmitters( + final ThemedReactContext reactContext, + final ReactSwipeRefreshLayout view) { + view.setOnRefreshListener( + new OnRefreshListener() { + @Override + public void onRefresh() { + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() + .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.uptimeMillis())); + } + }); + } + + @Nullable + @Override + public Map getExportedViewConstants() { + return MapBuilder.of( + "SIZE", + MapBuilder.of("DEFAULT", SwipeRefreshLayout.DEFAULT, "LARGE", SwipeRefreshLayout.LARGE)); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put("topRefresh", MapBuilder.of("registrationName", "onRefresh")) + .build(); + } +} From b4beba521779ef5bb4a9ef26987db7c3f34587b7 Mon Sep 17 00:00:00 2001 From: Alexey Lang Date: Tue, 24 Nov 2015 10:18:07 -0800 Subject: [PATCH 0086/1411] Fix logging JSAppRequireTime Reviewed By: andreicoman11 Differential Revision: D2690760 fb-gh-sync-id: 41e88c48deec05539e7681862d794ed650b81bfa --- Libraries/Utilities/PerformanceLogger.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js index 75d8478bc350..daafe46a5663 100644 --- a/Libraries/Utilities/PerformanceLogger.js +++ b/Libraries/Utilities/PerformanceLogger.js @@ -65,6 +65,15 @@ var PerformanceLogger = { } return; } + if (timespans[key].endTime) { + if (__DEV__) { + console.log( + 'PerformanceLogger: Attempting to end a timespan that has already ended ', + key + ); + } + return; + } timespans[key].endTime = performanceNow(); timespans[key].totalTime = From 9d333e6411e63afaa6c5129dece62b458268c492 Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 24 Nov 2015 10:29:40 -0800 Subject: [PATCH 0087/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index 6e3fe153cf26..87e421b499eb 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -6,4 +6,4 @@ React Native, as you've probably heard, is getting really popular and truth is w - If you don't know how to do something or not sure whether some behavior is expected or a bug, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. -- We welcome well-written issues and PRs that are ready for in-depth discussion; thank you for your contributions! +- We welcome clear issues and PRs that are ready for in-depth discussion; thank you for your contributions! From 3a00545bc77c74d4fd49584e262326d3dffabb2f Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Tue, 24 Nov 2015 09:46:45 -0800 Subject: [PATCH 0088/1411] Remove cookie handler on destroy Reviewed By: andreicoman11, astreet Differential Revision: D2690634 fb-gh-sync-id: a2ab8ce52dd501eed88f166b4c218886ea229744 --- .../react/modules/fresco/FrescoModule.java | 3 +-- .../modules/network/NetworkingModule.java | 23 ++++++++++--------- .../modules/network/OkHttpClientProvider.java | 11 --------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 8a980cb46e18..14b95c0aebdc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -80,8 +80,7 @@ public void loadLibrary(String libraryName) { } Context context = this.getReactApplicationContext().getApplicationContext(); - OkHttpClient okHttpClient = - OkHttpClientProvider.getCookieAwareOkHttpClient(getReactApplicationContext()); + OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); ImagePipelineConfig.Builder builder = OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index db316e3710e3..1156aa2fd195 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; -import java.net.CookieHandler; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.GuardedAsyncTask; @@ -58,6 +57,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms private final OkHttpClient mClient; + private final ForwardingCookieHandler mCookieHandler; private final @Nullable String mDefaultUserAgent; private boolean mShuttingDown; @@ -68,6 +68,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { super(reactContext); mClient = client; mClient.networkInterceptors().add(new StethoInterceptor()); + mCookieHandler = new ForwardingCookieHandler(reactContext); mShuttingDown = false; mDefaultUserAgent = defaultUserAgent; } @@ -76,7 +77,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule { * @param context the ReactContext of the application */ public NetworkingModule(final ReactApplicationContext context) { - this(context, null, OkHttpClientProvider.getCookieAwareOkHttpClient(context)); + this(context, null, OkHttpClientProvider.getOkHttpClient()); } /** @@ -85,13 +86,18 @@ public NetworkingModule(final ReactApplicationContext context) { * caller does not provide one explicitly */ public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) { - this(context, defaultUserAgent, OkHttpClientProvider.getCookieAwareOkHttpClient(context)); + this(context, defaultUserAgent, OkHttpClientProvider.getOkHttpClient()); } public NetworkingModule(ReactApplicationContext reactContext, OkHttpClient client) { this(reactContext, null, client); } + @Override + public void initialize() { + mClient.setCookieHandler(mCookieHandler); + } + @Override public String getName() { return "RCTNetworking"; @@ -102,10 +108,8 @@ public void onCatalystInstanceDestroy() { mShuttingDown = true; mClient.cancel(null); - CookieHandler cookieHandler = mClient.getCookieHandler(); - if (cookieHandler instanceof ForwardingCookieHandler) { - ((ForwardingCookieHandler) cookieHandler).destroy(); - } + mCookieHandler.destroy(); + mClient.setCookieHandler(null); } @ReactMethod @@ -319,10 +323,7 @@ protected void doInBackgroundGuarded(Void... params) { @ReactMethod public void clearCookies(com.facebook.react.bridge.Callback callback) { - CookieHandler cookieHandler = mClient.getCookieHandler(); - if (cookieHandler instanceof ForwardingCookieHandler) { - ((ForwardingCookieHandler) cookieHandler).clearCookies(callback); - } + mCookieHandler.clearCookies(callback); } private @Nullable MultipartBuilder constructMultipartBody( diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java index 699ffa5255c4..c9c1b733a6d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java @@ -13,8 +13,6 @@ import java.util.concurrent.TimeUnit; -import com.facebook.react.bridge.ReactContext; - import com.squareup.okhttp.OkHttpClient; /** @@ -25,7 +23,6 @@ public class OkHttpClientProvider { // Centralized OkHttpClient for all networking requests. private static @Nullable OkHttpClient sClient; - private static ForwardingCookieHandler sCookieHandler; public static OkHttpClient getOkHttpClient() { if (sClient == null) { @@ -34,14 +31,6 @@ public static OkHttpClient getOkHttpClient() { return sClient; } - public static OkHttpClient getCookieAwareOkHttpClient(ReactContext context) { - if (sCookieHandler == null) { - sCookieHandler = new ForwardingCookieHandler(context); - getOkHttpClient().setCookieHandler(sCookieHandler); - } - return getOkHttpClient(); - } - private static OkHttpClient createClient() { // TODO: #7108751 plug in stetho OkHttpClient client = new OkHttpClient(); From 5387df8dc569c3dfbb5ba4d66b88f70c5ec22d50 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Tue, 24 Nov 2015 09:46:58 -0800 Subject: [PATCH 0089/1411] Make ReactDatabaseSupplier use application context Summary: public We pass in a `ReactContext` but we really only need a context. Make sure we're using the application one. Reviewed By: astreet Differential Revision: D2690692 fb-gh-sync-id: 857d6571c9c01d35e12f09be4c8733cca007306f --- .../react/modules/storage/ReactDatabaseSupplier.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java index ef27b332ff3e..620d231461d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java @@ -42,9 +42,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { VALUE_COLUMN + " TEXT NOT NULL" + ")"; + private static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance; + private Context mContext; private @Nullable SQLiteDatabase mDb; - private static @Nullable ReactDatabaseSupplier mReactDatabaseSupplierInstance; private ReactDatabaseSupplier(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -52,10 +53,10 @@ private ReactDatabaseSupplier(Context context) { } public static ReactDatabaseSupplier getInstance(Context context) { - if (mReactDatabaseSupplierInstance == null) { - mReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context); + if (sReactDatabaseSupplierInstance == null) { + sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext()); } - return mReactDatabaseSupplierInstance; + return sReactDatabaseSupplierInstance; } @Override @@ -150,6 +151,6 @@ private synchronized void closeDatabase() { // For testing purposes only! public static void deleteInstance() { - mReactDatabaseSupplierInstance = null; + sReactDatabaseSupplierInstance = null; } } From 69c8dd50fe37b78207b49356d936a547b07685ce Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Tue, 24 Nov 2015 09:47:26 -0800 Subject: [PATCH 0090/1411] Avoid leaking FrescoModule when setting SoLoaderShim handler Summary: The anonymous class had an implicit reference back to FrescoModule that would in turn retain the context. public Reviewed By: astreet Differential Revision: D2690747 fb-gh-sync-id: 8a97c102e461b903c6adf7c65956baf364fa5faf --- .../react/modules/fresco/FrescoModule.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 14b95c0aebdc..666acc2fe159 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -65,13 +65,7 @@ public void initialize() { super.initialize(); // Make sure the SoLoaderShim is configured to use our loader for native libraries. // This code can be removed if using Fresco from Maven rather than from source - SoLoaderShim.setHandler( - new SoLoaderShim.Handler() { - @Override - public void loadLibrary(String libraryName) { - SoLoader.loadLibrary(libraryName); - } - }); + SoLoaderShim.setHandler(new FrescoHandler()); HashSet requestListeners = new HashSet<>(); requestListeners.add(new SystraceRequestListener()); @@ -110,4 +104,11 @@ public void clearSensitiveData() { imagePipelineFactory.getMainDiskStorageCache().clearAll(); imagePipelineFactory.getSmallImageDiskStorageCache().clearAll(); } + + private static class FrescoHandler implements SoLoaderShim.Handler { + @Override + public void loadLibrary(String libraryName) { + SoLoader.loadLibrary(libraryName); + } + } } From 2b22d22a8384a800043bc0141b3a36eaa59c0716 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Tue, 24 Nov 2015 09:47:39 -0800 Subject: [PATCH 0091/1411] Clear static arg arrays after use Reviewed By: astreet Differential Revision: D2690812 fb-gh-sync-id: b00291c57e294eece5772531e9f16e0c60b8b8f4 --- .../facebook/react/uimanager/ViewManagersPropertyCache.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index c34430cc6480..fd0ce6c41e49 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -72,11 +73,13 @@ public void updateViewProp( VIEW_MGR_ARGS[0] = viewToUpdate; VIEW_MGR_ARGS[1] = extractProperty(props); mSetter.invoke(viewManager, VIEW_MGR_ARGS); + Arrays.fill(VIEW_MGR_ARGS, null); } else { VIEW_MGR_GROUP_ARGS[0] = viewToUpdate; VIEW_MGR_GROUP_ARGS[1] = mIndex; VIEW_MGR_GROUP_ARGS[2] = extractProperty(props); mSetter.invoke(viewManager, VIEW_MGR_GROUP_ARGS); + Arrays.fill(VIEW_MGR_GROUP_ARGS, null); } } catch (Throwable t) { FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t); @@ -92,10 +95,12 @@ public void updateShadowNodeProp( if (mIndex == null) { SHADOW_ARGS[0] = extractProperty(props); mSetter.invoke(nodeToUpdate, SHADOW_ARGS); + Arrays.fill(SHADOW_ARGS, null); } else { SHADOW_GROUP_ARGS[0] = mIndex; SHADOW_GROUP_ARGS[1] = extractProperty(props); mSetter.invoke(nodeToUpdate, SHADOW_GROUP_ARGS); + Arrays.fill(SHADOW_GROUP_ARGS, null); } } catch (Throwable t) { FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t); From b86f14738ee8bcb925ad447e0daf58d7ee95f5bb Mon Sep 17 00:00:00 2001 From: Mark Vayngrib Date: Tue, 24 Nov 2015 11:03:57 -0800 Subject: [PATCH 0092/1411] support react-native field with fallback to browser field Summary: React Native is a lot more powerful an environment than the browser, so we need an alternate mapping, as specified [here](https://github.com/defunctzombie/node-browser-resolve#browser-field) An example: ```js { "browser": { "./lib/server": false }, "react-native": { "dgram": "react-native-udp", "fs": "react-native-level-fs" }, "chromeapp": { "dgram": "chrome-dgram", "fs": "level-filesystem" } } ``` on the other hand, if "react-native" is not present in package.json, you should fall back to "browser" other than the one (nesting) test added, the tests are unchanged, just done for both "react-native" and "browser" (I've implemented [react-native-udp](https://npmjs.org/package/react-native-udp) and [react-native-level-fs](https://npmjs.org/package/react-native-level-fs), but they obviously don't belong in the traditional "browser" field as they won't run anywhere except in React Native.) Closes https://github.com/facebook/react-native/pull/2208 Reviewed By: svcscm Differential Revision: D2691236 Pulled By: vjeux fb-gh-sync-id: 34041ed50bda4ec07f31d1dc50dcdfa428af2512 --- .../__tests__/DependencyGraph-test.js | 818 +++++++++++------- .../src/DependencyResolver/Package.js | 33 +- 2 files changed, 519 insertions(+), 332 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index b8caec76a19b..a89ce97aa064 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1362,331 +1362,491 @@ describe('DependencyGraph', function() { }); }); - pit('should support simple browser field in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); + testBrowserField('browser') + testBrowserField('react-native') + + function replaceBrowserField (json, fieldName) { + if (fieldName !== 'browser') { + json[fieldName] = json.browser + delete json.browser + } + + return json + } + + function testBrowserField (fieldName) { + pit('should support simple browser field in packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); }); - }); - pit('should support browser field in packages w/o .js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, - }); + pit('should support browser field in packages w/o .js ext ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); }); - }); - pit('should support mapping main in browser field json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', + pit('should support mapping main in browser field json ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + assetExts: ['png', 'jpg'], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); + pit('should work do correct browser mapping w/o js ext ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); - pit('should work do correct browser mapping w/o js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + assetExts: ['png', 'jpg'], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - }, - }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); }); + pit('should support browser mapping of files ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + './hello.js': './bye.js', + }, + }, fieldName)), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'require("../hello")', + }, + 'hello.js': 'hello', + 'bye.js': 'bye', + } + } + }); + var dgraph = new DependencyGraph({ ...defaults, roots: [root], }); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/not-node.js', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/dir/client.js', + path: '/root/aPackage/dir/client.js', + dependencies: ['../hello'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/bye.js', + path: '/root/aPackage/bye.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); }); - }); - pit('should support browser mapping of files', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - './hello.js': './bye.js', + pit('should support browser mapping for packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }, fieldName)), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', }, - }), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'require("../hello")', - }, - 'hello.js': 'hello', - 'bye.js': 'bye', - }, - }, - }); + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { - expect(deps) - .toEqual([ - { id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/not-node.js', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/client.js', - path: '/root/aPackage/dir/client.js', - dependencies: ['../hello'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/bye.js', - path: '/root/aPackage/bye.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); + + pit('should support browser mapping for packages ("' + fieldName + '")', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify(replaceBrowserField({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }, fieldName)), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); }); - }); + } - pit('should support browser mapping for packages', function() { + pit('should fall back to browser mapping from react-native mapping', function() { var root = '/root'; fs.__setMockFilesystem({ 'root': { @@ -1699,25 +1859,36 @@ describe('DependencyGraph', function() { 'aPackage': { 'package.json': JSON.stringify({ name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, + 'react-native': { + 'node-package': 'rn-package', + } }), 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - }, - }, + 'node_modules': { + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package' + }), + 'index.js': 'some node code', + }, + 'rn-package': { + 'package.json': JSON.stringify({ + 'name': 'rn-package', + browser: { + 'nested-package': 'nested-browser-package' + } + }), + 'index.js': 'require("nested-package")', + }, + 'nested-browser-package': { + 'package.json': JSON.stringify({ + 'name': 'nested-browser-package', + }), + 'index.js': 'some code' + } + } + } + } }); var dgraph = new DependencyGraph({ @@ -1745,8 +1916,17 @@ describe('DependencyGraph', function() { isPolyfill: false, resolution: undefined, }, - { id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', + { id: 'rn-package/index.js', + path: '/root/aPackage/node_modules/rn-package/index.js', + dependencies: ['nested-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'nested-browser-package/index.js', + path: '/root/aPackage/node_modules/nested-browser-package/index.js', dependencies: [], isAsset: false, isAsset_DEPRECATED: false, diff --git a/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js index d45a8923e35e..2117e3f1976a 100644 --- a/packager/react-packager/src/DependencyResolver/Package.js +++ b/packager/react-packager/src/DependencyResolver/Package.js @@ -15,17 +15,18 @@ class Package { getMain() { return this._read().then(json => { - if (typeof json.browser === 'string') { - return path.join(this.root, json.browser); + var replacements = getReplacements(json) + if (typeof replacements === 'string') { + return path.join(this.root, replacements); } let main = json.main || 'index'; - if (json.browser && typeof json.browser === 'object') { - main = json.browser[main] || - json.browser[main + '.js'] || - json.browser[main + '.json'] || - json.browser[main.replace(/(\.js|\.json)$/, '')] || + if (replacements && typeof replacements === 'object') { + main = replacements[main] || + replacements[main + '.js'] || + replacements[main + '.json'] || + replacements[main.replace(/(\.js|\.json)$/, '')] || main; } @@ -51,14 +52,14 @@ class Package { redirectRequire(name) { return this._read().then(json => { - const {browser} = json; + var replacements = getReplacements(json); - if (!browser || typeof browser !== 'object') { + if (!replacements || typeof replacements !== 'object') { return name; } if (name[0] !== '/') { - return browser[name] || name; + return replacements[name] || name; } if (!isAbsolutePath(name)) { @@ -66,9 +67,9 @@ class Package { } const relPath = './' + path.relative(this.root, name); - const redirect = browser[relPath] || - browser[relPath + '.js'] || - browser[relPath + '.json']; + const redirect = replacements[relPath] || + replacements[relPath + '.js'] || + replacements[relPath + '.json']; if (redirect) { return path.join( this.root, @@ -90,4 +91,10 @@ class Package { } } +function getReplacements(pkg) { + return pkg['react-native'] == null + ? pkg.browser + : pkg['react-native']; +} + module.exports = Package; From 2c14ab582a629e73619999a0d4b4577a852d0e40 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Tue, 24 Nov 2015 11:29:44 -0800 Subject: [PATCH 0093/1411] RN: Add "Dismiss All" to YellowBox Summary: Adds a "Dismiss All" button to YellowBox for people who see litter on the ground and continue walking. (Just kidding). public Reviewed By: vjeux Differential Revision: D2691764 fb-gh-sync-id: 9746b42bc1e5dc51f2320880f47f8cb17b952570 --- Libraries/ReactIOS/YellowBox.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Libraries/ReactIOS/YellowBox.js b/Libraries/ReactIOS/YellowBox.js index 5c1e0e37284d..012515cc48c1 100644 --- a/Libraries/ReactIOS/YellowBox.js +++ b/Libraries/ReactIOS/YellowBox.js @@ -122,7 +122,13 @@ const WarningRow = ({count, warning, onPress}) => { ); }; -const WarningInspector = ({count, warning, onClose, onDismiss}) => { +const WarningInspector = ({ + count, + warning, + onClose, + onDismiss, + onDismissAll, +}) => { const ScrollView = require('ScrollView'); const Text = require('Text'); const TouchableHighlight = require('TouchableHighlight'); @@ -151,7 +157,16 @@ const WarningInspector = ({count, warning, onClose, onDismiss}) => { style={styles.inspectorButton} underlayColor="transparent"> - Dismiss Warning + Dismiss + + + + + Dismiss All @@ -175,9 +190,13 @@ class YellowBox extends React.Component { }; this.dismissWarning = warning => { const {inspecting, warningMap} = this.state; - warningMap.delete(warning); + if (warning) { + warningMap.delete(warning); + } else { + warningMap.clear(); + } this.setState({ - inspecting: inspecting === warning ? null : inspecting, + inspecting: (warning && inspecting !== warning) ? inspecting : null, warningMap, }); }; @@ -217,6 +236,7 @@ class YellowBox extends React.Component { warning={inspecting} onClose={() => this.setState({inspecting: null})} onDismiss={() => this.dismissWarning(inspecting)} + onDismissAll={() => this.dismissWarning(null)} /> : null; @@ -280,11 +300,8 @@ var styles = StyleSheet.create({ bottom: 0, }, inspectorButton: { + flex: 1, padding: 22, - position: 'absolute', - left: 0, - right: 0, - bottom: 0, }, inspectorButtonText: { color: textColor, From 510f001390519137a951903d7143f10cd03fc03a Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Tue, 24 Nov 2015 12:10:48 -0800 Subject: [PATCH 0094/1411] put exception message in front of error log Summary: Exception message was the last part of the whole shown error. This is not optimal in case where there are deeply nested objects as parameters, which used to be displayed before the message. This diff moves the exception message to the front. public Reviewed By: javache Differential Revision: D2691426 fb-gh-sync-id: c6c9ad3ac4681a8102ea2c580f24382640b7246c --- React/Base/RCTBatchedBridge.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 26dcb57721ac..98998dd3c36d 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -785,8 +785,8 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i } NSString *message = [NSString stringWithFormat: - @"Exception thrown while invoking %@ on target %@ with params %@: %@", - method.JSMethodName, moduleData.name, params, exception]; + @"Exception '%@' was thrown while invoking %@ on target %@ with params %@", + exception, method.JSMethodName, moduleData.name, params]; RCTFatal(RCTErrorWithMessage(message)); } From 469a65217c4f0a3e46e69037d86a91b0a60d7bc3 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 24 Nov 2015 13:05:34 -0800 Subject: [PATCH 0095/1411] Add note in View.js render method that wrapper component is not used. Summary: Got bitten lately by View.js returning different component based on `__DEV__` Adding a warning in the render method that should make it cleaner in the future that this method is not actually being used in prod mode. Closes https://github.com/facebook/react-native/pull/4322 Reviewed By: svcscm Differential Revision: D2691880 Pulled By: vjeux fb-gh-sync-id: 119672740969a857ab6288b7914d52e8d40a1d95 --- Libraries/Components/View/View.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index ff2def0afabe..84ab491875d9 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -312,6 +312,10 @@ var View = React.createClass({ }, render: function() { + // WARNING: This method will not be used in production mode as in that mode we + // replace wrapper component View with generated native wrapper RCTView. Avoid + // adding functionality this component that you'd want to be available in both + // dev and prod modes. return ; }, }); From b5be05d82bfddb0c29317af14dc07652beb3cacc Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 24 Nov 2015 14:26:14 -0800 Subject: [PATCH 0096/1411] Fix flaky scrolling for TextInput when using rich text Summary: public This diff fixes the jumpy scrolling for multiline `` when using nested `` components to implement rich text highlighting. The fix is to disable scrolling on the underlying UITextView, and nest it inside another UIScrollView that we control. Reviewed By: ericvicenti, tadeuzagallo Differential Revision: D2674670 fb-gh-sync-id: bacee3ae485523cc26ca8102b714e081df230629 --- Examples/UIExplorer/TextInputExample.ios.js | 88 ++++++++++----------- Libraries/Text/RCTTextView.h | 1 - Libraries/Text/RCTTextView.m | 41 +++++----- Libraries/Text/RCTTextViewManager.m | 19 ++--- 4 files changed, 74 insertions(+), 75 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 5f13d822fd54..471920a0b4d0 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -301,6 +301,50 @@ exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Single and multi-line text inputs.'; exports.examples = [ + { + title: 'Multiline', + render: function() { + return ( + + + + + + + + + ); + } + }, + { + title: 'Attributed text', + render: function() { + return ; + } + }, { title: 'Auto-focus', render: function() { @@ -544,50 +588,6 @@ exports.examples = [ ); } }, - { - title: 'Multiline', - render: function() { - return ( - - - - - - - - - ); - } - }, - { - title: 'Attributed text', - render: function() { - return ; - } - }, { title: 'Blur on submit', render: function(): ReactElement { return ; }, diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index a6d8bca08917..9305442abe91 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -22,7 +22,6 @@ @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, copy) NSString *text; -@property (nonatomic, strong) UIColor *textColor; @property (nonatomic, strong) UIColor *placeholderTextColor; @property (nonatomic, strong) UIFont *font; @property (nonatomic, assign) NSInteger mostRecentEventCount; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 2af31f39aa65..f75a4c013ef1 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -44,6 +44,7 @@ @implementation RCTTextView NSMutableArray *_subviews; BOOL _blockTextShouldChange; UITextRange *_previousSelectionRange; + UIScrollView *_scrollView; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -55,15 +56,19 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _eventDispatcher = eventDispatcher; _placeholderTextColor = [self defaultPlaceholderTextColor]; - _textView = [[RCTUITextView alloc] initWithFrame:self.bounds]; + _textView = [[RCTUITextView alloc] initWithFrame:CGRectZero]; _textView.backgroundColor = [UIColor clearColor]; _textView.scrollsToTop = NO; + _textView.scrollEnabled = NO; _textView.delegate = self; + _scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; + [_scrollView addSubview:_textView]; + _previousSelectionRange = _textView.selectedTextRange; _subviews = [NSMutableArray new]; - [self addSubview:_textView]; + [self addSubview:_scrollView]; } return self; } @@ -139,16 +144,11 @@ - (void)performPendingTextUpdate // we temporarily block all textShouldChange events so they are not applied. _blockTextShouldChange = YES; - // We compute the new selectedRange manually to make sure the cursor is at the - // end of the newly inserted/deleted text after update. NSRange range = _textView.selectedRange; - CGPoint contentOffset = _textView.contentOffset; - _textView.attributedText = _pendingAttributedText; _pendingAttributedText = nil; _textView.selectedRange = range; [_textView layoutIfNeeded]; - _textView.contentOffset = contentOffset; [self _setPlaceholderVisibility]; @@ -174,11 +174,21 @@ - (void)updateFrames CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset); _textView.frame = frame; _placeholderView.frame = frame; + _scrollView.frame = frame; + [self updateContentSize]; _textView.textContainerInset = adjustedTextContainerInset; _placeholderView.textContainerInset = adjustedTextContainerInset; } +- (void)updateContentSize +{ + _textView.scrollEnabled = YES; + _scrollView.contentSize = _textView.contentSize; + _textView.frame = (CGRect){CGPointZero, _scrollView.contentSize}; + _textView.scrollEnabled = NO; +} + - (void)updatePlaceholder { [_placeholderView removeFromSuperview]; @@ -186,6 +196,8 @@ - (void)updatePlaceholder if (_placeholder) { _placeholderView = [[UITextView alloc] initWithFrame:self.bounds]; + _placeholderView.editable = NO; + _placeholderView.userInteractionEnabled = NO; _placeholderView.backgroundColor = [UIColor clearColor]; _placeholderView.scrollEnabled = false; _placeholderView.scrollsToTop = NO; @@ -211,16 +223,6 @@ - (void)setFont:(UIFont *)font [self updatePlaceholder]; } -- (UIColor *)textColor -{ - return _textView.textColor; -} - -- (void)setTextColor:(UIColor *)textColor -{ - _textView.textColor = textColor; -} - - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; @@ -305,10 +307,6 @@ - (void)textViewDidChangeSelection:(RCTUITextView *)textView }, }); } - - if (textView.editable && [textView isFirstResponder]) { - [textView scrollRangeToVisible:textView.selectedRange]; - } } - (void)setText:(NSString *)text @@ -369,6 +367,7 @@ - (void)textViewDidBeginEditing:(UITextView *)textView - (void)textViewDidChange:(UITextView *)textView { + [self updateContentSize]; [self _setPlaceholderVisibility]; _nativeEventCount++; [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index c5c04ee7fb32..94ba4609c94a 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -23,21 +23,22 @@ - (UIView *)view return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; } +RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) -RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL) -RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) -RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) -RCT_EXPORT_VIEW_PROPERTY(text, NSString) -RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL) -RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL) +RCT_REMAP_VIEW_PROPERTY(color, textView.textColor, UIColor) +RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL) +RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL) RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType) RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, textView.keyboardAppearance, UIKeyboardAppearance) +RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) +RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType) -RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL) -RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) -RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType) +RCT_REMAP_VIEW_PROPERTY(secureTextEntry, textView.secureTextEntry, BOOL) +RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL) +RCT_EXPORT_VIEW_PROPERTY(text, NSString) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView) { view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)]; From 9fc3991615b5dd6815713697945dd5d9bc300a66 Mon Sep 17 00:00:00 2001 From: Manuel Nakamurakare Date: Tue, 24 Nov 2015 14:52:09 -0800 Subject: [PATCH 0097/1411] added method to set thumb image Summary: this change will allow the slider to have different thumb images . Sets an image for the thumb. It only supports static images Closes https://github.com/facebook/react-native/pull/3849 Reviewed By: svcscm Differential Revision: D2665699 Pulled By: nicklockwood fb-gh-sync-id: 3a767e43170074e2419067d5c8eae61668ebb5e9 --- Examples/UIExplorer/SliderIOSExample.js | 6 +++ .../UIExplorer.xcodeproj/project.pbxproj | 40 ++++++++++++++---- .../UIExplorer/Base.lproj/LaunchScreen.xib | 8 +++- .../uie_thumb_big.imageset/Contents.json | 21 --------- .../testSliderExample_1@2x.png | Bin 40184 -> 69018 bytes .../UIExplorerSnapshotTests.m | 2 +- .../uie_thumb_big.png | Bin .../Components/SliderIOS/SliderIOS.ios.js | 7 ++- React/Views/RCTSlider.h | 2 + React/Views/RCTSlider.m | 10 +++++ React/Views/RCTSliderManager.m | 1 + 11 files changed, 63 insertions(+), 34 deletions(-) delete mode 100644 Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json rename Examples/UIExplorer/{UIExplorer/Images.xcassets/uie_thumb_big.imageset => }/uie_thumb_big.png (100%) diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js index 245fe10ad57e..9b3c584db151 100644 --- a/Examples/UIExplorer/SliderIOSExample.js +++ b/Examples/UIExplorer/SliderIOSExample.js @@ -83,5 +83,11 @@ exports.examples = [ render(): ReactElement { return ; } + }, + { + title: 'Custom thumb image', + render(): ReactElement { + return ; + } } ]; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 49a27dc61e74..2f10b53ae305 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -8,6 +8,11 @@ /* Begin PBXBuildFile section */ 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; }; + 1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; }; + 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; }; + 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; }; + 1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */; }; + 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 1323F18E1C04ABEB0091BED0 /* Thumbnails */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; @@ -50,11 +55,10 @@ 14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; - 272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; settings = {ASSET_TAGS = (); }; }; - 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; settings = {ASSET_TAGS = (); }; }; + 272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; }; + 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; }; 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */; settings = {ASSET_TAGS = (); }; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; - 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; }; 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; @@ -174,6 +178,11 @@ /* Begin PBXFileReference section */ 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = ""; }; + 1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = ""; }; + 1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = ""; }; + 1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = ""; }; + 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uie_thumb_big.png; sourceTree = ""; }; + 1323F18E1C04ABEB0091BED0 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = ""; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; @@ -231,7 +240,6 @@ 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = ""; }; 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootViewIntegrationTests.m; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; }; 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; }; @@ -314,6 +322,18 @@ name = Libraries; sourceTree = ""; }; + 1323F18D1C04ABAC0091BED0 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 13B07FB61A68108700A75B9A /* Info.plist */, + 1323F1851C04AB9F0091BED0 /* bunny.png */, + 1323F1861C04AB9F0091BED0 /* flux@3x.png */, + 1323F1871C04AB9F0091BED0 /* hawk.png */, + 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 13417FE41AA91428003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -377,10 +397,10 @@ 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, - 134366AE1BFD2C76000A2DE1 /* Thumbnails */, - 13B07FB61A68108700A75B9A /* Info.plist */, 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB71A68108700A75B9A /* main.m */, + 1323F18E1C04ABEB0091BED0 /* Thumbnails */, + 1323F18D1C04ABAC0091BED0 /* Supporting Files */, ); name = UIExplorer; sourceTree = ""; @@ -448,7 +468,6 @@ 143BC5971B21E3E100462512 /* Supporting Files */ = { isa = PBXGroup; children = ( - 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */, 143BC5981B21E3E100462512 /* Info.plist */, ); name = "Supporting Files"; @@ -832,9 +851,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 134366AF1BFD2C76000A2DE1 /* Thumbnails in Resources */, + 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */, + 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */, + 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + 1323F1891C04AB9F0091BED0 /* bunny.png in Resources */, + 1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -842,7 +865,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib index 3b7dcb4a082b..0556e6641fb0 100644 --- a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib +++ b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib @@ -1,7 +1,8 @@ - + - + + @@ -13,17 +14,20 @@ + diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json deleted file mode 100644 index cdd15d023904..000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "uie_thumb_big.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png index c38d68b526b0336c3b21be2cf6f5176f68efd2f2..45b9547a82be78b30072e4ce1218b8d0033f141b 100644 GIT binary patch literal 69018 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 literal 40184 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�c;GZV+4!h_meT5Ur&q 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 diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m index 2f3639cdc08d..81c11503ca2a 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m @@ -52,7 +52,7 @@ - (void)test##name \ RCT_TEST(LayoutExample) RCT_TEST(TextExample) RCT_TEST(SwitchExample) -RCT_TEST(SliderExample) +//RCT_TEST(SliderExample) // Disabled: #8985988 //RCT_TEST(TabBarExample) // Disabled: #8985988 - (void)testZZZNotInRecordMode diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png b/Examples/UIExplorer/uie_thumb_big.png similarity index 100% rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png rename to Examples/UIExplorer/uie_thumb_big.png diff --git a/Libraries/Components/SliderIOS/SliderIOS.ios.js b/Libraries/Components/SliderIOS/SliderIOS.ios.js index f151a29b7c67..925f9ad60d34 100644 --- a/Libraries/Components/SliderIOS/SliderIOS.ios.js +++ b/Libraries/Components/SliderIOS/SliderIOS.ios.js @@ -83,6 +83,11 @@ var SliderIOS = React.createClass({ */ trackImage: Image.propTypes.source, + /** + * Sets an image for the thumb. It only supports static images. + */ + thumbImage: Image.propTypes.source, + /** * Callback continuously called while the user is dragging the slider. */ @@ -114,7 +119,7 @@ var SliderIOS = React.createClass({ }); let {style, ...props} = this.props; - style = [styles.slider, this.props.style]; + style = [styles.slider, style]; return ( Date: Wed, 25 Nov 2015 12:36:36 +0530 Subject: [PATCH 0098/1411] Add Nalathe Kerala to showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 5d485df69d2f..52c4c92ee183 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -201,6 +201,12 @@ var apps = [ link: 'https://itunes.apple.com/in/app/myntra-fashion-shopping-app/id907394059', author: 'Myntra Designs', }, + { + name: 'Nalathe Kerala', + icon: 'https://lh3.googleusercontent.com/5N0WYat5WuFbhi5yR2ccdbqmiZ0wbTtKRG9GhT3YK7Z-qRvmykZyAgk0HNElOxD2JOPr=w300-rw', + link: 'https://play.google.com/store/apps/details?id=com.rhyble.nalathekerala', + author: 'Rhyble', + }, { name: 'Ncredible', icon: 'http://a3.mzstatic.com/us/r30/Purple2/v4/a9/00/74/a9007400-7ccf-df10-553b-3b6cb67f3f5f/icon350x350.png', From aaffb239cab5e730bbe83041fc9404946f2215e3 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Wed, 25 Nov 2015 00:49:17 -0800 Subject: [PATCH 0099/1411] Allow the output of the JSC profile to be specified in JS Reviewed By: astreet Differential Revision: D2674869 fb-gh-sync-id: 15e192015ecc9291359d6c988e793849eac97513 --- .../src/main/jni/react/JSCLegacyProfiler.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp index 64f47b00bc3d..5d2a7e499e18 100644 --- a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp +++ b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp @@ -7,6 +7,7 @@ #include #include "JSCHelpers.h" #include "JSCLegacyProfiler.h" +#include "Value.h" static JSValueRef nativeProfilerStart( JSContextRef ctx, @@ -20,7 +21,7 @@ static JSValueRef nativeProfilerStart( return JSValueMakeUndefined(ctx); } - JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL); + JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception); JSStartProfiling(ctx, title); JSStringRelease(title); return JSValueMakeUndefined(ctx); @@ -38,8 +39,16 @@ static JSValueRef nativeProfilerEnd( return JSValueMakeUndefined(ctx); } - JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL); - JSEndProfilingAndRender(ctx, title, "/sdcard/profile.json"); + std::string writeLocation("/sdcard/"); + if (argumentCount > 1) { + JSStringRef fileName = JSValueToStringCopy(ctx, arguments[1], exception); + writeLocation += facebook::react::String::ref(fileName).str(); + JSStringRelease(fileName); + } else { + writeLocation += "profile.json"; + } + JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception); + JSEndProfilingAndRender(ctx, title, writeLocation.c_str()); JSStringRelease(title); return JSValueMakeUndefined(ctx); } From fc5a8678d37685170b5e5e0f4086efdfa2756bab Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Wed, 25 Nov 2015 03:17:50 -0800 Subject: [PATCH 0100/1411] Implement efficient DiskCache.clear() Summary: public Ability to efficiently remove all keys with a particular prefix Reviewed By: tadeuzagallo Differential Revision: D2658741 fb-gh-sync-id: 3770f061c83288efe645162ae84a9fd9194d2fd6 --- Libraries/Storage/AsyncStorage.js | 25 +++++++++++++++++++++++++ React/Modules/RCTAsyncLocalStorage.m | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index c5be992e2aba..4b3a8e0008bb 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -137,6 +137,26 @@ var AsyncStorage = { }); }, + /** + * Erases all keys with a particular prefix. Useful if all your keys have a + * specific prefix. + */ + clearPrefix: function( + prefix: string, + callback?: ?(error: ?Error) => void + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.clearPrefix(prefix, function(error) { + callback && callback(convertError(error)); + if (error && convertError(error)){ + reject(convertError(error)); + } else { + resolve(null); + } + }); + }); + }, + /** * Gets *all* keys known to the app, for all callers, libraries, etc. Returns a `Promise` object. */ @@ -259,6 +279,11 @@ if (!RCTAsyncStorage.multiMerge) { delete AsyncStorage.multiMerge; } +// clearPrefix() only supported by certain backends +if (!RCTAsyncStorage.clearPrefix) { + delete AsyncStorage.clearPrefix; +} + function convertErrors(errs) { if (!errs) { return null; diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 273ef40c6bf8..eceb15847c2a 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -435,6 +435,24 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL callback(@[RCTNullIfNil(error)]); } +RCT_EXPORT_METHOD(clearPrefix:(NSString *)prefix callack:(RCTResponseSenderBlock)callback) +{ + NSDictionary *errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[errorOut]); + return; + } + + NSMutableArray *keys = [NSMutableArray array]; + for (NSString *key in _manifest.allKeys) { + if ([key hasPrefix:prefix]) { + [keys addObject:key]; + } + } + + [self multiRemove:keys callback:callback]; +} + RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; From 38db6fa4658e8074a91f2c541bee3d00fe3ea50a Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 25 Nov 2015 03:07:06 -0800 Subject: [PATCH 0101/1411] Remove scrollview support from UIManager, remove mainScrollView(delegate) Reviewed By: nicklockwood Differential Revision: D2692749 fb-gh-sync-id: 48975d2f09f3b2902dfa2e56ff9d34257b2395bc --- Libraries/Components/ScrollResponder.js | 34 +++++----- Libraries/Components/ScrollView/ScrollView.js | 1 - React/Modules/RCTUIManager.h | 13 ---- React/Modules/RCTUIManager.m | 67 ------------------- React/Views/RCTScrollView.m | 14 ++-- React/Views/RCTScrollViewManager.h | 1 - React/Views/RCTScrollViewManager.m | 40 +++++++++-- React/Views/RCTScrollableProtocol.h | 2 +- .../react/uimanager/UIManagerModule.java | 5 -- 9 files changed, 62 insertions(+), 115 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 75ceb561d522..3374cf22d53e 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -12,18 +12,19 @@ 'use strict'; var Dimensions = require('Dimensions'); -var NativeModules = require('NativeModules'); var Platform = require('Platform'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var React = require('React'); var Subscribable = require('Subscribable'); var TextInputState = require('TextInputState'); -var RCTUIManager = NativeModules.UIManager; -var RCTScrollViewConsts = RCTUIManager.RCTScrollView.Constants; +var { UIManager, ScrollViewManager } = require('NativeModules'); +var invariant = require('invariant'); var warning = require('warning'); +import type ReactComponent from 'ReactComponent'; + /** * Mixin that can be integrated in order to handle scrolling that plays well * with `ResponderEventPlugin`. Integrate with your platform specific scroll @@ -115,7 +116,6 @@ type Event = Object; var ScrollResponderMixin = { mixins: [Subscribable.Mixin], - statics: RCTScrollViewConsts, scrollResponderMixinGetInitialState: function(): State { return { isTouching: false, @@ -353,16 +353,15 @@ var ScrollResponderMixin = { */ scrollResponderScrollTo: function(offsetX: number, offsetY: number) { if (Platform.OS === 'android') { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.RCTScrollView.Commands.scrollTo, + UIManager.RCTScrollView.Commands.scrollTo, [Math.round(offsetX), Math.round(offsetY)], ); } else { - RCTUIManager.scrollTo( + ScrollViewManager.scrollTo( React.findNodeHandle(this), - offsetX, - offsetY, + { x: offsetX, y: offsetY } ); } }, @@ -373,16 +372,15 @@ var ScrollResponderMixin = { */ scrollResponderScrollWithouthAnimationTo: function(offsetX: number, offsetY: number) { if (Platform.OS === 'android') { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.RCTScrollView.Commands.scrollWithoutAnimationTo, + UIManager.RCTScrollView.Commands.scrollWithoutAnimationTo, [offsetX, offsetY], ); } else { - RCTUIManager.scrollWithoutAnimationTo( + ScrollViewManager.scrollWithoutAnimationTo( React.findNodeHandle(this), - offsetX, - offsetY + { x: offsetX, y: offsetY } ); } }, @@ -392,7 +390,11 @@ var ScrollResponderMixin = { * @param {object} rect Should have shape {x, y, width, height} */ scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) { - RCTUIManager.zoomToRect(React.findNodeHandle(this), rect); + if (Platform.OS === 'android') { + invariant('zoomToRect is not implemented'); + } else { + ScrollViewManager.zoomToRect(React.findNodeHandle(this), rect); + } }, /** @@ -408,7 +410,7 @@ var ScrollResponderMixin = { scrollResponderScrollNativeHandleToKeyboard: function(nodeHandle: any, additionalOffset?: number, preventNegativeScrollOffset?: bool) { this.additionalScrollOffset = additionalOffset || 0; this.preventNegativeScrollOffset = !!preventNegativeScrollOffset; - RCTUIManager.measureLayout( + UIManager.measureLayout( nodeHandle, React.findNodeHandle(this.getInnerViewNode()), this.scrollResponderTextInputFocusError, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 62b619ca381a..8069b9647be5 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -18,7 +18,6 @@ var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; var RCTScrollViewManager = require('NativeModules').ScrollViewManager; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); -var RCTUIManager = require('NativeModules').UIManager; var ScrollResponder = require('ScrollResponder'); var StyleSheet = require('StyleSheet'); var StyleSheetPropType = require('StyleSheetPropType'); diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 9fd8cb7e8f3d..daaf40ac2e91 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -44,19 +44,6 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ @interface RCTUIManager : NSObject -/** - * The UIIManager has the concept of a designated "main scroll view", which is - * useful for apps built around a central scrolling content area (e.g. a - * timeline). - */ -@property (nonatomic, weak) id mainScrollView; - -/** - * Allows native environment code to respond to "the main scroll view" events. - * see `RCTUIManager`'s `setMainScrollViewTag`. - */ -@property (nonatomic, readwrite, weak) id nativeMainScrollDelegate; - /** * Register a root view with the RCTUIManager. */ diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 679e84767553..a15d2516421b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1095,73 +1095,6 @@ static void RCTMeasureLayout(RCTShadowView *view, callback(@[results]); } -RCT_EXPORT_METHOD(setMainScrollViewTag:(nonnull NSNumber *)reactTag) -{ - [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - // - There should be at most one designated "main scroll view" - // - There should be at most one designated "`nativeMainScrollDelegate`" - // - The one designated main scroll view should have the one designated - // `nativeMainScrollDelegate` set as its `nativeMainScrollDelegate`. - if (uiManager.mainScrollView) { - uiManager.mainScrollView.nativeMainScrollDelegate = nil; - } - id view = viewRegistry[reactTag]; - if (view) { - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - uiManager.mainScrollView = (id)view; - uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate; - } else { - RCTLogError(@"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); - } - } else { - uiManager.mainScrollView = nil; - } - }]; -} - -// TODO: we could just pass point property -RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag - withOffsetX:(CGFloat)offsetX - offsetY:(CGFloat)offsetY) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:YES]; - } else { - RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); - } - }]; -} - -// TODO: we could just pass point property -RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(nonnull NSNumber *)reactTag - offsetX:(CGFloat)offsetX - offsetY:(CGFloat)offsetY) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:NO]; - } else { - RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); - } - }]; -} - -RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag - withRect:(CGRect)rect) -{ - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ - UIView *view = viewRegistry[reactTag]; - if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { - [(id)view zoomToRect:rect animated:YES]; - } else { - RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); - } - }]; -} - /** * JS sets what *it* considers to be the responder. Later, scroll views can use * this in order to determine if scrolling is appropriate. diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 2dc6f3886807..f1ef89572119 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -375,7 +375,7 @@ @implementation RCTScrollView CGRect _lastClippedToRect; } -@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate; +@synthesize nativeScrollDelegate = _nativeScrollDelegate; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { @@ -542,14 +542,14 @@ - (void)refreshContentInset - (void)delegateMethod:(UIScrollView *)scrollView \ { \ [_eventDispatcher sendScrollEventWithType:eventName reactTag:self.reactTag scrollView:scrollView userData:nil]; \ - if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \ - [_nativeMainScrollDelegate delegateMethod:scrollView]; \ + if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \ + [_nativeScrollDelegate delegateMethod:scrollView]; \ } \ } #define RCT_FORWARD_SCROLL_EVENT(call) \ -if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \ - [_nativeMainScrollDelegate call]; \ +if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \ + [_nativeScrollDelegate call]; \ } RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, RCTScrollEventTypeEndDeceleration) @@ -706,8 +706,8 @@ - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)vi - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { - if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { - return [_nativeMainScrollDelegate scrollViewShouldScrollToTop:scrollView]; + if ([_nativeScrollDelegate respondsToSelector:_cmd]) { + return [_nativeScrollDelegate scrollViewShouldScrollToTop:scrollView]; } return YES; } diff --git a/React/Views/RCTScrollViewManager.h b/React/Views/RCTScrollViewManager.h index 83b3126e8f1c..a53bf4c80796 100644 --- a/React/Views/RCTScrollViewManager.h +++ b/React/Views/RCTScrollViewManager.h @@ -19,4 +19,3 @@ @interface RCTScrollViewManager : RCTViewManager @end - diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index fdefe32cc463..702b75043944 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -70,7 +70,6 @@ - (UIView *)view - (NSDictionary *)constantsToExport { return @{ - // TODO: unused - remove these? @"DecelerationRate": @{ @"normal": @(UIScrollViewDecelerationRateNormal), @"fast": @(UIScrollViewDecelerationRateFast), @@ -118,7 +117,7 @@ - (UIView *)view RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - + RCTScrollView *view = viewRegistry[reactTag]; if (!view || ![view isKindOfClass:[RCTScrollView class]]) { RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); @@ -126,10 +125,44 @@ - (UIView *)view } [view endRefreshing]; - }]; } +RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag withOffset:(CGPoint)offset) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + UIView *view = viewRegistry[reactTag]; + if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { + [(id)view scrollToOffset:offset animated:YES]; + } else { + RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); + } + }]; +} + +RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(nonnull NSNumber *)reactTag withOffset:(CGPoint)offset) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + UIView *view = viewRegistry[reactTag]; + if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { + [(id)view scrollToOffset:offset animated:NO]; + } else { + RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); + } + }]; +} + +RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag withRect:(CGRect)rect) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + UIView *view = viewRegistry[reactTag]; + if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { + [(id)view zoomToRect:rect animated:YES]; + } else { + RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag); + } + }]; +} - (NSArray *)customDirectEventTypes { @@ -144,4 +177,3 @@ - (UIView *)view } @end - diff --git a/React/Views/RCTScrollableProtocol.h b/React/Views/RCTScrollableProtocol.h index f612a7afa2cc..f871d51925d4 100644 --- a/React/Views/RCTScrollableProtocol.h +++ b/React/Views/RCTScrollableProtocol.h @@ -15,7 +15,7 @@ */ @protocol RCTScrollableProtocol -@property (nonatomic, weak) NSObject *nativeMainScrollDelegate; +@property (nonatomic, weak) NSObject *nativeScrollDelegate; @property (nonatomic, readonly) CGSize contentSize; - (void)scrollToOffset:(CGPoint)offset; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 67b171ee9a34..38e78cc65b66 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -719,11 +719,6 @@ public void showPopupMenu( mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success); } - @ReactMethod - public void setMainScrollViewTag(int reactTag) { - // TODO(6588266): Implement if required - } - @ReactMethod public void configureNextLayoutAnimation( ReadableMap config, From bba71f146d011ea9674275b00ddf4861880436a7 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 25 Nov 2015 03:00:23 -0800 Subject: [PATCH 0102/1411] Defer rendering events popover until required Reviewed By: davidaurelio Differential Revision: D2690835 fb-gh-sync-id: 1d17c20b308e364c49f868d8861c6ad93957bffe --- Libraries/ReactNative/ReactNativeDefaultInjection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js index 5185af953d31..3ee1675c31dd 100644 --- a/Libraries/ReactNative/ReactNativeDefaultInjection.js +++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js @@ -86,6 +86,7 @@ function inject() { validAttributes: {}, uiViewClassName: 'RCTView', }); + // TODO #9121531: make this position: absolute; by default, to avoid interfering with flexbox ReactEmptyComponent.injection.injectEmptyComponent(RCTView); EventPluginUtils.injection.injectMount(ReactNativeMount); From 060664fd3d9331f062696e68179bac9cd4544a06 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 25 Nov 2015 03:09:00 -0800 Subject: [PATCH 0103/1411] Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33 --- .../FlexibleSizeExampleView.m | 3 +- .../RCTEventDispatcherTests.m | 2 +- .../UIExplorerUnitTests/RCTGzipTests.m | 22 +- .../CameraRoll/RCTAssetsLibraryImageLoader.m | 2 +- Libraries/CameraRoll/RCTImagePickerManager.m | 65 +++-- Libraries/Geolocation/RCTLocationObserver.m | 20 +- Libraries/Image/RCTImageLoader.m | 46 ++-- Libraries/Image/RCTImageStoreManager.m | 17 +- Libraries/LinkingIOS/RCTLinkingManager.m | 27 +-- Libraries/Network/RCTHTTPRequestHandler.m | 19 +- Libraries/Network/RCTNetworking.m | 66 +++--- .../RCTPushNotificationManager.m | 41 ++-- Libraries/RCTTest/RCTTestModule.m | 11 +- Libraries/RCTTest/RCTTestRunner.m | 3 +- Libraries/Settings/RCTSettingsManager.h | 2 +- Libraries/Settings/RCTSettingsManager.m | 35 +-- Libraries/WebSocket/RCTWebSocketExecutor.h | 2 +- Libraries/WebSocket/RCTWebSocketExecutor.m | 23 +- Libraries/WebSocket/RCTWebSocketModule.m | 11 +- React/Base/RCTBatchedBridge.m | 223 ++++++++++++------ React/Base/RCTBridge.h | 37 ++- React/Base/RCTBridge.m | 34 ++- React/Base/RCTBridgeModule.h | 2 +- React/Base/RCTEventDispatcher.m | 12 +- React/Base/RCTKeyboardObserver.m | 23 +- React/Base/RCTModuleData.h | 54 ++++- React/Base/RCTModuleData.m | 124 ++++++---- React/Base/RCTModuleMap.h | 16 -- React/Base/RCTModuleMap.m | 79 ------- React/Base/RCTPerformanceLogger.m | 15 +- React/Base/RCTUtils.h | 4 + React/Base/RCTUtils.m | 31 ++- React/Modules/RCTAccessibilityManager.m | 4 +- React/Modules/RCTAlertManager.m | 21 +- React/Modules/RCTAppState.m | 29 ++- React/Modules/RCTDevLoadingView.m | 27 +-- React/Modules/RCTDevMenu.m | 4 +- React/Modules/RCTExceptionsManager.h | 2 +- React/Modules/RCTExceptionsManager.m | 8 +- React/Modules/RCTRedBox.m | 7 +- React/Modules/RCTStatusBarManager.m | 13 +- React/Modules/RCTUIManager.m | 85 ++++--- React/Profiler/RCTProfile.m | 11 +- React/Profiler/RCTProfileTrampoline-arm.S | 9 + React/Profiler/RCTProfileTrampoline-arm64.S | 9 + React/Profiler/RCTProfileTrampoline-i386.S | 9 + React/Profiler/RCTProfileTrampoline-x86_64.S | 9 + React/React.xcodeproj/project.pbxproj | 12 +- React/Views/RCTComponentData.h | 5 +- React/Views/RCTComponentData.m | 41 ++-- React/Views/RCTModalHostViewController.m | 3 +- React/Views/RCTModalHostViewManager.m | 12 +- React/Views/RCTViewManager.h | 2 +- React/Views/RCTViewManager.m | 5 - 54 files changed, 754 insertions(+), 644 deletions(-) delete mode 100644 React/Base/RCTModuleMap.h delete mode 100644 React/Base/RCTModuleMap.m diff --git a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m index 134d128cf835..ce899207027a 100644 --- a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m +++ b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m @@ -52,8 +52,7 @@ @implementation FlexibleSizeExampleView - (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { + if ((self = [super initWithFrame:frame])) { _sizeUpdated = NO; AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m index 5761849d69d4..e0406d49727f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -62,7 +62,7 @@ - (void)setUp _bridge = [OCMockObject mockForClass:[RCTBridge class]]; _eventDispatcher = [RCTEventDispatcher new]; - ((id)_eventDispatcher).bridge = _bridge; + [_eventDispatcher setValue:_bridge forKey:@"bridge"]; _eventName = RCTNormalizeInputEventName(@"sampleEvent"); _body = @{ @"foo": @"bar" }; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m index 69162a764e07..55b53e48dbd1 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m @@ -16,6 +16,15 @@ #import "RCTUtils.h" #import "RCTNetworking.h" +#define RUN_RUNLOOP_WHILE(CONDITION) \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \ +while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \ +} \ +_Pragma("clang diagnostic pop") + extern BOOL RCTIsGzippedData(NSData *data); @interface RCTNetworking (Private) @@ -61,18 +70,21 @@ - (void)testDontRezipZippedData - (void)testRequestBodyEncoding { NSDictionary *query = @{ - @"url": @"http://example.com", - @"method": @"POST", - @"data": @{@"string": @"Hello World"}, - @"headers": @{@"Content-Encoding": @"gzip"}, - }; + @"url": @"http://example.com", + @"method": @"POST", + @"data": @{@"string": @"Hello World"}, + @"headers": @{@"Content-Encoding": @"gzip"}, + }; RCTNetworking *networker = [RCTNetworking new]; + [networker setValue:dispatch_get_main_queue() forKey:@"methodQueue"]; __block NSURLRequest *request = nil; [networker buildRequest:query completionBlock:^(NSURLRequest *_request) { request = _request; }]; + RUN_RUNLOOP_WHILE(request == nil); + XCTAssertNotNil(request); XCTAssertNotNil(request.HTTPBody); XCTAssertTrue(RCTIsGzippedData(request.HTTPBody)); diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m index 9b49742ace43..f5cd3cb25cec 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m @@ -151,7 +151,7 @@ @implementation RCTBridge (RCTAssetsLibraryImageLoader) - (ALAssetsLibrary *)assetsLibrary { - return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary]; + return [[self moduleForClass:[RCTAssetsLibraryImageLoader class]] assetsLibrary]; } @end diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index 87071863b8b5..0ad9e8e71ec4 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -32,16 +32,6 @@ @implementation RCTImagePickerManager RCT_EXPORT_MODULE(ImagePickerIOS); -- (instancetype)init -{ - if ((self = [super init])) { - _pickers = [NSMutableArray new]; - _pickerCallbacks = [NSMutableArray new]; - _pickerCancelCallbacks = [NSMutableArray new]; - } - return self; -} - - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -67,8 +57,6 @@ - (dispatch_queue_t)methodQueue return; } - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; @@ -77,11 +65,9 @@ - (dispatch_queue_t)methodQueue imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; } - [_pickers addObject:imagePicker]; - [_pickerCallbacks addObject:callback]; - [_pickerCancelCallbacks addObject:cancelCallback]; - - [rootViewController presentViewController:imagePicker animated:YES completion:nil]; + [self _presentPicker:imagePicker + successCallback:callback + cancelCallback:cancelCallback]; } RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config @@ -93,8 +79,6 @@ - (dispatch_queue_t)methodQueue return; } - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; @@ -109,30 +93,43 @@ - (dispatch_queue_t)methodQueue imagePicker.mediaTypes = allowedTypes; - [_pickers addObject:imagePicker]; - [_pickerCallbacks addObject:callback]; - [_pickerCancelCallbacks addObject:cancelCallback]; - - [rootViewController presentViewController:imagePicker animated:YES completion:nil]; + [self _presentPicker:imagePicker + successCallback:callback + cancelCallback:cancelCallback]; } - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - NSUInteger index = [_pickers indexOfObject:picker]; - RCTResponseSenderBlock callback = _pickerCallbacks[index]; + [self _dismissPicker:picker args:@[ + [info[UIImagePickerControllerReferenceURL] absoluteString] + ]]; +} - [_pickers removeObjectAtIndex:index]; - [_pickerCallbacks removeObjectAtIndex:index]; - [_pickerCancelCallbacks removeObjectAtIndex:index]; +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + [self _dismissPicker:picker args:nil]; +} - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - [rootViewController dismissViewControllerAnimated:YES completion:nil]; +- (void)_presentPicker:(UIImagePickerController *)imagePicker + successCallback:(RCTResponseSenderBlock)callback + cancelCallback:(RCTResponseSenderBlock)cancelCallback +{ + if (!_pickers) { + _pickers = [NSMutableArray new]; + _pickerCallbacks = [NSMutableArray new]; + _pickerCancelCallbacks = [NSMutableArray new]; + } + + [_pickers addObject:imagePicker]; + [_pickerCallbacks addObject:callback]; + [_pickerCancelCallbacks addObject:cancelCallback]; - callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]); + UIViewController *rootViewController = RCTKeyWindow().rootViewController; + [rootViewController presentViewController:imagePicker animated:YES completion:nil]; } -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args { NSUInteger index = [_pickers indexOfObject:picker]; RCTResponseSenderBlock callback = _pickerCancelCallbacks[index]; @@ -144,7 +141,7 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker UIViewController *rootViewController = RCTKeyWindow().rootViewController; [rootViewController dismissViewControllerAnimated:YES completion:nil]; - callback(@[]); + callback(args ?: @[]); } @end diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index ab02bd5824fd..9f9d57aa247f 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -111,19 +111,6 @@ @implementation RCTLocationObserver #pragma mark - Lifecycle -- (instancetype)init -{ - if ((self = [super init])) { - - _locationManager = [CLLocationManager new]; - _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY; - _locationManager.delegate = self; - - _pendingRequests = [NSMutableArray new]; - } - return self; -} - - (void)dealloc { [_locationManager stopUpdatingLocation]; @@ -139,6 +126,13 @@ - (dispatch_queue_t)methodQueue - (void)beginLocationUpdates { + if (!_locationManager) { + _locationManager = [CLLocationManager new]; + _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY; + _locationManager.delegate = self; + _pendingRequests = [NSMutableArray new]; + } + // Request location access permission if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [_locationManager requestWhenInUseAuthorization]; diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 7f67b53b7fda..acee9dbecea4 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -45,17 +45,17 @@ @implementation RCTImageLoader RCT_EXPORT_MODULE() -- (void)setBridge:(RCTBridge *)bridge +- (void)setUp { // Get image loaders and decoders NSMutableArray> *loaders = [NSMutableArray array]; NSMutableArray> *decoders = [NSMutableArray array]; - for (id module in bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) { - [loaders addObject:(id)module]; + for (Class moduleClass in _bridge.moduleClasses) { + if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) { + [loaders addObject:[_bridge moduleForClass:moduleClass]]; } - if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) { - [decoders addObject:(id)module]; + if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) { + [decoders addObject:[_bridge moduleForClass:moduleClass]]; } } @@ -85,17 +85,16 @@ - (void)setBridge:(RCTBridge *)bridge } }]; - _bridge = bridge; _loaders = loaders; _decoders = decoders; - _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL); - _URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB - diskCapacity:200 * 1024 * 1024 // 200MB - diskPath:@"React/RCTImageDownloader"]; } - (id)imageURLLoaderForURL:(NSURL *)URL { + if (!_loaders) { + [self setUp]; + } + if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -133,6 +132,10 @@ - (void)setBridge:(RCTBridge *)bridge - (id)imageDataDecoderForData:(NSData *)data { + if (!_decoders) { + [self setUp]; + } + if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -212,7 +215,17 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag } // All access to URL cache must be serialized + if (!_URLCacheQueue) { + _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL); + } dispatch_async(_URLCacheQueue, ^{ + + if (!_URLCache) { + _URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB + diskCapacity:200 * 1024 * 1024 // 200MB + diskPath:@"React/RCTImageDownloader"]; + } + RCTImageLoader *strongSelf = weakSelf; if (cancelled || !strongSelf) { return; @@ -385,14 +398,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data - (BOOL)canHandleRequest:(NSURLRequest *)request { - NSURL *requestURL = request.URL; - for (id module in _bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] && - [(id)module canLoadImageURL:requestURL]) { - return YES; - } - } - return NO; + return [self imageURLLoaderForURL:request.URL] != nil; } - (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate @@ -440,7 +446,7 @@ @implementation RCTBridge (RCTImageLoader) - (RCTImageLoader *)imageLoader { - return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])]; + return [self moduleForClass:[RCTImageLoader class]]; } @end diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index fe080101e7da..3bda2df35abe 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -30,15 +30,6 @@ @implementation RCTImageStoreManager RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _store = [NSMutableDictionary new]; - _id = 0; - } - return self; -} - - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block { dispatch_async(_methodQueue, ^{ @@ -52,6 +43,12 @@ - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block - (NSString *)_storeImageData:(NSData *)imageData { RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread"); + + if (!_store) { + _store = [NSMutableDictionary new]; + _id = 0; + } + NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++]; _store[imageTag] = imageData; return imageTag; @@ -225,7 +222,7 @@ @implementation RCTBridge (RCTImageStoreManager) - (RCTImageStoreManager *)imageStoreManager { - return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])]; + return [self moduleForClass:[RCTImageStoreManager class]]; } @end diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 39fff4eb9658..96c01ca2c47c 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -21,15 +21,20 @@ @implementation RCTLinkingManager RCT_EXPORT_MODULE() -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleOpenURLNotification:) - name:RCTOpenURLNotification - object:nil]; - } - return self; + _bridge = bridge; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleOpenURLNotification:) + name:RCTOpenURLNotification + object:nil]; +} + +- (NSDictionary *)constantsToExport +{ + NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; + return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)}; } - (void)dealloc @@ -75,10 +80,4 @@ - (void)handleOpenURLNotification:(NSNotification *)notification callback(@[@(canOpen)]); } -- (NSDictionary *)constantsToExport -{ - NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; - return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)}; -} - @end diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m index f52a20426ceb..1def6d4c7801 100644 --- a/Libraries/Network/RCTHTTPRequestHandler.m +++ b/Libraries/Network/RCTHTTPRequestHandler.m @@ -21,26 +21,16 @@ @implementation RCTHTTPRequestHandler RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory - valueOptions:NSPointerFunctionsStrongMemory - capacity:0]; - } - return self; -} - - (void)invalidate { [_session invalidateAndCancel]; _session = nil; - _delegates = nil; } - (BOOL)isValid { - return _delegates != nil; + // if session == nil and delegates != nil, we've been invalidated + return _session || !_delegates; } #pragma mark - NSURLRequestHandler @@ -62,12 +52,17 @@ - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request { // Lazy setup if (!_session && [self isValid]) { + NSOperationQueue *callbackQueue = [NSOperationQueue new]; callbackQueue.maxConcurrentOperationCount = 1; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue]; + + _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory + valueOptions:NSPointerFunctionsStrongMemory + capacity:0]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 749c9e0b6020..74b58972ccac 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -130,36 +130,34 @@ @implementation RCTNetworking RCT_EXPORT_MODULE() -- (void)setBridge:(RCTBridge *)bridge +- (id)handlerForRequest:(NSURLRequest *)request { - // get handlers - NSMutableArray> *handlers = [NSMutableArray array]; - for (id module in bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) { - [handlers addObject:(id)module]; - } - } + if (!_handlers) { - // Sort handlers in reverse priority order (highest priority first) - [handlers sortUsingComparator:^NSComparisonResult(id a, id b) { - float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; - float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; - if (priorityA > priorityB) { - return NSOrderedAscending; - } else if (priorityA < priorityB) { - return NSOrderedDescending; - } else { - return NSOrderedSame; + // get handlers + NSMutableArray> *handlers = [NSMutableArray array]; + for (Class moduleClass in _bridge.moduleClasses) { + if ([moduleClass conformsToProtocol:@protocol(RCTURLRequestHandler)]) { + [handlers addObject:[_bridge moduleForClass:moduleClass]]; + } } - }]; - _bridge = bridge; - _handlers = handlers; - _tasksByRequestID = [NSMutableDictionary new]; -} + // Sort handlers in reverse priority order (highest priority first) + [handlers sortUsingComparator:^NSComparisonResult(id a, id b) { + float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; + float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; + if (priorityA > priorityB) { + return NSOrderedAscending; + } else if (priorityA < priorityB) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + + _handlers = handlers; + } -- (id)handlerForRequest:(NSURLRequest *)request -{ if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -198,6 +196,8 @@ - (void)setBridge:(RCTBridge *)bridge - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query completionBlock:(void (^)(NSURLRequest *request))block { + RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue"); + NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET"; @@ -222,7 +222,10 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)q [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"]; } - block(request); + dispatch_async(_methodQueue, ^{ + block(request); + }); + return (RCTURLRequestCancellationBlock)nil; }]; } @@ -253,6 +256,8 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary *)query callback: (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback { + RCTAssertThread(_methodQueue, @"processData: must be called on method queue"); + if (!query) { return callback(nil, nil); } @@ -291,6 +296,8 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary - (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task { + RCTAssertThread(_methodQueue, @"sendData: must be called on method queue"); + if (data.length == 0) { return; } @@ -335,6 +342,8 @@ - (void)sendRequest:(NSURLRequest *)request incrementalUpdates:(BOOL)incrementalUpdates responseSender:(RCTResponseSenderBlock)responseSender { + RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue"); + __block RCTNetworkTask *task; RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { @@ -391,6 +400,9 @@ - (void)sendRequest:(NSURLRequest *)request task.uploadProgressBlock = uploadProgressBlock; if (task.requestID) { + if (_tasksByRequestID) { + _tasksByRequestID = [NSMutableDictionary new]; + } _tasksByRequestID[task.requestID] = task; responseSender(@[task.requestID]); } @@ -443,7 +455,7 @@ @implementation RCTBridge (RCTNetworking) - (RCTNetworking *)networking { - return self.modules[RCTBridgeModuleNameForClass([RCTNetworking class])]; + return [self moduleForClass:[RCTNetworking class]]; } @end diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 751e8ec349ea..0fda6ee10f0a 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -41,29 +41,11 @@ + (UILocalNotification *)UILocalNotification:(id)json @end @implementation RCTPushNotificationManager -{ - NSDictionary *_initialNotification; -} RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -- (instancetype)init -{ - if ((self = [super init])) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationReceived:) - name:RCTRemoteNotificationReceived - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationsRegistered:) - name:RCTRemoteNotificationsRegistered - object:nil]; - } - return self; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -72,7 +54,21 @@ - (void)dealloc - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; - _initialNotification = [bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationReceived:) + name:RCTRemoteNotificationReceived + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationsRegistered:) + name:RCTRemoteNotificationsRegistered + object:nil]; +} + +- (NSDictionary *)constantsToExport +{ + NSDictionary *initialNotification = [_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; + return @{@"initialNotification": RCTNullIfNil(initialNotification)}; } + (void)application:(__unused UIApplication *)application didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings @@ -200,13 +196,6 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification callback(@[permissions]); } -- (NSDictionary *)constantsToExport -{ - return @{ - @"initialNotification": RCTNullIfNil(_initialNotification), - }; -} - RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) { [RCTSharedApplication() presentLocalNotificationNow:notification]; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 3976f146ecf5..0692543e755a 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -29,14 +29,6 @@ - (dispatch_queue_t)methodQueue return _bridge.uiManager.methodQueue; } -- (instancetype)init -{ - if ((self = [super init])) { - _snapshotCounter = [NSMutableDictionary new]; - } - return self; -} - RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) { RCTAssert(_controller != nil, @"No snapshot controller configured."); @@ -44,6 +36,9 @@ - (instancetype)init [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { NSString *testName = NSStringFromSelector(_testSelector); + if (!_snapshotCounter) { + _snapshotCounter = [NSMutableDictionary new]; + } _snapshotCounter[testName] = (@([_snapshotCounter[testName] integerValue] + 1)).stringValue; NSError *error = nil; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 573c7e0a24a8..6c2e9b5d1675 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -112,8 +112,7 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices - NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); - RCTTestModule *testModule = rootView.bridge.modules[testModuleName]; + RCTTestModule *testModule = [rootView.bridge moduleForClass:[RCTTestModule class]]; RCTAssert(_testController != nil, @"_testController should not be nil"); testModule.controller = _testController; testModule.testSelector = test; diff --git a/Libraries/Settings/RCTSettingsManager.h b/Libraries/Settings/RCTSettingsManager.h index 274cc69aed43..e0c2908806a6 100644 --- a/Libraries/Settings/RCTSettingsManager.h +++ b/Libraries/Settings/RCTSettingsManager.h @@ -13,6 +13,6 @@ @interface RCTSettingsManager : NSObject -- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults; @end diff --git a/Libraries/Settings/RCTSettingsManager.m b/Libraries/Settings/RCTSettingsManager.m index 3e812e2462d7..2ff4f3b4d770 100644 --- a/Libraries/Settings/RCTSettingsManager.m +++ b/Libraries/Settings/RCTSettingsManager.m @@ -24,23 +24,31 @@ @implementation RCTSettingsManager RCT_EXPORT_MODULE() -- (instancetype)init +- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults { - return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]]; + if ((self = [self init])) { + _defaults = defaults; + } + return self; } -- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - _defaults = defaults; + _bridge = bridge; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(userDefaultsDidChange:) - name:NSUserDefaultsDidChangeNotification - object:_defaults]; + if (!_defaults) { + _defaults = [NSUserDefaults standardUserDefaults]; } - return self; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(userDefaultsDidChange:) + name:NSUserDefaultsDidChangeNotification + object:_defaults]; +} + +- (NSDictionary *)constantsToExport +{ + return @{@"settings": RCTJSONClean([_defaults dictionaryRepresentation])}; } - (void)dealloc @@ -59,13 +67,6 @@ - (void)userDefaultsDidChange:(NSNotification *)note body:RCTJSONClean([_defaults dictionaryRepresentation])]; } -- (NSDictionary *)constantsToExport -{ - return @{ - @"settings": RCTJSONClean([_defaults dictionaryRepresentation]) - }; -} - /** * Set one or more values in the settings. * TODO: would it be useful to have a callback for when this has completed? diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.h b/Libraries/WebSocket/RCTWebSocketExecutor.h index 2c17541f1e8c..9993cbc5a306 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.h +++ b/Libraries/WebSocket/RCTWebSocketExecutor.h @@ -15,7 +15,7 @@ @interface RCTWebSocketExecutor : NSObject -- (instancetype)initWithURL:(NSURL *)URL NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURL:(NSURL *)URL; @end diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 41ac0baf29f0..6ef4b4999fcd 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -36,19 +36,11 @@ @implementation RCTWebSocketExecutor RCT_EXPORT_MODULE() -- (instancetype)init -{ - NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults]; - NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"] ?: 8081; - NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy", port]; - return [self initWithURL:[RCTConvert NSURL:URLString]]; -} - - (instancetype)initWithURL:(NSURL *)URL { RCTAssertParam(URL); - if ((self = [super init])) { + if ((self = [self init])) { _url = URL; } return self; @@ -56,6 +48,13 @@ - (instancetype)initWithURL:(NSURL *)URL - (void)setUp { + if (!_url) { + NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults]; + NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"] ?: 8081; + NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy", port]; + _url = [RCTConvert NSURL:URLString]; + } + _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; _socket.delegate = self; @@ -193,11 +192,7 @@ - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)object - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), block); - } + RCTExecuteOnMainThread(block, NO); } - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block diff --git a/Libraries/WebSocket/RCTWebSocketModule.m b/Libraries/WebSocket/RCTWebSocketModule.m index df706f0d6f1e..a19335331941 100644 --- a/Libraries/WebSocket/RCTWebSocketModule.m +++ b/Libraries/WebSocket/RCTWebSocketModule.m @@ -41,14 +41,6 @@ @implementation RCTWebSocketModule @synthesize bridge = _bridge; -- (instancetype)init -{ - if ((self = [super init])) { - _sockets = [NSMutableDictionary new]; - } - return self; -} - - (void)dealloc { for (RCTSRWebSocket *socket in _sockets.allValues) { @@ -62,6 +54,9 @@ - (void)dealloc RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURL:URL]; webSocket.delegate = self; webSocket.reactTag = socketID; + if (!_sockets) { + _sockets = [NSMutableDictionary new]; + } _sockets[socketID] = webSocket; [webSocket open]; } diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 98998dd3c36d..0430f2b0af1f 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -18,7 +18,6 @@ #import "RCTJavaScriptLoader.h" #import "RCTLog.h" #import "RCTModuleData.h" -#import "RCTModuleMap.h" #import "RCTPerformanceLogger.h" #import "RCTProfile.h" #import "RCTSourceCode.h" @@ -48,6 +47,8 @@ @interface RCTBridge () + (instancetype)currentBridge; + (void)setCurrentBridge:(RCTBridge *)bridge; +@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; + @end @interface RCTBatchedBridge : RCTBridge @@ -63,10 +64,16 @@ @implementation RCTBatchedBridge BOOL _wasBatchActive; __weak id _javaScriptExecutor; NSMutableArray *_pendingCalls; - NSMutableArray *_moduleDataByID; - RCTModuleMap *_modulesByName; + NSMutableDictionary *_moduleDataByName; + NSArray *_moduleDataByID; + NSDictionary> *_modulesByName_DEPRECATED; + NSArray *_moduleClassesByID; CADisplayLink *_jsDisplayLink; NSMutableSet *_frameUpdateObservers; + + // Bridge startup stats (TODO: capture in perf logger) + NSUInteger _syncInitializedModules; + NSUInteger _asyncInitializedModules; } - (instancetype)initWithParentBridge:(RCTBridge *)bridge @@ -86,7 +93,6 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge _valid = YES; _loading = YES; _pendingCalls = [NSMutableArray new]; - _moduleDataByID = [NSMutableArray new]; _frameUpdateObservers = [NSMutableSet new]; _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; @@ -106,6 +112,8 @@ - (void)start dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t initModulesAndLoadSource = dispatch_group_create(); + + // Asynchronously load source code dispatch_group_enter(initModulesAndLoadSource); __weak RCTBatchedBridge *weakSelf = self; __block NSData *sourceCode; @@ -120,9 +128,13 @@ - (void)start dispatch_group_leave(initModulesAndLoadSource); }]; - // Synchronously initialize all native modules + // Synchronously initialize all native modules that cannot be deferred [self initModules]; +#if RCT_DEBUG + _syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue]; +#endif + if (RCTProfileIsProfiling()) { // Depends on moduleDataByID being loaded RCTProfileHookModules(self); @@ -132,22 +144,32 @@ - (void)start dispatch_group_enter(initModulesAndLoadSource); dispatch_async(bridgeQueue, ^{ dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create(); + + // Asynchronously initialize the JS executor dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ - [weakSelf setupExecutor]; + [weakSelf setUpExecutor]; }); + // Asynchronously gather the module config dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ if (weakSelf.isValid) { + RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig); config = [weakSelf moduleConfig]; RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig); + +#if RCT_DEBUG + NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue]; + _asyncInitializedModules = total - _syncInitializedModules; +#endif + } }); dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ - // We're not waiting for this complete to leave the dispatch group, since - // injectJSONConfiguration and executeSourceCode will schedule operations on the - // same queue anyway. + // We're not waiting for this to complete to leave dispatch group, since + // injectJSONConfiguration and executeSourceCode will schedule operations + // on the same queue anyway. RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); @@ -199,6 +221,28 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad } } +- (NSArray *)moduleClasses +{ + if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) { + RCTLogError(@"Bridge modules have not yet been initialized. You may be " + "trying to access a module too early in the startup procedure."); + } + return _moduleClassesByID; +} + +- (id)moduleForName:(NSString *)moduleName +{ + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (RCT_DEBUG) { + Class moduleClass = moduleData.moduleClass; + if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { + RCTLogError(@"Class %@ was not exported. Did you forget to use " + "RCT_EXPORT_MODULE()?", moduleClass); + } + } + return moduleData.instance; +} + - (void)initModules { RCTAssertMainThread(); @@ -220,54 +264,71 @@ - (void)initModules preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module; } - // Instantiate modules - _moduleDataByID = [NSMutableArray new]; - NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy]; + SEL setBridgeSelector = NSSelectorFromString(@"setBridge:"); + IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)]; + + // Set up moduleData and pre-initialize module instances + NSMutableArray *moduleDataByID = [NSMutableArray new]; + NSMutableDictionary *moduleDataByName = [NSMutableDictionary new]; for (Class moduleClass in RCTGetModuleClasses()) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); + id module = preregisteredModules[moduleName]; + if (!module) { + // Check if the module class, or any of its superclasses override init + // or setBridge:. If they do, we assume that they are expecting to be + // initialized when the bridge first loads. + if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod || + [moduleClass instancesRespondToSelector:setBridgeSelector]) { + module = [moduleClass new]; + if (!module) { + module = [NSNull null]; + } + } + } + + // Check for module name collisions. + // It's OK to have a name collision as long as the second instance is null. + if (module != [NSNull class] && _moduleDataByName[moduleName]) { + RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name " + "'%@', but name was already registered by class %@", moduleClass, + moduleName, _moduleDataByName[moduleName]); + } - // Check if module instance has already been registered for this name - 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 new] == nil, - @"Attempted to register RCTBridgeModule class %@ for the name " - "'%@', but name was already registered by class %@", moduleClass, - moduleName, [modulesByName[moduleName] class]); - } - } else { - // Module name hasn't been used before, so go ahead and instantiate - module = [moduleClass new]; - } - if (module) { - modulesByName[moduleName] = module; - } + // Instantiate moduleData (TODO: defer this until config generation) + RCTModuleData *moduleData; + if (module) { + if (module != [NSNull null]) { + moduleData = [[RCTModuleData alloc] initWithModuleInstance:module]; + } + } else { + moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass + bridge:self]; + } + if (moduleData) { + moduleDataByName[moduleName] = moduleData; + [moduleDataByID addObject:moduleData]; + } } // Store modules - _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName]; + _moduleDataByID = [moduleDataByID copy]; + _moduleDataByName = [moduleDataByName copy]; + _moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"]; /** * The executor is a bridge module, wait for it to be created and set it before * any other module has access to the bridge */ - _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; - - for (id module in _modulesByName.allValues) { - // Bridge must be set before moduleData is set up, as methodQueue - // initialization requires it (View Managers get their queue by calling - // self.bridge.uiManager.methodQueue) - if ([module respondsToSelector:@selector(setBridge:)]) { - module.bridge = self; - } + _javaScriptExecutor = [self moduleForClass:self.executorClass]; + + for (RCTModuleData *moduleData in _moduleDataByID) { + [moduleData setBridgeForInstance:self]; + } - RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor - moduleID:@(_moduleDataByID.count) - instance:module]; - [_moduleDataByID addObject:moduleData]; + for (RCTModuleData *moduleData in _moduleDataByID) { + if (moduleData.hasInstance) { + [moduleData methodQueue]; // initialize the queue + } } [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules @@ -275,7 +336,7 @@ - (void)initModules RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } -- (void)setupExecutor +- (void)setUpExecutor { [_javaScriptExecutor setUp]; } @@ -284,8 +345,8 @@ - (NSString *)moduleConfig { NSMutableArray *config = [NSMutableArray new]; for (RCTModuleData *moduleData in _moduleDataByID) { - [config addObject:moduleData.config]; - if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + [config addObject:RCTNullIfNil(moduleData.config)]; + if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { [_frameUpdateObservers addObject:moduleData]; id observer = (id)moduleData.instance; __weak typeof(self) weakSelf = self; @@ -336,7 +397,7 @@ - (void)executeSourceCode:(NSData *)sourceCode return; } - RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]]; sourceCodeModule.scriptURL = self.bundleURL; sourceCodeModule.scriptData = sourceCode; @@ -448,13 +509,14 @@ - (BOOL)isValid return _valid; } -- (NSDictionary *)modules +- (void)dispatchBlock:(dispatch_block_t)block + queue:(dispatch_queue_t)queue { - if (RCT_DEBUG && self.isValid && _modulesByName == nil) { - RCTLogError(@"Bridge modules have not yet been initialized. You may be " - "trying to access a module too early in the startup procedure."); + if (queue == RCTJSThread) { + [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; + } else if (queue) { + dispatch_async(queue, block); } - return _modulesByName; } #pragma mark - RCTInvalidating @@ -475,17 +537,19 @@ - (void)invalidate // Invalidate modules dispatch_group_t group = dispatch_group_create(); - for (RCTModuleData *moduleData in _moduleDataByID) { + for (RCTModuleData *moduleData in _moduleDataByName.allValues) { if (moduleData.instance == _javaScriptExecutor) { continue; } if ([moduleData.instance respondsToSelector:@selector(invalidate)]) { - [moduleData dispatchBlock:^{ + dispatch_group_enter(group); + [self dispatchBlock:^{ [(id)moduleData.instance invalidate]; - } dispatchGroup:group]; + dispatch_group_leave(group); + } queue:moduleData.methodQueue]; } - moduleData.queue = nil; + [moduleData invalidate]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ @@ -499,8 +563,10 @@ - (void)invalidate if (RCTProfileIsProfiling()) { RCTProfileUnhookModules(self); } + _moduleDataByName = nil; _moduleDataByID = nil; - _modulesByName = nil; + _moduleClassesByID = nil; + _modulesByName_DEPRECATED = nil; _frameUpdateObservers = nil; }]; @@ -683,15 +749,11 @@ - (void)handleBuffer:(NSArray *)buffer NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory - capacity:_moduleDataByID.count]; + capacity:_moduleDataByName.count]; [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) { RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue]; - if (RCT_DEBUG) { - // verify that class has been registered - (void)_modulesByName[moduleData.name]; - } - dispatch_queue_t queue = moduleData.queue; + dispatch_queue_t queue = moduleData.methodQueue; NSMutableOrderedSet *set = [buckets objectForKey:queue]; if (!set) { set = [NSMutableOrderedSet new]; @@ -739,10 +801,10 @@ - (void)batchDidComplete { // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? for (RCTModuleData *moduleData in _moduleDataByID) { - if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) { - [moduleData dispatchBlock:^{ + if (moduleData.hasInstance && [moduleData.instance respondsToSelector:@selector(batchDidComplete)]) { + [self dispatchBlock:^{ [moduleData.instance batchDidComplete]; - }]; + } queue:moduleData.methodQueue]; } } } @@ -812,12 +874,12 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) RCTProfileBeginFlowEvent(); - [moduleData dispatchBlock:^{ + [self dispatchBlock:^{ RCTProfileEndFlowEvent(); RCT_PROFILE_BEGIN_EVENT(0, name, nil); [observer didUpdateFrame:frameUpdate]; RCT_PROFILE_END_EVENT(0, @"objc_call,fps", nil); - }]; + } queue:moduleData.methodQueue]; } } @@ -851,3 +913,24 @@ - (void)stopProfiling:(void (^)(NSData *))callback } @end + +@implementation RCTBatchedBridge(Deprecated) + +- (NSDictionary *)modules +{ + if (!_modulesByName_DEPRECATED) { + // Check classes are set up + [self moduleClasses]; + NSMutableDictionary *modulesByName = [NSMutableDictionary new]; + for (NSString *moduleName in _moduleDataByName) { + id module = [self moduleForName:moduleName]; + if (module) { + modulesByName[moduleName] = module; + } + }; + _modulesByName_DEPRECATED = [modulesByName copy]; + } + return _modulesByName_DEPRECATED; +} + +@end diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index c16786b454e1..487773f5217a 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -59,6 +59,11 @@ typedef NSArray> *(^RCTBridgeModuleProviderBlock)(void); */ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); +/** + * This function checks if a class has been registered + */ +RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class); + /** * Async batched bridge used to communicate with the JavaScript application. */ @@ -98,10 +103,18 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; /** - * DEPRECATED: Do not use. + * Retrieve a bridge module instance by name or class. Note that modules are + * lazily instantiated, so calling these methods for the first time with a given + * module name/class may cause the class to be sychronously instantiated, + * blocking both the calling thread and main thread for a short time. + */ +- (id)moduleForName:(NSString *)moduleName; +- (id)moduleForClass:(Class)moduleClass; + +/** + * All registered bridge module classes. */ -#define RCT_IMPORT_METHOD(module, method) \ - _Pragma("message(\"This macro is no longer required\")") +@property (nonatomic, copy, readonly) NSArray *moduleClasses; /** * URL of the script that was loaded into the bridge. @@ -130,11 +143,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; -/** - * A dictionary of all registered RCTBridgeModule instances, keyed by moduleName. - */ -@property (nonatomic, copy, readonly) NSDictionary *modules; - /** * The launch options that were used to initialize the bridge. */ @@ -151,13 +159,18 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); @property (nonatomic, readonly, getter=isValid) BOOL valid; /** - * The block passed in the constructor with pre-initialized modules + * Reload the bundle and reset executor & modules. Safe to call from any thread. */ -@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; +- (void)reload; + +@end /** - * Reload the bundle and reset executor & modules. Safe to call from any thread. + * These properties and methods are deprecated and should not be used */ -- (void)reload; +@interface RCTBridge (Deprecated) + +@property (nonatomic, copy, readonly) NSDictionary *modules +__deprecated_msg("Use moduleClasses and/or moduleForName: instead"); @end diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 15f85c8da4a2..013a541368c5 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -37,6 +37,7 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZ @interface RCTBridge () @property (nonatomic, strong) RCTBatchedBridge *batchedBridge; +@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; @end @@ -87,9 +88,8 @@ void RCTRegisterModule(Class moduleClass) } /** - * Check if class has been registered + * This function checks if a class has been registered */ -BOOL RCTBridgeModuleClassIsRegistered(Class); BOOL RCTBridgeModuleClassIsRegistered(Class cls) { return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue]; @@ -230,9 +230,24 @@ - (void)bindKeys #endif } +- (NSArray *)moduleClasses +{ + return _batchedBridge.moduleClasses; +} + +- (id)moduleForName:(NSString *)moduleName +{ + return [_batchedBridge moduleForName:moduleName]; +} + +- (id)moduleForClass:(Class)moduleClass +{ + return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)]; +} + - (RCTEventDispatcher *)eventDispatcher { - return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])]; + return [self moduleForClass:[RCTEventDispatcher class]]; } - (void)reload @@ -281,10 +296,6 @@ - (void)logMessage:(NSString *)message level:(NSString *)level [_batchedBridge logMessage:message level:level]; } -- (NSDictionary *)modules -{ - return _batchedBridge.modules; -} #define RCT_INNER_BRIDGE_ONLY(...) \ - (void)__VA_ARGS__ \ @@ -303,3 +314,12 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args method:(__unused NSString *)method arguments:(__unused NSArray *)args); @end + +@implementation RCTBridge(Deprecated) + +- (NSDictionary *)modules +{ + return _batchedBridge.modules; +} + +@end diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index ee51f5d5b412..436b26c80bd0 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -76,7 +76,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \ * will be set automatically by the bridge when it initializes the module. * To implement this in your module, just add `@synthesize bridge = _bridge;` */ -@property (nonatomic, weak) RCTBridge *bridge; +@property (nonatomic, weak, readonly) RCTBridge *bridge; /** * The queue that will be used to call all exported methods. If omitted, this diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index d8d6f3268daa..b0d2be661d2a 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -97,14 +97,12 @@ @implementation RCTEventDispatcher RCT_EXPORT_MODULE() -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - _paused = YES; - _eventQueue = [NSMutableDictionary new]; - _eventQueueLock = [NSLock new]; - } - return self; + _bridge = bridge; + _paused = YES; + _eventQueue = [NSMutableDictionary new]; + _eventQueueLock = [NSLock new]; } - (void)setPaused:(BOOL)paused diff --git a/React/Base/RCTKeyboardObserver.m b/React/Base/RCTKeyboardObserver.m index fbe4e4f7647b..88523a549f4f 100644 --- a/React/Base/RCTKeyboardObserver.m +++ b/React/Base/RCTKeyboardObserver.m @@ -19,25 +19,24 @@ @implementation RCTKeyboardObserver RCT_EXPORT_MODULE() -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + _bridge = bridge; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; #define ADD_KEYBOARD_HANDLER(NAME, SELECTOR) \ - [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil] + [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil] - ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow); - ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow); - ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide); - ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide); - ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame); - ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame); + ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow); + ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow); + ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide); + ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide); + ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame); + ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame); #undef ADD_KEYBOARD_HANDLER - } - return self; } - (void)dealloc diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index cc3747eae8be..540059842035 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -9,28 +9,58 @@ #import -#import "RCTJavaScriptExecutor.h" +#import "RCTInvalidating.h" @protocol RCTBridgeMethod; +@protocol RCTBridgeModule; +@class RCTBridge; -@interface RCTModuleData : NSObject +@interface RCTModuleData : NSObject -@property (nonatomic, weak, readonly) id javaScriptExecutor; -@property (nonatomic, strong, readonly) NSNumber *moduleID; -@property (nonatomic, strong, readonly) id instance; +- (instancetype)initWithModuleClass:(Class)moduleClass + bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithModuleInstance:(id)instance NS_DESIGNATED_INITIALIZER; + +/** + * Sets the bridge for the module instance. This is only needed when using the + * `initWithModuleID:instance:` constructor. Otherwise, the bridge will be set + * automatically when the module is first accessed. + */ +- (void)setBridgeForInstance:(RCTBridge *)bridge; @property (nonatomic, strong, readonly) Class moduleClass; @property (nonatomic, copy, readonly) NSString *name; + +/** + * Returns the module methods. Note that this will gather the methods the first + * time it is called and then memoize the results. + */ @property (nonatomic, copy, readonly) NSArray> *methods; -@property (nonatomic, copy, readonly) NSArray *config; -@property (nonatomic, strong) dispatch_queue_t queue; +/** + * Returns YES if module instance has already been initialized; NO otherwise. + */ +@property (nonatomic, assign, readonly) BOOL hasInstance; + +/** + * Returns the current module instance. Note that this will init the instance + * if it has not already been created. To check if the module instance exists + * without causing it to be created, use `hasInstance` instead. + */ +@property (nonatomic, strong, readonly) id instance; -- (instancetype)initWithExecutor:(id)javaScriptExecutor - moduleID:(NSNumber *)moduleID - instance:(id)instance NS_DESIGNATED_INITIALIZER; +/** + * Returns the module method dispatch queue. Note that this will init both the + * queue and the module itself if they have not already been created. + */ +@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; -- (void)dispatchBlock:(dispatch_block_t)block; -- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group; +/** + * Returns the module config. Note that this will init the module if it has + * not already been created. This method can be called on any thread, but will + * block the main thread briefly if the module implements `constantsToExport`. + */ +@property (nonatomic, copy, readonly) NSArray *config; @end diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index ddd0384d49d6..11603850f73b 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -16,37 +16,75 @@ @implementation RCTModuleData { - NSDictionary *_constants; NSString *_queueName; + __weak RCTBridge *_bridge; } @synthesize methods = _methods; +@synthesize instance = _instance; +@synthesize methodQueue = _methodQueue; -- (instancetype)initWithExecutor:(id)javaScriptExecutor - moduleID:(NSNumber *)moduleID - instance:(id)instance +- (instancetype)initWithModuleClass:(Class)moduleClass + bridge:(RCTBridge *)bridge +{ + if ((self = [super init])) { + _moduleClass = moduleClass; + _bridge = bridge; + } + return self; +} + +- (instancetype)initWithModuleInstance:(id)instance { if ((self = [super init])) { - _javaScriptExecutor = javaScriptExecutor; - _moduleID = moduleID; _instance = instance; _moduleClass = [instance class]; - _name = RCTBridgeModuleNameForClass(_moduleClass); - - // Must be done at init time to ensure it's called on main thread - RCTAssertMainThread(); - if ([_instance respondsToSelector:@selector(constantsToExport)]) { - _constants = [_instance constantsToExport]; - } - - // Must be done at init time due to race conditions - (void)self.queue; } return self; } RCT_NOT_IMPLEMENTED(- (instancetype)init); +- (BOOL)hasInstance +{ + return _instance != nil; +} + +- (id)instance +{ + if (!_instance) { + _instance = [_moduleClass new]; + + // Bridge must be set before methodQueue is set up, as methodQueue + // initialization requires it (View Managers get their queue by calling + // self.bridge.uiManager.methodQueue) + [self setBridgeForInstance:_bridge]; + + // Initialize queue + [self methodQueue]; + } + return _instance; +} + +- (void)setBridgeForInstance:(RCTBridge *)bridge +{ + if ([_instance respondsToSelector:@selector(bridge)]) { + @try { + [(id)_instance setValue:bridge forKey:@"bridge"]; + } + @catch (NSException *exception) { + RCTLogError(@"%@ has no setter or ivar for its bridge, which is not " + "permitted. You must either @synthesize the bridge property, " + "or provide your own setter method.", self.name); + } + } +} + +- (NSString *)name +{ + return RCTBridgeModuleNameForClass(_moduleClass); +} + - (NSArray> *)methods { if (!_methods) { @@ -84,7 +122,15 @@ - (instancetype)initWithExecutor:(id)javaScriptExecutor - (NSArray *)config { - if (_constants.count == 0 && self.methods.count == 0) { + __block NSDictionary *constants; + if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) { + [self instance]; // Initialize instance + RCTExecuteOnMainThread(^{ + constants = [_instance constantsToExport]; + }, YES); + } + + if (constants.count == 0 && self.methods.count == 0) { return (id)kCFNull; // Nothing to export } @@ -101,9 +147,9 @@ - (NSArray *)config } NSMutableArray *config = [NSMutableArray new]; - [config addObject:_name]; - if (_constants.count) { - [config addObject:_constants]; + [config addObject:self.name]; + if (constants.count) { + [config addObject:constants]; } if (methods) { [config addObject:methods]; @@ -114,53 +160,39 @@ - (NSArray *)config return config; } -- (dispatch_queue_t)queue +- (dispatch_queue_t)methodQueue { - if (!_queue) { - BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)]; + if (!_methodQueue) { + BOOL implementsMethodQueue = [self.instance respondsToSelector:@selector(methodQueue)]; if (implementsMethodQueue) { - _queue = _instance.methodQueue; + _methodQueue = _instance.methodQueue; } - if (!_queue) { + if (!_methodQueue) { // Create new queue (store queueName, as it isn't retained by dispatch_queue) - _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", _name]; - _queue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL); + _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name]; + _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL); // assign it to the module if (implementsMethodQueue) { @try { - [(id)_instance setValue:_queue forKey:@"methodQueue"]; + [(id)_instance setValue:_methodQueue forKey:@"methodQueue"]; } @catch (NSException *exception) { RCTLogError(@"%@ is returning nil for it's methodQueue, which is not " "permitted. You must either return a pre-initialized " "queue, or @synthesize the methodQueue to let the bridge " - "create a queue for you.", _name); + "create a queue for you.", self.name); } } } } - return _queue; -} - -- (void)dispatchBlock:(dispatch_block_t)block -{ - [self dispatchBlock:block dispatchGroup:NULL]; + return _methodQueue; } -- (void)dispatchBlock:(dispatch_block_t)block - dispatchGroup:(dispatch_group_t)group +- (void)invalidate { - if (self.queue == RCTJSThread) { - [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } else if (self.queue) { - if (group != NULL) { - dispatch_group_async(group, self.queue, block); - } else { - dispatch_async(self.queue, block); - } - } + _methodQueue = nil; } @end diff --git a/React/Base/RCTModuleMap.h b/React/Base/RCTModuleMap.h deleted file mode 100644 index 48e7c73e66a7..000000000000 --- a/React/Base/RCTModuleMap.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface RCTModuleMap : NSDictionary - -- (instancetype)initWithDictionary:(NSDictionary *)modulesByName NS_DESIGNATED_INITIALIZER; - -@end diff --git a/React/Base/RCTModuleMap.m b/React/Base/RCTModuleMap.m deleted file mode 100644 index fef338763193..000000000000 --- a/React/Base/RCTModuleMap.m +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTModuleMap.h" - -#import "RCTBridge.h" -#import "RCTBridgeModule.h" -#import "RCTDefines.h" -#import "RCTLog.h" - -@implementation RCTModuleMap -{ - NSDictionary *_modulesByName; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)init) -RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:aDecoder) -RCT_NOT_IMPLEMENTED(- (instancetype)initWithObjects:(const id [])objects - forKeys:(const id [])keys - count:(NSUInteger)cnt) - -- (instancetype)initWithDictionary:(NSDictionary *)modulesByName -{ - if ((self = [super init])) { - _modulesByName = [modulesByName copy]; - } - return self; -} - -- (NSUInteger)count -{ - return _modulesByName.count; -} - -//declared in RCTBridge.m -extern BOOL RCTBridgeModuleClassIsRegistered(Class cls); - -- (id)objectForKey:(NSString *)moduleName -{ - id module = _modulesByName[moduleName]; - if (RCT_DEBUG) { - if (module) { - Class moduleClass = [module class]; - if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { - RCTLogError(@"Class %@ was not exported. Did you forget to use " - "RCT_EXPORT_MODULE()?", moduleClass); - } - } else { - Class moduleClass = NSClassFromString(moduleName); - module = _modulesByName[moduleName]; - if (module) { - RCTLogError(@"bridge.modules[name] expects a module name, not a class " - "name. Did you mean to pass '%@' instead?", - RCTBridgeModuleNameForClass(moduleClass)); - } - } - } - return module; -} - -- (NSEnumerator *)keyEnumerator -{ - return [_modulesByName keyEnumerator]; -} - -- (NSArray> *)allValues -{ - // don't perform validation in this case because we only want to error when - // an invalid module is specifically requested - return _modulesByName.allValues; -} - -@end diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index f099db677122..d7f45d6930c4 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -79,15 +79,14 @@ @implementation RCTPerformanceLogger @synthesize bridge = _bridge; -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(sendTimespans) - name:RCTContentDidAppearNotification - object:nil]; - } - return self; + _bridge = bridge; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sendTimespans) + name:RCTContentDidAppearNotification + object:nil]; } - (void)dealloc diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 6d399594e954..e138da40531b 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -27,6 +27,10 @@ RCT_EXTERN id RCTJSONClean(id object); // Get MD5 hash of a string RCT_EXTERN NSString *RCTMD5Hash(NSString *string); +// Execute the specified block on the main thread. Unlike dispatch_sync/async +// this will not context-switch if we're already running on the main thread. +RCT_EXTERN void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync); + // Get screen metrics in a thread-safe way RCT_EXTERN CGFloat RCTScreenScale(void); RCT_EXTERN CGSize RCTScreenSize(void); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 036ecc2bd38a..8d6f3238d0c9 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -183,18 +183,29 @@ id RCTJSONClean(id object) ]; } +void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync) +{ + if ([NSThread isMainThread]) { + block(); + } else if (sync) { + dispatch_sync(dispatch_get_main_queue(), ^{ + block(); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + CGFloat RCTScreenScale() { static CGFloat scale; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - scale = [UIScreen mainScreen].scale; - }); - } else { + RCTExecuteOnMainThread(^{ scale = [UIScreen mainScreen].scale; - } + }, YES); }); return scale; @@ -205,13 +216,9 @@ CGSize RCTScreenSize() static CGSize size; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - size = [UIScreen mainScreen].bounds.size; - }); - } else { + RCTExecuteOnMainThread(^{ size = [UIScreen mainScreen].bounds.size; - } + }, YES); }); return size; diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index eb4d319547b5..fd629cae33c0 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -59,6 +59,8 @@ + (NSString *)UIKitCategoryFromJSCategory:(NSString *)JSCategory - (instancetype)init { if ((self = [super init])) { + + // TODO: can this be moved out of the startup path? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNewContentSizeCategory:) name:UIContentSizeCategoryDidChangeNotification @@ -176,7 +178,7 @@ @implementation RCTBridge (RCTAccessibilityManager) - (RCTAccessibilityManager *)accessibilityManager { - return self.modules[RCTBridgeModuleNameForClass([RCTAccessibilityManager class])]; + return [self moduleForClass:[RCTAccessibilityManager class]]; } @end diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index 3bdb59b40472..74168866af06 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -28,17 +28,6 @@ @implementation RCTAlertManager RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _alerts = [NSMutableArray new]; - _alertControllers = [NSMutableArray new]; - _alertCallbacks = [NSMutableArray new]; - _alertButtonKeys = [NSMutableArray new]; - } - return self; -} - - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -118,6 +107,11 @@ - (void)invalidate index ++; } + if (!_alerts) { + _alerts = [NSMutableArray new]; + _alertCallbacks = [NSMutableArray new]; + _alertButtonKeys = [NSMutableArray new]; + } [_alerts addObject:alertView]; [_alertCallbacks addObject:callback ?: ^(__unused id unused) {}]; [_alertButtonKeys addObject:buttonKeys]; @@ -175,6 +169,11 @@ - (void)invalidate }]]; } + if (!_alertControllers) { + _alertControllers = [NSMutableArray new]; + } + [_alertControllers addObject:alertController]; + [presentingController presentViewController:alertController animated:YES completion:nil]; } } diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index f9ee2cb33093..6a22ae626f0e 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -44,27 +44,26 @@ @implementation RCTAppState #pragma mark - Lifecycle -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { + _bridge = bridge; - _lastKnownState = RCTCurrentAppBackgroundState(); - - for (NSString *name in @[UIApplicationDidBecomeActiveNotification, - UIApplicationDidEnterBackgroundNotification, - UIApplicationDidFinishLaunchingNotification]) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleAppStateDidChange) - name:name - object:nil]; - } + // Is this thread-safe? + _lastKnownState = RCTCurrentAppBackgroundState(); + for (NSString *name in @[UIApplicationDidBecomeActiveNotification, + UIApplicationDidEnterBackgroundNotification, + UIApplicationDidFinishLaunchingNotification]) { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMemoryWarning) - name:UIApplicationDidReceiveMemoryWarningNotification + selector:@selector(handleAppStateDidChange) + name:name object:nil]; } - return self; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleMemoryWarning) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; } - (void)handleMemoryWarning diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m index a0266b7ce60d..f522794ab316 100644 --- a/React/Modules/RCTDevLoadingView.m +++ b/React/Modules/RCTDevLoadingView.m @@ -34,23 +34,6 @@ + (void)setEnabled:(BOOL)enabled isEnabled = enabled; } -- (instancetype)init -{ - if ((self = [super init])) { - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(hide) - name:RCTJavaScriptDidLoadNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(hide) - name:RCTJavaScriptDidFailToLoadNotification - object:nil]; - } - return self; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -59,6 +42,16 @@ - (void)dealloc - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(hide) + name:RCTJavaScriptDidLoadNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(hide) + name:RCTJavaScriptDidFailToLoadNotification + object:nil]; [self showWithURL:bridge.bundleURL]; } diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index a57c4b6432f4..dd66c900b2cd 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -321,7 +321,7 @@ - (void)jsLoaded:(NSNotification *)notification // Check if live reloading is available _liveReloadURL = nil; - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = [_bridge moduleForClass:[RCTSourceCode class]]; if (!sourceCodeModule.scriptURL) { if (!sourceCodeModule) { RCTLogWarn(@"RCTSourceCode module not found"); @@ -614,7 +614,7 @@ @implementation RCTBridge (RCTDevMenu) - (RCTDevMenu *)devMenu { #if RCT_DEV - return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])]; + return [self moduleForClass:[RCTDevMenu class]]; #else return nil; #endif diff --git a/React/Modules/RCTExceptionsManager.h b/React/Modules/RCTExceptionsManager.h index 20ca8d26e219..ad5625287eb7 100644 --- a/React/Modules/RCTExceptionsManager.h +++ b/React/Modules/RCTExceptionsManager.h @@ -23,7 +23,7 @@ @interface RCTExceptionsManager : NSObject -- (instancetype)initWithDelegate:(id)delegate NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDelegate:(id)delegate; @property (nonatomic, assign) NSUInteger maxReloadAttempts; diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 9cc5d572212c..c536e20a10da 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -27,18 +27,12 @@ @implementation RCTExceptionsManager - (instancetype)initWithDelegate:(id)delegate { - if ((self = [super init])) { + if ((self = [self init])) { _delegate = delegate; - _maxReloadAttempts = 0; } return self; } -- (instancetype)init -{ - return [self initWithDelegate:nil]; -} - RCT_EXPORT_METHOD(reportSoftException:(NSString *)message stack:(NSDictionaryArray *)stack exceptionId:(nonnull NSNumber *)exceptionId) diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index 9f603afd0c86..6379c1164dbb 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -107,8 +107,9 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray { if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) { _lastStackTrace = stack; - // message is displayed using UILabel, which is unable to render text of unlimited length, so we truncate it - _lastErrorMessage = [message substringToIndex:MIN(10000, message.length)]; + // message is displayed using UILabel, which is unable to render text of + // unlimited length, so we truncate it + _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)]; [_stackTraceTableView reloadData]; @@ -326,7 +327,7 @@ @implementation RCTBridge (RCTRedBox) - (RCTRedBox *)redBox { - return self.modules[RCTBridgeModuleNameForClass([RCTRedBox class])]; + return [self moduleForClass:[RCTRedBox class]]; } @end diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 80e40c9aae14..dd29a078992d 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -46,14 +46,13 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() @synthesize bridge = _bridge; -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; - [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; - } - return self; + _bridge = bridge; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; + [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; } - (void)dealloc diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index a15d2516421b..51911fdb055c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -179,14 +179,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseS @end -@interface RCTUIManager () - -// NOTE: these are properties so that they can be accessed by unit tests -@property (nonatomic, strong) NSMutableDictionary *shadowViewRegistry; // RCT thread only -@property (nonatomic, strong) NSMutableDictionary *viewRegistry; // Main thread only - -@end - @implementation RCTUIManager { dispatch_queue_t _shadowQueue; @@ -199,6 +191,9 @@ @implementation RCTUIManager RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only RCTLayoutAnimation *_layoutAnimation; // Main thread only + NSMutableDictionary *_shadowViewRegistry; // RCT thread only + NSMutableDictionary *_viewRegistry; // Main thread only + // Keyed by viewName NSDictionary *_componentDataByName; @@ -214,31 +209,6 @@ @implementation RCTUIManager */ extern NSString *RCTBridgeModuleNameForClass(Class cls); -- (instancetype)init -{ - if ((self = [super init])) { - const char *queueName = "com.facebook.React.ShadowQueue"; - - if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) { - dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0); - _shadowQueue = dispatch_queue_create(queueName, attr); - } else { - _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); - } - - _shadowViewRegistry = [NSMutableDictionary new]; - _viewRegistry = [NSMutableDictionary new]; - - // Internal resources - _pendingUIBlocks = [NSMutableArray new]; - _rootViewTags = [NSMutableSet new]; - - _bridgeTransactionListeners = [NSMutableSet new]; - } - return self; -} - - (void)didReceiveNewContentSizeMultiplier { __weak RCTUIManager *weakSelf = self; @@ -276,18 +246,45 @@ - (void)invalidate }); } +- (NSMutableDictionary *)shadowViewRegistry +{ + // NOTE: this method only exists so that it can be accessed by unit tests + if (!_shadowViewRegistry) { + _shadowViewRegistry = [NSMutableDictionary new]; + } + return _shadowViewRegistry; +} + +- (NSMutableDictionary *)viewRegistry +{ + // NOTE: this method only exists so that it can be accessed by unit tests + if (!_viewRegistry) { + _viewRegistry = [NSMutableDictionary new]; + } + return _viewRegistry; +} + - (void)setBridge:(RCTBridge *)bridge { RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance"); _bridge = bridge; + _shadowViewRegistry = [NSMutableDictionary new]; + _viewRegistry = [NSMutableDictionary new]; + + // Internal resources + _pendingUIBlocks = [NSMutableArray new]; + _rootViewTags = [NSMutableSet new]; + + _bridgeTransactionListeners = [NSMutableSet new]; // Get view managers from bridge NSMutableDictionary *componentDataByName = [NSMutableDictionary new]; - for (RCTViewManager *manager in _bridge.modules.allValues) { - if ([manager isKindOfClass:[RCTViewManager class]]) { - RCTComponentData *componentData = [[RCTComponentData alloc] initWithManager:manager]; + for (Class moduleClass in _bridge.moduleClasses) { + if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) { + RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass + bridge:_bridge]; componentDataByName[componentData.name] = componentData; } } @@ -302,6 +299,17 @@ - (void)setBridge:(RCTBridge *)bridge - (dispatch_queue_t)methodQueue { + if (!_shadowQueue) { + const char *queueName = "com.facebook.React.ShadowQueue"; + + if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) { + dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0); + _shadowQueue = dispatch_queue_create(queueName, attr); + } else { + _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + } + } return _shadowQueue; } @@ -1126,12 +1134,11 @@ static void RCTMeasureLayout(RCTShadowView *view, [_componentDataByName enumerateKeysAndObjectsUsingBlock: ^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) { - RCTViewManager *manager = componentData.manager; NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; // Add manager class - constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass([manager class]); + constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass); // Add native props NSDictionary *viewConfig = [componentData viewConfig]; @@ -1216,7 +1223,7 @@ @implementation RCTBridge (RCTUIManager) - (RCTUIManager *)uiManager { - return self.modules[RCTBridgeModuleNameForClass([RCTUIManager class])]; + return [self moduleForClass:[RCTUIManager class]]; } @end diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index 276ae2d4f17b..fe93b658050e 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -30,6 +30,13 @@ #if RCT_DEV +@interface RCTBridge () + +- (void)dispatchBlock:(dispatch_block_t)block + queue:(dispatch_queue_t)queue; + +@end + #pragma mark - Constants NSString const *RCTProfileTraceEvents = @"traceEvents"; @@ -211,7 +218,7 @@ void RCTProfileHookModules(RCTBridge *bridge) RCTProfileHookedModules = YES; for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { - [moduleData dispatchBlock:^{ + [bridge dispatchBlock:^{ Class moduleClass = moduleData.moduleClass; Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); @@ -242,7 +249,7 @@ void RCTProfileHookModules(RCTBridge *bridge) objc_registerClassPair(proxyClass); object_setClass(moduleData.instance, proxyClass); - }]; + } queue:moduleData.methodQueue]; } } diff --git a/React/Profiler/RCTProfileTrampoline-arm.S b/React/Profiler/RCTProfileTrampoline-arm.S index 4835d261adcb..61c32df92855 100644 --- a/React/Profiler/RCTProfileTrampoline-arm.S +++ b/React/Profiler/RCTProfileTrampoline-arm.S @@ -1,3 +1,12 @@ +/** + * 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. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-arm64.S b/React/Profiler/RCTProfileTrampoline-arm64.S index 92ea42a833f0..435d86a4d517 100644 --- a/React/Profiler/RCTProfileTrampoline-arm64.S +++ b/React/Profiler/RCTProfileTrampoline-arm64.S @@ -1,3 +1,12 @@ +/** + * 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. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-i386.S b/React/Profiler/RCTProfileTrampoline-i386.S index 0ccd37b4fe09..7aed481325a2 100644 --- a/React/Profiler/RCTProfileTrampoline-i386.S +++ b/React/Profiler/RCTProfileTrampoline-i386.S @@ -1,3 +1,12 @@ +/** + * 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. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-x86_64.S b/React/Profiler/RCTProfileTrampoline-x86_64.S index 9a7a81c8594a..02fa010a4814 100644 --- a/React/Profiler/RCTProfileTrampoline-x86_64.S +++ b/React/Profiler/RCTProfileTrampoline-x86_64.S @@ -1,3 +1,12 @@ +/** + * 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. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index b04420043bcd..4250901f6893 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; }; 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; }; 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; }; - 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1385D0331B665AAE000A309B /* RCTModuleMap.m */; }; 13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; }; 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; @@ -48,6 +47,7 @@ 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; + 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; }; 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; }; 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; }; @@ -57,7 +57,6 @@ 1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */; }; 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; }; 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */; }; - 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; }; 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; }; 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; }; @@ -134,8 +133,6 @@ 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 = ""; }; - 1385D0331B665AAE000A309B /* RCTModuleMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMap.m; sourceTree = ""; }; - 1385D0351B6661DB000A309B /* RCTModuleMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModuleMap.h; sourceTree = ""; }; 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevLoadingView.h; sourceTree = ""; }; 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevLoadingView.m; sourceTree = ""; }; 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = ""; }; @@ -432,7 +429,6 @@ isa = PBXGroup; children = ( 14BF71811C04795500C97D0C /* RCTMacros.h */, - 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */, 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */, 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */, 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */, @@ -440,6 +436,7 @@ 1450FF811BCFF28A00208362 /* RCTProfile.m */, 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */, 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */, + 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */, 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */, ); path = Profiler; @@ -506,8 +503,6 @@ 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */, 14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */, 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */, - 1385D0351B6661DB000A309B /* RCTModuleMap.h */, - 1385D0331B665AAE000A309B /* RCTModuleMap.m */, 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */, 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */, 142014181B32094000CC17BA /* RCTPerformanceLogger.h */, @@ -643,6 +638,7 @@ 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */, 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */, 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */, + 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */, 13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */, 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */, 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */, @@ -683,8 +679,6 @@ 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */, 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, - 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */, - 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */, 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */, diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h index 9a43e9865a39..f7151c22b543 100644 --- a/React/Views/RCTComponentData.h +++ b/React/Views/RCTComponentData.h @@ -12,16 +12,19 @@ #import "RCTComponent.h" #import "RCTDefines.h" +@class RCTBridge; @class RCTShadowView; @class RCTViewManager; @class UIView; @interface RCTComponentData : NSObject +@property (nonatomic, readonly) Class managerClass; @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, strong, readonly) RCTViewManager *manager; -- (instancetype)initWithManager:(RCTViewManager *)manager NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithManagerClass:(Class)managerClass + bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary *)props; - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag; diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 20c64c3e1582..dbcbc10c9f2c 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -44,16 +44,21 @@ @implementation RCTComponentData RCTShadowView *_defaultShadowView; NSMutableDictionary *_viewPropBlocks; NSMutableDictionary *_shadowPropBlocks; + RCTBridge *_bridge; } -- (instancetype)initWithManager:(RCTViewManager *)manager +@synthesize manager = _manager; + +- (instancetype)initWithManagerClass:(Class)managerClass + bridge:(RCTBridge *)bridge { if ((self = [super init])) { - _manager = manager; + _bridge = bridge; + _managerClass = managerClass; _viewPropBlocks = [NSMutableDictionary new]; _shadowPropBlocks = [NSMutableDictionary new]; - _name = RCTBridgeModuleNameForClass([manager class]); + _name = RCTBridgeModuleNameForClass(_managerClass); RCTAssert(_name.length, @"Invalid moduleName '%@'", _name); if ([_name hasSuffix:@"Manager"]) { _name = [_name substringToIndex:_name.length - @"Manager".length]; @@ -62,13 +67,21 @@ - (instancetype)initWithManager:(RCTViewManager *)manager return self; } +- (RCTViewManager *)manager +{ + if (!_manager) { + _manager = [_bridge moduleForClass:_managerClass]; + } + return _manager; +} + RCT_NOT_IMPLEMENTED(- (instancetype)init) - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary *)props { RCTAssertMainThread(); - UIView *view = (UIView *)(props ? [_manager viewWithProps:props] : [_manager view]); + UIView *view = (UIView *)(props ? [self.manager viewWithProps:props] : [_manager view]); view.reactTag = tag; view.multipleTouchEnabled = YES; view.userInteractionEnabled = YES; // required for touch handling @@ -78,7 +91,7 @@ - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary *)props forShadowView:(RCTShadowV - (NSDictionary *)viewConfig { - Class managerClass = [_manager class]; - NSMutableArray *directEvents = [NSMutableArray new]; - if (RCTClassOverridesInstanceMethod(managerClass, @selector(customDirectEventTypes))) { - NSArray *events = [_manager customDirectEventTypes]; + if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customDirectEventTypes))) { + NSArray *events = [self.manager customDirectEventTypes]; if (RCT_DEBUG) { RCTAssert(!events || [events isKindOfClass:[NSArray class]], @"customDirectEventTypes must return an array, but %@ returned %@", - managerClass, [events class]); + _managerClass, [events class]); } for (NSString *event in events) { [directEvents addObject:RCTNormalizeInputEventName(event)]; @@ -326,12 +337,12 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV } NSMutableArray *bubblingEvents = [NSMutableArray new]; - if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) { - NSArray *events = [_manager customBubblingEventTypes]; + if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) { + NSArray *events = [self.manager customBubblingEventTypes]; if (RCT_DEBUG) { RCTAssert(!events || [events isKindOfClass:[NSArray class]], @"customBubblingEventTypes must return an array, but %@ returned %@", - managerClass, [events class]); + _managerClass, [events class]); } for (NSString *event in events) { [bubblingEvents addObject:RCTNormalizeInputEventName(event)]; @@ -340,7 +351,7 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV unsigned int count = 0; NSMutableDictionary *propTypes = [NSMutableDictionary new]; - Method *methods = class_copyMethodList(object_getClass(managerClass), &count); + Method *methods = class_copyMethodList(object_getClass(_managerClass), &count); for (unsigned int i = 0; i < count; i++) { Method method = methods[i]; SEL selector = method_getName(method); @@ -349,7 +360,7 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV NSRange nameRange = [methodName rangeOfString:@"_"]; if (nameRange.length) { NSString *name = [methodName substringFromIndex:nameRange.location + 1]; - NSString *type = ((NSArray *(*)(id, SEL))objc_msgSend)(managerClass, selector)[0]; + NSString *type = ((NSArray *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0]; if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) { RCTLogError(@"Property '%@' of component '%@' redefined from '%@' " "to '%@'", name, _name, propTypes[name], type); diff --git a/React/Views/RCTModalHostViewController.m b/React/Views/RCTModalHostViewController.m index 055036726bcd..ce40551bddc0 100644 --- a/React/Views/RCTModalHostViewController.m +++ b/React/Views/RCTModalHostViewController.m @@ -9,7 +9,8 @@ #import "RCTModalHostViewController.h" -@implementation RCTModalHostViewController { +@implementation RCTModalHostViewController +{ CGRect _lastViewFrame; } diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index 3ad022b213fb..ae3b21f2c063 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -20,18 +20,12 @@ @implementation RCTModalHostViewManager RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _hostViews = [NSHashTable weakObjectsHashTable]; - } - - return self; -} - - (UIView *)view { UIView *view = [[RCTModalHostView alloc] initWithBridge:self.bridge]; + if (_hostViews) { + _hostViews = [NSHashTable weakObjectsHashTable]; + } [_hostViews addObject:view]; return view; } diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index def09910d286..235f52a7b2ee 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -30,7 +30,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary *)constantsToExport -{ - return nil; -} - - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView { return nil; From 638fd11c5c7b1bd10069205f2063aac0e887b414 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Wed, 25 Nov 2015 05:20:34 -0800 Subject: [PATCH 0104/1411] Revert 3770f061c832 for further investigation Reviewed By: idevelop Differential Revision: D2695659 fb-gh-sync-id: b1ba529c648681faef5d4f07273722764722fbe1 --- Libraries/Storage/AsyncStorage.js | 25 ------------------------- React/Modules/RCTAsyncLocalStorage.m | 18 ------------------ 2 files changed, 43 deletions(-) diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index 4b3a8e0008bb..c5be992e2aba 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -137,26 +137,6 @@ var AsyncStorage = { }); }, - /** - * Erases all keys with a particular prefix. Useful if all your keys have a - * specific prefix. - */ - clearPrefix: function( - prefix: string, - callback?: ?(error: ?Error) => void - ): Promise { - return new Promise((resolve, reject) => { - RCTAsyncStorage.clearPrefix(prefix, function(error) { - callback && callback(convertError(error)); - if (error && convertError(error)){ - reject(convertError(error)); - } else { - resolve(null); - } - }); - }); - }, - /** * Gets *all* keys known to the app, for all callers, libraries, etc. Returns a `Promise` object. */ @@ -279,11 +259,6 @@ if (!RCTAsyncStorage.multiMerge) { delete AsyncStorage.multiMerge; } -// clearPrefix() only supported by certain backends -if (!RCTAsyncStorage.clearPrefix) { - delete AsyncStorage.clearPrefix; -} - function convertErrors(errs) { if (!errs) { return null; diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index eceb15847c2a..273ef40c6bf8 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -435,24 +435,6 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL callback(@[RCTNullIfNil(error)]); } -RCT_EXPORT_METHOD(clearPrefix:(NSString *)prefix callack:(RCTResponseSenderBlock)callback) -{ - NSDictionary *errorOut = [self _ensureSetup]; - if (errorOut) { - callback(@[errorOut]); - return; - } - - NSMutableArray *keys = [NSMutableArray array]; - for (NSString *key in _manifest.allKeys) { - if ([key hasPrefix:prefix]) { - [keys addObject:key]; - } - } - - [self multiRemove:keys callback:callback]; -} - RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; From 388d8c8f9bd2d27194779e682747359a00cb4b86 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 25 Nov 2015 13:41:40 +0000 Subject: [PATCH 0105/1411] Update breaking-changes.md --- breaking-changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/breaking-changes.md b/breaking-changes.md index b4448fb2aca8..77f27ca393ea 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -6,6 +6,7 @@ - YellowBox enabled by default: [8ab518](https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85) - React Native now uses Babel 6 (props to tadeuzagallo for upgrading!). We've been using React Native with Babel 6 at Facebook for quite a while now. Nevertheless, please report any errors related to Babel, such as transforms for some JS features not working as expected and we'll fix them. - Decorators won't work until [T2645](https://phabricator.babeljs.io/T2645) lands in Babel. +- Exporting default class that extends a base class won't work due to Babel's [T2694](https://phabricator.babeljs.io/T2694). ## 0.15 From ca016e4eb30e4535c42e8e108ea6ff06e2b44b44 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 25 Nov 2015 04:56:15 -0800 Subject: [PATCH 0106/1411] Add perf markers for cold start Summary: public Adds useful systrace perf markers for cold start analysis Reviewed By: mikearmstrong001 Differential Revision: D2695629 fb-gh-sync-id: d964f28a1f3e10a13c441a17b0300c980d4914e8 --- .../facebook/react/CoreModulesPackage.java | 15 +++- .../react/ReactInstanceManagerImpl.java | 73 ++++++++++++++++--- .../react/bridge/CatalystInstanceImpl.java | 50 +++++++++---- .../react/bridge/NativeModuleRegistry.java | 47 ++++++------ .../react/uimanager/UIManagerModule.java | 18 ++++- 5 files changed, 147 insertions(+), 56 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index a7bc29aa63a1..02bd96d0dc0f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -32,6 +32,7 @@ import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.debug.DebugComponentOwnershipModule; import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.facebook.systrace.Systrace; /** * Package defining core framework modules (e.g. UIManager). It should be used for modules that @@ -53,6 +54,16 @@ @Override public List createNativeModules( ReactApplicationContext catalystApplicationContext) { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createUIManagerModule"); + UIManagerModule uiManagerModule; + try { + uiManagerModule = new UIManagerModule( + catalystApplicationContext, + mReactInstanceManager.createAllViewManagers(catalystApplicationContext)); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + return Arrays.asList( new AnimationsDebugModule( catalystApplicationContext, @@ -64,9 +75,7 @@ public List createNativeModules( new SourceCodeModule( mReactInstanceManager.getSourceUrl(), mReactInstanceManager.getDevSupportManager().getSourceMapUrl()), - new UIManagerModule( - catalystApplicationContext, - mReactInstanceManager.createAllViewManagers(catalystApplicationContext)), + uiManagerModule, new DebugComponentOwnershipModule(catalystApplicationContext)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index f8e0cdc0448a..deb6bf21c29b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -55,6 +55,7 @@ import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import com.facebook.soloader.SoLoader; +import com.facebook.systrace.Systrace; /** * This class is managing instances of {@link CatalystInstance}. It expose a way to configure @@ -465,11 +466,16 @@ public void detachRootView(ReactRootView rootView) { @Override public List createAllViewManagers( ReactApplicationContext catalystApplicationContext) { - List allViewManagers = new ArrayList<>(); - for (ReactPackage reactPackage : mPackages) { - allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext)); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createAllViewManagers"); + try { + List allViewManagers = new ArrayList<>(); + for (ReactPackage reactPackage : mPackages) { + allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext)); + } + return allViewManagers; + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } - return allViewManagers; } @VisibleForTesting @@ -586,30 +592,73 @@ private ReactApplicationContext createReactContext( reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); } - CoreModulesPackage coreModulesPackage = - new CoreModulesPackage(this, mBackBtnHandler); - processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "createAndProcessCoreModulesPackage"); + try { + CoreModulesPackage coreModulesPackage = + new CoreModulesPackage(this, mBackBtnHandler); + processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } // TODO(6818138): Solve use-case of native/js modules overriding for (ReactPackage reactPackage : mPackages) { - processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "createAndProcessCustomReactPackage"); + try { + processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry"); + NativeModuleRegistry nativeModuleRegistry; + try { + nativeModuleRegistry = nativeRegistryBuilder.build(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig"); + JavaScriptModulesConfig javaScriptModulesConfig; + try { + javaScriptModulesConfig = jsModulesBuilder.build(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) - .setRegistry(nativeRegistryBuilder.build()) - .setJSModulesConfig(jsModulesBuilder.build()) + .setRegistry(nativeModuleRegistry) + .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(mDevSupportManager); - CatalystInstance catalystInstance = catalystInstanceBuilder.build(); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance"); + CatalystInstance catalystInstance; + try { + catalystInstance = catalystInstanceBuilder.build(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + if (mBridgeIdleDebugListener != null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } reactContext.initializeWithInstance(catalystInstance); - catalystInstance.runJSBundle(); + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle"); + try { + catalystInstance.runJSBundle(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } return reactContext; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 4b987e78d256..08f5a702100b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -88,8 +88,13 @@ private CatalystInstanceImpl( new Runnable() { @Override public void run() { - initializeBridge(jsExecutor, jsModulesConfig); - initLatch.countDown(); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge"); + try { + initializeBridge(jsExecutor, jsModulesConfig); + initLatch.countDown(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } } }); @@ -108,22 +113,31 @@ private void initializeBridge( mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); - mBridge = new ReactBridge( - jsExecutor, - new NativeModulesReactCallback(), - mCatalystQueueConfiguration.getNativeModulesQueueThread()); - mBridge.setGlobalVariable( - "__fbBatchedBridgeConfig", - buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); - Systrace.registerListener(mTraceListener); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor"); + try { + mBridge = new ReactBridge( + jsExecutor, + new NativeModulesReactCallback(), + mCatalystQueueConfiguration.getNativeModulesQueueThread()); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); + try { + mBridge.setGlobalVariable( + "__fbBatchedBridgeConfig", + buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); + mBridge.setGlobalVariable( + "__RCTProfileIsProfiling", + Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false"); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } } @Override public void runJSBundle() { - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "CatalystInstance_runJSBundle"); - try { final CountDownLatch initLatch = new CountDownLatch(1); mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( @@ -135,10 +149,16 @@ public void run() { incrementPendingJSCalls(); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "loadJSScript"); try { mJSBundleLoader.loadScript(mBridge); + + // This is registered after JS starts since it makes a JS call + Systrace.registerListener(mTraceListener); } catch (JSExecutionException e) { mNativeModuleCallExceptionHandler.handleException(e); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } initLatch.countDown(); @@ -149,8 +169,6 @@ public void run() { "Timed out loading JS!"); } catch (InterruptedException e) { throw new RuntimeException(e); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java index 72b3ad666953..80b1b4616119 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java @@ -13,7 +13,6 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -186,32 +185,38 @@ public Builder add(NativeModule module) { } public NativeModuleRegistry build() { - JsonFactory jsonFactory = new JsonFactory(); - StringWriter writer = new StringWriter(); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateJSON"); + String moduleDefinitionJson; try { - JsonGenerator jg = jsonFactory.createGenerator(writer); - jg.writeStartObject(); - for (ModuleDefinition module : mModuleDefinitions) { - jg.writeObjectFieldStart(module.name); - jg.writeNumberField("moduleID", module.id); - jg.writeObjectFieldStart("methods"); - for (int i = 0; i < module.methods.size(); i++) { - MethodRegistration method = module.methods.get(i); - jg.writeObjectFieldStart(method.name); - jg.writeNumberField("methodID", i); - jg.writeStringField("type", method.method.getType()); + JsonFactory jsonFactory = new JsonFactory(); + StringWriter writer = new StringWriter(); + try { + JsonGenerator jg = jsonFactory.createGenerator(writer); + jg.writeStartObject(); + for (ModuleDefinition module : mModuleDefinitions) { + jg.writeObjectFieldStart(module.name); + jg.writeNumberField("moduleID", module.id); + jg.writeObjectFieldStart("methods"); + for (int i = 0; i < module.methods.size(); i++) { + MethodRegistration method = module.methods.get(i); + jg.writeObjectFieldStart(method.name); + jg.writeNumberField("methodID", i); + jg.writeStringField("type", method.method.getType()); + jg.writeEndObject(); + } + jg.writeEndObject(); + module.target.writeConstantsField(jg, "constants"); jg.writeEndObject(); } jg.writeEndObject(); - module.target.writeConstantsField(jg, "constants"); - jg.writeEndObject(); + jg.close(); + } catch (IOException ioe) { + throw new RuntimeException("Unable to serialize Java module configuration", ioe); } - jg.writeEndObject(); - jg.close(); - } catch (IOException ioe) { - throw new RuntimeException("Unable to serialize Java module configuration", ioe); + moduleDefinitionJson = writer.getBuffer().toString(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } - String moduleDefinitionJson = writer.getBuffer().toString(); return new NativeModuleRegistry(mModuleDefinitions, mModuleInstances, moduleDefinitionJson); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 38e78cc65b66..1512f305f7e7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -106,10 +106,7 @@ public UIManagerModule(ReactApplicationContext reactContext, List v mShadowNodeRegistry); DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics(); DisplayMetricsHolder.setDisplayMetrics(displayMetrics); - - mModuleConstants = UIManagerModuleConstantsHelper.createConstants( - displayMetrics, - viewManagerList); + mModuleConstants = createConstants(displayMetrics, viewManagerList); reactContext.addLifecycleEventListener(this); } @@ -143,6 +140,19 @@ public void onCatalystInstanceDestroy() { mEventDispatcher.onCatalystInstanceDestroyed(); } + private static Map createConstants( + DisplayMetrics displayMetrics, + List viewManagerList) { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants"); + try { + return UIManagerModuleConstantsHelper.createConstants( + displayMetrics, + viewManagerList); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + /** * Registers a new root view. JS can use the returned tag with manageChildren to add/remove * children to this view. From a663d4d8d5daa601c43d85871fe8b335eb848dd2 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Wed, 25 Nov 2015 07:22:28 -0800 Subject: [PATCH 0107/1411] Use for-loop instead of forEach() in a hot path Summary: public Replaces the usage of forEach() with a for loop on a hot code path Reviewed By: tadeuzagallo Differential Revision: D2690727 fb-gh-sync-id: b7cbcda5cf80a0e31753f49c01e145abb789f3e5 --- .../System/JSTimers/JSTimersExecution.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js index bc2b74d220f0..07a324d41f4c 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js @@ -121,9 +121,11 @@ var JSTimersExecution = { var passImmediates = JSTimersExecution.immediates.slice(); JSTimersExecution.immediates = []; - passImmediates.forEach((timerID) => { - JSTimersExecution.callTimer(timerID); - }); + // Use for loop rather than forEach as per @vjeux's advice + // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051 + for (var i = 0; i < passImmediates.length; ++i) { + JSTimersExecution.callTimer(passImmediates[i]); + } } BridgeProfiling.profileEnd(); From e966cd104110a980f343b794d85ac1b3afb2449e Mon Sep 17 00:00:00 2001 From: Chester Wood Date: Wed, 25 Nov 2015 12:43:39 -0800 Subject: [PATCH 0108/1411] Set navigator.product to ReactNative Summary: Fix for [Issue 1331](https://github.com/facebook/react-native/issues/1331). Sets navigator.product to ReactNative and navigator.productSub to the version string in package.json. Note that the code requires package.json, which works fine in the RN packager, but webpack users will probably a need to configure a json loader in their config file. Tested using UIExplorer and console.log printout of the product variables in xcode and Chrome debugger. Closes https://github.com/facebook/react-native/pull/4083 Reviewed By: svcscm Differential Revision: D2696881 Pulled By: vjeux fb-gh-sync-id: 511446432dcd0ec658100715129c77153e743423 --- .../Initialization/InitializeJavaScriptAppEngine.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index ec13c1615032..9c2b7f58438b 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -154,6 +154,11 @@ function setUpGeolocation() { polyfillGlobal('geolocation', require('Geolocation'), GLOBAL.navigator); } +function setUpProduct() { + Object.defineProperty(GLOBAL.navigator, 'product', {value: 'ReactNative'}); +} + + function setUpWebSockets() { polyfillGlobal('WebSocket', require('WebSocket')); } @@ -197,6 +202,7 @@ setUpPromise(); setUpErrorHandler(); setUpXHR(); setUpGeolocation(); +setUpProduct(); setUpWebSockets(); setUpProfile(); setUpFlowChecker(); From 0d2eb748f744c54b24ea2363cce76dd9747433d5 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Wed, 25 Nov 2015 14:10:03 -0800 Subject: [PATCH 0109/1411] =?UTF-8?q?[Docs]=20Re-imagining=20the=20showcas?= =?UTF-8?q?e=20=F0=9F=98=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- website/src/react-native/css/react-native.css | 11 +- website/src/react-native/showcase.js | 298 +++++++++++------- 2 files changed, 197 insertions(+), 112 deletions(-) diff --git a/website/src/react-native/css/react-native.css b/website/src/react-native/css/react-native.css index 54e3930f94b7..034d52e26e2e 100644 --- a/website/src/react-native/css/react-native.css +++ b/website/src/react-native/css/react-native.css @@ -701,6 +701,10 @@ figure { width: 650px; } +.showcaseSection .inner-content { + width: 800px; +} + .nosidebar .inner-content { float: none; margin: 0 auto; @@ -1084,18 +1088,17 @@ div[data-twttr-id] iframe { } .showcaseHeader { - text-align: center; padding-bottom: 15px; padding-top: 15px; + text-align: center; } .showcase { - margin: 30px auto; + margin: 30px auto 10px auto; width: 100%; display: inline-block; text-align: center; vertical-align: top; - transition: 0.2s opacity ease-in; } @media only screen @@ -1109,7 +1112,7 @@ div[data-twttr-id] iframe { @media only screen and (min-device-width: 1024px) { .showcase { - width: 25%; + width: 20%; } } diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 706615c45b79..987580237dd9 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -11,13 +11,111 @@ var React = require('React'); var Site = require('Site'); var center = require('center'); -var apps = [ +var featured = [ + { + name: 'Facebook Groups', + icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple69/v4/57/f8/4c/57f84c0c-793d-5f9a-95ee-c212d0369e37/mzl.ugjwfhzx.png', + link: 'https://itunes.apple.com/us/app/facebook-groups/id931735837?mt=8', + author: 'Facebook', + }, { - name: 'AIGA Design Conference 2015: New Orleans', + name: 'Facebook Ads Manager', + icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple5/v4/9e/16/86/9e1686ef-cc55-805a-c977-538ddb5e6832/mzl.gqbhwitj.png', + linkAppStore: 'https://itunes.apple.com/us/app/facebook-ads-manager/id964397083?mt=8', + linkPlayStore: 'https://play.google.com/store/apps/details?id=com.facebook.adsmanager', + author: 'Facebook', + blogs: [ + "https://code.facebook.com/posts/1014532261909640/react-native-bringing-modern-web-techniques-to-mobile/", + "https://code.facebook.com/posts/1189117404435352/react-native-for-android-how-we-built-the-first-cross-platform-react-native-app/", + "https://code.facebook.com/posts/435862739941212/making-react-native-apps-accessible/", + ], + }, + { + name: 'AIGA Design Conference 2015', icon: 'http://a5.mzstatic.com/us/r30/Purple69/v4/b0/4b/29/b04b2939-88d2-f61f-dec9-24fae083d8b3/icon175x175.png', link: 'https://itunes.apple.com/us/app/aiga-design-conference-2015/id1038145272?ls=1&mt=8', author: 'W&Co', }, + { + name: 'Discord', + icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg', + link: 'https://itunes.apple.com/us/app/discord-chat-for-gamers/id985746746?mt=8', + author: 'Hammer & Chisel', + }, + { + name: 'Discovery VR', + icon: 'http://a2.mzstatic.com/us/r30/Purple6/v4/d1/d5/f4/d1d5f437-9f6b-b5aa-5fe7-47bd19f934bf/icon175x175.png', + link: 'https://itunes.apple.com/us/app/discovery-vr/id1030815031?mt=8', + author: 'Discovery Communications', + blog: [ + "https://medium.com/ios-os-x-development/an-ios-developer-on-react-native-1f24786c29f0", + ], + }, + { + name: 'Exponent', + icon: 'http://a4.mzstatic.com/us/r30/Purple2/v4/3a/d3/c9/3ad3c96c-5e14-f988-4bdd-0fdc95efd140/icon175x175.png', + link: 'http://exponentjs.com/', + author: 'Exponent', + }, + { + name: 'Lrn', + icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple1/v4/41/a9/e9/41a9e9b6-7801-aef7-2400-2eca14923321/mzl.adyswxad.png', + link: 'https://itunes.apple.com/us/app/lrn-learn-to-code-at-your/id1019622677', + author: 'Lrn Labs, Inc', + }, + { + name: 'Myntra', + icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/9c/78/df/9c78dfa6-0061-1af2-5026-3e1d5a073c94/icon350x350.png', + link: 'https://itunes.apple.com/in/app/myntra-fashion-shopping-app/id907394059', + author: 'Myntra Designs', + }, + { + name: 'React Native Playground', + icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple1/v4/20/ec/8e/20ec8eb8-9e12-6686-cd16-7ac9e3ef1d52/mzl.ngvuoybx.png', + linkAppStore: 'https://itunes.apple.com/us/app/react-native-playground/id1002032944?mt=8', + linkPlayStore: 'https://play.google.com/store/apps/details?id=org.rnplay.playground', + author: 'Joshua Sierles', + }, + { + name: 'Running', + icon: 'http://a1.mzstatic.com/us/r30/Purple3/v4/33/eb/4f/33eb4f73-c7e3-8659-9285-f758e403485b/icon175x175.jpeg', + link: 'https://gyrosco.pe/running/', + author: 'Gyroscope Innovations', + blogs: [ + 'https://blog.gyrosco.pe/the-making-of-gyroscope-running-a4ad10acc0d0', + ], + }, + { + name: 'Squad', + icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/e8/5b/3f/e85b3f52-72f3-f427-a32e-a73efe2e9682/icon175x175.jpeg', + link: 'https://itunes.apple.com/us/app/squad-snaps-for-groups-friends/id1043626975?mt=8', + author: 'Tackk Inc.', + }, + { + name: 'Start - medication manager for depression', + icon: 'http://a1.mzstatic.com/us/r30/Purple49/v4/de/9b/6f/de9b6fe8-84ea-7a12-ba2c-0a6d6c7b10b0/icon175x175.png', + link: 'https://itunes.apple.com/us/app/start-medication-manager-for/id1012099928?mt=8', + author: 'Iodine Inc.', + }, + { + name: 'Townske', + icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/8b/42/20/8b4220af-5165-91fd-0f05-014332df73ef/icon175x175.png', + link: 'https://itunes.apple.com/us/app/townske-stunning-city-guides/id1018136179?ls=1&mt=8', + author: 'Townske PTY LTD', + }, + { + name: 'Tucci', + icon: 'http://a3.mzstatic.com/us/r30/Purple3/v4/c0/0c/95/c00c95ce-4cc5-e516-db77-5c5164b89189/icon175x175.jpeg', + link: 'https://itunes.apple.com/app/apple-store/id1039661754?mt=8', + author: 'Clay Allsopp & Tiffany Young', + blogs: [ + 'https://medium.com/@clayallsopp/making-tucci-what-is-it-and-why-eaa2bf94c1df#.lmm3dmkaf', + 'https://medium.com/@clayallsopp/making-tucci-the-technical-details-cc7aded6c75f#.wf72nq372', + ], + }, +]; + +var apps = [ { name: 'Beetroot', icon: 'http://is1.mzstatic.com/image/pf/us/r30/Purple5/v4/66/fd/dd/66fddd70-f848-4fc5-43ee-4d52197ccab8/pr_source.png', @@ -44,54 +142,17 @@ var apps = [ author: 'The Formations Factory Ltd', }, { - name: 'DareU - dare your friends, dare the world', + name: 'DareU', icon: 'http://a3.mzstatic.com/us/r30/Purple6/v4/10/fb/6a/10fb6a50-57c8-061a-d865-503777bf7f00/icon175x175.png', link: 'https://itunes.apple.com/us/app/dareu-dare-your-friends-dare/id1046434563?mt=8', author: 'Rishabh Mehan', }, - { - name: 'Discord', - icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg', - link: 'https://itunes.apple.com/us/app/discord-chat-for-gamers/id985746746?mt=8', - author: 'Hammer & Chisel', - }, - { - name: 'Discovery VR', - icon: 'http://a2.mzstatic.com/us/r30/Purple6/v4/d1/d5/f4/d1d5f437-9f6b-b5aa-5fe7-47bd19f934bf/icon175x175.png', - link: 'https://itunes.apple.com/us/app/discovery-vr/id1030815031?mt=8', - author: 'Discovery Communications', - }, { name: 'DropBot', icon: 'http://a2.mzstatic.com/us/r30/Purple69/v4/fb/df/73/fbdf73e0-22d2-c936-3115-1defa195acba/icon175x175.png', link: 'https://itunes.apple.com/us/app/dropbot-phone-replacement/id1000855694?mt=8', author: 'Peach Labs', }, - { - name: 'Exponent', - icon: 'http://a4.mzstatic.com/us/r30/Purple2/v4/3a/d3/c9/3ad3c96c-5e14-f988-4bdd-0fdc95efd140/icon175x175.png', - link: 'https://itunes.apple.com/ca/app/exponent/id982107779?mt=8', - author: 'Exponent', - }, - { - name: 'F8', - icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple5/v4/bf/d9/50/bfd9504e-a1bd-67c5-b50b-24e97016dae9/pr_source.jpg', - link: 'https://itunes.apple.com/us/app/f8/id853467066?mt=8', - author: 'Facebook', - }, - { - name: 'Facebook Groups', - icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple69/v4/57/f8/4c/57f84c0c-793d-5f9a-95ee-c212d0369e37/mzl.ugjwfhzx.png', - link: 'https://itunes.apple.com/us/app/facebook-groups/id931735837?mt=8', - author: 'Facebook', - }, - { - name: 'Facebook Ads Manager', - icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple5/v4/9e/16/86/9e1686ef-cc55-805a-c977-538ddb5e6832/mzl.gqbhwitj.png', - linkAppStore: 'https://itunes.apple.com/us/app/facebook-ads-manager/id964397083?mt=8', - linkPlayStore: 'https://play.google.com/store/apps/details?id=com.facebook.adsmanager', - author: 'Facebook', - }, { name: 'Fan of it', icon: 'http://a4.mzstatic.com/us/r30/Purple3/v4/c9/3f/e8/c93fe8fb-9332-e744-f04a-0f4f78e42aa8/icon350x350.png', @@ -159,12 +220,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/loaddocs/id1041596066', author: 'LoadDocs', }, - { - name: 'Lrn', - icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple1/v4/41/a9/e9/41a9e9b6-7801-aef7-2400-2eca14923321/mzl.adyswxad.png', - link: 'https://itunes.apple.com/us/app/lrn-learn-to-code-at-your/id1019622677', - author: 'Lrn Labs, Inc', - }, { name: 'Lumpen Radio', icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple1/v4/46/43/00/464300b1-fae3-9640-d4a2-0eb050ea3ff2/mzl.xjjawige.png', @@ -180,13 +235,8 @@ var apps = [ { name: 'MaxReward - Android', icon: 'https://lh3.googleusercontent.com/yynCUCdEnyW6T96xCto8KzWQr4Yeiy0M6c2p8auYMIyFgAZVBsjf4JCEX7QkPijhBg=w175-rw', - link: 'https://play.google.com/store/apps/details?id=com.bitstrek.maxreward&hl=en', - author: 'Neil Ma', - }, - { - name: 'MaxReward - iOS', - icon: 'https://lh3.googleusercontent.com/yynCUCdEnyW6T96xCto8KzWQr4Yeiy0M6c2p8auYMIyFgAZVBsjf4JCEX7QkPijhBg=w175-rw', - link: 'https://itunes.apple.com/us/app/maxreward/id1050479192?ls=1&mt=8', + linkAppStore: 'https://itunes.apple.com/us/app/maxreward/id1050479192?ls=1&mt=8', + linkPlayStore: 'https://play.google.com/store/apps/details?id=com.bitstrek.maxreward&hl=en', author: 'Neil Ma', }, { @@ -201,12 +251,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/mr.-dapper-men-fashion-app/id989735184?ls=1&mt=8', author: 'wei ping woon', }, - { - name: 'Myntra', - icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/9c/78/df/9c78dfa6-0061-1af2-5026-3e1d5a073c94/icon350x350.png', - link: 'https://itunes.apple.com/in/app/myntra-fashion-shopping-app/id907394059', - author: 'Myntra Designs', - }, { name: 'Nalathe Kerala', icon: 'https://lh3.googleusercontent.com/5N0WYat5WuFbhi5yR2ccdbqmiZ0wbTtKRG9GhT3YK7Z-qRvmykZyAgk0HNElOxD2JOPr=w300-rw', @@ -267,13 +311,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/repairshopr-payments-lite/id1023262888?mt=8', author: 'Jed Tiotuico', }, - { - name: 'RN Playground', - icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple1/v4/20/ec/8e/20ec8eb8-9e12-6686-cd16-7ac9e3ef1d52/mzl.ngvuoybx.png', - linkAppStore: 'https://itunes.apple.com/us/app/react-native-playground/id1002032944?mt=8', - linkPlayStore: 'https://play.google.com/store/apps/details?id=org.rnplay.playground', - author: 'Joshua Sierles', - }, { name: 'Rota Employer - Hire On Demand', link: 'https://itunes.apple.com/us/app/rota-employer-hire-on-demand/id1042270305?mt=8', @@ -298,18 +335,6 @@ var apps = [ link: 'https://geo.itunes.apple.com/us/app/spero-for-cancer/id1033923573?mt=8', author: 'Spero.io', }, - { - name: 'Squad', - icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/e8/5b/3f/e85b3f52-72f3-f427-a32e-a73efe2e9682/icon175x175.jpeg', - link: 'https://itunes.apple.com/us/app/squad-snaps-for-groups-friends/id1043626975?mt=8', - author: 'Tackk Inc.', - }, - { - name: 'Start - medication manager for depression', - icon: 'http://a1.mzstatic.com/us/r30/Purple49/v4/de/9b/6f/de9b6fe8-84ea-7a12-ba2c-0a6d6c7b10b0/icon175x175.png', - link: 'https://itunes.apple.com/us/app/start-medication-manager-for/id1012099928?mt=8', - author: 'Iodine Inc.', - }, { name: 'Tabtor Parent', icon: 'http://a1.mzstatic.com/us/r30/Purple4/v4/80/50/9d/80509d05-18f4-a0b8-0cbb-9ba927d04477/icon175x175.jpeg', @@ -322,12 +347,6 @@ var apps = [ link: 'https://itunes.apple.com/cn/app/tong-xing-wang/id914254459?mt=8', author: 'Ho Yin Tsun Eugene', }, - { - name: 'Townske - Stunning City Guides Made By Locals & Traveller', - icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/8b/42/20/8b4220af-5165-91fd-0f05-014332df73ef/icon175x175.png', - link: 'https://itunes.apple.com/us/app/townske-stunning-city-guides/id1018136179?ls=1&mt=8', - author: 'Townske PTY LTD', - }, { name: 'WOOP', icon: 'http://a4.mzstatic.com/us/r30/Purple6/v4/b0/44/f9/b044f93b-dbf3-9ae5-0f36-9b4956628cab/icon350x350.jpeg', @@ -366,39 +385,102 @@ var apps = [ }, ]; -var showcase = React.createClass({ - renderLinks: function(app) { +var AppList = React.createClass({ + + render: function() { + return ( +
    + {this.props.apps.map(this._renderApp)} +
    + ) + }, + + _renderApp: function(app, i) { + var inner = ( +
    + {app.name} +

    {app.name}

    + {app.linkAppStore && app.linkPlayStore ? this._renderLinks(app) : null} +

    By {app.author}

    + {this._renderBlogPosts(app)} +
    + ); + + if (app.linkAppStore && app.linkPlayStore) { + return (
    {inner}
    ); + } + + return ( + + ); + }, + + _renderBlogPosts: function(app) { + if (!app.blogs) { + return; + } + + if (app.blogs.length === 1) { + return ( +

    Blog post

    + ); + } else if (app.blogs.length > 1) { + return ( +

    Blog posts: {app.blogs.map(this._renderBlogPost)}

    + ); + } + }, + + _renderBlogPost: function(url, i) { return ( -

    iOS - Android

    + + {i + 1}  + ); }, + + _renderLinks: function(app) { + return ( +

    + iOS - + Android +

    + ); + }, +}); + +var showcase = React.createClass({ render: function() { return ( -
    +
    +
    +

    Apps using React Native

    +
    +

    The following is a list of some of the public apps using React Native and are published on the Apple App Store or the Google Play Store. Not all are implemented 100% in React Native -- many are hybrid native/React Native. Can you tell which parts are which? :)

    +

    Want to add your app? Found an app that no longer works or no longer uses React Native? Please submit a pull request on GitHub to update this page!

    +
    +
    -

    Apps using React Native

    +

    Featured Apps

    -

    - Here is a list of apps using React Native. Submit a pull request on GitHub to list your app. -

    +

    These are some of the most well-crafted React Native apps that we have come across.
    Be sure to check them out to get a feel for what React Native is capable of!

    +
    +
    + +
    + +
    +

    All Apps

    +

    Not all apps can be featured, otherwise we would have to create some other category like "super featured" and that's just silly. But that doesn't mean you shouldn't check these apps out!

    +
    +
    +
    - { - apps.map((app, i) => { - var inner = ( -
    - {app.name} -

    {app.name}

    - {app.linkAppStore && app.linkPlayStore ? this.renderLinks(app) : null} -

    By {app.author}

    -
    - ); - if (app.linkAppStore && app.linkPlayStore) { - return (
    {inner}
    ); - } - return ({inner}); - }) - }
    ); From c11d861807def358928babcde74a3ec57a73f52b Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Wed, 25 Nov 2015 14:15:47 -0800 Subject: [PATCH 0110/1411] [Docs] Update style of paragraphs within showcase --- website/src/react-native/css/react-native.css | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/react-native/css/react-native.css b/website/src/react-native/css/react-native.css index 034d52e26e2e..8547254340e6 100644 --- a/website/src/react-native/css/react-native.css +++ b/website/src/react-native/css/react-native.css @@ -1131,6 +1131,7 @@ div[data-twttr-id] iframe { .showcase p { margin-top: 5px; + font-weight: normal; } .showcase a { From b65f1f223488b52963f80a67bb41956103263d27 Mon Sep 17 00:00:00 2001 From: Kyle Corbitt Date: Wed, 25 Nov 2015 17:03:51 -0800 Subject: [PATCH 0111/1411] Use elevation to implement shadows on Android Summary: This PR includes a working interface to Android's `elevation` property implemented as an RN style. Elevation is the only (easy) [platform-supported way to create shadows](http://developer.android.com/training/material/shadows-clipping.html). For it to work note that you must be running on Android 5.0+, and add `elevation` to a view with a `backgroundColor` set. These are platform limitations. This PR is not intended to be merged in its current state, but rather to inform the discussion from #2768. At a minimum, before merging we would need to add the elevation style to the docs and rebase this to master (**EDIT** I have now rebased on master because from v0.14.2 too many commits were being pulled in -- haven't tested it since the rebase though). Additionally, it might be good to add tests, although I couldn't find any for the Android code. I'm happy to get that done if this feature gets signed off by the React Native team. Finally, as I argued in #2768 I think that `elevation` is a useful abstraction ov Closes https://github.com/facebook/react-native/pull/4180 Reviewed By: svcscm Differential Revision: D2684746 Pulled By: mkonicek fb-gh-sync-id: 825f3ccd20c4b0eea9d11b5f0e3a6b018b7e4378 --- .../Components/View/ViewStylePropTypes.js | 8 ++++++++ .../view/ReactViewBackgroundDrawable.java | 19 +++++++++++++++++++ .../react/views/view/ReactViewManager.java | 8 ++++++++ 3 files changed, 35 insertions(+) diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index e36b143a1e4b..9f7ba776aab7 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -47,6 +47,14 @@ var ViewStylePropTypes = { ), shadowOpacity: ReactPropTypes.number, shadowRadius: ReactPropTypes.number, + /** + * (Android-only) Sets the elevation of a view, using Android's underlying + * [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation). + * This adds a drop shadow to the item and affects z-order for overlapping views. + * Only supported on Android 5.0+, has no effect on earlier versions. + * @platform android + */ + elevation: ReactPropTypes.number, }; module.exports = ViewStylePropTypes; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java index 84289b62d605..4a8805614e5c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java @@ -17,12 +17,14 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathEffect; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Build; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.csslayout.CSSConstants; @@ -123,6 +125,23 @@ public int getOpacity() { return ColorUtil.getOpacityFromColor(ColorUtil.multiplyColorAlpha(mColor, mAlpha)); } + /* Android's elevation implementation requires this to be implemented to know where to draw the shadow. */ + @Override + public void getOutline(Outline outline) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + super.getOutline(outline); + return; + } + if(!CSSConstants.isUndefined(mBorderRadius) && mBorderRadius > 0) { + float extraRadiusFromBorderWidth = (mBorderWidth != null) + ? mBorderWidth.get(Spacing.ALL) / 2f + : 0; + outline.setRoundRect(getBounds(), mBorderRadius + extraRadiusFromBorderWidth); + } else { + super.getOutline(outline); + } + } + public void setBorderWidth(int position, float width) { if (mBorderWidth == null) { mBorderWidth = new Spacing(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index ac0dfee0f0e9..6e002fb9f978 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -64,6 +64,14 @@ public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) { view.setBorderStyle(borderStyle); } + @ReactProp(name = "elevation") + public void setElevation(ReactViewGroup view, float elevation) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + view.setElevation(PixelUtil.toPixelFromDIP(elevation)); + } + // Do nothing on API < 21 + } + @ReactProp(name = "pointerEvents") public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEventsStr) { if (pointerEventsStr != null) { From ae09a10c9553016cce9a3131ff442fea8fa1e180 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Wed, 25 Nov 2015 17:06:59 -0800 Subject: [PATCH 0112/1411] Add onLoadX support on Android Summary: ~~This is a WIP, just finished the first bit and wanted to get some feedback to see if this approach seems appropriate, as I haven't done a lot of Android development.~~ Looks ready for review now. Closes https://github.com/facebook/react-native/pull/3791 Reviewed By: svcscm Differential Revision: D2672262 Pulled By: mkonicek fb-gh-sync-id: 1e8f1cc6658fb719a68f7da455f30a7c9b1db730 --- Examples/UIExplorer/ImageExample.js | 46 +++++++++++ Libraries/Image/Image.android.js | 17 +++- Libraries/Image/Image.ios.js | 3 - .../react/views/image/ImageLoadEvent.java | 73 +++++++++++++++++ .../react/views/image/ReactImageManager.java | 20 +++++ .../react/views/image/ReactImageView.java | 79 +++++++++++++++++-- 6 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 4cab0c1a1cda..68b16b2d103e 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -28,6 +28,44 @@ var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACS var ImageCapInsetsExample = require('./ImageCapInsetsExample'); +var NetworkImageCallbackExample = React.createClass({ + getInitialState: function() { + return { + events: [], + }; + }, + + componentWillMount() { + this.setState({mountTime: new Date()}); + }, + + render: function() { + var { mountTime } = this.state; + + return ( + + this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)} + onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)} + onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)} + /> + + + {this.state.events.join('\n')} + + + ); + }, + + _loadEventFired(event) { + this.setState((state) => { + return state.events = [...state.events, event]; + }); + } +}); + var NetworkImageExample = React.createClass({ watchID: (null: ?number), @@ -92,6 +130,14 @@ exports.examples = [ ); }, }, + { + title: 'Image Loading Events', + render: function() { + return ( + + ); + }, + }, { title: 'Error Handler', render: function() { diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 66308f682c0b..9d9558c9687f 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -56,6 +56,7 @@ var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, { resizeMode: true, progressiveRenderingEnabled: true, fadeDuration: true, + shouldNotifyLoadEvents: true, }); var Image = React.createClass({ @@ -75,7 +76,18 @@ var Image = React.createClass({ ]).isRequired, progressiveRenderingEnabled: PropTypes.bool, fadeDuration: PropTypes.number, - style: StyleSheetPropType(ImageStylePropTypes), + /** + * Invoked on load start + */ + onLoadStart: PropTypes.func, + /** + * Invoked when load completes successfully + */ + onLoad: PropTypes.func, + /** + * Invoked when load either succeeds or fails + */ + onLoadEnd: PropTypes.func, /** * Used to locate this view in end-to-end tests. */ @@ -137,9 +149,11 @@ var Image = React.createClass({ if (source && source.uri) { var {width, height} = source; var style = flattenStyle([{width, height}, styles.base, this.props.style]); + var {onLoadStart, onLoad, onLoadEnd} = this.props; var nativeProps = merge(this.props, { style, + shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd), src: source.uri, }); @@ -186,6 +200,7 @@ var cfg = { defaultImageSrc: true, imageTag: true, progressHandlerRegistered: true, + shouldNotifyLoadEvents: true, }, }; var RKImage = requireNativeComponent('RCTImageView', Image, cfg); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 4da2d53e5096..6943bf909e33 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -113,7 +113,6 @@ var Image = React.createClass({ onLayout: PropTypes.func, /** * Invoked on load start - * @platform ios */ onLoadStart: PropTypes.func, /** @@ -128,12 +127,10 @@ var Image = React.createClass({ onError: PropTypes.func, /** * Invoked when load completes successfully - * @platform ios */ onLoad: PropTypes.func, /** * Invoked when load either succeeds or fails - * @platform ios */ onLoadEnd: PropTypes.func, }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java new file mode 100644 index 000000000000..fc183507a63c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.image; + +import android.support.annotation.IntDef; + +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class ImageLoadEvent extends Event { + @IntDef({ON_ERROR, ON_LOAD, ON_LOAD_END, ON_LOAD_START, ON_PROGRESS}) + @Retention(RetentionPolicy.SOURCE) + @interface ImageEventType {} + + // Currently ON_ERROR and ON_PROGRESS are not implemented, these can be added + // easily once support exists in fresco. + public static final int ON_ERROR = 1; + public static final int ON_LOAD = 2; + public static final int ON_LOAD_END = 3; + public static final int ON_LOAD_START = 4; + public static final int ON_PROGRESS = 5; + + private final int mEventType; + + public ImageLoadEvent(int viewId, long timestampMs, @ImageEventType int eventType) { + super(viewId, timestampMs); + mEventType = eventType; + } + + public static String eventNameForType(@ImageEventType int eventType) { + switch(eventType) { + case ON_ERROR: + return "topError"; + case ON_LOAD: + return "topLoad"; + case ON_LOAD_END: + return "topLoadEnd"; + case ON_LOAD_START: + return "topLoadStart"; + case ON_PROGRESS: + return "topProgress"; + default: + throw new IllegalStateException("Invalid image event: " + Integer.toString(eventType)); + } + } + + @Override + public String getEventName() { + return ImageLoadEvent.eventNameForType(mEventType); + } + + @Override + public short getCoalescingKey() { + // Intentionally casting mEventType because it is guaranteed to be small + // enough to fit into short. + return (short) mEventType; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 0158ff072c40..19ff94205332 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -11,10 +11,13 @@ import javax.annotation.Nullable; +import java.util.Map; + import android.graphics.Color; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; +import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ReactProp; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -102,6 +105,23 @@ public void setFadeDuration(ReactImageView view, int durationMs) { view.setFadeDuration(durationMs); } + @ReactProp(name = "shouldNotifyLoadEvents") + public void setLoadHandlersRegistered(ReactImageView view, boolean shouldNotifyLoadEvents) { + view.setShouldNotifyLoadEvents(shouldNotifyLoadEvents); + } + + @Override + public @Nullable Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.of( + ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_START), + MapBuilder.of("registrationName", "onLoadStart"), + ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD), + MapBuilder.of("registrationName", "onLoad"), + ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_END), + MapBuilder.of("registrationName", "onLoadEnd") + ); + } + @Override protected void onAfterUpdateTransaction(ReactImageView view) { super.onAfterUpdateTransaction(view); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index d741356fe7f1..c889c81837da 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -20,11 +20,15 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; +import android.graphics.drawable.Animatable; import android.net.Uri; +import android.os.SystemClock; import com.facebook.common.util.UriUtil; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; +import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.controller.ControllerListener; +import com.facebook.drawee.controller.ForwardingControllerListener; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; @@ -32,11 +36,15 @@ import com.facebook.drawee.interfaces.DraweeController; import com.facebook.drawee.view.GenericDraweeView; import com.facebook.imagepipeline.common.ResizeOptions; +import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.request.BasePostprocessor; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.Postprocessor; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.events.EventDispatcher; /** * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view @@ -104,8 +112,9 @@ public void process(Bitmap output, Bitmap source) { private boolean mIsLocalImage; private final AbstractDraweeControllerBuilder mDraweeControllerBuilder; private final RoundedCornerPostprocessor mRoundedCornerPostprocessor; - private final @Nullable Object mCallerContext; private @Nullable ControllerListener mControllerListener; + private @Nullable ControllerListener mControllerForTesting; + private final @Nullable Object mCallerContext; private int mFadeDurationMs = -1; private boolean mProgressiveRenderingEnabled; @@ -127,6 +136,48 @@ public ReactImageView( mCallerContext = callerContext; } + public void setShouldNotifyLoadEvents(boolean shouldNotify) { + if (!shouldNotify) { + mControllerListener = null; + } else { + final EventDispatcher mEventDispatcher = ((ReactContext) getContext()). + getNativeModule(UIManagerModule.class).getEventDispatcher(); + + mControllerListener = new BaseControllerListener() { + @Override + public void onSubmit(String id, Object callerContext) { + mEventDispatcher.dispatchEvent( + new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_START) + ); + } + + @Override + public void onFinalImageSet( + String id, + @Nullable final ImageInfo imageInfo, + @Nullable Animatable animatable) { + if (imageInfo != null) { + mEventDispatcher.dispatchEvent( + new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_END) + ); + mEventDispatcher.dispatchEvent( + new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD) + ); + } + } + + @Override + public void onFailure(String id, Throwable throwable) { + mEventDispatcher.dispatchEvent( + new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_END) + ); + } + }; + } + + mIsDirty = true; + } + public void setBorderColor(int borderColor) { mBorderColor = borderColor; mIsDirty = true; @@ -217,21 +268,33 @@ public void maybeUpdateView() { .setProgressiveRenderingEnabled(mProgressiveRenderingEnabled) .build(); - DraweeController draweeController = mDraweeControllerBuilder - .reset() + // This builder is reused + mDraweeControllerBuilder.reset(); + + mDraweeControllerBuilder .setAutoPlayAnimations(true) .setCallerContext(mCallerContext) .setOldController(getController()) - .setImageRequest(imageRequest) - .setControllerListener(mControllerListener) - .build(); - setController(draweeController); + .setImageRequest(imageRequest); + + if (mControllerListener != null && mControllerForTesting != null) { + ForwardingControllerListener combinedListener = new ForwardingControllerListener(); + combinedListener.addListener(mControllerListener); + combinedListener.addListener(mControllerForTesting); + mDraweeControllerBuilder.setControllerListener(combinedListener); + } else if (mControllerForTesting != null) { + mDraweeControllerBuilder.setControllerListener(mControllerForTesting); + } else if (mControllerListener != null) { + mDraweeControllerBuilder.setControllerListener(mControllerListener); + } + + setController(mDraweeControllerBuilder.build()); mIsDirty = false; } // VisibleForTesting public void setControllerListener(ControllerListener controllerListener) { - mControllerListener = controllerListener; + mControllerForTesting = controllerListener; mIsDirty = true; maybeUpdateView(); } From 0e11379dbb2ffef1ac3dcc2a70d0014c162ba286 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 25 Nov 2015 17:34:55 -0800 Subject: [PATCH 0113/1411] Extract the Require polyfill from the default list of polyfills Summary: Extracts the module system from the list of dependencies and adds it back to the bundling process. Unbundling and Prepack has their own module systems. jest does as well. This is not normally part of the resolver, nor polyfills. In fact, I think we'll eventually start treating polyfills as normal modules just like core-js. The prelude is the weird part right now but I think that we'll eventually move the DEV flag to be module level instead of global and we can just get rid of this prelude. public Reviewed By: davidaurelio Differential Revision: D2693701 fb-gh-sync-id: a59ccda0fa15fcfcda52897e8290805eed1b92b3 --- .../src/Bundler/__tests__/Bundler-test.js | 7 +++++ packager/react-packager/src/Bundler/index.js | 11 ++++++-- .../src/Resolver/__tests__/Resolver-test.js | 26 +---------------- packager/react-packager/src/Resolver/index.js | 28 +++++++++++++++---- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js index ac8494bec9c3..9ce3f7c46da4 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -46,6 +46,7 @@ describe('Bundler', function() { } var getDependencies; + var getModuleSystemDependencies; var wrapModule; var bundler; var assetServer; @@ -53,10 +54,12 @@ describe('Bundler', function() { beforeEach(function() { getDependencies = jest.genMockFn(); + getModuleSystemDependencies = jest.genMockFn(); wrapModule = jest.genMockFn(); Resolver.mockImpl(function() { return { getDependencies: getDependencies, + getModuleSystemDependencies: getModuleSystemDependencies, wrapModule: wrapModule, }; }); @@ -112,6 +115,10 @@ describe('Bundler', function() { }); }); + getModuleSystemDependencies.mockImpl(function() { + return []; + }); + JSTransformer.prototype.loadFileAndTransform .mockImpl(function(path) { return Promise.resolve({ diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 5f5d795905e5..e097667ff58c 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -146,23 +146,30 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; + const moduleSystem = this._resolver.getModuleSystemDependencies( + { dev: isDev, platform } + ); + return this.getDependencies(entryFile, isDev, platform).then((response) => { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); + // Prepend the module system polyfill to the top of dependencies + var dependencies = moduleSystem.concat(response.dependencies); + let bar; if (process.stdout.isTTY) { bar = new ProgressBar('transforming [:bar] :percent :current/:total', { complete: '=', incomplete: ' ', width: 40, - total: response.dependencies.length, + total: dependencies.length, }); } bbundle.setMainModuleId(response.mainModuleId); return Promise.all( - response.dependencies.map( + dependencies.map( module => this._transformModule( bbundle, response, diff --git a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js index f58589215d5b..bf6363c520da 100644 --- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -79,27 +79,15 @@ describe('Resolver', function() { expect(result.mainModuleId).toEqual('index'); expect(result.dependencies[result.dependencies.length - 1]).toBe(module); expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([ - { path: 'polyfills/prelude.js', - id: 'polyfills/prelude.js', - isPolyfill: true, - dependencies: [] - }, - { path: 'polyfills/require.js', - id: 'polyfills/require.js', - isPolyfill: true, - dependencies: ['polyfills/prelude.js'] - }, { path: 'polyfills/polyfills.js', id: 'polyfills/polyfills.js', isPolyfill: true, - dependencies: ['polyfills/prelude.js', 'polyfills/require.js'] + dependencies: [] }, { id: 'polyfills/console.js', isPolyfill: true, path: 'polyfills/console.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js' ], }, @@ -107,8 +95,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/error-guard.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js' ], @@ -117,8 +103,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/String.prototype.es6.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js' @@ -128,8 +112,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/Array.prototype.es6.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', @@ -140,8 +122,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/Array.es6.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', @@ -153,8 +133,6 @@ describe('Resolver', function() { isPolyfill: true, path: 'polyfills/babelHelpers.js', dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', @@ -218,8 +196,6 @@ describe('Resolver', function() { id: 'some module', isPolyfill: true, dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', 'polyfills/polyfills.js', 'polyfills/console.js', 'polyfills/error-guard.js', diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js index 60820faa16a3..a4e546e07bbe 100644 --- a/packager/react-packager/src/Resolver/index.js +++ b/packager/react-packager/src/Resolver/index.js @@ -99,7 +99,7 @@ class Resolver { return this._depGraph.getDependencies(main, opts.platform).then( resolutionResponse => { - this._getPolyfillDependencies(opts.dev).reverse().forEach( + this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) ); @@ -108,12 +108,28 @@ class Resolver { ); } - _getPolyfillDependencies(isDev) { - const polyfillModuleNames = [ - isDev + getModuleSystemDependencies(options) { + const opts = getDependenciesValidateOpts(options); + + const prelude = opts.dev ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), + : path.join(__dirname, 'polyfills/prelude.js'); + + const moduleSystem = path.join(__dirname, 'polyfills/require.js'); + + return [ + prelude, + moduleSystem + ].map(moduleName => new Polyfill({ + path: moduleName, + id: moduleName, + dependencies: [], + isPolyfill: true, + })); + } + + _getPolyfillDependencies() { + const polyfillModuleNames = [ path.join(__dirname, 'polyfills/polyfills.js'), path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), From fab8e38759f620f242557e1219c2e6c8c5766f1f Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 25 Nov 2015 21:26:32 -0800 Subject: [PATCH 0114/1411] Fix ABC order of docs nav --- website/server/extractDocs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 1d8abc600257..8279a6f8d0e7 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -192,8 +192,8 @@ var components = [ '../Libraries/Image/Image.ios.js', '../Libraries/CustomComponents/ListView/ListView.js', '../Libraries/Components/MapView/MapView.js', - '../Libraries/CustomComponents/Navigator/Navigator.js', '../Libraries/Modal/Modal.js', + '../Libraries/CustomComponents/Navigator/Navigator.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js', '../Libraries/Picker/PickerIOS.ios.js', '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js', From 16350ae09b4fb5bbeb3edc8cc9df623e4048c6b1 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Wed, 25 Nov 2015 21:16:58 -0800 Subject: [PATCH 0115/1411] Remove AnimationRegistry from UIManageModule Summary: public UIManageModule creates AnimationRegistry but never uses it, this diff moves it to NativeViewHierarchyManager who owns it. UIViewOperationQueue depends on AnimationRegistry to perform some of the enqueued operations, so it accessed it through a getting in NativeViewHierarchyManager. This will also make sure NativeViewHierarchyManager. and UIViewOperationQueue operate on the same AnimationManager (previously, that wasn't really enforced). This is needed so I can move away UIViewOperationQueue creation off the UIManagerModule. This diff should have no functional changes whatsoever. Reviewed By: astreet Differential Revision: D2462605 fb-gh-sync-id: 1e3cd64908f51126362f2b5fb39b1efa6521854e --- .../react/uimanager/NativeViewHierarchyManager.java | 10 ++++++---- .../com/facebook/react/uimanager/UIManagerModule.java | 9 ++------- .../facebook/react/uimanager/UIViewOperationQueue.java | 5 ++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 0b7b7e1f08ba..cf3c5645587e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -67,10 +67,8 @@ private final JSResponderHandler mJSResponderHandler = new JSResponderHandler(); private final RootViewManager mRootViewManager = new RootViewManager(); - public NativeViewHierarchyManager( - AnimationRegistry animationRegistry, - ViewManagerRegistry viewManagers) { - mAnimationRegistry = animationRegistry; + public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { + mAnimationRegistry = new AnimationRegistry(); mViewManagers = viewManagers; mTagsToViews = new SparseArray<>(); mTagsToViewManagers = new SparseArray<>(); @@ -78,6 +76,10 @@ public NativeViewHierarchyManager( mRootViewsContext = new SparseArray<>(); } + public AnimationRegistry getAnimationRegistry() { + return mAnimationRegistry; + } + public void updateProperties(int tag, CatalystStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 1512f305f7e7..f8609dc611e5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -22,7 +22,6 @@ import com.facebook.csslayout.CSSLayoutContext; import com.facebook.infer.annotation.Assertions; import com.facebook.react.animation.Animation; -import com.facebook.react.animation.AnimationRegistry; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; @@ -78,7 +77,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private final NativeViewHierarchyManager mNativeViewHierarchyManager; private final EventDispatcher mEventDispatcher; - private final AnimationRegistry mAnimationRegistry = new AnimationRegistry(); private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry(); private final ViewManagerRegistry mViewManagers; private final CSSLayoutContext mLayoutContext = new CSSLayoutContext(); @@ -94,13 +92,10 @@ public UIManagerModule(ReactApplicationContext reactContext, List v super(reactContext); mViewManagers = new ViewManagerRegistry(viewManagerList); mEventDispatcher = new EventDispatcher(reactContext); - mNativeViewHierarchyManager = new NativeViewHierarchyManager( - mAnimationRegistry, - mViewManagers); + mNativeViewHierarchyManager = new NativeViewHierarchyManager(mViewManagers); mOperationsQueue = new UIViewOperationQueue( reactContext, - mNativeViewHierarchyManager, - mAnimationRegistry); + mNativeViewHierarchyManager); mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( mOperationsQueue, mShadowNodeRegistry); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index a931b5e0f976..d118561be2df 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -456,10 +456,9 @@ public void execute() { /* package */ UIViewOperationQueue( ReactApplicationContext reactContext, - NativeViewHierarchyManager nativeViewHierarchyManager, - AnimationRegistry animationRegistry) { + NativeViewHierarchyManager nativeViewHierarchyManager) { mNativeViewHierarchyManager = nativeViewHierarchyManager; - mAnimationRegistry = animationRegistry; + mAnimationRegistry = nativeViewHierarchyManager.getAnimationRegistry(); mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext); } From aeda31428df7b88c684f15edd9687ed6ed8cfc21 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Wed, 25 Nov 2015 21:17:13 -0800 Subject: [PATCH 0116/1411] When addRootView() is called in a background thread, execute it as a generic UIOperation command in UIViewOperationQueue Summary: public There is really no reason NativeViewHierarchyManager.addRootView() should be performed synchroniously when called from background thread, as long as it is executed before every other command in UIViewOperationQueue, and we can ensure that by putting add view command at the front of the queue. When that happpens, the queue should always be empty anyway, but it's best to be safe. This eliminates an unnecessary blocking call and should overall make the code simpler and safer (Semaphores can timeout). Reviewed By: astreet Differential Revision: D2462680 fb-gh-sync-id: 784ac6573a455019b93628c70992f3830b9d6f1f --- .../react/uimanager/UIManagerModule.java | 30 ++--------------- .../react/uimanager/UIViewOperationQueue.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index f8609dc611e5..5633c32b98dc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -14,8 +14,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; import android.util.DisplayMetrics; @@ -31,8 +29,6 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.SoftAssertions; -import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; @@ -75,7 +71,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements // increment here is 10 private static final int ROOT_VIEW_TAG_INCREMENT = 10; - private final NativeViewHierarchyManager mNativeViewHierarchyManager; private final EventDispatcher mEventDispatcher; private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry(); private final ViewManagerRegistry mViewManagers; @@ -92,10 +87,9 @@ public UIManagerModule(ReactApplicationContext reactContext, List v super(reactContext); mViewManagers = new ViewManagerRegistry(viewManagerList); mEventDispatcher = new EventDispatcher(reactContext); - mNativeViewHierarchyManager = new NativeViewHierarchyManager(mViewManagers); mOperationsQueue = new UIViewOperationQueue( reactContext, - mNativeViewHierarchyManager); + new NativeViewHierarchyManager(mViewManagers)); mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( mOperationsQueue, mShadowNodeRegistry); @@ -197,26 +191,8 @@ public void run() { mShadowNodeRegistry.addRootNode(rootCSSNode); - if (UiThreadUtil.isOnUiThread()) { - mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); - } else { - final Semaphore semaphore = new Semaphore(0); - getReactApplicationContext().runOnUiQueueThread( - new Runnable() { - @Override - public void run() { - mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); - semaphore.release(); - } - }); - try { - SoftAssertions.assertCondition( - semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS), - "Timed out adding root view"); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } + // register it within NativeViewHierarchyManager + mOperationsQueue.addRootView(tag, rootView, themedRootContext); return tag; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index d118561be2df..21f1128d8ebe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -13,13 +13,17 @@ import javax.annotation.concurrent.GuardedBy; import java.util.ArrayList; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import com.facebook.react.animation.Animation; import com.facebook.react.animation.AnimationRegistry; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -448,6 +452,7 @@ public void execute() { private final Object mDispatchRunnablesLock = new Object(); private final DispatchUIFrameCallback mDispatchUIFrameCallback; + private final ReactApplicationContext mReactApplicationContext; @GuardedBy("mDispatchRunnablesLock") private final ArrayList mDispatchUIRunnables = new ArrayList<>(); @@ -460,6 +465,7 @@ public void execute() { mNativeViewHierarchyManager = nativeViewHierarchyManager; mAnimationRegistry = nativeViewHierarchyManager.getAnimationRegistry(); mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext); + mReactApplicationContext = reactContext; } public void setViewHierarchyUpdateDebugListener( @@ -471,6 +477,32 @@ public boolean isEmpty() { return mOperations.isEmpty(); } + public void addRootView( + final int tag, + final SizeMonitoringFrameLayout rootView, + final ThemedReactContext themedRootContext) { + if (UiThreadUtil.isOnUiThread()) { + mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); + } else { + final Semaphore semaphore = new Semaphore(0); + mReactApplicationContext.runOnUiQueueThread( + new Runnable() { + @Override + public void run() { + mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); + semaphore.release(); + } + }); + try { + SoftAssertions.assertCondition( + semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS), + "Timed out adding root view"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + public void enqueueRemoveRootView(int rootViewTag) { mOperations.add(new RemoveRootViewOperation(rootViewTag)); } From 76e033ead9d5d1b3c867abf419c0d9cca0bcdaa1 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Wed, 25 Nov 2015 21:17:26 -0800 Subject: [PATCH 0117/1411] Extract shadow hierarchy logic from UIManagerModule into UIImplementation Summary: public This diff extracts all shadow hierarchy-specific logic from UIManagerModule into a UIImplementation class. This will later allow using in alternative UIImplementations in future. Reviewed By: astreet Differential Revision: D2457849 fb-gh-sync-id: 532128ce1d67b525cdf03794a5a29d7e9ed0ab90 --- .../react/uimanager/UIImplementation.java | 644 ++++++++++++++++++ .../react/uimanager/UIManagerModule.java | 494 ++------------ 2 files changed, 697 insertions(+), 441 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java new file mode 100644 index 000000000000..b9eb9a64ffe6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -0,0 +1,644 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.facebook.react.uimanager; + +import javax.annotation.Nullable; + +import java.util.Arrays; + +import com.facebook.csslayout.CSSLayoutContext; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.animation.Animation; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.systrace.Systrace; +import com.facebook.systrace.SystraceMessage; + +/** + * An class that is used to receive React commands from JS and translate them into a + * shadow node hierarchy that is then mapped to a native view hierarchy. + */ +public class UIImplementation { + + private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry(); + private final ViewManagerRegistry mViewManagers; + private final CSSLayoutContext mLayoutContext = new CSSLayoutContext(); + private final UIViewOperationQueue mOperationsQueue; + private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; + private final int[] mMeasureBuffer = new int[4]; + + public UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) { + mOperationsQueue = new UIViewOperationQueue( + reactContext, + new NativeViewHierarchyManager(viewManagers)); + mViewManagers = viewManagers; + mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( + mOperationsQueue, + mShadowNodeRegistry); + } + + /** + * Registers a root node with a given tag, size and ThemedReactContext + * and adds it to a node registry. + */ + public void registerRootView( + SizeMonitoringFrameLayout rootView, + int tag, + int width, + int height, + ThemedReactContext context) { + final ReactShadowNode rootCSSNode = new ReactShadowNode(); + rootCSSNode.setReactTag(tag); + rootCSSNode.setThemedContext(context); + rootCSSNode.setStyleWidth(width); + rootCSSNode.setStyleHeight(height); + rootCSSNode.setViewClassName("Root"); + mShadowNodeRegistry.addRootNode(rootCSSNode); + + // register it within NativeViewHierarchyManager + mOperationsQueue.addRootView(tag, rootView, context); + } + + /** + * Unregisters a root node with a given tag. + */ + public void removeRootView(int rootViewTag) { + mShadowNodeRegistry.removeRootNode(rootViewTag); + mOperationsQueue.enqueueRemoveRootView(rootViewTag); + } + + /** + * Invoked when native view that corresponds to a root node has its size changed. + */ + public void updateRootNodeSize( + int rootViewTag, + int newWidth, + int newHeight, + EventDispatcher eventDispatcher) { + ReactShadowNode rootCSSNode = mShadowNodeRegistry.getNode(rootViewTag); + rootCSSNode.setStyleWidth(newWidth); + rootCSSNode.setStyleHeight(newHeight); + + // If we're in the middle of a batch, the change will automatically be dispatched at the end of + // the batch. As all batches are executed as a single runnable on the event queue this should + // always be empty, but that calling architecture is an implementation detail. + if (mOperationsQueue.isEmpty()) { + dispatchViewUpdates(eventDispatcher, -1); // -1 = no associated batch id + } + } + + /** + * Invoked by React to create a new node with a given tag, class name and properties. + */ + public void createView(int tag, String className, int rootViewTag, ReadableMap props) { + ViewManager viewManager = mViewManagers.get(className); + ReactShadowNode cssNode = viewManager.createShadowNodeInstance(); + ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); + cssNode.setReactTag(tag); + cssNode.setViewClassName(className); + cssNode.setRootNode(rootNode); + cssNode.setThemedContext(rootNode.getThemedContext()); + + mShadowNodeRegistry.addNode(cssNode); + + CatalystStylesDiffMap styles = null; + if (props != null) { + styles = new CatalystStylesDiffMap(props); + cssNode.updateProperties(styles); + } + + if (!cssNode.isVirtual()) { + mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles); + } + } + + public void dropViews(ReadableArray viewTags) { + int size = viewTags.size(), realViewsCount = 0; + int realViewTags[] = new int[size]; + for (int i = 0; i < size; i++) { + int tag = viewTags.getInt(i); + ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); + if (!cssNode.isVirtual()) { + realViewTags[realViewsCount++] = tag; + } + mShadowNodeRegistry.removeNode(tag); + } + if (realViewsCount > 0) { + mNativeViewHierarchyOptimizer.handleDropViews(realViewTags, realViewsCount); + } + } + + /** + * Invoked by React to create a new node with a given tag has its properties changed. + */ + public void updateView(int tag, String className, ReadableMap props) { + ViewManager viewManager = mViewManagers.get(className); + if (viewManager == null) { + throw new IllegalViewOperationException("Got unknown view type: " + className); + } + ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); + if (cssNode == null) { + throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag); + } + + if (props != null) { + CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props); + cssNode.updateProperties(styles); + if (!cssNode.isVirtual()) { + mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); + } + } + } + + /** + * Invoked when there is a mutation in a node tree. + * + * @param tag react tag of the node we want to manage + * @param indicesToRemove ordered (asc) list of indicies at which view should be removed + * @param viewsToAdd ordered (asc based on mIndex property) list of tag-index pairs that represent + * a view which should be added at the specified index + * @param tagsToDelete list of tags corresponding to views that should be removed + */ + public void manageChildren( + int viewTag, + @Nullable ReadableArray moveFrom, + @Nullable ReadableArray moveTo, + @Nullable ReadableArray addChildTags, + @Nullable ReadableArray addAtIndices, + @Nullable ReadableArray removeFrom) { + ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); + + int numToMove = moveFrom == null ? 0 : moveFrom.size(); + int numToAdd = addChildTags == null ? 0 : addChildTags.size(); + int numToRemove = removeFrom == null ? 0 : removeFrom.size(); + + if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) { + throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!"); + } + + if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) { + throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!"); + } + + // We treat moves as an add and a delete + ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd]; + int[] indicesToRemove = new int[numToMove + numToRemove]; + int[] tagsToRemove = new int[indicesToRemove.length]; + int[] tagsToDelete = new int[numToRemove]; + + if (numToMove > 0) { + Assertions.assertNotNull(moveFrom); + Assertions.assertNotNull(moveTo); + for (int i = 0; i < numToMove; i++) { + int moveFromIndex = moveFrom.getInt(i); + int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag(); + viewsToAdd[i] = new ViewAtIndex( + tagToMove, + moveTo.getInt(i)); + indicesToRemove[i] = moveFromIndex; + tagsToRemove[i] = tagToMove; + } + } + + if (numToAdd > 0) { + Assertions.assertNotNull(addChildTags); + Assertions.assertNotNull(addAtIndices); + for (int i = 0; i < numToAdd; i++) { + int viewTagToAdd = addChildTags.getInt(i); + int indexToAddAt = addAtIndices.getInt(i); + viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt); + } + } + + if (numToRemove > 0) { + Assertions.assertNotNull(removeFrom); + for (int i = 0; i < numToRemove; i++) { + int indexToRemove = removeFrom.getInt(i); + int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag(); + indicesToRemove[numToMove + i] = indexToRemove; + tagsToRemove[numToMove + i] = tagToRemove; + tagsToDelete[i] = tagToRemove; + } + } + + // NB: moveFrom and removeFrom are both relative to the starting state of the View's children. + // moveTo and addAt are both relative to the final state of the View's children. + // + // 1) Sort the views to add and indices to remove by index + // 2) Iterate the indices being removed from high to low and remove them. Going high to low + // makes sure we remove the correct index when there are multiple to remove. + // 3) Iterate the views being added by index low to high and add them. Like the view removal, + // iteration direction is important to preserve the correct index. + + Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR); + Arrays.sort(indicesToRemove); + + // Apply changes to CSSNode hierarchy + int lastIndexRemoved = -1; + for (int i = indicesToRemove.length - 1; i >= 0; i--) { + int indexToRemove = indicesToRemove[i]; + if (indexToRemove == lastIndexRemoved) { + throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: " + + viewTag); + } + cssNodeToManage.removeChildAt(indicesToRemove[i]); + lastIndexRemoved = indicesToRemove[i]; + } + + for (int i = 0; i < viewsToAdd.length; i++) { + ViewAtIndex viewAtIndex = viewsToAdd[i]; + ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag); + if (cssNodeToAdd == null) { + throw new IllegalViewOperationException("Trying to add unknown view tag: " + + viewAtIndex.mTag); + } + cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); + } + + if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { + mNativeViewHierarchyOptimizer.handleManageChildren( + cssNodeToManage, + indicesToRemove, + tagsToRemove, + viewsToAdd, + tagsToDelete); + } + + for (int i = 0; i < tagsToDelete.length; i++) { + removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); + } + } + + /** + * Replaces the View specified by oldTag with the View specified by newTag within oldTag's parent. + */ + public void replaceExistingNonRootView(int oldTag, int newTag) { + if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) { + throw new IllegalViewOperationException("Trying to add or replace a root tag!"); + } + + ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag); + if (oldNode == null) { + throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag); + } + + ReactShadowNode parent = oldNode.getParent(); + if (parent == null) { + throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag); + } + + int oldIndex = parent.indexOf(oldNode); + if (oldIndex < 0) { + throw new IllegalStateException("Didn't find child tag in parent"); + } + + WritableArray tagsToAdd = Arguments.createArray(); + tagsToAdd.pushInt(newTag); + + WritableArray addAtIndices = Arguments.createArray(); + addAtIndices.pushInt(oldIndex); + + WritableArray indicesToRemove = Arguments.createArray(); + indicesToRemove.pushInt(oldIndex); + + manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove); + } + + /** + * Method which takes a container tag and then releases all subviews for that container upon + * receipt. + * TODO: The method name is incorrect and will be renamed, #6033872 + * @param containerTag the tag of the container for which the subviews must be removed + */ + public void removeSubviewsFromContainerWithID(int containerTag) { + ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag); + if (containerNode == null) { + throw new IllegalViewOperationException( + "Trying to remove subviews of an unknown view tag: " + containerTag); + } + + WritableArray indicesToRemove = Arguments.createArray(); + for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) { + indicesToRemove.pushInt(childIndex); + } + + manageChildren(containerTag, null, null, null, null, indicesToRemove); + } + + /** + * Find the touch target child native view in the supplied root view hierarchy, given a react + * target location. + * + * This method is currently used only by Element Inspector DevTool. + * + * @param reactTag the tag of the root view to traverse + * @param targetX target X location + * @param targetY target Y location + * @param callback will be called if with the identified child view react ID, and measurement + * info. If no view was found, callback will be invoked with no data. + */ + public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) { + mOperationsQueue.enqueueFindTargetForTouch(reactTag, targetX, targetY, callback); + } + + /** + * Determines the location on screen, width, and height of the given view and returns the values + * via an async callback. + */ + public void measure(int reactTag, Callback callback) { + // This method is called by the implementation of JS touchable interface (see Touchable.js for + // more details) at the moment of touch activation. That is after user starts the gesture from + // a touchable view with a given reactTag, or when user drag finger back into the press + // activation area of a touchable view that have been activated before. + mOperationsQueue.enqueueMeasure(reactTag, callback); + } + + /** + * Measures the view specified by tag relative to the given ancestorTag. This means that the + * returned x, y are relative to the origin x, y of the ancestor view. Results are stored in the + * given outputBuffer. We allow ancestor view and measured view to be the same, in which case + * the position always will be (0, 0) and method will only measure the view dimensions. + */ + public void measureLayout( + int tag, + int ancestorTag, + Callback errorCallback, + Callback successCallback) { + try { + measureLayout(tag, ancestorTag, mMeasureBuffer); + float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); + float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); + float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); + float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); + successCallback.invoke(relativeX, relativeY, width, height); + } catch (IllegalViewOperationException e) { + errorCallback.invoke(e.getMessage()); + } + } + + /** + * Like {@link #measure} and {@link #measureLayout} but measures relative to the immediate parent. + */ + public void measureLayoutRelativeToParent( + int tag, + Callback errorCallback, + Callback successCallback) { + try { + measureLayoutRelativeToParent(tag, mMeasureBuffer); + float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); + float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); + float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); + float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); + successCallback.invoke(relativeX, relativeY, width, height); + } catch (IllegalViewOperationException e) { + errorCallback.invoke(e.getMessage()); + } + } + + /** + * Invoked at the end of the transaction to commit any updates to the node hierarchy. + */ + public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) { + for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) { + int tag = mShadowNodeRegistry.getRootTag(i); + ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); + notifyOnBeforeLayoutRecursive(cssRoot); + + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") + .arg("rootTag", tag) + .flush(); + try { + cssRoot.calculateLayout(mLayoutContext); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher); + } + + mNativeViewHierarchyOptimizer.onBatchComplete(); + mOperationsQueue.dispatchViewUpdates(batchId); + } + + /** + * Registers a new Animation that can then be added to a View using {@link #addAnimation}. + */ + public void registerAnimation(Animation animation) { + mOperationsQueue.enqueueRegisterAnimation(animation); + } + + /** + * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it + */ + public void addAnimation(int reactTag, int animationID, Callback onSuccess) { + assertViewExists(reactTag, "addAnimation"); + mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess); + } + + /** + * Removes an existing Animation, canceling it if it was in progress. + */ + public void removeAnimation(int reactTag, int animationID) { + assertViewExists(reactTag, "removeAnimation"); + mOperationsQueue.enqueueRemoveAnimation(animationID); + } + + public void setJSResponder(int reactTag, boolean blockNativeResponder) { + assertViewExists(reactTag, "setJSResponder"); + ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); + while (node.isVirtual() || node.isLayoutOnly()) { + node = node.getParent(); + } + mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); + } + + public void clearJSResponder() { + mOperationsQueue.enqueueClearJSResponder(); + } + + public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) { + assertViewExists(reactTag, "dispatchViewManagerCommand"); + mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); + } + + /** + * Show a PopupMenu. + * + * @param reactTag the tag of the anchor view (the PopupMenu is displayed next to this view); this + * needs to be the tag of a native view (shadow views can not be anchors) + * @param items the menu items as an array of strings + * @param error will be called if there is an error displaying the menu + * @param success will be called with the position of the selected item as the first argument, or + * no arguments if the menu is dismissed + */ + public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) { + assertViewExists(reactTag, "showPopupMenu"); + mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success); + } + + public void sendAccessibilityEvent(int tag, int eventType) { + mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); + } + + public void onHostResume() { + mOperationsQueue.resumeFrameCallback(); + } + + public void onHostPause() { + mOperationsQueue.pauseFrameCallback(); + } + + public void onHostDestroy() { + } + + public void setViewHierarchyUpdateDebugListener( + @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { + mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); + } + + private void removeShadowNode(ReactShadowNode nodeToRemove) { + mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); + for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { + removeShadowNode(nodeToRemove.getChildAt(i)); + } + nodeToRemove.removeAllChildren(); + } + + private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) { + ReactShadowNode node = mShadowNodeRegistry.getNode(tag); + ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag); + if (node == null || ancestor == null) { + throw new IllegalViewOperationException( + "Tag " + (node == null ? tag : ancestorTag) + " does not exist"); + } + + if (node != ancestor) { + ReactShadowNode currentParent = node.getParent(); + while (currentParent != ancestor) { + if (currentParent == null) { + throw new IllegalViewOperationException( + "Tag " + ancestorTag + " is not an ancestor of tag " + tag); + } + currentParent = currentParent.getParent(); + } + } + + measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer); + } + + private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) { + ReactShadowNode node = mShadowNodeRegistry.getNode(tag); + if (node == null) { + throw new IllegalViewOperationException("No native view for tag " + tag + " exists!"); + } + ReactShadowNode parent = node.getParent(); + if (parent == null) { + throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!"); + } + + measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer); + } + + private void measureLayoutRelativeToVerifiedAncestor( + ReactShadowNode node, + ReactShadowNode ancestor, + int[] outputBuffer) { + int offsetX = 0; + int offsetY = 0; + if (node != ancestor) { + offsetX = Math.round(node.getLayoutX()); + offsetY = Math.round(node.getLayoutY()); + ReactShadowNode current = node.getParent(); + while (current != ancestor) { + Assertions.assertNotNull(current); + assertNodeDoesNotNeedCustomLayoutForChildren(current); + offsetX += Math.round(current.getLayoutX()); + offsetY += Math.round(current.getLayoutY()); + current = current.getParent(); + } + assertNodeDoesNotNeedCustomLayoutForChildren(ancestor); + } + + outputBuffer[0] = offsetX; + outputBuffer[1] = offsetY; + outputBuffer[2] = node.getScreenWidth(); + outputBuffer[3] = node.getScreenHeight(); + } + + private void assertViewExists(int reactTag, String operationNameForExceptionMessage) { + if (mShadowNodeRegistry.getNode(reactTag) == null) { + throw new IllegalViewOperationException( + "Unable to execute operation " + operationNameForExceptionMessage + " on view with " + + "tag: " + reactTag + ", since the view does not exists"); + } + } + + private void assertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) { + ViewManager viewManager = Assertions.assertNotNull(mViewManagers.get(node.getViewClass())); + ViewGroupManager viewGroupManager; + if (viewManager instanceof ViewGroupManager) { + viewGroupManager = (ViewGroupManager) viewManager; + } else { + throw new IllegalViewOperationException("Trying to use view " + node.getViewClass() + + " as a parent, but its Manager doesn't extends ViewGroupManager"); + } + if (viewGroupManager != null && viewGroupManager.needsCustomLayoutForChildren()) { + throw new IllegalViewOperationException( + "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to" + + " an ancestor that requires custom layout for it's children (" + node.getViewClass() + + "). Use measure instead."); + } + } + + private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) { + if (!cssNode.hasUpdates()) { + return; + } + for (int i = 0; i < cssNode.getChildCount(); i++) { + notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i)); + } + cssNode.onBeforeLayout(); + } + + private void applyUpdatesRecursive( + ReactShadowNode cssNode, + float absoluteX, + float absoluteY, + EventDispatcher eventDispatcher) { + if (!cssNode.hasUpdates()) { + return; + } + + if (!cssNode.isVirtualAnchor()) { + for (int i = 0; i < cssNode.getChildCount(); i++) { + applyUpdatesRecursive( + cssNode.getChildAt(i), + absoluteX + cssNode.getLayoutX(), + absoluteY + cssNode.getLayoutY(), + eventDispatcher); + } + } + + int tag = cssNode.getReactTag(); + if (!mShadowNodeRegistry.isRootNode(tag)) { + cssNode.dispatchUpdates( + absoluteX, + absoluteY, + mOperationsQueue, + mNativeViewHierarchyOptimizer, + eventDispatcher); + } + cssNode.markUpdateSeen(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 5633c32b98dc..ff473aaf683f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -72,31 +72,23 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private static final int ROOT_VIEW_TAG_INCREMENT = 10; private final EventDispatcher mEventDispatcher; - private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry(); - private final ViewManagerRegistry mViewManagers; - private final CSSLayoutContext mLayoutContext = new CSSLayoutContext(); private final Map mModuleConstants; - private final UIViewOperationQueue mOperationsQueue; - private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; - private final int[] mMeasureBuffer = new int[4]; + private final UIImplementation mUIImplementation; private int mNextRootViewTag = 1; private int mBatchId = 0; public UIManagerModule(ReactApplicationContext reactContext, List viewManagerList) { super(reactContext); - mViewManagers = new ViewManagerRegistry(viewManagerList); mEventDispatcher = new EventDispatcher(reactContext); - mOperationsQueue = new UIViewOperationQueue( - reactContext, - new NativeViewHierarchyManager(mViewManagers)); - mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( - mOperationsQueue, - mShadowNodeRegistry); DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics(); DisplayMetricsHolder.setDisplayMetrics(displayMetrics); mModuleConstants = createConstants(displayMetrics, viewManagerList); reactContext.addLifecycleEventListener(this); + + mUIImplementation = new UIImplementation( + reactContext, + new ViewManagerRegistry(viewManagerList)); } @Override @@ -111,16 +103,17 @@ public Map getConstants() { @Override public void onHostResume() { - mOperationsQueue.resumeFrameCallback(); + mUIImplementation.onHostResume(); } @Override public void onHostPause() { - mOperationsQueue.pauseFrameCallback(); + mUIImplementation.onHostPause(); } @Override public void onHostDestroy() { + mUIImplementation.onHostDestroy(); } @Override @@ -158,22 +151,23 @@ public int addMeasuredRootView(final SizeMonitoringFrameLayout rootView) { final int tag = mNextRootViewTag; mNextRootViewTag += ROOT_VIEW_TAG_INCREMENT; - final ReactShadowNode rootCSSNode = new ReactShadowNode(); - rootCSSNode.setReactTag(tag); - final ThemedReactContext themedRootContext = - new ThemedReactContext(getReactApplicationContext(), rootView.getContext()); - rootCSSNode.setThemedContext(themedRootContext); + final int width; + final int height; // If LayoutParams sets size explicitly, we can use that. Otherwise get the size from the view. if (rootView.getLayoutParams() != null && rootView.getLayoutParams().width > 0 && rootView.getLayoutParams().height > 0) { - rootCSSNode.setStyleWidth(rootView.getLayoutParams().width); - rootCSSNode.setStyleHeight(rootView.getLayoutParams().height); + width = rootView.getLayoutParams().width; + height = rootView.getLayoutParams().height; } else { - rootCSSNode.setStyleWidth(rootView.getWidth()); - rootCSSNode.setStyleHeight(rootView.getHeight()); + width = rootView.getWidth(); + height = rootView.getHeight(); } - rootCSSNode.setViewClassName("Root"); + + final ThemedReactContext themedRootContext = + new ThemedReactContext(getReactApplicationContext(), rootView.getContext()); + + mUIImplementation.registerRootView(rootView, tag, width, height, themedRootContext); rootView.setOnSizeChangedListener( new SizeMonitoringFrameLayout.OnSizeChangedListener() { @@ -183,98 +177,39 @@ public void onSizeChanged(final int width, final int height, int oldW, int oldH) new Runnable() { @Override public void run() { - updateRootNodeSize(rootCSSNode, width, height); + updateRootNodeSize(tag, width, height); } }); } }); - mShadowNodeRegistry.addRootNode(rootCSSNode); - - // register it within NativeViewHierarchyManager - mOperationsQueue.addRootView(tag, rootView, themedRootContext); - return tag; } @ReactMethod public void removeRootView(int rootViewTag) { - mShadowNodeRegistry.removeRootNode(rootViewTag); - mOperationsQueue.enqueueRemoveRootView(rootViewTag); + mUIImplementation.removeRootView(rootViewTag); } - private void updateRootNodeSize(ReactShadowNode rootCSSNode, int newWidth, int newHeight) { + private void updateRootNodeSize(int rootViewTag, int newWidth, int newHeight) { getReactApplicationContext().assertOnNativeModulesQueueThread(); - rootCSSNode.setStyleWidth(newWidth); - rootCSSNode.setStyleHeight(newHeight); - - // If we're in the middle of a batch, the change will automatically be dispatched at the end of - // the batch. As all batches are executed as a single runnable on the event queue this should - // always be empty, but that calling architecture is an implementation detail. - if (mOperationsQueue.isEmpty()) { - dispatchViewUpdates(-1); // -1 = no associated batch id - } + mUIImplementation.updateRootNodeSize(rootViewTag, newWidth, newHeight, mEventDispatcher); } @ReactMethod public void createView(int tag, String className, int rootViewTag, ReadableMap props) { - ViewManager viewManager = mViewManagers.get(className); - ReactShadowNode cssNode = viewManager.createShadowNodeInstance(); - ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); - cssNode.setReactTag(tag); - cssNode.setViewClassName(className); - cssNode.setRootNode(rootNode); - cssNode.setThemedContext(rootNode.getThemedContext()); - - mShadowNodeRegistry.addNode(cssNode); - - CatalystStylesDiffMap styles = null; - if (props != null) { - styles = new CatalystStylesDiffMap(props); - cssNode.updateProperties(styles); - } - - if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles); - } + mUIImplementation.createView(tag, className, rootViewTag, props); } @ReactMethod public void dropViews(ReadableArray viewTags) { - int size = viewTags.size(), realViewsCount = 0; - int realViewTags[] = new int[size]; - for (int i = 0; i < size; i++) { - int tag = viewTags.getInt(i); - ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); - if (!cssNode.isVirtual()) { - realViewTags[realViewsCount++] = tag; - } - mShadowNodeRegistry.removeNode(tag); - } - if (realViewsCount > 0) { - mNativeViewHierarchyOptimizer.handleDropViews(realViewTags, realViewsCount); - } + mUIImplementation.dropViews(viewTags); } @ReactMethod public void updateView(int tag, String className, ReadableMap props) { - ViewManager viewManager = mViewManagers.get(className); - if (viewManager == null) { - throw new IllegalViewOperationException("Got unknown view type: " + className); - } - ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); - if (cssNode == null) { - throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag); - } - - if (props != null) { - CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props); - cssNode.updateProperties(styles); - if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); - } - } + mUIImplementation.updateView(tag, className, props); } /** @@ -296,115 +231,13 @@ public void manageChildren( @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices, @Nullable ReadableArray removeFrom) { - ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); - - int numToMove = moveFrom == null ? 0 : moveFrom.size(); - int numToAdd = addChildTags == null ? 0 : addChildTags.size(); - int numToRemove = removeFrom == null ? 0 : removeFrom.size(); - - if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) { - throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!"); - } - - if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) { - throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!"); - } - - // We treat moves as an add and a delete - ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd]; - int[] indicesToRemove = new int[numToMove + numToRemove]; - int[] tagsToRemove = new int[indicesToRemove.length]; - int[] tagsToDelete = new int[numToRemove]; - - if (numToMove > 0) { - Assertions.assertNotNull(moveFrom); - Assertions.assertNotNull(moveTo); - for (int i = 0; i < numToMove; i++) { - int moveFromIndex = moveFrom.getInt(i); - int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag(); - viewsToAdd[i] = new ViewAtIndex( - tagToMove, - moveTo.getInt(i)); - indicesToRemove[i] = moveFromIndex; - tagsToRemove[i] = tagToMove; - } - } - - if (numToAdd > 0) { - Assertions.assertNotNull(addChildTags); - Assertions.assertNotNull(addAtIndices); - for (int i = 0; i < numToAdd; i++) { - int viewTagToAdd = addChildTags.getInt(i); - int indexToAddAt = addAtIndices.getInt(i); - viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt); - } - } - - if (numToRemove > 0) { - Assertions.assertNotNull(removeFrom); - for (int i = 0; i < numToRemove; i++) { - int indexToRemove = removeFrom.getInt(i); - int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag(); - indicesToRemove[numToMove + i] = indexToRemove; - tagsToRemove[numToMove + i] = tagToRemove; - tagsToDelete[i] = tagToRemove; - } - } - - // NB: moveFrom and removeFrom are both relative to the starting state of the View's children. - // moveTo and addAt are both relative to the final state of the View's children. - // - // 1) Sort the views to add and indices to remove by index - // 2) Iterate the indices being removed from high to low and remove them. Going high to low - // makes sure we remove the correct index when there are multiple to remove. - // 3) Iterate the views being added by index low to high and add them. Like the view removal, - // iteration direction is important to preserve the correct index. - - Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR); - Arrays.sort(indicesToRemove); - - // Apply changes to CSSNode hierarchy - int lastIndexRemoved = -1; - for (int i = indicesToRemove.length - 1; i >= 0; i--) { - int indexToRemove = indicesToRemove[i]; - if (indexToRemove == lastIndexRemoved) { - throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: " - + viewTag); - } - cssNodeToManage.removeChildAt(indicesToRemove[i]); - lastIndexRemoved = indicesToRemove[i]; - } - - for (int i = 0; i < viewsToAdd.length; i++) { - ViewAtIndex viewAtIndex = viewsToAdd[i]; - ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag); - if (cssNodeToAdd == null) { - throw new IllegalViewOperationException("Trying to add unknown view tag: " - + viewAtIndex.mTag); - } - cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); - } - - if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { - mNativeViewHierarchyOptimizer.handleManageChildren( - cssNodeToManage, - indicesToRemove, - tagsToRemove, - viewsToAdd, - tagsToDelete); - } - - for (int i = 0; i < tagsToDelete.length; i++) { - removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); - } - } - - private void removeShadowNode(ReactShadowNode nodeToRemove) { - mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); - for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { - removeShadowNode(nodeToRemove.getChildAt(i)); - } - nodeToRemove.removeAllChildren(); + mUIImplementation.manageChildren( + viewTag, + moveFrom, + moveTo, + addChildTags, + addAtIndices, + removeFrom); } /** @@ -414,35 +247,7 @@ private void removeShadowNode(ReactShadowNode nodeToRemove) { */ @ReactMethod public void replaceExistingNonRootView(int oldTag, int newTag) { - if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) { - throw new IllegalViewOperationException("Trying to add or replace a root tag!"); - } - - ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag); - if (oldNode == null) { - throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag); - } - - ReactShadowNode parent = oldNode.getParent(); - if (parent == null) { - throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag); - } - - int oldIndex = parent.indexOf(oldNode); - if (oldIndex < 0) { - throw new IllegalStateException("Didn't find child tag in parent"); - } - - WritableArray tagsToAdd = Arguments.createArray(); - tagsToAdd.pushInt(newTag); - - WritableArray addAtIndices = Arguments.createArray(); - addAtIndices.pushInt(oldIndex); - - WritableArray indicesToRemove = Arguments.createArray(); - indicesToRemove.pushInt(oldIndex); - - manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove); + mUIImplementation.replaceExistingNonRootView(oldTag, newTag); } /** @@ -453,18 +258,7 @@ public void replaceExistingNonRootView(int oldTag, int newTag) { */ @ReactMethod public void removeSubviewsFromContainerWithID(int containerTag) { - ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag); - if (containerNode == null) { - throw new IllegalViewOperationException( - "Trying to remove subviews of an unknown view tag: " + containerTag); - } - - WritableArray indicesToRemove = Arguments.createArray(); - for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) { - indicesToRemove.pushInt(childIndex); - } - - manageChildren(containerTag, null, null, null, null, indicesToRemove); + mUIImplementation.removeSubviewsFromContainerWithID(containerTag); } /** @@ -472,12 +266,8 @@ public void removeSubviewsFromContainerWithID(int containerTag) { * via an async callback. */ @ReactMethod - public void measure(final int reactTag, final Callback callback) { - // This method is called by the implementation of JS touchable interface (see Touchable.js for - // more details) at the moment of touch activation. That is after user starts the gesture from - // a touchable view with a given reactTag, or when user drag finger back into the press - // activation area of a touchable view that have been activated before. - mOperationsQueue.enqueueMeasure(reactTag, callback); + public void measure(int reactTag, Callback callback) { + mUIImplementation.measure(reactTag, callback); } /** @@ -496,16 +286,7 @@ public void measureLayout( int ancestorTag, Callback errorCallback, Callback successCallback) { - try { - measureLayout(tag, ancestorTag, mMeasureBuffer); - float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); - float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); - float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); - float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); - successCallback.invoke(relativeX, relativeY, width, height); - } catch (IllegalViewOperationException e) { - errorCallback.invoke(e.getMessage()); - } + mUIImplementation.measureLayout(tag, ancestorTag, errorCallback, successCallback); } /** @@ -520,94 +301,7 @@ public void measureLayoutRelativeToParent( int tag, Callback errorCallback, Callback successCallback) { - try { - measureLayoutRelativeToParent(tag, mMeasureBuffer); - float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); - float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); - float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); - float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); - successCallback.invoke(relativeX, relativeY, width, height); - } catch (IllegalViewOperationException e) { - errorCallback.invoke(e.getMessage()); - } - } - - private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) { - ReactShadowNode node = mShadowNodeRegistry.getNode(tag); - ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag); - if (node == null || ancestor == null) { - throw new IllegalViewOperationException( - "Tag " + (node == null ? tag : ancestorTag) + " does not exist"); - } - - if (node != ancestor) { - ReactShadowNode currentParent = node.getParent(); - while (currentParent != ancestor) { - if (currentParent == null) { - throw new IllegalViewOperationException( - "Tag " + ancestorTag + " is not an ancestor of tag " + tag); - } - currentParent = currentParent.getParent(); - } - } - - measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer); - } - - private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) { - ReactShadowNode node = mShadowNodeRegistry.getNode(tag); - if (node == null) { - throw new IllegalViewOperationException("No native view for tag " + tag + " exists!"); - } - ReactShadowNode parent = node.getParent(); - if (parent == null) { - throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!"); - } - - measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer); - } - - private void measureLayoutRelativeToVerifiedAncestor( - ReactShadowNode node, - ReactShadowNode ancestor, - int[] outputBuffer) { - int offsetX = 0; - int offsetY = 0; - if (node != ancestor) { - offsetX = Math.round(node.getLayoutX()); - offsetY = Math.round(node.getLayoutY()); - ReactShadowNode current = node.getParent(); - while (current != ancestor) { - Assertions.assertNotNull(current); - assertNodeDoesNotNeedCustomLayoutForChildren(current); - offsetX += Math.round(current.getLayoutX()); - offsetY += Math.round(current.getLayoutY()); - current = current.getParent(); - } - assertNodeDoesNotNeedCustomLayoutForChildren(ancestor); - } - - outputBuffer[0] = offsetX; - outputBuffer[1] = offsetY; - outputBuffer[2] = node.getScreenWidth(); - outputBuffer[3] = node.getScreenHeight(); - } - - private void assertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) { - ViewManager viewManager = Assertions.assertNotNull(mViewManagers.get(node.getViewClass())); - ViewGroupManager viewGroupManager; - if (viewManager instanceof ViewGroupManager) { - viewGroupManager = (ViewGroupManager) viewManager; - } else { - throw new IllegalViewOperationException("Trying to use view " + node.getViewClass() + - " as a parent, but its Manager doesn't extends ViewGroupManager"); - } - if (viewGroupManager != null && viewGroupManager.needsCustomLayoutForChildren()) { - throw new IllegalViewOperationException( - "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to" + - " an ancestor that requires custom layout for it's children (" + node.getViewClass() + - "). Use measure instead."); - } + mUIImplementation.measureLayoutRelativeToParent(tag, errorCallback, successCallback); } /** @@ -626,7 +320,7 @@ public void findSubviewIn( final int reactTag, final ReadableArray point, final Callback callback) { - mOperationsQueue.enqueueFindTargetForTouch( + mUIImplementation.findSubviewIn( reactTag, Math.round(PixelUtil.toPixelFromDIP(point.getDouble(0))), Math.round(PixelUtil.toPixelFromDIP(point.getDouble(1))), @@ -637,47 +331,36 @@ public void findSubviewIn( * Registers a new Animation that can then be added to a View using {@link #addAnimation}. */ public void registerAnimation(Animation animation) { - mOperationsQueue.enqueueRegisterAnimation(animation); + mUIImplementation.registerAnimation(animation); } /** * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it */ - public void addAnimation(final int reactTag, final int animationID, final Callback onSuccess) { - assertViewExists(reactTag, "addAnimation"); - mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess); + public void addAnimation(int reactTag, int animationID, Callback onSuccess) { + mUIImplementation.addAnimation(reactTag, animationID, onSuccess); } /** * Removes an existing Animation, canceling it if it was in progress. */ public void removeAnimation(int reactTag, int animationID) { - assertViewExists(reactTag, "removeAnimation"); - mOperationsQueue.enqueueRemoveAnimation(animationID); + mUIImplementation.removeAnimation(reactTag, animationID); } @ReactMethod public void setJSResponder(int reactTag, boolean blockNativeResponder) { - assertViewExists(reactTag, "setJSResponder"); - ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); - while (node.isVirtual() || node.isLayoutOnly()) { - node = node.getParent(); - } - mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); + mUIImplementation.setJSResponder(reactTag, blockNativeResponder); } @ReactMethod public void clearJSResponder() { - mOperationsQueue.enqueueClearJSResponder(); + mUIImplementation.clearJSResponder(); } @ReactMethod - public void dispatchViewManagerCommand( - int reactTag, - int commandId, - ReadableArray commandArgs) { - assertViewExists(reactTag, "dispatchViewManagerCommand"); - mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); + public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) { + mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs); } /** @@ -691,13 +374,8 @@ public void dispatchViewManagerCommand( * no arguments if the menu is dismissed */ @ReactMethod - public void showPopupMenu( - int reactTag, - ReadableArray items, - Callback error, - Callback success) { - assertViewExists(reactTag, "showPopupMenu"); - mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success); + public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) { + mUIImplementation.showPopupMenu(reactTag, items, error, success); } @ReactMethod @@ -708,14 +386,6 @@ public void configureNextLayoutAnimation( // TODO(6588266): Implement if required } - private void assertViewExists(int reactTag, String operationNameForExceptionMessage) { - if (mShadowNodeRegistry.getNode(reactTag) == null) { - throw new IllegalViewOperationException( - "Unable to execute operation " + operationNameForExceptionMessage + " on view with " + - "tag: " + reactTag + ", since the view does not exists"); - } - } - /** * To implement the transactional requirement mentioned in the class javadoc, we only commit * UI changes to the actual view hierarchy once a batch of JS->Java calls have been completed. @@ -739,7 +409,7 @@ public void onBatchComplete() { .arg("BatchId", batchId) .flush(); try { - dispatchViewUpdates(batchId); + mUIImplementation.dispatchViewUpdates(mEventDispatcher, batchId); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } @@ -747,73 +417,15 @@ public void onBatchComplete() { public void setViewHierarchyUpdateDebugListener( @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { - mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); + mUIImplementation.setViewHierarchyUpdateDebugListener(listener); } public EventDispatcher getEventDispatcher() { return mEventDispatcher; } - private void dispatchViewUpdates(final int batchId) { - for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) { - int tag = mShadowNodeRegistry.getRootTag(i); - ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); - notifyOnBeforeLayoutRecursive(cssRoot); - - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") - .arg("rootTag", tag) - .flush(); - try { - cssRoot.calculateLayout(mLayoutContext); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - applyUpdatesRecursive(cssRoot, 0f, 0f); - } - - mNativeViewHierarchyOptimizer.onBatchComplete(); - mOperationsQueue.dispatchViewUpdates(batchId); - } - - private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) { - if (!cssNode.hasUpdates()) { - return; - } - for (int i = 0; i < cssNode.getChildCount(); i++) { - notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i)); - } - cssNode.onBeforeLayout(); - } - - private void applyUpdatesRecursive(ReactShadowNode cssNode, float absoluteX, float absoluteY) { - if (!cssNode.hasUpdates()) { - return; - } - - if (!cssNode.isVirtualAnchor()) { - for (int i = 0; i < cssNode.getChildCount(); i++) { - applyUpdatesRecursive( - cssNode.getChildAt(i), - absoluteX + cssNode.getLayoutX(), - absoluteY + cssNode.getLayoutY()); - } - } - - int tag = cssNode.getReactTag(); - if (!mShadowNodeRegistry.isRootNode(tag)) { - cssNode.dispatchUpdates( - absoluteX, - absoluteY, - mOperationsQueue, - mNativeViewHierarchyOptimizer, - mEventDispatcher); - } - cssNode.markUpdateSeen(); - } - @ReactMethod public void sendAccessibilityEvent(int tag, int eventType) { - mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); + mUIImplementation.sendAccessibilityEvent(tag, eventType); } - } From e379536c113c7adde291854f1cd1808e6df64574 Mon Sep 17 00:00:00 2001 From: Julius Jurgelenas Date: Thu, 26 Nov 2015 12:43:39 +0200 Subject: [PATCH 0118/1411] Showcase: add blog post link to Formations Factory Company name search app --- website/src/react-native/showcase.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 987580237dd9..7d9bbfd816a9 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -140,6 +140,9 @@ var apps = [ icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/fd/47/53/fd47537c-5861-e208-d1d1-1e26b5e45a36/icon350x350.jpeg', link: 'https://itunes.apple.com/us/app/company-name-search/id1043824076', author: 'The Formations Factory Ltd', + blogs: [ + 'https://medium.com/formations-factory/creating-company-name-search-app-with-react-native-36a049b0848d', + ], }, { name: 'DareU', From 5b5b55027b1c63f4b1979a5af789f350898bdce5 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 26 Nov 2015 03:04:33 -0800 Subject: [PATCH 0119/1411] Added support for custom color and image for map annotations Summary: public This diff extends RCTMap annotations with an `image` and `tintColor` property, which can be used to render completely custom pin graphics. The tintColor applies to both regular pins and custom pin images, allowing you to provide varied pin colors without needing multiple graphic assets. Reviewed By: fredliu Differential Revision: D2685581 fb-gh-sync-id: c7cf0af5c90fd8d1e9b3fec4b89206440b47ba8f --- Examples/UIExplorer/MapViewExample.js | 137 ++++++++++++++++++++++-- Libraries/Components/MapView/MapView.js | 114 ++++++++++++++------ React/Base/RCTConvert.m | 17 +-- React/Base/RCTUtils.h | 6 ++ React/Base/RCTUtils.m | 64 +++++++++++ React/Views/RCTConvert+MapKit.m | 5 + React/Views/RCTMapManager.m | 69 +++++++++--- React/Views/RCTPointAnnotation.h | 2 + 8 files changed, 356 insertions(+), 58 deletions(-) diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 572017574759..9a7e2e1aab23 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -149,9 +149,6 @@ var MapViewExample = React.createClass({ getInitialState() { return { - mapRegion: null, - mapRegionInput: null, - annotations: null, isFirstLoad: true, }; }, @@ -163,12 +160,12 @@ var MapViewExample = React.createClass({ style={styles.map} onRegionChange={this._onRegionChange} onRegionChangeComplete={this._onRegionChangeComplete} - region={this.state.mapRegion || undefined} - annotations={this.state.annotations || undefined} + region={this.state.mapRegion} + annotations={this.state.annotations} /> ); @@ -208,6 +205,114 @@ var MapViewExample = React.createClass({ }); +var CalloutMapViewExample = React.createClass({ + + getInitialState() { + return { + isFirstLoad: true, + }; + }, + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + this.setState({ + isFirstLoad: false, + annotations: [{ + longitude: region.longitude, + latitude: region.latitude, + title: 'More Info...', + hasRightCallout: true, + onRightCalloutPress: () => { + alert('You Are Here'); + }, + }], + }); + }; + } + + return ( + + ); + }, + +}); + +var CustomPinColorMapViewExample = React.createClass({ + + getInitialState() { + return { + isFirstLoad: true, + }; + }, + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + this.setState({ + isFirstLoad: false, + annotations: [{ + longitude: region.longitude, + latitude: region.latitude, + title: 'You Are Purple', + tintColor: MapView.PinColors.PURPLE, + }], + }); + }; + } + + return ( + + ); + }, + +}); + +var CustomPinImageMapViewExample = React.createClass({ + + getInitialState() { + return { + isFirstLoad: true, + }; + }, + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + this.setState({ + isFirstLoad: false, + annotations: [{ + longitude: region.longitude, + latitude: region.latitude, + title: 'Thumbs Up!', + image: require('image!uie_thumb_big'), + }], + }); + }; + } + + return ( + + ); + }, + +}); + var styles = StyleSheet.create({ map: { height: 150, @@ -249,5 +354,23 @@ exports.examples = [ render() { return ; } - } + }, + { + title: 'Callout example', + render() { + return ; + } + }, + { + title: 'Custom pin color', + render() { + return ; + } + }, + { + title: 'Custom pin image', + render() { + return ; + } + }, ]; diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 58950c490714..26cd3f61ee62 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -12,7 +12,9 @@ 'use strict'; var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var Image = require('Image'); var NativeMethodsMixin = require('NativeMethodsMixin'); +var PinColors = require('NativeModules').UIManager.RCTMap.Constants.PinColors; var Platform = require('Platform'); var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); @@ -21,6 +23,8 @@ var View = require('View'); var deepDiffer = require('deepDiffer'); var insetsDiffer = require('insetsDiffer'); var merge = require('merge'); +var processColor = require('processColor'); +var resolveAssetSource = require('resolveAssetSource'); var requireNativeComponent = require('requireNativeComponent'); type Event = Object; @@ -194,7 +198,21 @@ var MapView = React.createClass({ /** * annotation id */ - id: React.PropTypes.string + id: React.PropTypes.string, + + /** + * The pin color. This can be any valid color string, or you can use one + * of the predefined PinColors constants. Applies to both standard pins + * and custom pin images. + * @platform ios + */ + tintColor: React.PropTypes.string, + + /** + * Custom pin image. This must be a static image resource inside the app. + * @platform ios + */ + image: Image.propTypes.source, })), @@ -235,47 +253,79 @@ var MapView = React.createClass({ active: React.PropTypes.bool, }, - _onChange: function(event: Event) { - if (event.nativeEvent.continuous) { - this.props.onRegionChange && - this.props.onRegionChange(event.nativeEvent.region); - } else { - this.props.onRegionChangeComplete && - this.props.onRegionChangeComplete(event.nativeEvent.region); - } - }, - - _onPress: function(event: Event) { - if (event.nativeEvent.action === 'annotation-click') { - this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation); - } + render: function() { - if (event.nativeEvent.action === 'callout-click') { - if (!this.props.annotations) { - return; - } + let {annotations} = this.props; + annotations = annotations && annotations.map((annotation: Object) => { + let {tintColor, image} = annotation; + return { + ...annotation, + tintColor: tintColor && processColor(tintColor), + image: image && resolveAssetSource(image), + }; + }); - // Find the annotation with the id of what has been pressed - for (var i = 0; i < this.props.annotations.length; i++) { - var annotation = this.props.annotations[i]; - if (annotation.id === event.nativeEvent.annotationId) { - // Pass the right function - if (event.nativeEvent.side === 'left') { - annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent); - } else if (event.nativeEvent.side === 'right') { - annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent); + // TODO: these should be separate events, to reduce bridge traffic + if (annotations || this.props.onAnnotationPress) { + var onPress = (event: Event) => { + if (event.nativeEvent.action === 'annotation-click') { + this.props.onAnnotationPress && + this.props.onAnnotationPress(event.nativeEvent.annotation); + } else if (event.nativeEvent.action === 'callout-click') { + // Find the annotation with the id that was pressed + for (let i = 0, l = annotations.length; i < l; i++) { + let annotation = annotations[i]; + if (annotation.id === event.nativeEvent.annotationId) { + // Pass the right function + if (event.nativeEvent.side === 'left') { + annotation.onLeftCalloutPress && + annotation.onLeftCalloutPress(event.nativeEvent); + } else if (event.nativeEvent.side === 'right') { + annotation.onRightCalloutPress && + annotation.onRightCalloutPress(event.nativeEvent); + } + break; + } } } - } + }; + } + // TODO: these should be separate events, to reduce bridge traffic + if (this.props.onRegionChange || this.props.onRegionChangeComplete) { + var onChange = (event: Event) => { + if (event.nativeEvent.continuous) { + this.props.onRegionChange && + this.props.onRegionChange(event.nativeEvent.region); + } else { + this.props.onRegionChangeComplete && + this.props.onRegionChangeComplete(event.nativeEvent.region); + } + }; } - }, - render: function() { - return ; + return ( + + ); }, }); +/** + * Standard iOS MapView pin color constants, to be used with the + * `annotation.tintColor` property. You are not obliged to use these, + * but they are useful for matching the standard iOS look and feel. + */ +MapView.PinColors = PinColors && { + RED: PinColors.RED, + GREEN: PinColors.GREEN, + PURPLE: PinColors.PURPLE, +}; + var RCTMap = requireNativeComponent('RCTMap', MapView, { nativeOnly: {onChange: true, onPress: true} }); diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 53c6b9cad226..8fc14e102fb0 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -397,6 +397,9 @@ + (type)type:(id)json \ + (UIColor *)UIColor:(id)json { + if (!json) { + return nil; + } if ([json isKindOfClass:[NSArray class]]) { NSArray *components = [self NSNumberArray:json]; CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0; @@ -404,13 +407,16 @@ + (UIColor *)UIColor:(id)json green:[self CGFloat:components[1]] blue:[self CGFloat:components[2]] alpha:alpha]; - } else { + } else if ([json isKindOfClass:[NSNumber class]]) { NSUInteger argb = [self NSUInteger:json]; CGFloat a = ((argb >> 24) & 0xFF) / 255.0; CGFloat r = ((argb >> 16) & 0xFF) / 255.0; CGFloat g = ((argb >> 8) & 0xFF) / 255.0; CGFloat b = (argb & 0xFF) / 255.0; return [UIColor colorWithRed:r green:g blue:b alpha:a]; + } else { + RCTLogConvertError(json, @"a color"); + return nil; } } @@ -460,12 +466,9 @@ + (UIImage *)UIImage:(id)json NSURL *URL = [self NSURL:path]; NSString *scheme = URL.scheme.lowercaseString; if ([scheme isEqualToString:@"file"]) { - if (RCTIsXCAssetURL(URL)) { - // Image may reside inside a .car file, in which case we have no choice - // but to use +[UIImage imageNamed] - but this method isn't thread safe - NSString *assetName = RCTBundlePathForURL(URL); - image = [UIImage imageNamed:assetName]; - } else { + NSString *assetName = RCTBundlePathForURL(URL); + image = [UIImage imageNamed:assetName]; + if (!image) { // Attempt to load from the file system NSString *filePath = URL.path; if (filePath.pathExtension.length == 0) { diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index e138da40531b..e8f3781a6c61 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -84,6 +84,9 @@ RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); RCT_EXTERN id RCTNilIfNull(id value); RCT_EXTERN id RCTNullIfNil(id value); +// Convert NaN or infinite values to zero, as these aren't JSON-safe +RCT_EXTERN double RCTZeroIfNaN(double value); + // Convert data to a Base64-encoded data URL RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data); @@ -96,3 +99,6 @@ RCT_EXTERN NSString *RCTBundlePathForURL(NSURL *URL); // Determines if a given image URL actually refers to an XCAsset RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL); + +// Converts a CGColor to a hex string +RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 8d6f3238d0c9..b7a751488378 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -420,6 +420,11 @@ id RCTNilIfNull(id value) return value == (id)kCFNull ? nil : value; } +RCT_EXTERN double RCTZeroIfNaN(double value) +{ + return isnan(value) || isinf(value) ? 0 : value; +} + NSURL *RCTDataURL(NSString *mimeType, NSData *data) { return [NSURL URLWithString: @@ -511,3 +516,62 @@ BOOL RCTIsXCAssetURL(NSURL *imageURL) } return YES; } + +static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4]) +{ + CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color)); + const CGFloat *components = CGColorGetComponents(color); + switch (model) + { + case kCGColorSpaceModelMonochrome: + { + rgba[0] = components[0]; + rgba[1] = components[0]; + rgba[2] = components[0]; + rgba[3] = components[1]; + break; + } + case kCGColorSpaceModelRGB: + { + rgba[0] = components[0]; + rgba[1] = components[1]; + rgba[2] = components[2]; + rgba[3] = components[3]; + break; + } + case kCGColorSpaceModelCMYK: + case kCGColorSpaceModelDeviceN: + case kCGColorSpaceModelIndexed: + case kCGColorSpaceModelLab: + case kCGColorSpaceModelPattern: + case kCGColorSpaceModelUnknown: + { + +#ifdef RCT_DEBUG + //unsupported format + RCTLogError(@"Unsupported color model: %i", model); +#endif + + rgba[0] = 0.0; + rgba[1] = 0.0; + rgba[2] = 0.0; + rgba[3] = 1.0; + break; + } + } +} + +NSString *RCTColorToHexString(CGColorRef color) +{ + CGFloat rgba[4]; + RCTGetRGBAColorComponents(color, rgba); + uint8_t r = rgba[0]*255; + uint8_t g = rgba[1]*255; + uint8_t b = rgba[2]*255; + uint8_t a = rgba[3]*255; + if (a < 255) { + return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a]; + } else { + return [NSString stringWithFormat:@"#%02x%02x%02x", r, g, b]; + } +} diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index 9206c3a4050e..dab5b7661719 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -61,6 +61,11 @@ + (RCTPointAnnotation *)RCTPointAnnotation:(id)json shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; + shape.tintColor = [RCTConvert UIColor:json[@"tintColor"]]; + shape.image = [RCTConvert UIImage:json[@"image"]]; + if (shape.tintColor && shape.image) { + shape.image = [shape.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } return shape; } diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 5372793a8166..75602578d334 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -14,6 +14,7 @@ #import "RCTConvert+MapKit.h" #import "RCTEventDispatcher.h" #import "RCTMap.h" +#import "RCTUtils.h" #import "UIView+React.h" #import "RCTPointAnnotation.h" @@ -55,6 +56,17 @@ - (UIView *)view [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES]; } +- (NSDictionary *)constantsToExport +{ + return @{ + @"PinColors": @{ + @"RED": RCTColorToHexString([MKPinAnnotationView redPinColor].CGColor), + @"GREEN": RCTColorToHexString([MKPinAnnotationView greenPinColor].CGColor), + @"PURPLE": RCTColorToHexString([MKPinAnnotationView purplePinColor].CGColor), + } + }; +} + #pragma mark MKMapViewDelegate - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view @@ -81,10 +93,41 @@ - (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(R return nil; } - MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"RCTAnnotation"]; + MKAnnotationView *annotationView; + if (annotation.image) { + if (annotation.tintColor) { + + NSString *const reuseIdentifier = @"RCTImageViewAnnotation"; + NSInteger imageViewTag = 99; + annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier]; + if (!annotationView) { + annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + UIImageView *imageView = [UIImageView new]; + imageView.tag = imageViewTag; + [annotationView addSubview:imageView]; + } + + UIImageView *imageView = (UIImageView *)[annotationView viewWithTag:imageViewTag]; + imageView.image = annotation.image; + imageView.tintColor = annotation.tintColor; + [imageView sizeToFit]; + imageView.center = CGPointZero; + + } else { + NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]); + annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + annotationView.image = annotation.image; + } + + } else { + + NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]); + annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + ((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop; + ((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor]; + } annotationView.canShowCallout = true; - annotationView.animatesDrop = annotation.animateDrop; annotationView.leftCalloutAccessoryView = nil; if (annotation.hasLeftCallout) { @@ -172,13 +215,17 @@ - (void)_regionChanged:(RCTMap *)mapView BOOL needZoom = NO; CGFloat newLongitudeDelta = 0.0f; MKCoordinateRegion region = mapView.region; - // On iOS 7, it's possible that we observe invalid locations during initialization of the map. - // Filter those out. + + // On iOS 7, it's possible that we observe invalid locations during + // initialization of the map. Filter those out. if (!CLLocationCoordinate2DIsValid(region.center)) { return; } - // Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time. - // So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound. + + // Calculation on float is not 100% accurate. If user zoom to max/min and then + // move, it's likely the map will auto zoom to max/min from time to time. + // So let's try to make map zoom back to 99% max or 101% min so that there is + // some buffer, and moving the map won't constantly hit the max/min bound. if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) { needZoom = YES; newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer); @@ -204,15 +251,13 @@ - (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous return; } - #define FLUSH_NAN(value) (isnan(value) ? 0 : value) - mapView.onChange(@{ @"continuous": @(continuous), @"region": @{ - @"latitude": @(FLUSH_NAN(region.center.latitude)), - @"longitude": @(FLUSH_NAN(region.center.longitude)), - @"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)), - @"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)), + @"latitude": @(RCTZeroIfNaN(region.center.latitude)), + @"longitude": @(RCTZeroIfNaN(region.center.longitude)), + @"latitudeDelta": @(RCTZeroIfNaN(region.span.latitudeDelta)), + @"longitudeDelta": @(RCTZeroIfNaN(region.span.longitudeDelta)), } }); } diff --git a/React/Views/RCTPointAnnotation.h b/React/Views/RCTPointAnnotation.h index 0646608d4805..1ee8fb05508a 100644 --- a/React/Views/RCTPointAnnotation.h +++ b/React/Views/RCTPointAnnotation.h @@ -15,5 +15,7 @@ @property (nonatomic, assign) BOOL hasLeftCallout; @property (nonatomic, assign) BOOL hasRightCallout; @property (nonatomic, assign) BOOL animateDrop; +@property (nonatomic, strong) UIColor *tintColor; +@property (nonatomic, strong) UIImage *image; @end From 9365414559a8ae629496f78d78ba043afdee29ee Mon Sep 17 00:00:00 2001 From: SungHyun Park Date: Thu, 26 Nov 2015 03:32:57 -0800 Subject: [PATCH 0120/1411] added TRACE_TAG_REACT_APPS constants Summary: added required constants Closes https://github.com/facebook/react-native/pull/4366 Reviewed By: svcscm Differential Revision: D2699433 Pulled By: astreet fb-gh-sync-id: 33c69b248f682e1b31bb2ef79cb0253ddd8d8817 --- ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java index 20867d7e248d..4ffaa3eb4a26 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java +++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java @@ -20,6 +20,7 @@ public class Systrace { public static final long TRACE_TAG_REACT_JAVA_BRIDGE = 0L; public static final long TRACE_TAG_REACT_FRESCO = 0L; + public static final long TRACE_TAG_REACT_APPS = 0L; public enum EventScope { THREAD('t'), From ea96a7edb8a17fbcc57db99217783302bb790056 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 26 Nov 2015 03:15:04 -0800 Subject: [PATCH 0121/1411] Avoid dispatch_async on RCTProfile when not profiling Summary: public Fixes #3953 Bail out soon when the profiler is not running + move string formating into the macro so that it happens in a background queue. Reviewed By: jspahrsummers Differential Revision: D2696167 fb-gh-sync-id: a1b91ee4459078ab9a4c0be62bd23362ec05e208 --- React/Base/RCTBatchedBridge.m | 3 +-- React/Profiler/RCTProfile.h | 36 ++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 0430f2b0af1f..89861dedaf53 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -871,12 +871,11 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink for (RCTModuleData *moduleData in _frameUpdateObservers) { id observer = (id)moduleData.instance; if (!observer.paused) { - RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) RCTProfileBeginFlowEvent(); [self dispatchBlock:^{ RCTProfileEndFlowEvent(); - RCT_PROFILE_BEGIN_EVENT(0, name, nil); + RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp], nil); [observer didUpdateFrame:frameUpdate]; RCT_PROFILE_END_EVENT(0, @"objc_call,fps", nil); } queue:moduleData.methodQueue]; diff --git a/React/Profiler/RCTProfile.h b/React/Profiler/RCTProfile.h index 5829a8eb4b93..2e6e921d3d26 100644 --- a/React/Profiler/RCTProfile.h +++ b/React/Profiler/RCTProfile.h @@ -66,13 +66,16 @@ RCT_EXTERN void _RCTProfileBeginEvent(NSThread *calleeThread, uint64_t tag, NSString *name, NSDictionary *args); -#define RCT_PROFILE_BEGIN_EVENT(...) { \ - NSThread *calleeThread = [NSThread currentThread]; \ - NSTimeInterval time = CACurrentMediaTime(); \ - dispatch_async(RCTProfileGetQueue(), ^{ \ - _RCTProfileBeginEvent(calleeThread, time, __VA_ARGS__); \ - }); \ -} +#define RCT_PROFILE_BEGIN_EVENT(...) \ + do { \ + if (RCTProfileIsProfiling()) { \ + NSThread *calleeThread = [NSThread currentThread]; \ + NSTimeInterval time = CACurrentMediaTime(); \ + dispatch_async(RCTProfileGetQueue(), ^{ \ + _RCTProfileBeginEvent(calleeThread, time, __VA_ARGS__); \ + }); \ + } \ + } while(0) /** * The ID returned by BeginEvent should then be passed into EndEvent, with the @@ -86,14 +89,17 @@ RCT_EXTERN void _RCTProfileEndEvent(NSThread *calleeThread, NSString *category, NSDictionary *args); -#define RCT_PROFILE_END_EVENT(...) { \ - NSThread *calleeThread = [NSThread currentThread]; \ - NSString *threadName = RCTCurrentThreadName(); \ - NSTimeInterval time = CACurrentMediaTime(); \ - dispatch_async(RCTProfileGetQueue(), ^{ \ - _RCTProfileEndEvent(calleeThread, threadName, time, __VA_ARGS__); \ - }); \ -} +#define RCT_PROFILE_END_EVENT(...) \ + do { \ + if (RCTProfileIsProfiling()) { \ + NSThread *calleeThread = [NSThread currentThread]; \ + NSString *threadName = RCTCurrentThreadName(); \ + NSTimeInterval time = CACurrentMediaTime(); \ + dispatch_async(RCTProfileGetQueue(), ^{ \ + _RCTProfileEndEvent(calleeThread, threadName, time, __VA_ARGS__); \ + }); \ + } \ + } while(0) /** * Collects the initial event information for the event and returns a reference ID From bf0934a5318bb506c1d6c05730cbe6093725c24a Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 26 Nov 2015 04:07:56 -0800 Subject: [PATCH 0122/1411] Delete RCTRootView's deprecated 'initialProperties' property Reviewed By: majak Differential Revision: D2597411 fb-gh-sync-id: 547837cac8e22e7fe78db6213741634b87bcf571 --- React/Base/RCTRootView.h | 8 -------- React/Base/RCTRootView.m | 8 -------- 2 files changed, 16 deletions(-) diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index fdb7d6b9b22e..710711704000 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -74,14 +74,6 @@ extern NSString *const RCTContentDidAppearNotification; */ @property (nonatomic, strong, readonly) RCTBridge *bridge; -/** - * DEPRECATED: access app properties via appProperties property instead - * - * The default properties to apply to the view when the script bundle - * is first loaded. Defaults to nil/empty. - */ -@property (nonatomic, copy, readonly) NSDictionary *initialProperties DEPRECATED_MSG_ATTRIBUTE ("use appProperties instead"); - /** * The properties to apply to the view. Use this property to update * application properties and rerender the view. Initialized with diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index fb9116d620ab..6be45621c43e 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -53,7 +53,6 @@ @implementation RCTRootView RCTBridge *_bridge; NSString *_moduleName; NSDictionary *_launchOptions; - NSDictionary *_initialProperties; RCTRootContentView *_contentView; } @@ -71,7 +70,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _bridge = bridge; _moduleName = moduleName; - _initialProperties = [initialProperties copy]; _appProperties = [initialProperties copy]; _loadingViewFadeDelay = 0.25; _loadingViewFadeDuration = 0.25; @@ -214,12 +212,6 @@ - (void)layoutSubviews }; } -- (NSDictionary *)initialProperties -{ - RCTLogWarn(@"Using deprecated 'initialProperties' property. Use 'appProperties' instead."); - return _initialProperties; -} - - (void)setAppProperties:(NSDictionary *)appProperties { RCTAssertMainThread(); From 4b6b71664ecdb9a7888bf45f46f673bcce613579 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 26 Nov 2015 05:39:28 -0800 Subject: [PATCH 0123/1411] Fix buck error Reviewed By: idevelop Differential Revision: D2699529 fb-gh-sync-id: d71a5abf04b767a58dd6f054a88a8bf0c561aa8d --- Libraries/Components/MapView/MapView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 26cd3f61ee62..b0468e1877c3 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -14,8 +14,8 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var Image = require('Image'); var NativeMethodsMixin = require('NativeMethodsMixin'); -var PinColors = require('NativeModules').UIManager.RCTMap.Constants.PinColors; var Platform = require('Platform'); +var RCTMapConstants = require('NativeModules').UIManager.RCTMap.Constants; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var View = require('View'); @@ -320,6 +320,7 @@ var MapView = React.createClass({ * `annotation.tintColor` property. You are not obliged to use these, * but they are useful for matching the standard iOS look and feel. */ +let PinColors = RCTMapConstants && RCTMapConstants.PinColors; MapView.PinColors = PinColors && { RED: PinColors.RED, GREEN: PinColors.GREEN, From 977a40ed04209fc7f792de9bb989df353873cfe8 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 26 Nov 2015 05:33:38 -0800 Subject: [PATCH 0124/1411] =?UTF-8?q?Don=E2=80=99t=20error=20for=20known?= =?UTF-8?q?=20files=20that=20are=20reported=20as=20=E2=80=9Cadded=E2=80=9D?= =?UTF-8?q?=20by=20watchman?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: tadeuzagallo Differential Revision: D2696636 fb-gh-sync-id: 89f90aba2a22d9c3e93632df4c4bc01791525833 --- .../DependencyGraph/HasteMap.js | 6 ++-- .../__tests__/DependencyGraph-test.js | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js index e1626fef8067..38d27e3e3b66 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; - const path = require('path'); const getPlatformExtension = require('../lib/getPlatformExtension'); const Promise = require('promise'); @@ -118,11 +117,12 @@ class HasteMap { const moduleMap = this._map[name]; const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM; + const existingModule = moduleMap[modulePlatform]; - if (moduleMap[modulePlatform]) { + if (existingModule && existingModule.path !== mod.path) { throw new Error( `Naming collision detected: ${mod.path} ` + - `collides with ${moduleMap[modulePlatform].path}` + `collides with ${existingModule.path}` ); } diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index a89ce97aa064..d830cc825dda 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -3821,6 +3821,36 @@ describe('DependencyGraph', function() { }); }); }); + + pit('should not error when the watcher reports a known file as added', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'var b = require("b");', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + 'module.exports = function() {};', + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { + triggerFileChange('add', 'index.js', root, mockStat); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js'); + }); + }); }); describe('getAsyncDependencies', () => { From a7e2059c152a2aef52b3071256e1376f28f205dd Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 26 Nov 2015 06:38:11 -0800 Subject: [PATCH 0125/1411] Fix class pair register on reload Summary: public It was possible after reload to detach from the new instance, removing the markers. Reviewed By: jspahrsummers Differential Revision: D2696208 fb-gh-sync-id: ad8f5d449f51c7c74a20ae7c0cafc4fc786ea390 --- React/Profiler/RCTProfile.m | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index fe93b658050e..f311a35045d6 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -48,7 +48,6 @@ - (void)dispatchBlock:(dispatch_block_t)block // This is actually a BOOL - but has to be compatible with OSAtomic static volatile uint32_t RCTProfileProfiling; -static BOOL RCTProfileHookedModules; static NSDictionary *RCTProfileInfo; static NSMutableDictionary *RCTProfileOngoingEvents; static NSTimeInterval RCTProfileStartTime; @@ -210,19 +209,21 @@ void RCTProfileHookModules(RCTBridge *bridge) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-pointer-compare" - if (RCTProfileTrampoline == NULL || RCTProfileHookedModules) { + if (RCTProfileTrampoline == NULL) { return; } #pragma clang diagnostic pop - RCTProfileHookedModules = YES; - for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { [bridge dispatchBlock:^{ Class moduleClass = moduleData.moduleClass; Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); if (!proxyClass) { + proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); + if (proxyClass) { + object_setClass(moduleData.instance, proxyClass); + } return; } @@ -255,19 +256,12 @@ void RCTProfileHookModules(RCTBridge *bridge) void RCTProfileUnhookModules(RCTBridge *bridge) { - if (!RCTProfileHookedModules) { - return; - } - - RCTProfileHookedModules = NO; - dispatch_group_enter(RCTProfileGetUnhookGroup()); for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { Class proxyClass = object_getClass(moduleData.instance); if (moduleData.moduleClass != proxyClass) { object_setClass(moduleData.instance, moduleData.moduleClass); - objc_disposeClassPair(proxyClass); } } From c0f60d201839ce8ec572afb4da164c4773fd94e1 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 26 Nov 2015 07:25:54 -0800 Subject: [PATCH 0126/1411] Moved React Native Android unit tests to open source Reviewed By: mkonicek Differential Revision: D2685799 fb-gh-sync-id: 56f061df58641c8cb13fc16bad5f87039f0c49fb --- .../react/bridge/CatalystTestHelper.java | 64 +++ .../facebook/react/bridge/SimpleArray.java | 145 ++++++ .../com/facebook/react/bridge/SimpleMap.java | 168 +++++++ .../modules/network/NetworkingModuleTest.java | 466 ++++++++++++++++++ .../storage/AsyncStorageModuleTest.java | 352 +++++++++++++ .../modules/timing/TimingModuleTest.java | 180 +++++++ 6 files changed, 1375 insertions(+) create mode 100644 ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleArray.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleMap.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java new file mode 100644 index 000000000000..27a1b7a9fee7 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +import com.facebook.react.bridge.queue.CatalystQueueConfiguration; +import com.facebook.react.bridge.queue.CatalystQueueConfigurationImpl; +import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; +import com.facebook.react.bridge.queue.MessageQueueThreadSpec; +import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; +import com.facebook.react.uimanager.UIManagerModule; + +import org.robolectric.RuntimeEnvironment; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Utility for creating pre-configured instances of core react components for tests. + */ +public class CatalystTestHelper { + + /** + * @return a ReactApplicationContext that has a CatalystInstance mock returned by + * {@link #createMockCatalystInstance} + */ + public static ReactApplicationContext createCatalystContextForTest() { + ReactApplicationContext context = + new ReactApplicationContext(RuntimeEnvironment.application); + context.initializeWithInstance(createMockCatalystInstance()); + return context; + } + + /** + * @return a CatalystInstance mock that has a default working CatalystQueueConfiguration. + */ + public static CatalystInstance createMockCatalystInstance() { + CatalystQueueConfigurationSpec spec = CatalystQueueConfigurationSpec.builder() + .setJSQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) + .setNativeModulesQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) + .build(); + CatalystQueueConfiguration catalystQueueConfiguration = CatalystQueueConfigurationImpl.create( + spec, + new QueueThreadExceptionHandler() { + @Override + public void handleException(Exception e) { + throw new RuntimeException(e); + } + }); + + CatalystInstance reactInstance = mock(CatalystInstance.class); + when(reactInstance.getCatalystQueueConfiguration()).thenReturn(catalystQueueConfiguration); + when(reactInstance.getNativeModule(UIManagerModule.class)) + .thenReturn(mock(UIManagerModule.class)); + + return reactInstance; + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleArray.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleArray.java new file mode 100644 index 000000000000..15656bde422e --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleArray.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A simple read/write array that can be used in tests in place of {@link WritableNativeArray}. + */ +public class SimpleArray implements ReadableArray, WritableArray { + + private final List mBackingList; + + public static SimpleArray from(List list) { + return new SimpleArray(list); + } + + public static SimpleArray of(Object... values) { + return new SimpleArray(values); + } + + private SimpleArray(Object... values) { + mBackingList = Arrays.asList(values); + } + + private SimpleArray(List list) { + mBackingList = new ArrayList(list); + } + + public SimpleArray() { + mBackingList = new ArrayList(); + } + + @Override + public int size() { + return mBackingList.size(); + } + + @Override + public boolean isNull(int index) { + return mBackingList.get(index) == null; + } + + @Override + public double getDouble(int index) { + return (Double) mBackingList.get(index); + } + + @Override + public int getInt(int index) { + return (Integer) mBackingList.get(index); + } + + @Override + public String getString(int index) { + return (String) mBackingList.get(index); + } + + @Override + public SimpleArray getArray(int index) { + return (SimpleArray) mBackingList.get(index); + } + + @Override + public boolean getBoolean(int index) { + return (Boolean) mBackingList.get(index); + } + + @Override + public SimpleMap getMap(int index) { + return (SimpleMap) mBackingList.get(index); + } + + @Override + public ReadableType getType(int index) { + return null; + } + + @Override + public void pushBoolean(boolean value) { + mBackingList.add(value); + } + + @Override + public void pushDouble(double value) { + mBackingList.add(value); + } + + @Override + public void pushInt(int value) { + mBackingList.add(value); + } + + @Override + public void pushString(String value) { + mBackingList.add(value); + } + + @Override + public void pushArray(WritableArray array) { + mBackingList.add(array); + } + + @Override + public void pushMap(WritableMap map) { + mBackingList.add(map); + } + + @Override + public void pushNull() { + mBackingList.add(null); + } + + @Override + public String toString() { + return mBackingList.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SimpleArray that = (SimpleArray) o; + + if (mBackingList != null ? !mBackingList.equals(that.mBackingList) : that.mBackingList != null) + return false; + + return true; + } + + @Override + public int hashCode() { + return mBackingList != null ? mBackingList.hashCode() : 0; + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleMap.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleMap.java new file mode 100644 index 000000000000..f86f04e8cc62 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/SimpleMap.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.bridge; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A simple read/write map that can be used in tests in place of {@link WritableNativeMap}. + */ +public class SimpleMap implements ReadableMap, WritableMap { + + private final Map mBackingMap; + + public static SimpleMap of(Object... keysAndValues) { + return new SimpleMap(keysAndValues); + } + + /** + * @param keysAndValues keys and values, interleaved + */ + private SimpleMap(Object... keysAndValues) { + if (keysAndValues.length % 2 != 0) { + throw new IllegalArgumentException("You must provide the same number of keys and values"); + } + mBackingMap = new HashMap(); + for (int i = 0; i < keysAndValues.length; i += 2) { + mBackingMap.put(keysAndValues[i], keysAndValues[i + 1]); + } + } + + public SimpleMap() { + mBackingMap = new HashMap(); + } + + @Override + public boolean hasKey(String name) { + return mBackingMap.containsKey(name); + } + + @Override + public boolean isNull(String name) { + return mBackingMap.get(name) == null; + } + + @Override + public boolean getBoolean(String name) { + return (Boolean) mBackingMap.get(name); + } + + @Override + public double getDouble(String name) { + return (Double) mBackingMap.get(name); + } + + @Override + public int getInt(String name) { + return (Integer) mBackingMap.get(name); + } + + @Override + public String getString(String name) { + return (String) mBackingMap.get(name); + } + + @Override + public SimpleMap getMap(String name) { + return (SimpleMap) mBackingMap.get(name); + } + + @Override + public SimpleArray getArray(String name) { + return (SimpleArray) mBackingMap.get(name); + } + + @Override + public ReadableType getType(String name) { + throw new UnsupportedOperationException("Method not implemented"); + } + + @Override + public ReadableMapKeySetIterator keySetIterator() { + return new ReadableMapKeySetIterator() { + Iterator mIterator = mBackingMap.keySet().iterator(); + + @Override + public boolean hasNextKey() { + return mIterator.hasNext(); + } + + @Override + public String nextKey() { + return mIterator.next(); + } + }; + } + + @Override + public void putBoolean(String key, boolean value) { + mBackingMap.put(key, value); + } + + @Override + public void putDouble(String key, double value) { + mBackingMap.put(key, value); + } + + @Override + public void putInt(String key, int value) { + mBackingMap.put(key, value); + } + + @Override + public void putString(String key, String value) { + mBackingMap.put(key, value); + } + + @Override + public void putNull(String key) { + mBackingMap.put(key, null); + } + + @Override + public void putMap(String key, WritableMap value) { + mBackingMap.put(key, value); + } + + @Override + public void merge(ReadableMap source) { + mBackingMap.putAll(((SimpleMap) source).mBackingMap); + } + + @Override + public void putArray(String key, WritableArray value) { + mBackingMap.put(key, value); + } + + @Override + public String toString() { + return mBackingMap.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SimpleMap that = (SimpleMap) o; + + if (mBackingMap != null ? !mBackingMap.equals(that.mBackingMap) : that.mBackingMap != null) + return false; + + return true; + } + + @Override + public int hashCode() { + return mBackingMap != null ? mBackingMap.hashCode() : 0; + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java new file mode 100644 index 000000000000..454a3cb986af --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -0,0 +1,466 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.network; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.SimpleArray; +import com.facebook.react.bridge.SimpleMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; + +import com.squareup.okhttp.Call; +import com.squareup.okhttp.Headers; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.MultipartBuilder; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import okio.Buffer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link NetworkingModule}. + */ +@PrepareForTest({ + Arguments.class, + Call.class, + RequestBodyUtil.class, + MultipartBuilder.class, + NetworkingModule.class, + OkHttpClient.class}) +@RunWith(RobolectricTestRunner.class) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +public class NetworkingModuleTest { + + @Rule + public PowerMockRule rule = new PowerMockRule(); + + @Test + public void testGetWithoutHeaders() throws Exception { + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + + networkingModule.sendRequest( + "GET", + "http://somedomain/foo", + 0, + SimpleArray.of(), + null, + true); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://somedomain/foo"); + // We set the User-Agent header by default + assertThat(argumentCaptor.getValue().headers().size()).isEqualTo(1); + assertThat(argumentCaptor.getValue().method()).isEqualTo("GET"); + } + + @Test + public void testFailGetWithInvalidHeadersStruct() throws Exception { + RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class); + ReactApplicationContext context = mock(ReactApplicationContext.class); + when(context.getJSModule(any(Class.class))).thenReturn(emitter); + + OkHttpClient httpClient = mock(OkHttpClient.class); + NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); + + List invalidHeaders = Arrays.asList(SimpleArray.of("foo")); + + mockEvents(); + + networkingModule.sendRequest( + "GET", + "http://somedoman/foo", + 0, + SimpleArray.from(invalidHeaders), + null, + true); + + verifyErrorEmit(emitter, 0); + } + + @Test + public void testFailPostWithoutContentType() throws Exception { + RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class); + ReactApplicationContext context = mock(ReactApplicationContext.class); + when(context.getJSModule(any(Class.class))).thenReturn(emitter); + + OkHttpClient httpClient = mock(OkHttpClient.class); + NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient); + + SimpleMap body = new SimpleMap(); + body.putString("string", "This is request body"); + + mockEvents(); + + networkingModule.sendRequest( + "POST", + "http://somedomain/bar", + 0, + SimpleArray.of(), + body, + true); + + verifyErrorEmit(emitter, 0); + } + + private static void verifyErrorEmit(RCTDeviceEventEmitter emitter, int requestId) { + ArgumentCaptor captor = ArgumentCaptor.forClass(WritableArray.class); + verify(emitter).emit(eq("didCompleteNetworkResponse"), captor.capture()); + + WritableArray array = captor.getValue(); + assertThat(array.getInt(0)).isEqualTo(requestId); + assertThat(array.getString(1)).isNotNull(); + } + + private static void mockEvents() { + PowerMockito.mockStatic(Arguments.class); + Mockito.when(Arguments.createArray()).thenAnswer( + new Answer() { + @Override + public WritableArray answer(InvocationOnMock invocation) throws Throwable { + return new SimpleArray(); + } + }); + + Mockito.when(Arguments.createMap()).thenAnswer( + new Answer() { + @Override + public WritableMap answer(InvocationOnMock invocation) throws Throwable { + return new SimpleMap(); + } + }); + } + + @Test + public void testSuccessfullPostRequest() throws Exception { + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + + SimpleMap body = new SimpleMap(); + body.putString("string", "This is request body"); + + networkingModule.sendRequest( + "POST", + "http://somedomain/bar", + 0, + SimpleArray.of(SimpleArray.of("Content-Type", "text/plain")), + body, + true); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://somedomain/bar"); + assertThat(argumentCaptor.getValue().headers().size()).isEqualTo(2); + assertThat(argumentCaptor.getValue().method()).isEqualTo("POST"); + assertThat(argumentCaptor.getValue().body().contentType().type()).isEqualTo("text"); + assertThat(argumentCaptor.getValue().body().contentType().subtype()).isEqualTo("plain"); + Buffer contentBuffer = new Buffer(); + argumentCaptor.getValue().body().writeTo(contentBuffer); + assertThat(contentBuffer.readUtf8()).isEqualTo("This is request body"); + } + + @Test + public void testHeaders() throws Exception { + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + + List headers = Arrays.asList( + SimpleArray.of("Accept", "text/plain"), + SimpleArray.of("User-Agent", "React test agent/1.0")); + + networkingModule.sendRequest( + "GET", + "http://someurl/baz", + 0, + SimpleArray.from(headers), + null, + true); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + Headers requestHeaders = argumentCaptor.getValue().headers(); + assertThat(requestHeaders.size()).isEqualTo(2); + assertThat(requestHeaders.get("Accept")).isEqualTo("text/plain"); + assertThat(requestHeaders.get("User-Agent")).isEqualTo("React test agent/1.0"); + } + + @Test + public void testMultipartPostRequestSimple() throws Exception { + PowerMockito.mockStatic(RequestBodyUtil.class); + when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class))) + .thenReturn(mock(InputStream.class)); + when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))) + .thenReturn(mock(RequestBody.class)); + + SimpleMap body = new SimpleMap(); + SimpleArray formData = new SimpleArray(); + SimpleMap bodyPart = new SimpleMap(); + bodyPart.putString("string", "value"); + bodyPart.putArray( + "headers", + SimpleArray.from( + Arrays.asList( + SimpleArray.of("content-disposition", "name")))); + formData.pushMap(bodyPart); + body.putArray("formData", formData); + + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + networkingModule.sendRequest( + "POST", + "http://someurl/uploadFoo", + 0, + new SimpleArray(), + body, + true); + + // verify url, method, headers + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://someurl/uploadFoo"); + assertThat(argumentCaptor.getValue().method()).isEqualTo("POST"); + assertThat(argumentCaptor.getValue().body().contentType().type()). + isEqualTo(MultipartBuilder.FORM.type()); + assertThat(argumentCaptor.getValue().body().contentType().subtype()). + isEqualTo(MultipartBuilder.FORM.subtype()); + Headers requestHeaders = argumentCaptor.getValue().headers(); + assertThat(requestHeaders.size()).isEqualTo(1); + } + + @Test + public void testMultipartPostRequestHeaders() throws Exception { + PowerMockito.mockStatic(RequestBodyUtil.class); + when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class))) + .thenReturn(mock(InputStream.class)); + when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))) + .thenReturn(mock(RequestBody.class)); + + List headers = Arrays.asList( + SimpleArray.of("Accept", "text/plain"), + SimpleArray.of("User-Agent", "React test agent/1.0"), + SimpleArray.of("content-type", "multipart/form-data")); + + SimpleMap body = new SimpleMap(); + SimpleArray formData = new SimpleArray(); + SimpleMap bodyPart = new SimpleMap(); + bodyPart.putString("string", "value"); + bodyPart.putArray( + "headers", + SimpleArray.from( + Arrays.asList( + SimpleArray.of("content-disposition", "name")))); + formData.pushMap(bodyPart); + body.putArray("formData", formData); + + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + networkingModule.sendRequest( + "POST", + "http://someurl/uploadFoo", + 0, + SimpleArray.from(headers), + body, + true); + + // verify url, method, headers + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://someurl/uploadFoo"); + assertThat(argumentCaptor.getValue().method()).isEqualTo("POST"); + assertThat(argumentCaptor.getValue().body().contentType().type()). + isEqualTo(MultipartBuilder.FORM.type()); + assertThat(argumentCaptor.getValue().body().contentType().subtype()). + isEqualTo(MultipartBuilder.FORM.subtype()); + Headers requestHeaders = argumentCaptor.getValue().headers(); + assertThat(requestHeaders.size()).isEqualTo(3); + assertThat(requestHeaders.get("Accept")).isEqualTo("text/plain"); + assertThat(requestHeaders.get("User-Agent")).isEqualTo("React test agent/1.0"); + assertThat(requestHeaders.get("content-type")).isEqualTo("multipart/form-data"); + } + + @Test + public void testMultipartPostRequestBody() throws Exception { + InputStream inputStream = mock(InputStream.class); + PowerMockito.mockStatic(RequestBodyUtil.class); + when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class))) + .thenReturn(inputStream); + when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))).thenCallRealMethod(); + when(inputStream.available()).thenReturn("imageUri".length()); + + final MultipartBuilder multipartBuilder = mock(MultipartBuilder.class); + PowerMockito.whenNew(MultipartBuilder.class).withNoArguments().thenReturn(multipartBuilder); + when(multipartBuilder.type(any(MediaType.class))).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return multipartBuilder; + } + }); + when(multipartBuilder.addPart(any(Headers.class), any(RequestBody.class))).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return multipartBuilder; + } + }); + when(multipartBuilder.build()).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return mock(RequestBody.class); + } + }); + + List headers = Arrays.asList( + SimpleArray.of("content-type", "multipart/form-data")); + + SimpleMap body = new SimpleMap(); + SimpleArray formData = new SimpleArray(); + body.putArray("formData", formData); + + SimpleMap bodyPart = new SimpleMap(); + bodyPart.putString("string", "locale"); + bodyPart.putArray( + "headers", + SimpleArray.from( + Arrays.asList( + SimpleArray.of("content-disposition", "user")))); + formData.pushMap(bodyPart); + + SimpleMap imageBodyPart = new SimpleMap(); + imageBodyPart.putString("uri", "imageUri"); + imageBodyPart.putArray( + "headers", + SimpleArray.from( + Arrays.asList( + SimpleArray.of("content-type", "image/jpg"), + SimpleArray.of("content-disposition", "filename=photo.jpg")))); + formData.pushMap(imageBodyPart); + + OkHttpClient httpClient = mock(OkHttpClient.class); + when(httpClient.newCall(any(Request.class))).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Call callMock = mock(Call.class); + return callMock; + } + }); + + NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient); + networkingModule.sendRequest( + "POST", + "http://someurl/uploadFoo", + 0, + SimpleArray.from(headers), + body, + true); + + // verify RequestBodyPart for image + PowerMockito.verifyStatic(times(1)); + RequestBodyUtil.getFileInputStream(any(ReactContext.class), eq("imageUri")); + PowerMockito.verifyStatic(times(1)); + RequestBodyUtil.create(MediaType.parse("image/jpg"), inputStream); + + // verify body + verify(multipartBuilder).build(); + verify(multipartBuilder).type(MultipartBuilder.FORM); + ArgumentCaptor headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class); + ArgumentCaptor bodyArgumentCaptor = ArgumentCaptor.forClass(RequestBody.class); + verify(multipartBuilder, times(2)). + addPart(headersArgumentCaptor.capture(), bodyArgumentCaptor.capture()); + + List bodyHeaders = headersArgumentCaptor.getAllValues(); + assertThat(bodyHeaders.size()).isEqualTo(2); + List bodyRequestBody = bodyArgumentCaptor.getAllValues(); + assertThat(bodyRequestBody.size()).isEqualTo(2); + + assertThat(bodyHeaders.get(0).get("content-disposition")).isEqualTo("user"); + assertThat(bodyRequestBody.get(0).contentType()).isNull(); + assertThat(bodyRequestBody.get(0).contentLength()).isEqualTo("locale".getBytes().length); + assertThat(bodyHeaders.get(1).get("content-disposition")).isEqualTo("filename=photo.jpg"); + assertThat(bodyRequestBody.get(1).contentType()).isEqualTo(MediaType.parse("image/jpg")); + assertThat(bodyRequestBody.get(1).contentLength()).isEqualTo("imageUri".getBytes().length); + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java new file mode 100644 index 000000000000..bd7076fc9258 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java @@ -0,0 +1,352 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.storage; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.SimpleArray; +import com.facebook.react.bridge.SimpleMap; +import com.facebook.react.modules.storage.AsyncStorageModule; +import com.facebook.react.modules.storage.ReactDatabaseSupplier; + + +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.mockito.Mockito.mock; +import static org.fest.assertions.api.Assertions.assertThat; + +/** + * Tests for {@link AsyncStorageModule}. + */ +@PrepareForTest({Arguments.class}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@RunWith(RobolectricTestRunner.class) +public class AsyncStorageModuleTest { + + private AsyncStorageModule mStorage; + private SimpleArray mEmptyArray; + + private static class FakeFragmentContext extends ContextWrapper { + + public FakeFragmentContext(Context base) { + super(base); + } + } + + @Rule + public PowerMockRule rule = new PowerMockRule(); + + @Before + public void prepareModules() { + PowerMockito.mockStatic(Arguments.class); + Mockito.when(Arguments.createArray()).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return new SimpleArray(); + } + }); + + Mockito.when(Arguments.createMap()).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return new SimpleMap(); + } + }); + + // don't use Robolectric before initializing mocks + mStorage = new AsyncStorageModule(new ReactApplicationContext( + new FakeFragmentContext(RuntimeEnvironment.application))); + mEmptyArray = new SimpleArray(); + } + + @After + public void cleanUp() { + RuntimeEnvironment.application.deleteDatabase(ReactDatabaseSupplier.DATABASE_NAME); + ReactDatabaseSupplier.deleteInstance(); + } + + @Test + public void testMultiSetMultiGet() { + final String key1 = "foo1"; + final String key2 = "foo2"; + final String fakeKey = "fakeKey"; + final String value1 = "bar1"; + final String value2 = "bar2"; + SimpleArray keyValues = new SimpleArray(); + keyValues.pushArray(getArray(key1, value1)); + keyValues.pushArray(getArray(key2, value2)); + + Callback setCallback = mock(Callback.class); + mStorage.multiSet(keyValues, setCallback); + Mockito.verify(setCallback, Mockito.times(1)).invoke(); + + SimpleArray keys = new SimpleArray(); + keys.pushString(key1); + keys.pushString(key2); + + Callback getCallback = mock(Callback.class); + mStorage.multiGet(keys, getCallback); + Mockito.verify(getCallback, Mockito.times(1)).invoke(null, keyValues); + + keys.pushString(fakeKey); + SimpleArray row3 = new SimpleArray(); + row3.pushString(fakeKey); + row3.pushString(null); + keyValues.pushArray(row3); + + Callback getCallback2 = mock(Callback.class); + mStorage.multiGet(keys, getCallback2); + Mockito.verify(getCallback2, Mockito.times(1)).invoke(null, keyValues); + } + + @Test + public void testMultiRemove() { + final String key1 = "foo1"; + final String key2 = "foo2"; + final String value1 = "bar1"; + final String value2 = "bar2"; + + SimpleArray keyValues = new SimpleArray(); + keyValues.pushArray(getArray(key1, value1)); + keyValues.pushArray(getArray(key2, value2)); + mStorage.multiSet(keyValues, mock(Callback.class)); + + SimpleArray keys = new SimpleArray(); + keys.pushString(key1); + keys.pushString(key2); + + Callback getCallback = mock(Callback.class); + mStorage.multiRemove(keys, getCallback); + Mockito.verify(getCallback, Mockito.times(1)).invoke(); + + Callback getAllCallback = mock(Callback.class); + mStorage.getAllKeys(getAllCallback); + Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, mEmptyArray); + + mStorage.multiSet(keyValues, mock(Callback.class)); + + keys.pushString("fakeKey"); + Callback getCallback2 = mock(Callback.class); + mStorage.multiRemove(keys, getCallback2); + Mockito.verify(getCallback2, Mockito.times(1)).invoke(); + + Callback getAllCallback2 = mock(Callback.class); + mStorage.getAllKeys(getAllCallback2); + Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray); + } + + @Test + public void testMultiMerge() throws Exception { + final String mergeKey = "mergeTest"; + + JSONObject value = new JSONObject(); + value.put("foo1", "bar1"); + value.put("foo2", createJSONArray("val1", "val2", 3)); + value.put("foo3", 1001); + value.put("foo4", createJSONObject("key1", "randomValueThatWillNeverBeUsed")); + + mStorage.multiSet(SimpleArray.of(getArray(mergeKey, value.toString())), mock(Callback.class)); + { + Callback callback = mock(Callback.class); + mStorage.multiGet(getArray(mergeKey), callback); + Mockito.verify(callback, Mockito.times(1)) + .invoke(null, SimpleArray.of(getArray(mergeKey, value.toString()))); + } + + value.put("foo1", 1001); + value.put("foo2", createJSONObject("key1", "val1")); + value.put("foo3", "bar1"); + value.put("foo4", createJSONArray("val1", "val2", 3)); + + JSONObject newValue = new JSONObject(); + newValue.put("foo2", createJSONObject("key2", "val2")); + + JSONObject newValue2 = new JSONObject(); + newValue2.put("foo2", createJSONObject("key1", "val3")); + + mStorage.multiMerge( + SimpleArray.of( + SimpleArray.of(mergeKey, value.toString()), + SimpleArray.of(mergeKey, newValue.toString()), + SimpleArray.of(mergeKey, newValue2.toString())), + mock(Callback.class)); + + value.put("foo2", createJSONObject("key1", "val3", "key2", "val2")); + Callback callback = mock(Callback.class); + mStorage.multiGet(getArray(mergeKey), callback); + Mockito.verify(callback, Mockito.times(1)) + .invoke(null, SimpleArray.of(getArray(mergeKey, value.toString()))); + } + + @Test + public void testGetAllKeys() { + final String[] keys = {"foo", "foo2"}; + final String[] values = {"bar", "bar2"}; + SimpleArray keyValues = new SimpleArray(); + keyValues.pushArray(getArray(keys[0], values[0])); + keyValues.pushArray(getArray(keys[1], values[1])); + mStorage.multiSet(keyValues, mock(Callback.class)); + + SimpleArray storedKeys = new SimpleArray(); + storedKeys.pushString(keys[0]); + storedKeys.pushString(keys[1]); + + Callback getAllCallback = mock(Callback.class); + mStorage.getAllKeys(getAllCallback); + Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, storedKeys); + + Callback getAllCallback2 = mock(Callback.class); + mStorage.multiRemove(getArray(keys[0]), mock(Callback.class)); + + mStorage.getAllKeys(getAllCallback2); + Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, getArray(keys[1])); + + mStorage.multiRemove(getArray(keys[1]), mock(Callback.class)); + Callback getAllCallback3 = mock(Callback.class); + mStorage.getAllKeys(getAllCallback3); + Mockito.verify(getAllCallback3, Mockito.times(1)).invoke(null, mEmptyArray); + } + + @Test + public void testClear() { + SimpleArray keyValues = new SimpleArray(); + keyValues.pushArray(getArray("foo", "foo2")); + keyValues.pushArray(getArray("bar", "bar2")); + mStorage.multiSet(keyValues, mock(Callback.class)); + + Callback clearCallback2 = mock(Callback.class); + mStorage.clear(clearCallback2); + Mockito.verify(clearCallback2, Mockito.times(1)).invoke(); + + Callback getAllCallback2 = mock(Callback.class); + mStorage.getAllKeys(getAllCallback2); + Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray); + } + + @Test + public void testHugeMultiGetMultiGet() { + // Test with many keys, so that it's above the 999 limit per batch imposed by SQLite. + final int keyCount = 1001; + // don't set keys that divide by this magical number, so that we can check that multiGet works, + // and returns null for missing keys + final int magicalNumber = 343; + + SimpleArray keyValues = new SimpleArray(); + for (int i = 0; i < keyCount; i++) { + if (i % magicalNumber > 0) { + keyValues.pushArray(getArray("key" + i, "value" + i)); + } + } + mStorage.multiSet(keyValues, mock(Callback.class)); + SimpleArray keys = new SimpleArray(); + for (int i = 0; i < keyCount; i++) { + keys.pushString("key" + i); + } + mStorage.multiGet( + keys, new Callback() { + @Override + public void invoke(Object... args) { + assertThat(args.length).isEqualTo(2); + SimpleArray resultArray = (SimpleArray) args[1]; + + assertThat(resultArray.size()).isEqualTo(keyCount); + boolean keyReceived[] = new boolean[keyCount]; + for (int i = 0; i < keyCount; i++) { + String key = resultArray.getArray(i).getString(0).substring(3); + int idx = Integer.parseInt(key); + assertThat(keyReceived[idx]).isFalse(); + keyReceived[idx] = true; + + if (idx % magicalNumber > 0) { + String value = resultArray.getArray(i).getString(1).substring(5); + assertThat(key).isEqualTo(value); + } else { + assertThat(resultArray.getArray(i).isNull(1)); + } + } + } + }); + + // Test removal in same test, since it's costly to set up the test again. + // Remove only odd keys + SimpleArray keyRemoves = new SimpleArray(); + for (int i = 0; i < keyCount; i++) { + if (i % 2 > 0) { + keyRemoves.pushString("key" + i); + } + } + mStorage.multiRemove(keyRemoves, mock(Callback.class)); + mStorage.getAllKeys( + new Callback() { + @Override + public void invoke(Object... args) { + SimpleArray resultArray = (SimpleArray) args[1]; + assertThat(resultArray.size()).isEqualTo(499); + for (int i = 0; i < resultArray.size(); i++) { + String key = resultArray.getString(i).substring(3); + int idx = Integer.parseInt(key); + assertThat(idx % 2).isEqualTo(0); + } + } + }); + } + + private static JSONArray createJSONArray(Object... objects) { + return new JSONArray(Arrays.asList(objects)); + } + + private static JSONObject createJSONObject(Object... keysAndValues) { + if (keysAndValues.length % 2 != 0) { + throw new IllegalArgumentException("You must provide the same number of keys and values"); + } + Map map = new HashMap(); + for (int i = 0; i < keysAndValues.length; i += 2) { + map.put(keysAndValues[i], keysAndValues[i + 1]); + } + return new JSONObject(map); + } + + private SimpleArray getArray(String... values) { + SimpleArray array = new SimpleArray(); + for (String value : values) { + array.pushString(value); + } + return array; + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java new file mode 100644 index 000000000000..0d14cdc43b89 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.timing; + +import android.view.Choreographer; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.SimpleArray; +import com.facebook.react.uimanager.ReactChoreographer; +import com.facebook.react.common.SystemClock; +import com.facebook.react.modules.core.JSTimersExecution; +import com.facebook.react.modules.core.Timing; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.mockito.Mockito.*; + +/** + * Tests for {@link Timing}. + */ +@PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@RunWith(RobolectricTestRunner.class) +public class TimingModuleTest { + + private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms + + private Timing mTiming; + private ReactChoreographer mChoreographerMock; + private PostFrameCallbackHandler mPostFrameCallbackHandler; + private long mCurrentTimeNs; + private JSTimersExecution mJSTimersMock; + + @Rule + public PowerMockRule rule = new PowerMockRule(); + + @Before + public void prepareModules() { + PowerMockito.mockStatic(Arguments.class); + when(Arguments.createArray()).thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return new SimpleArray(); + } + }); + + PowerMockito.mockStatic(SystemClock.class); + when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000); + when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs); + + mChoreographerMock = mock(ReactChoreographer.class); + PowerMockito.mockStatic(ReactChoreographer.class); + when(ReactChoreographer.getInstance()).thenReturn(mChoreographerMock); + + CatalystInstance catalystInstance = mock(CatalystInstance.class); + ReactApplicationContext reactContext = mock(ReactApplicationContext.class); + when(reactContext.getCatalystInstance()).thenReturn(catalystInstance); + + mCurrentTimeNs = 0; + mPostFrameCallbackHandler = new PostFrameCallbackHandler(); + + doAnswer(mPostFrameCallbackHandler) + .when(mChoreographerMock) + .postFrameCallback( + eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), + any(Choreographer.FrameCallback.class)); + + mTiming = new Timing(reactContext); + mJSTimersMock = mock(JSTimersExecution.class); + when(catalystInstance.getJSModule(JSTimersExecution.class)).thenReturn(mJSTimersMock); + mTiming.initialize(); + } + + private void stepChoreographerFrame() { + Choreographer.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback(); + mCurrentTimeNs += FRAME_TIME_NS; + if (callback != null) { + callback.doFrame(mCurrentTimeNs); + } + } + + @Test + public void testSimpleTimer() { + mTiming.onHostResume(); + mTiming.createTimer(1, 0, 0, false); + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(1)); + reset(mJSTimersMock); + stepChoreographerFrame(); + verifyNoMoreInteractions(mJSTimersMock); + } + + @Test + public void testSimpleRecurringTimer() { + mTiming.createTimer(100, 0, 0, true); + mTiming.onHostResume(); + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(100)); + + reset(mJSTimersMock); + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(100)); + } + + @Test + public void testCancelRecurringTimer() { + mTiming.onHostResume(); + mTiming.createTimer(105, 0, 0, true); + + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(105)); + + reset(mJSTimersMock); + mTiming.deleteTimer(105); + stepChoreographerFrame(); + verifyNoMoreInteractions(mJSTimersMock); + } + + @Test + public void testPausingAndResuming() { + mTiming.onHostResume(); + mTiming.createTimer(41, 0, 0, true); + + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(41)); + + reset(mJSTimersMock); + mTiming.onHostPause(); + stepChoreographerFrame(); + verifyNoMoreInteractions(mJSTimersMock); + + reset(mJSTimersMock); + mTiming.onHostResume(); + stepChoreographerFrame(); + verify(mJSTimersMock).callTimers(SimpleArray.of(41)); + } + + private static class PostFrameCallbackHandler implements Answer { + + private Choreographer.FrameCallback mFrameCallback; + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + mFrameCallback = (Choreographer.FrameCallback) args[1]; + return null; + } + + public Choreographer.FrameCallback getAndResetFrameCallback() { + Choreographer.FrameCallback callback = mFrameCallback; + mFrameCallback = null; + return callback; + } + } +} From bb11e05c33ce1910d776eb8dd16f65eb4ad2776e Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 26 Nov 2015 07:13:16 -0800 Subject: [PATCH 0127/1411] Default empty view to RCTView with position: absolute Reviewed By: spicyj, davidaurelio Differential Revision: D2693494 fb-gh-sync-id: fc00b995d7d4fdaaedf4c3e5382354f0a1f4ac23 --- .../ReactNative/ReactNativeDefaultInjection.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js index 3ee1675c31dd..0d55d670c40e 100644 --- a/Libraries/ReactNative/ReactNativeDefaultInjection.js +++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js @@ -23,6 +23,7 @@ var EventPluginUtils = require('EventPluginUtils'); var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder'); var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin'); var NodeHandle = require('NodeHandle'); +var ReactElement = require('ReactElement'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactEmptyComponent = require('ReactEmptyComponent'); @@ -37,7 +38,6 @@ var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); -var createReactNativeComponentClass = require('createReactNativeComponentClass'); var invariant = require('invariant'); // Just to ensure this gets packaged, since its only caller is from Native. @@ -81,13 +81,15 @@ function inject() { ReactNativeComponentEnvironment ); - // Can't import View here because it depends on React to make its composite - var RCTView = createReactNativeComponentClass({ - validAttributes: {}, - uiViewClassName: 'RCTView', - }); - // TODO #9121531: make this position: absolute; by default, to avoid interfering with flexbox - ReactEmptyComponent.injection.injectEmptyComponent(RCTView); + var EmptyComponent = () => { + // Can't import View at the top because it depends on React to make its composite + var View = require('View'); + return ReactElement.createElement(View, { + collapsable: true, + style: { position: 'absolute' } + }); + }; + ReactEmptyComponent.injection.injectEmptyComponent(EmptyComponent); EventPluginUtils.injection.injectMount(ReactNativeMount); From a636ddd9f07d8d3e6febb2118a7c7a4d225ad129 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 26 Nov 2015 08:36:02 -0800 Subject: [PATCH 0128/1411] Backout D2677289 [react_native] View recycling in JS Summary: public We're seeing related crashes. The diff has no tests, the perf tests weren't conclusive, and the person who'd be supporting it no longer is available to work on it. We can try this again later in a less rushed manner with proper perf testing. Reviewed By: davidaurelio Differential Revision: D2696615 fb-gh-sync-id: 3b6814ac12af19516146d5c42d2add8321b10db5 --- .../ReactNative/ReactNativeBaseComponent.js | 21 +- Libraries/ReactNative/ReactNativeMount.js | 2 - .../ReactNativeReconcileTransaction.js | 9 +- Libraries/ReactNative/ReactNativeViewPool.js | 264 ------------------ .../uimanager/NativeViewHierarchyManager.java | 15 +- .../NativeViewHierarchyOptimizer.java | 4 - .../react/uimanager/UIImplementation.java | 17 +- .../react/uimanager/UIManagerModule.java | 5 - .../uimanager/UIManagerModuleConstants.java | 8 - .../UIManagerModuleConstantsHelper.java | 2 - .../react/uimanager/UIViewOperationQueue.java | 23 -- .../facebook/react/uimanager/ViewProps.java | 2 +- 12 files changed, 27 insertions(+), 345 deletions(-) delete mode 100644 Libraries/ReactNative/ReactNativeViewPool.js diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index b187a39f0f97..cfd3df3e832d 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -16,7 +16,6 @@ var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactNativeViewPool = require('ReactNativeViewPool'); var ReactMultiChild = require('ReactMultiChild'); var RCTUIManager = require('NativeModules').UIManager; @@ -89,7 +88,6 @@ ReactNativeBaseComponent.Mixin = { unmountComponent: function() { deleteAllListeners(this._rootNodeID); this.unmountChildren(); - ReactNativeViewPool.release(this); this._rootNodeID = null; }, @@ -206,7 +204,24 @@ ReactNativeBaseComponent.Mixin = { mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; - var tag = ReactNativeViewPool.acquire(this); + var tag = ReactNativeTagHandles.allocateTag(); + + if (__DEV__) { + deepFreezeAndThrowOnMutationInDev(this._currentElement.props); + } + + var updatePayload = ReactNativeAttributePayload.create( + this._currentElement.props, + this.viewConfig.validAttributes + ); + + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + RCTUIManager.createView( + tag, + this.viewConfig.uiViewClassName, + nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, + updatePayload + ); this._registerListenersUponCreation(this._currentElement.props); this.initializeChildren( diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index aeef4cba77af..02dbcb40fc7e 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -15,7 +15,6 @@ var RCTUIManager = require('NativeModules').UIManager; var ReactElement = require('ReactElement'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactNativeViewPool = require('ReactNativeViewPool'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); @@ -217,7 +216,6 @@ var ReactNativeMount = { ReactNativeMount.unmountComponentAtNode(containerTag); // call back into native to remove all of the subviews from this container RCTUIManager.removeRootView(containerTag); - ReactNativeViewPool.clearPoolForRootView(containerTag); }, /** diff --git a/Libraries/ReactNative/ReactNativeReconcileTransaction.js b/Libraries/ReactNative/ReactNativeReconcileTransaction.js index aff41081d359..309630e3cc36 100644 --- a/Libraries/ReactNative/ReactNativeReconcileTransaction.js +++ b/Libraries/ReactNative/ReactNativeReconcileTransaction.js @@ -14,7 +14,6 @@ var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var Transaction = require('Transaction'); -var ReactNativeViewPool = require('ReactNativeViewPool'); /** * Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during @@ -36,18 +35,12 @@ var ON_DOM_READY_QUEUEING = { } }; -var RN_VIEW_POOL_WRAPPER = { - close: function() { - ReactNativeViewPool.onReconcileTransactionClose(); - }, -}; - /** * Executed within the scope of the `Transaction` instance. Consider these as * being member methods, but with an implied ordering while being isolated from * each other. */ -var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING, RN_VIEW_POOL_WRAPPER]; +var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING]; /** * Currently: diff --git a/Libraries/ReactNative/ReactNativeViewPool.js b/Libraries/ReactNative/ReactNativeViewPool.js deleted file mode 100644 index dd0a596165a0..000000000000 --- a/Libraries/ReactNative/ReactNativeViewPool.js +++ /dev/null @@ -1,264 +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 ReactNativeViewPool - */ -'use strict'; - -var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); -var RCTUIManager = require('NativeModules').UIManager; -var Platform = require('Platform'); - -var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -var emptyFunction = require('emptyFunction'); -var flattenStyle = require('flattenStyle'); - -var EMPTY_POOL = [[]]; - -var ENABLED = !!RCTUIManager.dropViews; - -/* indicies used for _addToPool arrays */ -var TAGS_IDX = 0; -var KEYS_IDX = 1; -var PROPS_IDX = 2; - -var _pools = {}; -var _poolSize = {}; - -var layoutOnlyProps = RCTUIManager.layoutOnlyProps; - -function isCollapsableForStyle(style) { - var flatStyle = flattenStyle(style); - for (var styleKey in flatStyle) { - if (layoutOnlyProps[styleKey] !== true) { - return false; - } - } - return true; -} - -function isCollapsable(viewRef) { - var props = viewRef._currentElement.props; - if (props.collapsable !== undefined && !props.collapsable) { - return false; - } - var validAttributes = viewRef.viewConfig.validAttributes; - for (var propKey in props) { - if (!!validAttributes[propKey] && propKey !== 'style' && propKey !== 'collapsable') { - return false; - } - } - return !props.style || isCollapsableForStyle(viewRef._currentElement.props.style); -} - -function enqueueCreate(viewRef, rootTag) { - var tag = ReactNativeTagHandles.allocateTag(); - - if (__DEV__) { - deepFreezeAndThrowOnMutationInDev(viewRef._currentElement.props); - } - - var updatePayload = ReactNativeAttributePayload.create( - viewRef._currentElement.props, - viewRef.viewConfig.validAttributes - ); - - RCTUIManager.createView( - tag, - viewRef.viewConfig.uiViewClassName, - rootTag, - updatePayload - ); - - return tag; -} - -function getViewTag(viewRef) { - return ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(viewRef._rootNodeID); -} - -function getViewProps(viewRef) { - return viewRef._currentElement.props; -} - -function getViewValidAttributes(viewRef) { - return viewRef.viewConfig.validAttributes; -} - -function getRootViewTag(viewRef) { - var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(viewRef._rootNodeID); - return ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID]; -} - -function poolKey(viewRef) { - var viewClass = viewRef.viewConfig.uiViewClassName; - if (Platform.OS === 'android' && viewClass === 'RCTView') { - return isCollapsable(viewRef) ? 'CollapsedRCTView' : 'RCTView'; - } - return viewClass; -} - -class ReactNativeViewPool { - constructor() { - this._pool = {}; - this._poolQueue = {}; - this._addToPool = [[],[],[]]; - this._viewsToDelete = []; - if (__DEV__) { - this._recycleStats = {}; - this._deleteStats = {}; - } - } - - onReconcileTransactionClose() { - // flush all deletes, move object from pool_queue to the actual pool - if (this._viewsToDelete.length > 0) { - RCTUIManager.dropViews(this._viewsToDelete); - } - var addToPoolTags = this._addToPool[TAGS_IDX]; - var addToPoolKeys = this._addToPool[KEYS_IDX]; - var addToPoolProps = this._addToPool[PROPS_IDX]; - for (var i = addToPoolTags.length - 1; i >= 0; i--) { - var nativeTag = addToPoolTags[i]; - var key = addToPoolKeys[i]; - var props = addToPoolProps[i]; - var views = this._pool[key] || [[],[]]; - views[0].push(nativeTag); - views[1].push(props); - this._pool[key] = views; - } - this._viewsToDelete = []; - this._addToPool = [[],[],[]]; - this._poolQueue = {}; - } - - acquire(viewRef, rootTag) { - var key = poolKey(viewRef); - if ((this._pool[key] || EMPTY_POOL)[0].length) { - var views = this._pool[key]; - var nativeTag = views[0].pop(); - var oldProps = views[1].pop(); - var updatePayload = ReactNativeAttributePayload.diff( - oldProps, - getViewProps(viewRef), - getViewValidAttributes(viewRef) - ); - if (__DEV__) { - this._recycleStats[key] = (this._recycleStats[key] || 0) + 1; - } - - if (updatePayload) { - RCTUIManager.updateView( - nativeTag, - viewRef.viewConfig.uiViewClassName, - updatePayload - ); - } - return nativeTag; - } else { - // If there is no view available for the given pool key, we just enqueue call to create one - return enqueueCreate(viewRef, rootTag); - } - } - - release(viewRef) { - var key = poolKey(viewRef); - var nativeTag = getViewTag(viewRef); - var pooledCount = (this._pool[key] || EMPTY_POOL)[0].length + (this._poolQueue[key] || 0); - if (pooledCount < (_poolSize[key] || 0)) { - // we have room in the pool for this view - // we can add it to the queue so that it will be added to the actual pull in - // onReconcileTransactionClose - this._addToPool[TAGS_IDX].push(nativeTag); - this._addToPool[KEYS_IDX].push(key); - this._addToPool[PROPS_IDX].push(getViewProps(viewRef)); - this._poolQueue[key] = (this._poolQueue[key] || 0) + 1; - } else { - if (__DEV__) { - if (_poolSize[key]) { - this._deleteStats[key] = (this._deleteStats[key] || 0) + 1; - } - } - this._viewsToDelete.push(nativeTag); - } - } - - clear() { - for (var key in this._pool) { - var poolTags = this._pool[key][0]; - for (var i = poolTags.length - 1; i >= 0; i--) { - this._viewsToDelete.push(poolTags[i]); - } - } - var addToPoolTags = this._addToPool[0]; - for (var i = addToPoolTags.length - 1; i >= 0; i--) { - this._viewsToDelete.push(addToPoolTags[i]); - } - this._addToPool = [[],[],[]]; - this.onReconcileTransactionClose(); - } - - printStats() { - if (__DEV__) { - console.log('Stats', this._recycleStats, this._deleteStats); - } - } -} - -module.exports = { - - onReconcileTransactionClose: function() { - if (ENABLED) { - for (var pool in _pools) { - _pools[pool].onReconcileTransactionClose(); - } - } - }, - - acquire: function(viewRef) { - var rootTag = getRootViewTag(viewRef); - if (ENABLED) { - var pool = _pools[rootTag]; - if (!pool) { - pool = _pools[rootTag] = new ReactNativeViewPool(); - } - return pool.acquire(viewRef, rootTag); - } else { - return enqueueCreate(viewRef, rootTag); - } - }, - - release: ENABLED ? function(viewRef) { - var pool = _pools[getRootViewTag(viewRef)]; - if (pool) { - pool.release(viewRef); - } - } : emptyFunction, - - clearPoolForRootView: ENABLED ? function(rootID) { - var pool = _pools[rootID]; - if (pool) { - pool.clear(); - delete _pools[rootID]; - } - } : emptyFunction, - - configure: function(pool_size) { - _poolSize = pool_size; - }, - - printStats: function() { - if (__DEV__) { - console.log('Pool size', _poolSize); - for (var pool in _pools) { - _pools[pool].onReconcileTransactionClose(); - } - } - }, -}; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index cf3c5645587e..fddd7047820f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -351,7 +351,7 @@ public void manageChildren( viewsToAdd, tagsToDelete)); } - detachView(viewToDestroy); + dropView(viewToDestroy); } } } @@ -380,15 +380,10 @@ public void addRootView( view.setId(tag); } - public void dropView(int tag) { - mTagsToViews.remove(tag); - mTagsToViewManagers.remove(tag); - } - /** * Releases all references to given native View. */ - private void detachView(View view) { + private void dropView(View view) { UiThreadUtil.assertOnUiThread(); if (!mRootTags.get(view.getId())) { // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} @@ -403,11 +398,13 @@ private void detachView(View view) { for (int i = viewGroupManager.getChildCount(viewGroup) - 1; i >= 0; i--) { View child = viewGroupManager.getChildAt(viewGroup, i); if (mTagsToViews.get(child.getId()) != null) { - detachView(child); + dropView(child); } } viewGroupManager.removeAllViews(viewGroup); } + mTagsToViews.remove(view.getId()); + mTagsToViewManagers.remove(view.getId()); } public void removeRootView(int rootViewTag) { @@ -417,7 +414,7 @@ public void removeRootView(int rootViewTag) { "View with tag " + rootViewTag + " is not registered as a root view"); } View rootView = mTagsToViews.get(rootViewTag); - detachView(rootView); + dropView(rootView); mRootTags.delete(rootViewTag); mRootViewsContext.remove(rootViewTag); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java index e01fab28dcce..fd3946eeb7c9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java @@ -90,10 +90,6 @@ public void handleCreateView( } } - public void handleDropViews(int[] viewTagsToDrop, int length) { - mUIViewOperationQueue.enqueueDropViews(viewTagsToDrop, length); - } - /** * Handles native children cleanup when css node is removed from hierarchy */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index b9eb9a64ffe6..1718fe2bbab1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -124,22 +124,6 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p } } - public void dropViews(ReadableArray viewTags) { - int size = viewTags.size(), realViewsCount = 0; - int realViewTags[] = new int[size]; - for (int i = 0; i < size; i++) { - int tag = viewTags.getInt(i); - ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); - if (!cssNode.isVirtual()) { - realViewTags[realViewsCount++] = tag; - } - mShadowNodeRegistry.removeNode(tag); - } - if (realViewsCount > 0) { - mNativeViewHierarchyOptimizer.handleDropViews(realViewTags, realViewsCount); - } - } - /** * Invoked by React to create a new node with a given tag has its properties changed. */ @@ -509,6 +493,7 @@ public void setViewHierarchyUpdateDebugListener( private void removeShadowNode(ReactShadowNode nodeToRemove) { mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); + mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { removeShadowNode(nodeToRemove.getChildAt(i)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index ff473aaf683f..04724229bed3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -202,11 +202,6 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p mUIImplementation.createView(tag, className, rootViewTag, props); } - @ReactMethod - public void dropViews(ReadableArray viewTags) { - mUIImplementation.dropViews(viewTags); - } - @ReactMethod public void updateView(int tag, String className, ReadableMap props) { mUIImplementation.updateView(tag, className, props); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java index eab1c3489170..3287ebf7854c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java @@ -154,12 +154,4 @@ public static Map getConstants(DisplayMetrics displayMetrics) { return constants; } - - public static Map getLayoutOnlyPropsConstants() { - HashMap constants = new HashMap<>(); - for (String propName : ViewProps.LAYOUT_ONLY_PROPS) { - constants.put(propName, Boolean.TRUE); - } - return constants; - } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java index d5b566529729..bce7c37361b2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java @@ -25,7 +25,6 @@ private static final String CUSTOM_BUBBLING_EVENT_TYPES_KEY = "customBubblingEventTypes"; private static final String CUSTOM_DIRECT_EVENT_TYPES_KEY = "customDirectEventTypes"; - private static final String LAYOUT_ONLY_PROPS = "layoutOnlyProps"; /** * Generates map of constants that is then exposed by {@link UIManagerModule}. The constants map @@ -76,7 +75,6 @@ constants.put(CUSTOM_BUBBLING_EVENT_TYPES_KEY, bubblingEventTypesConstants); constants.put(CUSTOM_DIRECT_EVENT_TYPES_KEY, directEventTypesConstants); - constants.put(LAYOUT_ONLY_PROPS, UIManagerModuleConstants.getLayoutOnlyPropsConstants()); return constants; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 21f1128d8ebe..ed507fd3ec63 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -147,25 +147,6 @@ public void execute() { } } - private final class DropViewsOperation extends ViewOperation { - - private final int[] mViewTagsToDrop; - private final int mArrayLength; - - public DropViewsOperation(int[] viewTagsToDrop, int length) { - super(-1); - mViewTagsToDrop = viewTagsToDrop; - mArrayLength = length; - } - - @Override - public void execute() { - for (int i = 0; i < mArrayLength; i++) { - mNativeViewHierarchyManager.dropView(mViewTagsToDrop[i]); - } - } - } - private final class ManageChildrenOperation extends ViewOperation { private final @Nullable int[] mIndicesToRemove; @@ -556,10 +537,6 @@ public void enqueueCreateView( initialProps)); } - public void enqueueDropViews(int[] viewTagsToDrop, int length) { - mOperations.add(new DropViewsOperation(viewTagsToDrop, length)); - } - public void enqueueUpdateProperties(int reactTag, String className, CatalystStylesDiffMap props) { mOperations.add(new UpdatePropertiesOperation(reactTag, props)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index d19ea165e904..7e87a8419d85 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -84,7 +84,7 @@ public class ViewProps { Spacing.BOTTOM }; - /*package*/ static final HashSet LAYOUT_ONLY_PROPS = new HashSet<>( + private static final HashSet LAYOUT_ONLY_PROPS = new HashSet<>( Arrays.asList( ALIGN_SELF, ALIGN_ITEMS, From 8911b72d9e78d78b6f8eb99efe21b1205047fd16 Mon Sep 17 00:00:00 2001 From: Tim Park Date: Thu, 26 Nov 2015 07:09:59 -0800 Subject: [PATCH 0129/1411] Add Polyline support to MapView Summary: Per issue #1925, add support for Polyline to MapView. Briefly, if you have a MapView declared as: then setting this.state.overlays = [{ coordinates: [ { latitude: 35.5, longitude: -5.5 }, { latitude: 35.6, longitude: -5.6 }, ... ], strokeColor: 'rgba(255, 0, 0, 0.5)', lineWidth: 3, }]; will draw a red line between the points in locations with a width of 3 and equally blended with the background. Closes https://github.com/facebook/react-native/pull/4153 Reviewed By: svcscm Differential Revision: D2697347 Pulled By: nicklockwood fb-gh-sync-id: a436e4ed8d4e43f2872b39b4694fad7c02de8fe5 --- Examples/UIExplorer/MapViewExample.js | 45 +++++++++++ Libraries/Components/MapView/MapView.js | 72 +++++++++++++++-- React/React.xcodeproj/project.pbxproj | 24 ++++-- React/Views/RCTConvert+MapKit.h | 17 ++-- React/Views/RCTConvert+MapKit.m | 68 ++++++++-------- React/Views/RCTMap.h | 12 +-- React/Views/RCTMap.m | 77 ++++++++++++++----- ...CTPointAnnotation.h => RCTMapAnnotation.h} | 2 +- ...CTPointAnnotation.m => RCTMapAnnotation.m} | 4 +- React/Views/RCTMapManager.m | 28 +++++-- React/Views/RCTMapOverlay.h | 18 +++++ React/Views/RCTMapOverlay.m | 14 ++++ 12 files changed, 296 insertions(+), 85 deletions(-) rename React/Views/{RCTPointAnnotation.h => RCTMapAnnotation.h} (90%) rename React/Views/{RCTPointAnnotation.m => RCTMapAnnotation.m} (82%) create mode 100644 React/Views/RCTMapOverlay.h create mode 100644 React/Views/RCTMapOverlay.m diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 9a7e2e1aab23..863187edab61 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -313,6 +313,45 @@ var CustomPinImageMapViewExample = React.createClass({ }); +var CustomOverlayMapViewExample = React.createClass({ + + getInitialState() { + return { + isFirstLoad: true, + }; + }, + + render() { + if (this.state.isFirstLoad) { + var onRegionChangeComplete = (region) => { + this.setState({ + isFirstLoad: false, + overlays: [{ + coordinates:[ + {latitude: 32.47, longitude: -107.85}, + {latitude: 45.13, longitude: -94.48}, + {latitude: 39.27, longitude: -83.25}, + {latitude: 32.47, longitude: -107.85}, + ], + strokeColor: '#f007', + lineWidth: 3, + }], + }); + }; + } + + return ( + + ); + }, + +}); + var styles = StyleSheet.create({ map: { height: 150, @@ -373,4 +412,10 @@ exports.examples = [ return ; } }, + { + title: 'Custom overlay', + render() { + return ; + } + }, ]; diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index b0468e1877c3..9324a2aeca43 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -45,7 +45,6 @@ var MapView = React.createClass({ // TODO: add a base64 (or similar) encoder here annotation.id = encodeURIComponent(JSON.stringify(annotation)); } - return annotation; }); @@ -54,16 +53,37 @@ var MapView = React.createClass({ }); }, + checkOverlayIds: function (overlays: Array) { + + var newOverlays = overlays.map(function (overlay) { + if (!overlay.id) { + // TODO: add a base64 (or similar) encoder here + overlay.id = encodeURIComponent(JSON.stringify(overlay)); + } + return overlay; + }); + + this.setState({ + overlays: newOverlays + }); + }, + componentWillMount: function() { if (this.props.annotations) { this.checkAnnotationIds(this.props.annotations); } + if (this.props.overlays) { + this.checkOverlayIds(this.props.overlays); + } }, componentWillReceiveProps: function(nextProps: Object) { if (nextProps.annotations) { this.checkAnnotationIds(nextProps.annotations); } + if (nextProps.overlays) { + this.checkOverlayIds(nextProps.overlays); + } }, propTypes: { @@ -195,11 +215,6 @@ var MapView = React.createClass({ onLeftCalloutPress: React.PropTypes.func, onRightCalloutPress: React.PropTypes.func, - /** - * annotation id - */ - id: React.PropTypes.string, - /** * The pin color. This can be any valid color string, or you can use one * of the predefined PinColors constants. Applies to both standard pins @@ -213,7 +228,36 @@ var MapView = React.createClass({ * @platform ios */ image: Image.propTypes.source, + + /** + * annotation id + */ + id: React.PropTypes.string, + })), + + /** + * Map overlays + */ + overlays: React.PropTypes.arrayOf(React.PropTypes.shape({ + /** + * Polyline coordinates + */ + coordinates: React.PropTypes.arrayOf(React.PropTypes.shape({ + latitude: React.PropTypes.number.isRequired, + longitude: React.PropTypes.number.isRequired + })), + /** + * Line attributes + */ + lineWidth: React.PropTypes.number, + strokeColor: React.PropTypes.string, + fillColor: React.PropTypes.string, + + /** + * Overlay id + */ + id: React.PropTypes.string })), /** @@ -255,7 +299,7 @@ var MapView = React.createClass({ render: function() { - let {annotations} = this.props; + let {annotations, overlays} = this.props; annotations = annotations && annotations.map((annotation: Object) => { let {tintColor, image} = annotation; return { @@ -264,10 +308,21 @@ var MapView = React.createClass({ image: image && resolveAssetSource(image), }; }); + overlays = overlays && overlays.map((overlay: Object) => { + let {strokeColor, fillColor} = overlay; + return { + ...overlay, + strokeColor: strokeColor && processColor(strokeColor), + fillColor: fillColor && processColor(fillColor), + }; + }); // TODO: these should be separate events, to reduce bridge traffic - if (annotations || this.props.onAnnotationPress) { + if (annotations) { var onPress = (event: Event) => { + if (!annotations) { + return; + } if (event.nativeEvent.action === 'annotation-click') { this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation); @@ -308,6 +363,7 @@ var MapView = React.createClass({ diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 4250901f6893..92b4cf7f6a8d 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; 13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; }; 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; }; + 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.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 */; }; @@ -38,7 +39,7 @@ 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; }; 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; }; - 13B202041BFB948C00C07393 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTPointAnnotation.m */; }; + 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; }; 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; }; @@ -144,6 +145,10 @@ 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 = ""; }; + 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapOverlay.h; sourceTree = ""; }; + 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapOverlay.m; sourceTree = ""; }; + 13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeMethod.h; sourceTree = ""; }; + 13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewDelegate.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 = ""; }; @@ -170,8 +175,8 @@ 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; }; 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; }; 13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = ""; }; - 13B202021BFB948C00C07393 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = ""; }; - 13B202031BFB948C00C07393 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = ""; }; + 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; }; + 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; }; 13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = ""; }; 13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = ""; }; 13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = ""; }; @@ -342,11 +347,11 @@ 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */, 13C325281AA63B6A0048765F /* RCTComponent.h */, 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */, + 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */, 13AB90C01B6FA36700713B4F /* RCTComponentData.m */, 13EF7F441BC69646003F47DD /* RCTImageComponent.h */, 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */, 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */, - 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */, 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */, 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */, 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */, @@ -354,6 +359,10 @@ 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */, 14435CE11AAC4AE100FC20F4 /* RCTMap.h */, 14435CE21AAC4AE100FC20F4 /* RCTMap.m */, + 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */, + 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */, + 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */, + 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */, 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */, 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */, 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */, @@ -362,8 +371,6 @@ 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */, 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */, 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */, - 13B202021BFB948C00C07393 /* RCTPointAnnotation.h */, - 13B202031BFB948C00C07393 /* RCTPointAnnotation.m */, 13B0800C1A69489C00A75B9A /* RCTNavigator.h */, 13B0800D1A69489C00A75B9A /* RCTNavigator.m */, 13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */, @@ -484,6 +491,7 @@ 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */, 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */, 830213F31A654E0800B993E6 /* RCTBridgeModule.h */, + 13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */, 83CBBACA1A6023D300E9B192 /* RCTConvert.h */, 83CBBACB1A6023D300E9B192 /* RCTConvert.m */, 13AF1F851AE6E777005F5298 /* RCTDefines.h */, @@ -509,6 +517,7 @@ 142014171B32094000CC17BA /* RCTPerformanceLogger.m */, 830A229C1A66C68A008503DA /* RCTRootView.h */, 830A229D1A66C68A008503DA /* RCTRootView.m */, + 13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */, 83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */, 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */, 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */, @@ -619,7 +628,7 @@ 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */, 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, - 13B202041BFB948C00C07393 /* RCTPointAnnotation.m in Sources */, + 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */, 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */, 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */, 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */, @@ -658,6 +667,7 @@ 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */, + 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */, 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */, 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */, 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */, diff --git a/React/Views/RCTConvert+MapKit.h b/React/Views/RCTConvert+MapKit.h index d3e7fbc153f3..42add42d9ede 100644 --- a/React/Views/RCTConvert+MapKit.h +++ b/React/Views/RCTConvert+MapKit.h @@ -9,21 +9,24 @@ #import -#import "RCTPointAnnotation.h" #import "RCTConvert.h" +@class RCTMapAnnotation; +@class RCTMapOverlay; + @interface RCTConvert (MapKit) + (MKCoordinateSpan)MKCoordinateSpan:(id)json; + (MKCoordinateRegion)MKCoordinateRegion:(id)json; -+ (MKShape *)MKShape:(id)json; + (MKMapType)MKMapType:(id)json; -+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json; -typedef NSArray MKShapeArray; -+ (MKShapeArray *)MKShapeArray:(id)json; ++ (RCTMapAnnotation *)RCTMapAnnotation:(id)json; ++ (RCTMapOverlay *)RCTMapOverlay:(id)json; + +typedef NSArray RCTMapAnnotationArray; ++ (NSArray *)RCTMapAnnotationArray:(id)json; -typedef NSArray RCTPointAnnotationArray; -+ (RCTPointAnnotationArray *)RCTPointAnnotationArray:(id)json; +typedef NSArray RCTMapOverlayArray; ++ (NSArray *)RCTMapOverlayArray:(id)json; @end diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index dab5b7661719..78729fe04568 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -9,7 +9,8 @@ #import "RCTConvert+MapKit.h" #import "RCTConvert+CoreLocation.h" -#import "RCTPointAnnotation.h" +#import "RCTMapAnnotation.h" +#import "RCTMapOverlay.h" @implementation RCTConvert(MapKit) @@ -30,45 +31,52 @@ + (MKCoordinateRegion)MKCoordinateRegion:(id)json }; } -+ (MKShape *)MKShape:(id)json -{ - json = [self NSDictionary:json]; - - // TODO: more shape types - MKShape *shape = [MKPointAnnotation new]; - shape.coordinate = [self CLLocationCoordinate2D:json]; - shape.title = [RCTConvert NSString:json[@"title"]]; - shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; - return shape; -} - -RCT_ARRAY_CONVERTER(MKShape) - RCT_ENUM_CONVERTER(MKMapType, (@{ @"standard": @(MKMapTypeStandard), @"satellite": @(MKMapTypeSatellite), @"hybrid": @(MKMapTypeHybrid), }), MKMapTypeStandard, integerValue) -+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json ++ (RCTMapAnnotation *)RCTMapAnnotation:(id)json +{ + json = [self NSDictionary:json]; + RCTMapAnnotation *annotation = [RCTMapAnnotation new]; + annotation.coordinate = [self CLLocationCoordinate2D:json]; + annotation.title = [RCTConvert NSString:json[@"title"]]; + annotation.subtitle = [RCTConvert NSString:json[@"subtitle"]]; + annotation.identifier = [RCTConvert NSString:json[@"id"]]; + annotation.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; + annotation.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; + annotation.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; + annotation.tintColor = [RCTConvert UIColor:json[@"tintColor"]]; + annotation.image = [RCTConvert UIImage:json[@"image"]]; + if (annotation.tintColor && annotation.image) { + annotation.image = [annotation.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + return annotation; +} + +RCT_ARRAY_CONVERTER(RCTMapAnnotation) + ++ (RCTMapOverlay *)RCTMapOverlay:(id)json { json = [self NSDictionary:json]; - RCTPointAnnotation *shape = [RCTPointAnnotation new]; - shape.coordinate = [self CLLocationCoordinate2D:json]; - shape.title = [RCTConvert NSString:json[@"title"]]; - shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; - shape.identifier = [RCTConvert NSString:json[@"id"]]; - shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; - shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; - shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; - shape.tintColor = [RCTConvert UIColor:json[@"tintColor"]]; - shape.image = [RCTConvert UIImage:json[@"image"]]; - if (shape.tintColor && shape.image) { - shape.image = [shape.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + NSArray *locations = [RCTConvert NSDictionaryArray:json[@"coordinates"]]; + CLLocationCoordinate2D coordinates[locations.count]; + NSUInteger index = 0; + for (NSDictionary *location in locations) { + coordinates[index++] = [RCTConvert CLLocationCoordinate2D:location]; } - return shape; + + RCTMapOverlay *overlay = [RCTMapOverlay polylineWithCoordinates:coordinates + count:locations.count]; + + overlay.strokeColor = [RCTConvert UIColor:json[@"strokeColor"]]; + overlay.identifier = [RCTConvert NSString:json[@"id"]]; + overlay.lineWidth = [RCTConvert CGFloat:json[@"lineWidth"] ?: @1]; + return overlay; } -RCT_ARRAY_CONVERTER(RCTPointAnnotation) +RCT_ARRAY_CONVERTER(RCTMapOverlay) @end diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h index c9025d6d346a..a83bb93dad91 100644 --- a/React/Views/RCTMap.h +++ b/React/Views/RCTMap.h @@ -13,9 +13,9 @@ #import "RCTConvert+MapKit.h" #import "RCTComponent.h" -extern const CLLocationDegrees RCTMapDefaultSpan; -extern const NSTimeInterval RCTMapRegionChangeObserveInterval; -extern const CGFloat RCTMapZoomBoundBuffer; +RCT_EXTERN const CLLocationDegrees RCTMapDefaultSpan; +RCT_EXTERN const NSTimeInterval RCTMapRegionChangeObserveInterval; +RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer; @interface RCTMap: MKMapView @@ -25,11 +25,13 @@ extern const CGFloat RCTMapZoomBoundBuffer; @property (nonatomic, assign) CGFloat maxDelta; @property (nonatomic, assign) UIEdgeInsets legalLabelInsets; @property (nonatomic, strong) NSTimer *regionChangeObserveTimer; -@property (nonatomic, copy) NSArray *annotationIds; +@property (nonatomic, copy) NSArray *annotationIDs; +@property (nonatomic, copy) NSArray *overlayIDs; @property (nonatomic, copy) RCTBubblingEventBlock onChange; @property (nonatomic, copy) RCTBubblingEventBlock onPress; -- (void)setAnnotations:(RCTPointAnnotationArray *)annotations; +- (void)setAnnotations:(RCTMapAnnotationArray *)annotations; +- (void)setOverlays:(RCTMapOverlayArray *)overlays; @end diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m index 238c7a4d3270..4f542c4c4a30 100644 --- a/React/Views/RCTMap.m +++ b/React/Views/RCTMap.m @@ -11,6 +11,8 @@ #import "RCTEventDispatcher.h" #import "RCTLog.h" +#import "RCTMapAnnotation.h" +#import "RCTMapOverlay.h" #import "RCTUtils.h" const CLLocationDegrees RCTMapDefaultSpan = 0.005; @@ -107,32 +109,34 @@ - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated [super setRegion:region animated:animated]; } -- (void)setAnnotations:(RCTPointAnnotationArray *)annotations +// TODO: this doesn't preserve order. Should it? If so we should change the +// algorithm. If not, it would be more efficient to use an NSSet +- (void)setAnnotations:(RCTMapAnnotationArray *)annotations { - NSMutableArray *newAnnotationIds = [NSMutableArray new]; - NSMutableArray *annotationsToDelete = [NSMutableArray new]; - NSMutableArray *annotationsToAdd = [NSMutableArray new]; + NSMutableArray *newAnnotationIDs = [NSMutableArray new]; + NSMutableArray *annotationsToDelete = [NSMutableArray new]; + NSMutableArray *annotationsToAdd = [NSMutableArray new]; - for (RCTPointAnnotation *annotation in annotations) { - if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { + for (RCTMapAnnotation *annotation in annotations) { + if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { continue; } - [newAnnotationIds addObject:annotation.identifier]; + [newAnnotationIDs addObject:annotation.identifier]; - // If the current set does not contain the new annotation, mark it as add - if (![self.annotationIds containsObject:annotation.identifier]) { + // If the current set does not contain the new annotation, mark it to add + if (![_annotationIDs containsObject:annotation.identifier]) { [annotationsToAdd addObject:annotation]; } } - for (RCTPointAnnotation *annotation in self.annotations) { - if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { + for (RCTMapAnnotation *annotation in self.annotations) { + if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { continue; } - // If the new set does not contain an existing annotation, mark it as delete - if (![newAnnotationIds containsObject:annotation.identifier]) { + // If the new set does not contain an existing annotation, mark it to delete + if (![newAnnotationIDs containsObject:annotation.identifier]) { [annotationsToDelete addObject:annotation]; } } @@ -145,14 +149,51 @@ - (void)setAnnotations:(RCTPointAnnotationArray *)annotations [self addAnnotations:(NSArray> *)annotationsToAdd]; } - NSMutableArray *newIds = [NSMutableArray new]; - for (RCTPointAnnotation *annotation in self.annotations) { - if ([annotation isKindOfClass:[MKUserLocation class]]) { + self.annotationIDs = newAnnotationIDs; +} + +// TODO: this doesn't preserve order. Should it? If so we should change the +// algorithm. If not, it would be more efficient to use an NSSet +- (void)setOverlays:(RCTMapOverlayArray *)overlays +{ + NSMutableArray *newOverlayIDs = [NSMutableArray new]; + NSMutableArray *overlaysToDelete = [NSMutableArray new]; + NSMutableArray *overlaysToAdd = [NSMutableArray new]; + + for (RCTMapOverlay *overlay in overlays) { + if (![overlay isKindOfClass:[RCTMapOverlay class]]) { + continue; + } + + [newOverlayIDs addObject:overlay.identifier]; + + // If the current set does not contain the new annotation, mark it to add + if (![_annotationIDs containsObject:overlay.identifier]) { + [overlaysToAdd addObject:overlay]; + } + } + + for (RCTMapOverlay *overlay in self.overlays) { + if (![overlay isKindOfClass:[RCTMapOverlay class]]) { continue; } - [newIds addObject:annotation.identifier]; + + // If the new set does not contain an existing annotation, mark it to delete + if (![newOverlayIDs containsObject:overlay.identifier]) { + [overlaysToDelete addObject:overlay]; + } } - self.annotationIds = newIds; + + if (overlaysToDelete.count) { + [self removeOverlays:(NSArray> *)overlaysToDelete]; + } + + if (overlaysToAdd.count) { + [self addOverlays:(NSArray> *)overlaysToAdd + level:MKOverlayLevelAboveRoads]; + } + + self.overlayIDs = newOverlayIDs; } @end diff --git a/React/Views/RCTPointAnnotation.h b/React/Views/RCTMapAnnotation.h similarity index 90% rename from React/Views/RCTPointAnnotation.h rename to React/Views/RCTMapAnnotation.h index 1ee8fb05508a..0f4479c484bd 100644 --- a/React/Views/RCTPointAnnotation.h +++ b/React/Views/RCTMapAnnotation.h @@ -9,7 +9,7 @@ #import -@interface RCTPointAnnotation : MKPointAnnotation +@interface RCTMapAnnotation : MKPointAnnotation @property (nonatomic, copy) NSString *identifier; @property (nonatomic, assign) BOOL hasLeftCallout; diff --git a/React/Views/RCTPointAnnotation.m b/React/Views/RCTMapAnnotation.m similarity index 82% rename from React/Views/RCTPointAnnotation.m rename to React/Views/RCTMapAnnotation.m index aaaf2d7e20a2..0b90fe8ab1e8 100644 --- a/React/Views/RCTPointAnnotation.m +++ b/React/Views/RCTMapAnnotation.m @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTPointAnnotation.h" +#import "RCTMapAnnotation.h" -@implementation RCTPointAnnotation +@implementation RCTMapAnnotation @end diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 75602578d334..6c22c6b16c4a 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -16,7 +16,8 @@ #import "RCTMap.h" #import "RCTUtils.h" #import "UIView+React.h" -#import "RCTPointAnnotation.h" +#import "RCTMapAnnotation.h" +#import "RCTMapOverlay.h" #import @@ -48,7 +49,8 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat) RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) -RCT_EXPORT_VIEW_PROPERTY(annotations, RCTPointAnnotationArray) +RCT_EXPORT_VIEW_PROPERTY(annotations, RCTMapAnnotationArray) +RCT_EXPORT_VIEW_PROPERTY(overlays, RCTMapOverlayArray) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) @@ -71,9 +73,9 @@ - (UIView *)view - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view { - if (mapView.onPress && [view.annotation isKindOfClass:[RCTPointAnnotation class]]) { + if (mapView.onPress && [view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation; + RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; mapView.onPress(@{ @"action": @"annotation-click", @"annotation": @{ @@ -87,9 +89,9 @@ - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)vi } } -- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation +- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTMapAnnotation *)annotation { - if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { + if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { return nil; } @@ -142,12 +144,24 @@ - (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(R return annotationView; } +- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView rendererForOverlay:(RCTMapOverlay *)overlay +{ + if ([overlay isKindOfClass:[RCTMapOverlay class]]) { + MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay]; + polylineRenderer.strokeColor = overlay.strokeColor; + polylineRenderer.lineWidth = overlay.lineWidth; + return polylineRenderer; + } else { + return nil; + } +} + - (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { if (mapView.onPress) { // Pass to js - RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation; + RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; mapView.onPress(@{ @"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right", @"action": @"callout-click", diff --git a/React/Views/RCTMapOverlay.h b/React/Views/RCTMapOverlay.h new file mode 100644 index 000000000000..a6fcdad5e452 --- /dev/null +++ b/React/Views/RCTMapOverlay.h @@ -0,0 +1,18 @@ +/** + * 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 RCTMapOverlay : MKPolyline + +@property (nonatomic, copy) NSString *identifier; +@property (nonatomic, strong) UIColor *strokeColor; +@property (nonatomic, assign) CGFloat lineWidth; + +@end diff --git a/React/Views/RCTMapOverlay.m b/React/Views/RCTMapOverlay.m new file mode 100644 index 000000000000..2a52dd5b28fb --- /dev/null +++ b/React/Views/RCTMapOverlay.m @@ -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 "RCTMapOverlay.h" + +@implementation RCTMapOverlay + +@end From 0f89696f76845925de425bbd6dd29cae8e5f0b43 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 26 Nov 2015 17:39:15 +0000 Subject: [PATCH 0130/1411] Update KnownIssues.md --- docs/KnownIssues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 0af7876044a7..b7a219d59f27 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -69,9 +69,9 @@ This is a result of how Android rendering works. This feature is not being worke Another issue with `overflow: 'hidden'` on Android: a view is not clipped by the parent's `borderRadius` even if the parent has `overflow: 'hidden'` enabled – the corners of the inner view will be visible outside of the rounded corners. This is only on Android; it works as expected on iOS. See a [demo of the bug](https://rnplay.org/apps/BlGjdQ) and the [corresponding issue](https://github.com/facebook/react-native/issues/3198). -### No support for shadows on Android +### View shadows -We don't support shadows on Android currently. These are notoriously hard to implement as they require drawing outside of a view's bounds and Android's invalidation logic has a hard time with that. A possible solution is to use [elevation](https://developer.android.com/training/material/shadows-clipping.html), but more experimentation will be required. +The `shadow*` [view styles](/react-native/docs/view.html#style) apply on iOS, and the `elevation` view prop is available on Android. Setting `elevation` on Android is equivalent to using the [native elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation), and has the same limitations (most significantly, it only works on Android 5.0+). Setting `elevation` on Android also affects the z-order for overlapping views. ### Android M permissions From fd6ec34cdcfb82febcfaea39c0f5cf3e19c12ff6 Mon Sep 17 00:00:00 2001 From: mkonicek-tester Date: Thu, 26 Nov 2015 18:01:53 +0000 Subject: [PATCH 0131/1411] Update KnownIssues.md --- docs/KnownIssues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index b350cae6a62e..f133bb952f1d 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -59,7 +59,7 @@ There are known cases where the APIs could be made more consistent across iOS an ### Using 3rd-party native modules -There are many awesome 3rd party modules: https://react.parts/native +There are many awesome 3rd-party modules: https://react.parts/native Adding these to your apps should be made simpler. Here's [an example](https://github.com/apptailor/react-native-google-signin) how this is done currently. From ab68d8e8d1a1856d0f374c137ea2669e13799808 Mon Sep 17 00:00:00 2001 From: mkonicek-tester Date: Thu, 26 Nov 2015 18:07:17 +0000 Subject: [PATCH 0132/1411] Alpha-sort modules in Known Issues --- docs/KnownIssues.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index f133bb952f1d..4db8dfa4bcea 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -22,25 +22,25 @@ Our provisional plan for common views and modules includes: #### Views ``` -Swipe Refresh -Spinner ART Maps Modal +Spinner +Swipe Refresh Webview ``` #### Modules ``` -Net Info -Camera Roll App State +Alert +Camera Roll Dialog Media +Net Info Pasteboard PushNotificationIOS -Alert ``` ### Some props are only supported on one platform From 8ebe136f61bcb648581df11e0818d762540ab630 Mon Sep 17 00:00:00 2001 From: mkonicek-tester Date: Thu, 26 Nov 2015 18:07:58 +0000 Subject: [PATCH 0133/1411] Alpha-sort modules --- docs/KnownIssues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 4db8dfa4bcea..6e2955b1b6b2 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -33,8 +33,8 @@ Webview #### Modules ``` -App State Alert +App State Camera Roll Dialog Media From 9c322df7b8ffa7c1467ebafe9b278eaa78bd1b29 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 26 Nov 2015 10:07:12 -0800 Subject: [PATCH 0134/1411] =?UTF-8?q?Don=E2=80=99t=20do=20manual=20connect?= =?UTF-8?q?ion=20counting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed By: tadeuzagallo Differential Revision: D2696017 fb-gh-sync-id: 039abf9b7ed1570590e96e7077bb48a5493e8471 --- .../src/SocketInterface/SocketServer.js | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/packager/react-packager/src/SocketInterface/SocketServer.js b/packager/react-packager/src/SocketInterface/SocketServer.js index 56562e273c99..2e5335270d57 100644 --- a/packager/react-packager/src/SocketInterface/SocketServer.js +++ b/packager/react-packager/src/SocketInterface/SocketServer.js @@ -52,13 +52,11 @@ class SocketServer { setImmediate(() => process.exit(1)); }); - this._numConnections = 0; this._server.on('connection', (sock) => this._handleConnection(sock)); // Disable the file watcher. options.nonPersistent = true; this._packagerServer = new Server(options); - this._jobs = 0; this._dieEventually(MAX_STARTUP_TIME); } @@ -68,8 +66,6 @@ class SocketServer { _handleConnection(sock) { debug('connection to server', process.pid); - this._numConnections++; - sock.on('close', () => this._numConnections--); const bunser = new bser.BunserBuf(); sock.on('data', (buf) => bunser.append(buf)); @@ -95,7 +91,6 @@ class SocketServer { const handleError = (error) => { debug('request error', error); - this._jobs--; this._reply(sock, m.id, 'error', error.stack); // Fatal error from JSTransformer transform workers. @@ -106,7 +101,6 @@ class SocketServer { switch (m.type) { case 'getDependencies': - this._jobs++; this._packagerServer.getDependencies(m.data).then( ({ dependencies }) => this._reply(sock, m.id, 'result', dependencies), handleError, @@ -114,7 +108,6 @@ class SocketServer { break; case 'buildBundle': - this._jobs++; this._packagerServer.buildBundle(m.data).then( (result) => this._reply(sock, m.id, 'result', result), handleError, @@ -122,7 +115,6 @@ class SocketServer { break; case 'buildPrepackBundle': - this._jobs++; this._packagerServer.buildPrepackBundle(m.data).then( (result) => this._reply(sock, m.id, 'result', result), handleError, @@ -130,7 +122,6 @@ class SocketServer { break; case 'getOrderedDependencyPaths': - this._jobs++; this._packagerServer.getOrderedDependencyPaths(m.data).then( (dependencies) => this._reply(sock, m.id, 'result', dependencies), handleError, @@ -156,17 +147,19 @@ class SocketServer { // Debounce the kill timer to make sure all the bytes are sent through // the socket and the client has time to fully finish and disconnect. this._dieEventually(); - this._jobs--; } _dieEventually(delay = MAX_IDLE_TIME) { clearTimeout(this._deathTimer); this._deathTimer = setTimeout(() => { - if (this._jobs <= 0 && this._numConnections <= 0) { - debug('server dying', process.pid); - process.exit(); - } - this._dieEventually(); + this._server.getConnections((error, numConnections) => { + // error is passed when connection count is below 0 + if (error || numConnections <= 0) { + debug('server dying', process.pid); + process.exit(); + } + this._dieEventually(); + }); }, delay); } From e1533fdff6892aac734efda9ea488dd87c7ae938 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Fri, 27 Nov 2015 00:12:36 -0800 Subject: [PATCH 0135/1411] Move BridgeProfile call to give better systrace info Reviewed By: tadeuzagallo, sahrens Differential Revision: D2697510 fb-gh-sync-id: 00ec4d5835331b78c554c904dd29661dad9f6f97 --- Libraries/Utilities/MessageQueue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 8b336bd7e461..faefc7827048 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -146,12 +146,12 @@ class MessageQueue { } __callFunction(module, method, args) { - BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`); this._lastFlush = new Date().getTime(); if (isFinite(module)) { method = this._methodTable[module][method]; module = this._moduleTable[module]; } + BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`); if (__DEV__ && SPY_MODE) { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } From e1adea86c601fca146a58f9eae69d24662c45900 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Fri, 27 Nov 2015 02:04:32 -0800 Subject: [PATCH 0136/1411] add timestamps to determine string convert time Reviewed By: astreet Differential Revision: D2684174 fb-gh-sync-id: feed759025e131fd913c6188f783d6920ad1839c --- ReactAndroid/src/main/jni/react/JSCExecutor.cpp | 13 +++++++++++++ ReactAndroid/src/main/jni/react/jni/OnLoad.cpp | 5 +++++ ReactAndroid/src/main/jni/react/jni/OnLoad.h | 13 +++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 ReactAndroid/src/main/jni/react/jni/OnLoad.h diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index ca7afa29a30b..52eb369e77b9 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -10,6 +10,7 @@ #include #include #include "Value.h" +#include "jni/OnLoad.h" #ifdef WITH_JSC_EXTRA_TRACING #include @@ -96,7 +97,19 @@ JSCExecutor::~JSCExecutor() { void JSCExecutor::executeApplicationScript( const std::string& script, const std::string& sourceURL) { + JNIEnv* env = Environment::current(); + jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker"); + jmethodID logMarkerMethod = facebook::react::getLogMarkerMethod(); + jstring startStringMarker = env->NewStringUTF("executeApplicationScript_startStringConvert"); + jstring endStringMarker = env->NewStringUTF("executeApplicationScript_endStringConvert"); + + env->CallStaticVoidMethod(markerClass, logMarkerMethod, startStringMarker); String jsScript(script.c_str()); + env->CallStaticVoidMethod(markerClass, logMarkerMethod, endStringMarker); + + env->DeleteLocalRef(startStringMarker); + env->DeleteLocalRef(endStringMarker); + String jsSourceURL(sourceURL.c_str()); #ifdef WITH_FBSYSTRACE FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::executeApplicationScript", diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index e3ceddd5fdb1..6afa306da05e 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -16,6 +16,7 @@ #include "JSLoader.h" #include "ReadableNativeArray.h" #include "ProxyExecutor.h" +#include "OnLoad.h" #ifdef WITH_FBSYSTRACE #include @@ -747,6 +748,10 @@ static void createProxyExecutor(JNIEnv *env, jobject obj, jobject executorInstan } +jmethodID getLogMarkerMethod() { + return bridge::gLogMarkerMethod; +} + extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { return initialize(vm, [] { // get the current env diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.h b/ReactAndroid/src/main/jni/react/jni/OnLoad.h new file mode 100644 index 000000000000..9da83c447b96 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.h @@ -0,0 +1,13 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +namespace facebook { +namespace react { + +jmethodID getLogMarkerMethod(); + +} // namespace react +} // namespace facebook From 699a75b01f2147919ff2f700234d6a7d9a18e261 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Fri, 27 Nov 2015 02:05:14 -0800 Subject: [PATCH 0137/1411] Clear Fresco caches when going low on memory Reviewed By: astreet Differential Revision: D2699721 fb-gh-sync-id: 2d4685885ee254546496b0517633e5fe2de840d6 --- .../react/modules/fresco/FrescoModule.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 666acc2fe159..a9a16ecae3d5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -11,7 +11,9 @@ import java.util.HashSet; +import android.content.ComponentCallbacks2; import android.content.Context; +import android.content.res.Configuration; import android.support.annotation.Nullable; import com.facebook.cache.common.CacheKey; @@ -37,7 +39,7 @@ *

    Does not expose any methods to JavaScript code. For initialization and cleanup only. */ public class FrescoModule extends ReactContextBaseJavaModule implements - ModuleDataCleaner.Cleanable { + ModuleDataCleaner.Cleanable, ComponentCallbacks2 { @Nullable private RequestListener mRequestListener; @Nullable private DiskCacheConfig mDiskCacheConfig; @@ -88,6 +90,14 @@ public void initialize() { ImagePipelineConfig config = builder.build(); Fresco.initialize(context, config); + + getReactApplicationContext().getApplicationContext().registerComponentCallbacks(this); + } + + @Override + public void onCatalystInstanceDestroy() { + getReactApplicationContext().getApplicationContext().unregisterComponentCallbacks(this); + clearMemoryCaches(); } @Override @@ -105,6 +115,26 @@ public void clearSensitiveData() { imagePipelineFactory.getSmallImageDiskStorageCache().clearAll(); } + @Override + public void onTrimMemory(int level) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE || + level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + clearMemoryCaches(); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void onLowMemory() { + } + + private void clearMemoryCaches() { + Fresco.getImagePipeline().clearMemoryCaches(); + } + private static class FrescoHandler implements SoLoaderShim.Handler { @Override public void loadLibrary(String libraryName) { From 2c0679bed12bfdd4f6359282eb2feb3018ce636c Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Fri, 27 Nov 2015 02:05:56 -0800 Subject: [PATCH 0138/1411] Reuse DraweeControllerBuilder instead of allocating one for every image Reviewed By: astreet Differential Revision: D2699801 fb-gh-sync-id: 883e788f0a5c7231bf26f1ba4149115a15487366 --- .../facebook/react/views/image/ReactImageManager.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 19ff94205332..8803652d1153 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -32,7 +32,7 @@ public String getName() { return REACT_CLASS; } - private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder; + private @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder; private final @Nullable Object mCallerContext; public ReactImageManager( @@ -43,16 +43,20 @@ public ReactImageManager( } public ReactImageManager() { + // Lazily initialize as FrescoModule have not been initialized yet mDraweeControllerBuilder = null; mCallerContext = null; } @Override public ReactImageView createViewInstance(ThemedReactContext context) { + if (mDraweeControllerBuilder == null) { + mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder(); + } + return new ReactImageView( context, - mDraweeControllerBuilder == null ? - Fresco.newDraweeControllerBuilder() : mDraweeControllerBuilder, + mDraweeControllerBuilder, mCallerContext); } From f86691a44914ce35fd328e278a960ce284a80da1 Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Fri, 27 Nov 2015 02:51:58 -0800 Subject: [PATCH 0139/1411] added `didSetProps` for views and shadow views Summary: Views and shadow views might want to configure themself once all of their props were set. So far there was no way to do it without writing some synchronization code. This diff adds a `didSetProps` call on both uiviews and shadow views, passing names of all props that were set for convenience. public Reviewed By: nicklockwood Differential Revision: D2699512 fb-gh-sync-id: 65f76e7bcbf5751d5b550261a953c463ed2f4e8a --- React/Views/RCTComponent.h | 7 +++++++ React/Views/RCTComponentData.m | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/React/Views/RCTComponent.h b/React/Views/RCTComponent.h index 2940b431145e..5236f850b799 100644 --- a/React/Views/RCTComponent.h +++ b/React/Views/RCTComponent.h @@ -36,6 +36,13 @@ typedef void (^RCTBubblingEventBlock)(NSDictionary *body); @optional +/** + * Called each time props have been set. + * Not all props have to be set - React can set only changed ones. + * @param changedProps String names of all set props. + */ +- (void)didSetProps:(NSArray *)changedProps; + // TODO: Deprecate this // This method is called after layout has been performed for all views known // to the RCTViewManager. It is only called on UIViews, not shadow views. diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index dbcbc10c9f2c..8aa82bf28b5f 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -302,6 +302,10 @@ - (void)setProps:(NSDictionary *)props forView:(id [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { [self propBlockForKey:key defaultView:_defaultView](view, json); }]; + + if ([view respondsToSelector:@selector(didSetProps:)]) { + [view didSetProps:[props allKeys]]; + } } - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowView *)shadowView @@ -318,6 +322,9 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV [self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json); }]; + if ([shadowView respondsToSelector:@selector(didSetProps:)]) { + [shadowView didSetProps:[props allKeys]]; + } [shadowView updateLayout]; } From be285c43d1f86419c3bc537bff3889feae6ba85b Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Fri, 27 Nov 2015 02:52:14 -0800 Subject: [PATCH 0140/1411] use didSetProps instead of updateLayout Summary: There is no point in using `updateLayout` when we have `didSetProps`. The only a bit risky part is calling `dirtyLayout` in `setFrame:forView:` instead of `updateLayout`, but since setting frame shouldn't really change border/margin/padding it should be ok. Depends on D2699512. public Reviewed By: nicklockwood Differential Revision: D2700012 fb-gh-sync-id: a7c33b3b4e3ddc195bebebb8b03934131af016fb --- React/Modules/RCTUIManager.m | 2 +- React/Views/RCTComponentData.m | 1 - React/Views/RCTShadowView.h | 5 +---- React/Views/RCTShadowView.m | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 51911fdb055c..e4f569e5e1bf 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -382,7 +382,7 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view rootShadowView.frame = frame; } - [rootShadowView updateLayout]; + [rootShadowView dirtyLayout]; [self batchDidComplete]; }); diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 8aa82bf28b5f..4a0b45ad1e1b 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -325,7 +325,6 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV if ([shadowView respondsToSelector:@selector(didSetProps:)]) { [shadowView didSetProps:[props allKeys]]; } - [shadowView updateLayout]; } - (NSDictionary *)viewConfig diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index e565e73d14ed..d77bb3aea04e 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -164,10 +164,7 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry - (void)setTextComputed NS_REQUIRES_SUPER; - (BOOL)isTextDirty; -/** - * Triggers a recalculation of the shadow view's layout. - */ -- (void)updateLayout NS_REQUIRES_SUPER; +- (void)didSetProps:(NSArray *)changedProps NS_REQUIRES_SUPER; /** * Computes the recursive offset, meaning the sum of all descendant offsets - diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index e47bec2443e0..75625fd7d3e0 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -580,7 +580,7 @@ - (void)setBackgroundColor:(UIColor *)color [self dirtyPropagation]; } -- (void)updateLayout +- (void)didSetProps:(NSArray *)changedProps { if (_recomputePadding) { RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding); From 01983c2f0ab4e85c1f7db7f0073942cd7fee3836 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 27 Nov 2015 03:57:38 -0800 Subject: [PATCH 0141/1411] Ability to run unit tests in react-android-github via gradle Reviewed By: mkonicek Differential Revision: D2699804 fb-gh-sync-id: 7b31287407bacf2e8e3de6ee1c723a11bb2a0f27 --- ReactAndroid/build.gradle | 8 ++++++ ReactAndroid/gradle.properties | 6 +++++ .../storage/AsyncStorageModuleTest.java | 2 +- .../modules/timing/TimingModuleTest.java | 6 ++--- .../configuration/MockitoConfiguration.java | 26 +++++++++++++++++++ 5 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index b9dc2f04610e..ef343d4a7fdf 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -256,6 +256,14 @@ dependencies { compile 'com.squareup.okhttp:okhttp-ws:2.5.0' compile 'com.squareup.okio:okio:1.6.0' compile 'org.webkit:android-jsc:r174650' + + testCompile "junit:junit:${JUNIT_VERSION}" + testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}" + testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}" + testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}" + testCompile("org.robolectric:robolectric:${ROBOLECTRIC_VERSION}") } apply from: 'release.gradle' diff --git a/ReactAndroid/gradle.properties b/ReactAndroid/gradle.properties index 96433c14b48e..e038115dd4ea 100644 --- a/ReactAndroid/gradle.properties +++ b/ReactAndroid/gradle.properties @@ -6,3 +6,9 @@ POM_ARTIFACT_ID=react-native POM_PACKAGING=aar android.useDeprecatedNdk=true + +MOCKITO_CORE_VERSION=1.+ +POWERMOCK_VERSION=1.6.2 +ROBOLECTRIC_VERSION=3.0 +JUNIT_VERSION=4.12 +FEST_ASSERT_CORE_VERSION=2.0M10 diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java index bd7076fc9258..c11e9ba932a4 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java @@ -53,7 +53,7 @@ * Tests for {@link AsyncStorageModule}. */ @PrepareForTest({Arguments.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*"}) @RunWith(RobolectricTestRunner.class) public class AsyncStorageModuleTest { diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java index 0d14cdc43b89..219bccf13b35 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java @@ -77,9 +77,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { PowerMockito.mockStatic(ReactChoreographer.class); when(ReactChoreographer.getInstance()).thenReturn(mChoreographerMock); - CatalystInstance catalystInstance = mock(CatalystInstance.class); + CatalystInstance reactInstance = mock(CatalystInstance.class); ReactApplicationContext reactContext = mock(ReactApplicationContext.class); - when(reactContext.getCatalystInstance()).thenReturn(catalystInstance); + when(reactContext.getCatalystInstance()).thenReturn(reactInstance); mCurrentTimeNs = 0; mPostFrameCallbackHandler = new PostFrameCallbackHandler(); @@ -92,7 +92,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { mTiming = new Timing(reactContext); mJSTimersMock = mock(JSTimersExecution.class); - when(catalystInstance.getJSModule(JSTimersExecution.class)).thenReturn(mJSTimersMock); + when(reactInstance.getJSModule(JSTimersExecution.class)).thenReturn(mJSTimersMock); mTiming.initialize(); } diff --git a/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java b/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java new file mode 100644 index 000000000000..20f4aa035f43 --- /dev/null +++ b/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package org.mockito.configuration; + +/** + * Disables the Mockito cache to prevent Mockito & Robolectric bugs. + * + * Mockito loads this with reflection, so this class might appear unused. + */ +@SuppressWarnings("unused") +public class MockitoConfiguration extends DefaultMockitoConfiguration { + + /* (non-Javadoc) + * @see org.mockito.configuration.IMockitoConfiguration#enableClassCache() + */ + public boolean enableClassCache() { + return false; + } +} From af1475fc4ce95c1c38404a71e932fd179724188f Mon Sep 17 00:00:00 2001 From: Oleksandr Stashuk Date: Fri, 27 Nov 2015 04:24:33 -0800 Subject: [PATCH 0142/1411] revert of D2699721 Reviewed By: lexs Differential Revision: D2700660 fb-gh-sync-id: c59fc46e03146af9a1a6ce836070924d6bcfaba6 --- .../react/modules/fresco/FrescoModule.java | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index a9a16ecae3d5..666acc2fe159 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -11,9 +11,7 @@ import java.util.HashSet; -import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.res.Configuration; import android.support.annotation.Nullable; import com.facebook.cache.common.CacheKey; @@ -39,7 +37,7 @@ *

    Does not expose any methods to JavaScript code. For initialization and cleanup only. */ public class FrescoModule extends ReactContextBaseJavaModule implements - ModuleDataCleaner.Cleanable, ComponentCallbacks2 { + ModuleDataCleaner.Cleanable { @Nullable private RequestListener mRequestListener; @Nullable private DiskCacheConfig mDiskCacheConfig; @@ -90,14 +88,6 @@ public void initialize() { ImagePipelineConfig config = builder.build(); Fresco.initialize(context, config); - - getReactApplicationContext().getApplicationContext().registerComponentCallbacks(this); - } - - @Override - public void onCatalystInstanceDestroy() { - getReactApplicationContext().getApplicationContext().unregisterComponentCallbacks(this); - clearMemoryCaches(); } @Override @@ -115,26 +105,6 @@ public void clearSensitiveData() { imagePipelineFactory.getSmallImageDiskStorageCache().clearAll(); } - @Override - public void onTrimMemory(int level) { - if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE || - level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { - clearMemoryCaches(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - } - - @Override - public void onLowMemory() { - } - - private void clearMemoryCaches() { - Fresco.getImagePipeline().clearMemoryCaches(); - } - private static class FrescoHandler implements SoLoaderShim.Handler { @Override public void loadLibrary(String libraryName) { From af753a8a4d0842531bc7549fa78b2a4bfbf5dbdd Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Fri, 27 Nov 2015 03:48:57 -0800 Subject: [PATCH 0143/1411] Add NativeProfiling module that uses RN nativeProfiler or console.profile Reviewed By: jspahrsummers Differential Revision: D2696072 fb-gh-sync-id: 6a2b9793424cf9a82b9472e6d468cbd81dd6780a --- Libraries/Utilities/CPUProfiler.js | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Libraries/Utilities/CPUProfiler.js diff --git a/Libraries/Utilities/CPUProfiler.js b/Libraries/Utilities/CPUProfiler.js new file mode 100644 index 000000000000..445706c7b088 --- /dev/null +++ b/Libraries/Utilities/CPUProfiler.js @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CPUProfiler + * @flow + */ +'use strict'; + +var _label; +var _nestingLevel = 0; + +var CPUProfiler = { + + start(profileName: string) { + if (_nestingLevel === 0) { + if (global.nativeProfilerStart) { + _label = profileName; + global.nativeProfilerStart(profileName); + } else if (console.profile) { + console.profile(profileName); + } + } + _nestingLevel++; + }, + end() { + _nestingLevel--; + if (_nestingLevel === 0) { + if (global.nativeProfilerEnd) { + global.nativeProfilerEnd(_label); + } else if (console.profileEnd) { + console.profileEnd(); + } + _label = undefined; + } + } +}; + +module.exports = CPUProfiler; From 9e30c3b218c08afb51ae0e274f15422b9bcca480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Fri, 27 Nov 2015 06:25:35 -0800 Subject: [PATCH 0144/1411] add native module overriding Differential Revision: D2700638 fb-gh-sync-id: a88ffaf864be848e1bba22e443d301e4623f04ec --- .../facebook/react/bridge/BaseJavaModule.java | 5 ++ .../facebook/react/bridge/NativeModule.java | 20 +++++-- .../react/bridge/NativeModuleRegistry.java | 55 +++++++++---------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java index 061611e7408d..1a84c05fa410 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java @@ -327,6 +327,11 @@ public void initialize() { // do nothing } + @Override + public boolean canOverrideExistingModule() { + return false; + } + @Override public void onCatalystInstanceDestroy() { // do nothing diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java index 480e42182079..905b5be93521 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java @@ -22,7 +22,7 @@ * register themselves using {@link CxxModuleWrapper}. */ public interface NativeModule { - public static interface NativeMethod { + interface NativeMethod { void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters); String getType(); } @@ -31,28 +31,36 @@ public static interface NativeMethod { * @return the name of this module. This will be the name used to {@code require()} this module * from javascript. */ - public String getName(); + String getName(); /** * @return methods callable from JS on this module */ - public Map getMethods(); + Map getMethods(); /** * Append a field which represents the constants this module exports * to JS. If no constants are exported this should do nothing. */ - public void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException; + void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException; /** * This is called at the end of {@link CatalystApplicationFragment#createCatalystInstance()} * after the CatalystInstance has been created, in order to initialize NativeModules that require * the CatalystInstance or JS modules. */ - public void initialize(); + void initialize(); + + /** + * Return true if you intend to override some other native module that was registered e.g. as part + * of a different package (such as the core one). Trying to override without returning true from + * this method is considered an error and will throw an exception during initialization. By + * default all modules return false. + */ + boolean canOverrideExistingModule(); /** * Called before {CatalystInstance#onHostDestroy} */ - public void onCatalystInstanceDestroy(); + void onCatalystInstanceDestroy(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java index 80b1b4616119..50bdd69bbe2d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java @@ -13,11 +13,10 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.Map; -import java.util.Set; import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.SetBuilder; import com.facebook.infer.annotation.Assertions; import com.facebook.systrace.Systrace; @@ -30,13 +29,13 @@ public class NativeModuleRegistry { private final ArrayList mModuleTable; - private final Map, NativeModule> mModuleInstances; + private final Map, NativeModule> mModuleInstances; private final String mModuleDescriptions; private final ArrayList mBatchCompleteListenerModules; private NativeModuleRegistry( ArrayList moduleTable, - Map, NativeModule> moduleInstances, + Map, NativeModule> moduleInstances, String moduleDescriptions) { mModuleTable = moduleTable; mModuleInstances = moduleInstances; @@ -160,32 +159,24 @@ public MethodRegistration(String name, String tracingName, NativeModule.NativeMe public static class Builder { - private ArrayList mModuleDefinitions; - private Map, NativeModule> mModuleInstances; - private Set mSeenModuleNames; - - public Builder() { - mModuleDefinitions = new ArrayList(); - mModuleInstances = MapBuilder.newHashMap(); - mSeenModuleNames = SetBuilder.newHashSet(); - } + private final HashMap mModules = MapBuilder.newHashMap(); public Builder add(NativeModule module) { - ModuleDefinition registration = new ModuleDefinition( - mModuleDefinitions.size(), - module.getName(), - module); - Assertions.assertCondition( - !mSeenModuleNames.contains(module.getName()), - "Module " + module.getName() + " was already registered!"); - mSeenModuleNames.add(module.getName()); - mModuleDefinitions.add(registration); - mModuleInstances.put((Class) module.getClass(), module); + NativeModule existing = mModules.get(module.getName()); + if (existing != null && !module.canOverrideExistingModule()) { + throw new IllegalStateException("Native module " + module.getClass().getSimpleName() + + " tried to override " + existing.getClass().getSimpleName() + " for module name " + + module.getName() + ". If this was your intention, return true from " + + module.getClass().getSimpleName() + "#canOverrideExistingModule()"); + } + mModules.put(module.getName(), module); return this; } public NativeModuleRegistry build() { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateJSON"); + ArrayList moduleTable = new ArrayList<>(); + Map, NativeModule> moduleInstances = MapBuilder.newHashMap(); String moduleDefinitionJson; try { JsonFactory jsonFactory = new JsonFactory(); @@ -193,19 +184,23 @@ public NativeModuleRegistry build() { try { JsonGenerator jg = jsonFactory.createGenerator(writer); jg.writeStartObject(); - for (ModuleDefinition module : mModuleDefinitions) { - jg.writeObjectFieldStart(module.name); - jg.writeNumberField("moduleID", module.id); + int idx = 0; + for (NativeModule module : mModules.values()) { + ModuleDefinition moduleDef = new ModuleDefinition(idx++, module.getName(), module); + moduleTable.add(moduleDef); + moduleInstances.put(module.getClass(), module); + jg.writeObjectFieldStart(moduleDef.name); + jg.writeNumberField("moduleID", moduleDef.id); jg.writeObjectFieldStart("methods"); - for (int i = 0; i < module.methods.size(); i++) { - MethodRegistration method = module.methods.get(i); + for (int i = 0; i < moduleDef.methods.size(); i++) { + MethodRegistration method = moduleDef.methods.get(i); jg.writeObjectFieldStart(method.name); jg.writeNumberField("methodID", i); jg.writeStringField("type", method.method.getType()); jg.writeEndObject(); } jg.writeEndObject(); - module.target.writeConstantsField(jg, "constants"); + moduleDef.target.writeConstantsField(jg, "constants"); jg.writeEndObject(); } jg.writeEndObject(); @@ -217,7 +212,7 @@ public NativeModuleRegistry build() { } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } - return new NativeModuleRegistry(mModuleDefinitions, mModuleInstances, moduleDefinitionJson); + return new NativeModuleRegistry(moduleTable, moduleInstances, moduleDefinitionJson); } } } From 60db876f666e256eba8527251ce7035cfbca6014 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 27 Nov 2015 05:39:00 -0800 Subject: [PATCH 0145/1411] Wrapped UIManager native module for better abstraction Summary: public RCTUIManager is a public module with several useful methods, however, unlike most such modules, it does not have a JS wrapper that would allow it to be required directly. Besides making it more cumbersome to use, this also makes it impossible to modify the UIManager API, or smooth over differences between platforms in the JS layer without breaking all of the call sites. This diff adds a simple JS wrapper file for the UIManager module to make it easier to work with. Reviewed By: tadeuzagallo Differential Revision: D2700348 fb-gh-sync-id: dd9030eface100b1baf756da11bae355dc0f266f --- Examples/UIExplorer/ImageEditingExample.js | 3 ++- .../DrawerAndroid/DrawerLayoutAndroid.android.js | 13 +++++++------ Libraries/Components/TextInput/TextInput.js | 8 ++++---- Libraries/Components/TextInput/TextInputState.js | 14 +++++++------- .../ToolbarAndroid/ToolbarAndroid.android.js | 4 ++-- .../Touchable/TouchableNativeFeedback.android.js | 10 +++++----- Libraries/Components/View/View.js | 4 ++-- .../ViewPager/ViewPagerAndroid.android.js | 12 +++++------- Libraries/Components/WebView/WebView.android.js | 14 +++++++------- Libraries/CustomComponents/ListView/ListView.js | 1 - Libraries/LayoutAnimation/LayoutAnimation.js | 4 ++-- Libraries/Portal/Portal.js | 6 +++--- Libraries/RKBackendNode/queryLayoutByID.js | 4 ++-- Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js | 8 +++----- Libraries/ReactIOS/NativeMethodsMixin.js | 9 ++++----- Libraries/ReactIOS/requireNativeComponent.js | 8 ++++---- .../ReactNative/ReactNativeBaseComponent.js | 8 ++++---- .../ReactNative/ReactNativeDOMIDOperations.js | 6 +++--- .../ReactNativeGlobalResponderHandler.js | 6 +++--- Libraries/ReactNative/ReactNativeMount.js | 9 ++++----- .../ReactNative/ReactNativeTextComponent.js | 6 +++--- Libraries/ReactNative/UIManagerStatTracker.js | 14 +++++++------- Libraries/Utilities/Dimensions.js | 4 ++-- Libraries/Utilities/UIManager.js | 16 ++++++++++++++++ Libraries/react-native/react-native.js | 1 + 25 files changed, 102 insertions(+), 90 deletions(-) create mode 100644 Libraries/Utilities/UIManager.js diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js index affd36796772..56ab1c4ba342 100644 --- a/Examples/UIExplorer/ImageEditingExample.js +++ b/Examples/UIExplorer/ImageEditingExample.js @@ -25,10 +25,11 @@ var { StyleSheet, Text, TouchableHighlight, + UIManager, View, } = React; var ImageEditingManager = NativeModules.ImageEditingManager; -var RCTScrollViewConsts = NativeModules.UIManager.RCTScrollView.Constants; +var RCTScrollViewConsts = UIManager.RCTScrollView.Constants; var PAGE_SIZE = 20; diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 407f237e5052..4b209bd41792 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -10,15 +10,16 @@ */ 'use strict'; -var DrawerConsts = require('NativeModules').UIManager.AndroidDrawerLayout.Constants; var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); var ReactPropTypes = require('ReactPropTypes'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); -var RCTUIManager = require('NativeModules').UIManager; var StyleSheet = require('StyleSheet'); +var UIManager = require('UIManager'); var View = require('View'); +var DrawerConsts = UIManager.AndroidDrawerLayout.Constants; + var dismissKeyboard = require('dismissKeyboard'); var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); @@ -182,17 +183,17 @@ var DrawerLayoutAndroid = React.createClass({ }, openDrawer: function() { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), - RCTUIManager.AndroidDrawerLayout.Commands.openDrawer, + UIManager.AndroidDrawerLayout.Commands.openDrawer, null ); }, closeDrawer: function() { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( this._getDrawerLayoutHandle(), - RCTUIManager.AndroidDrawerLayout.Commands.closeDrawer, + UIManager.AndroidDrawerLayout.Commands.closeDrawer, null ); }, diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index c9728d96ab9d..e7416e833bf0 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -16,7 +16,6 @@ var EventEmitter = require('EventEmitter'); var NativeMethodsMixin = require('NativeMethodsMixin'); var Platform = require('Platform'); var PropTypes = require('ReactPropTypes'); -var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); var ReactChildren = require('ReactChildren'); var StyleSheet = require('StyleSheet'); @@ -24,6 +23,7 @@ var Text = require('Text'); var TextInputState = require('TextInputState'); var TimerMixin = require('react-timer-mixin'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +var UIManager = require('UIManager'); var View = require('View'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); @@ -496,11 +496,11 @@ var TextInput = React.createClass({ }, _renderAndroid: function() { - var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize]; + var autoCapitalize = UIManager.UIText.AutocapitalizationType[this.props.autoCapitalize]; var textAlign = - RCTUIManager.AndroidTextInput.Constants.TextAlign[this.props.textAlign]; + UIManager.AndroidTextInput.Constants.TextAlign[this.props.textAlign]; var textAlignVertical = - RCTUIManager.AndroidTextInput.Constants.TextAlignVertical[this.props.textAlignVertical]; + UIManager.AndroidTextInput.Constants.TextAlignVertical[this.props.textAlignVertical]; var children = this.props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); diff --git a/Libraries/Components/TextInput/TextInputState.js b/Libraries/Components/TextInput/TextInputState.js index f3e35b78c2f0..276bba0d781b 100644 --- a/Libraries/Components/TextInput/TextInputState.js +++ b/Libraries/Components/TextInput/TextInputState.js @@ -16,7 +16,7 @@ 'use strict'; var Platform = require('Platform'); -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); var TextInputState = { /** @@ -41,11 +41,11 @@ var TextInputState = { if (this._currentlyFocusedID !== textFieldID && textFieldID !== null) { this._currentlyFocusedID = textFieldID; if (Platform.OS === 'ios') { - RCTUIManager.focus(textFieldID); + UIManager.focus(textFieldID); } else if (Platform.OS === 'android') { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( textFieldID, - RCTUIManager.AndroidTextInput.Commands.focusTextInput, + UIManager.AndroidTextInput.Commands.focusTextInput, null ); } @@ -61,11 +61,11 @@ var TextInputState = { if (this._currentlyFocusedID === textFieldID && textFieldID !== null) { this._currentlyFocusedID = null; if (Platform.OS === 'ios') { - RCTUIManager.blur(textFieldID); + UIManager.blur(textFieldID); } else if (Platform.OS === 'android') { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( textFieldID, - RCTUIManager.AndroidTextInput.Commands.blurTextInput, + UIManager.AndroidTextInput.Commands.blurTextInput, null ); } diff --git a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js index 774e602bee68..eb904d867af9 100644 --- a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js +++ b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js @@ -13,10 +13,10 @@ var Image = require('Image'); var NativeMethodsMixin = require('NativeMethodsMixin'); -var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var ReactPropTypes = require('ReactPropTypes'); +var UIManager = require('UIManager'); var View = require('View'); var requireNativeComponent = require('requireNativeComponent'); @@ -154,7 +154,7 @@ var ToolbarAndroid = React.createClass({ action.icon = resolveAssetSource(action.icon); } if (action.show) { - action.show = RCTUIManager.ToolbarAndroid.Constants.ShowAsAction[action.show]; + action.show = UIManager.ToolbarAndroid.Constants.ShowAsAction[action.show]; } nativeActions.push(action); } diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index dd479bdfefbb..bc875eda531c 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -11,11 +11,11 @@ 'use strict'; var PropTypes = require('ReactPropTypes'); -var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var Touchable = require('Touchable'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +var UIManager = require('UIManager'); var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); @@ -181,17 +181,17 @@ var TouchableNativeFeedback = React.createClass({ }, _dispatchHotspotUpdate: function(destX, destY) { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.RCTView.Commands.hotspotUpdate, + UIManager.RCTView.Commands.hotspotUpdate, [destX || 0, destY || 0] ); }, _dispatchPressedStateChange: function(pressed) { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.RCTView.Commands.setPressed, + UIManager.RCTView.Commands.setPressed, [pressed] ); }, diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 84ab491875d9..beaffb75c09a 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -13,11 +13,11 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var PropTypes = require('ReactPropTypes'); -var RCTUIManager = require('NativeModules').UIManager; var React = require('React'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); +var UIManager = require('UIManager'); var ViewStylePropTypes = require('ViewStylePropTypes'); var requireNativeComponent = require('requireNativeComponent'); @@ -327,7 +327,7 @@ var RCTView = requireNativeComponent('RCTView', View, { }); if (__DEV__) { - var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {}; + var viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {}; for (var prop in viewConfig.nativeProps) { var viewAny: any = View; // Appease flow if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) { diff --git a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js index 7151917ff637..ec08841ec763 100644 --- a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js +++ b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js @@ -7,15 +7,13 @@ 'use strict'; var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModules = require('NativeModules'); var React = require('React'); var ReactElement = require('ReactElement'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var ReactPropTypes = require('ReactPropTypes'); +var UIManager = require('UIManager'); var View = require('View'); -var RCTUIManager = NativeModules.UIManager; - var dismissKeyboard = require('dismissKeyboard'); var requireNativeComponent = require('requireNativeComponent'); @@ -157,9 +155,9 @@ var ViewPagerAndroid = React.createClass({ * The transition between pages will be animated. */ setPage: function(selectedPage: number) { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.AndroidViewPager.Commands.setPage, + UIManager.AndroidViewPager.Commands.setPage, [selectedPage], ); }, @@ -169,9 +167,9 @@ var ViewPagerAndroid = React.createClass({ * The transition between pages will be *not* be animated. */ setPageWithoutAnimation: function(selectedPage: number) { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( React.findNodeHandle(this), - RCTUIManager.AndroidViewPager.Commands.setPageWithoutAnimation, + UIManager.AndroidViewPager.Commands.setPageWithoutAnimation, [selectedPage], ); }, diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index f3ce5956d80e..d5569dcad39d 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -14,6 +14,7 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); +var UIManager = require('UIManager'); var View = require('View'); var keyMirror = require('keyMirror'); @@ -21,7 +22,6 @@ var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); var PropTypes = React.PropTypes; -var RCTUIManager = require('NativeModules').UIManager; var RCT_WEBVIEW_REF = 'webview'; @@ -129,25 +129,25 @@ var WebView = React.createClass({ }, goForward: function() { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( this.getWebWiewHandle(), - RCTUIManager.RCTWebView.Commands.goForward, + UIManager.RCTWebView.Commands.goForward, null ); }, goBack: function() { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( this.getWebWiewHandle(), - RCTUIManager.RCTWebView.Commands.goBack, + UIManager.RCTWebView.Commands.goBack, null ); }, reload: function() { - RCTUIManager.dispatchViewManagerCommand( + UIManager.dispatchViewManagerCommand( this.getWebWiewHandle(), - RCTUIManager.RCTWebView.Commands.reload, + UIManager.RCTWebView.Commands.reload, null ); }, diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index fb99f1687595..5706ff0f91b2 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -28,7 +28,6 @@ var ListViewDataSource = require('ListViewDataSource'); var React = require('React'); -var RCTUIManager = require('NativeModules').UIManager; var RCTScrollViewManager = require('NativeModules').ScrollViewManager; var ScrollView = require('ScrollView'); var ScrollResponder = require('ScrollResponder'); diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js index 881b499f2ba3..140c35ff8785 100644 --- a/Libraries/LayoutAnimation/LayoutAnimation.js +++ b/Libraries/LayoutAnimation/LayoutAnimation.js @@ -12,7 +12,7 @@ 'use strict'; var PropTypes = require('ReactPropTypes'); -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); var keyMirror = require('keyMirror'); @@ -71,7 +71,7 @@ type Config = { function configureNext(config: Config, onAnimationDidEnd?: Function) { configChecker({config}, 'config', 'LayoutAnimation.configureNext'); - RCTUIManager.configureNextLayoutAnimation( + UIManager.configureNextLayoutAnimation( config, onAnimationDidEnd || function() {}, function() { /* unused */ } ); } diff --git a/Libraries/Portal/Portal.js b/Libraries/Portal/Portal.js index ac80e04109fe..f6ed4c8f7860 100644 --- a/Libraries/Portal/Portal.js +++ b/Libraries/Portal/Portal.js @@ -8,8 +8,8 @@ var Platform = require('Platform'); var React = require('React'); -var RCTUIManager = require('NativeModules').UIManager; var StyleSheet = require('StyleSheet'); +var UIManager = require('UIManager'); var View = require('View'); var _portalRef: any; @@ -135,9 +135,9 @@ var Portal = React.createClass({ // TextViews have no text set at the moment of populating event. setTimeout(() => { if (this._getOpenModals().length > 0) { - RCTUIManager.sendAccessibilityEvent( + UIManager.sendAccessibilityEvent( React.findNodeHandle(this), - RCTUIManager.AccessibilityEventTypes.typeWindowStateChanged); + UIManager.AccessibilityEventTypes.typeWindowStateChanged); } }, 0); } diff --git a/Libraries/RKBackendNode/queryLayoutByID.js b/Libraries/RKBackendNode/queryLayoutByID.js index d6492e6da487..5d038c232bb1 100644 --- a/Libraries/RKBackendNode/queryLayoutByID.js +++ b/Libraries/RKBackendNode/queryLayoutByID.js @@ -12,7 +12,7 @@ 'use strict'; var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); type OnSuccessCallback = ( left: number, @@ -51,7 +51,7 @@ var queryLayoutByID = function( onSuccess: OnSuccessCallback ): void { // Native bridge doesn't *yet* surface errors. - RCTUIManager.measure( + UIManager.measure( ReactNativeTagHandles.rootNodeIDToTag[rootNodeID], onSuccess ); diff --git a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js index 55b22a959934..6c1d887e555d 100644 --- a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -12,16 +12,14 @@ 'use strict'; var EventPropagators = require('EventPropagators'); -var NativeModules = require('NativeModules'); var SyntheticEvent = require('SyntheticEvent'); +var UIManager = require('UIManager'); var merge = require('merge'); var warning = require('warning'); -var RCTUIManager = NativeModules.UIManager; - -var customBubblingEventTypes = RCTUIManager.customBubblingEventTypes; -var customDirectEventTypes = RCTUIManager.customDirectEventTypes; +var customBubblingEventTypes = UIManager.customBubblingEventTypes; +var customDirectEventTypes = UIManager.customDirectEventTypes; var allTypesByEventName = {}; diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index b2b5ba41928d..33b99994a72d 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -11,10 +11,9 @@ */ 'use strict'; -var NativeModules = require('NativeModules'); -var RCTUIManager = NativeModules.UIManager; var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var TextInputState = require('TextInputState'); +var UIManager = require('UIManager'); var findNodeHandle = require('findNodeHandle'); var invariant = require('invariant'); @@ -78,7 +77,7 @@ var NativeMethodsMixin = { * prop](/react-native/docs/view.html#onlayout) instead. */ measure: function(callback: MeasureOnSuccessCallback) { - RCTUIManager.measure( + UIManager.measure( findNodeHandle(this), mountSafeCallback(this, callback) ); @@ -97,7 +96,7 @@ var NativeMethodsMixin = { onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void /* currently unused */ ) { - RCTUIManager.measureLayout( + UIManager.measureLayout( findNodeHandle(this), relativeToNativeNode, mountSafeCallback(this, onFail), @@ -121,7 +120,7 @@ var NativeMethodsMixin = { this.viewConfig.validAttributes ); - RCTUIManager.updateView( + UIManager.updateView( findNodeHandle(this), this.viewConfig.uiViewClassName, updatePayload diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index 11defc0ada13..a046fcbd1498 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -11,8 +11,8 @@ */ 'use strict'; -var RCTUIManager = require('NativeModules').UIManager; var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +var UIManager = require('UIManager'); var UnimplementedView = require('UnimplementedView'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); @@ -27,7 +27,7 @@ var warning = require('warning'); /** * Used to create React components that directly wrap native component * implementations. Config information is extracted from data exported from the - * RCTUIManager module. You should also wrap the native component in a + * UIManager module. You should also wrap the native component in a * hand-written component with full propTypes definitions and other * documentation - pass the hand-written component in as `componentInterface` to * verify all the native props are documented via `propTypes`. @@ -46,13 +46,13 @@ function requireNativeComponent( componentInterface?: ?ComponentInterface, extraConfig?: ?{nativeOnly?: Object}, ): Function { - var viewConfig = RCTUIManager[viewName]; + var viewConfig = UIManager[viewName]; if (!viewConfig || !viewConfig.NativeProps) { warning(false, 'Native component for "%s" does not exist', viewName); return UnimplementedView; } var nativeProps = { - ...RCTUIManager.RCTView.NativeProps, + ...UIManager.RCTView.NativeProps, ...viewConfig.NativeProps, }; viewConfig.uiViewClassName = viewName; diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index cfd3df3e832d..765c0429063b 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -17,7 +17,7 @@ var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); var warning = require('warning'); @@ -123,7 +123,7 @@ ReactNativeBaseComponent.Mixin = { ); createdTags[i] = mountImage.tag; } - RCTUIManager + UIManager .manageChildren(containerTag, null, null, createdTags, indexes, null); } }, @@ -151,7 +151,7 @@ ReactNativeBaseComponent.Mixin = { ); if (updatePayload) { - RCTUIManager.updateView( + UIManager.updateView( ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID), this.viewConfig.uiViewClassName, updatePayload @@ -216,7 +216,7 @@ ReactNativeBaseComponent.Mixin = { ); var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); - RCTUIManager.createView( + UIManager.createView( tag, this.viewConfig.uiViewClassName, nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, diff --git a/Libraries/ReactNative/ReactNativeDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js index af17f9e0742e..fbf90db50337 100644 --- a/Libraries/ReactNative/ReactNativeDOMIDOperations.js +++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js @@ -13,8 +13,8 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); -var RCTUIManager = require('NativeModules').UIManager; var ReactPerf = require('ReactPerf'); +var UIManager = require('UIManager'); /** * Updates a component's children by processing a series of updates. @@ -59,7 +59,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { for (var updateParentTagString in byContainerTag) { var updateParentTagNumber = +updateParentTagString; var childUpdatesToSend = byContainerTag[updateParentTagNumber]; - RCTUIManager.manageChildren( + UIManager.manageChildren( updateParentTagNumber, childUpdatesToSend.moveFromIndices, childUpdatesToSend.moveToIndices, @@ -93,7 +93,7 @@ var ReactNativeDOMIDOperations = { 'dangerouslyReplaceNodeWithMarkupByID', function(id, mountImage) { var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id); - RCTUIManager.replaceExistingNonRootView(oldTag, mountImage.tag); + UIManager.replaceExistingNonRootView(oldTag, mountImage.tag); ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag); } ), diff --git a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js index 1f548c3ebcc2..1094e60451b8 100644 --- a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js +++ b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js @@ -11,18 +11,18 @@ */ 'use strict'; -var RCTUIManager = require('NativeModules').UIManager; var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var UIManager = require('UIManager'); var ReactNativeGlobalResponderHandler = { onChange: function(from: string, to: string, blockNativeResponder: boolean) { if (to !== null) { - RCTUIManager.setJSResponder( + UIManager.setJSResponder( ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to), blockNativeResponder ); } else { - RCTUIManager.clearJSResponder(); + UIManager.clearJSResponder(); } } }; diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 02dbcb40fc7e..1f6dab51b8a4 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -11,14 +11,13 @@ */ 'use strict'; -var RCTUIManager = require('NativeModules').UIManager; - var ReactElement = require('ReactElement'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); +var UIManager = require('UIManager'); var emptyObject = require('emptyObject'); var instantiateReactComponent = require('instantiateReactComponent'); @@ -191,7 +190,7 @@ var ReactNativeMount = { ); var addChildTags = [mountImage.tag]; var addAtIndices = [0]; - RCTUIManager.manageChildren( + UIManager.manageChildren( ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), null, // moveFromIndices null, // moveToIndices @@ -215,7 +214,7 @@ var ReactNativeMount = { ) { ReactNativeMount.unmountComponentAtNode(containerTag); // call back into native to remove all of the subviews from this container - RCTUIManager.removeRootView(containerTag); + UIManager.removeRootView(containerTag); }, /** @@ -256,7 +255,7 @@ var ReactNativeMount = { ReactReconciler.unmountComponent(instance); var containerTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID); - RCTUIManager.removeSubviewsFromContainerWithID(containerTag); + UIManager.removeSubviewsFromContainerWithID(containerTag); }, getNode: function(rootNodeID: string): number { diff --git a/Libraries/ReactNative/ReactNativeTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js index a7694c1394c9..e9f67ce59fdb 100644 --- a/Libraries/ReactNative/ReactNativeTextComponent.js +++ b/Libraries/ReactNative/ReactNativeTextComponent.js @@ -12,7 +12,7 @@ 'use strict'; var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); var assign = require('Object.assign'); var invariant = require('invariant'); @@ -39,7 +39,7 @@ assign(ReactNativeTextComponent.prototype, { this._rootNodeID = rootID; var tag = ReactNativeTagHandles.allocateTag(); var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); - RCTUIManager.createView( + UIManager.createView( tag, 'RCTRawText', nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, @@ -57,7 +57,7 @@ assign(ReactNativeTextComponent.prototype, { var nextStringText = '' + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; - RCTUIManager.updateView( + UIManager.updateView( ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID( this._rootNodeID ), diff --git a/Libraries/ReactNative/UIManagerStatTracker.js b/Libraries/ReactNative/UIManagerStatTracker.js index ef9c64b2bbb5..a3b8779234f5 100644 --- a/Libraries/ReactNative/UIManagerStatTracker.js +++ b/Libraries/ReactNative/UIManagerStatTracker.js @@ -11,7 +11,7 @@ */ 'use strict'; -var RCTUIManager = require('NativeModules').UIManager; +var UIManager = require('UIManager'); var installed = false; var UIManagerStatTracker = { @@ -32,20 +32,20 @@ var UIManagerStatTracker = { statLogHandle = setImmediate(printStats); } } - var createViewOrig = RCTUIManager.createView; - RCTUIManager.createView = function(tag, className, rootTag, props) { + var createViewOrig = UIManager.createView; + UIManager.createView = function(tag, className, rootTag, props) { incStat('createView', 1); incStat('setProp', Object.keys(props || []).length); createViewOrig(tag, className, rootTag, props); }; - var updateViewOrig = RCTUIManager.updateView; - RCTUIManager.updateView = function(tag, className, props) { + var updateViewOrig = UIManager.updateView; + UIManager.updateView = function(tag, className, props) { incStat('updateView', 1); incStat('setProp', Object.keys(props || []).length); updateViewOrig(tag, className, props); }; - var manageChildrenOrig = RCTUIManager.manageChildren; - RCTUIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) { + var manageChildrenOrig = UIManager.manageChildren; + UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) { incStat('manageChildren', 1); incStat('move', Object.keys(moveFrom || []).length); incStat('remove', Object.keys(remove || []).length); diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js index 31fa0d4dc4f3..319aa84622e3 100644 --- a/Libraries/Utilities/Dimensions.js +++ b/Libraries/Utilities/Dimensions.js @@ -11,11 +11,11 @@ */ 'use strict'; -var NativeModules = require('NativeModules'); +var UIManager = require('UIManager'); var invariant = require('invariant'); -var dimensions = NativeModules.UIManager.Dimensions; +var dimensions = UIManager.Dimensions; // We calculate the window dimensions in JS so that we don't encounter loss of // precision in transferring the dimensions (which could be non-integers) over diff --git a/Libraries/Utilities/UIManager.js b/Libraries/Utilities/UIManager.js new file mode 100644 index 000000000000..3cdfb2e91db1 --- /dev/null +++ b/Libraries/Utilities/UIManager.js @@ -0,0 +1,16 @@ +/** + * 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 UIManager + * @flow + */ +'use strict'; + +var UIManager = require('NativeModules').UIManager; + +module.exports = UIManager; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 29ff95205868..2264fa5c6657 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -78,6 +78,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { Settings: require('Settings'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), + UIManager: require('UIManager'), VibrationIOS: require('VibrationIOS'), // Plugins From 01a0facf33fac82fd37be92fa515e8559c4a7636 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 27 Nov 2015 06:52:29 -0800 Subject: [PATCH 0146/1411] Fixed onFocus/onBlur events for multiline TextInput Summary: public onFocus and onBlur were not firing for multiline TextInputs. Reviewed By: tadeuzagallo Differential Revision: D2699846 fb-gh-sync-id: 7e64309bc631a42a99f989f615fef927dc50217c --- Examples/UIExplorer/TextInputExample.ios.js | 88 ++++++++++----------- Libraries/Text/RCTTextField.m | 18 +++-- Libraries/Text/RCTTextView.m | 55 +++++++------ 3 files changed, 87 insertions(+), 74 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 471920a0b4d0..826139048cb5 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -301,50 +301,6 @@ exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Single and multi-line text inputs.'; exports.examples = [ - { - title: 'Multiline', - render: function() { - return ( - - - - - - - - - ); - } - }, - { - title: 'Attributed text', - render: function() { - return ; - } - }, { title: 'Auto-focus', render: function() { @@ -592,4 +548,48 @@ exports.examples = [ title: 'Blur on submit', render: function(): ReactElement { return ; }, }, + { + title: 'Multiline', + render: function() { + return ( + + + + + + + + + ); + } + }, + { + title: 'Attributed text', + render: function() { + return ; + } + }, ]; diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index 222d280e1576..de4fd867bd35 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -239,12 +239,19 @@ - (void)sendSelectionEvent } } -- (BOOL)becomeFirstResponder +- (void)reactWillMakeFirstResponder { _jsRequestingFirstResponder = YES; - BOOL result = [super becomeFirstResponder]; +} + +- (void)reactDidMakeFirstResponder +{ _jsRequestingFirstResponder = NO; - return result; +} + +- (BOOL)canBecomeFirstResponder +{ + return _jsRequestingFirstResponder; } - (BOOL)resignFirstResponder @@ -261,9 +268,4 @@ - (BOOL)resignFirstResponder return result; } -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - @end diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index f75a4c013ef1..412dd47acc06 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -22,6 +22,9 @@ @interface RCTUITextView : UITextView @end @implementation RCTUITextView +{ + BOOL _jsRequestingFirstResponder; +} - (void)paste:(id)sender { @@ -29,12 +32,26 @@ - (void)paste:(id)sender [super paste:sender]; } +- (void)reactWillMakeFirstResponder +{ + _jsRequestingFirstResponder = YES; +} + +- (BOOL)canBecomeFirstResponder +{ + return _jsRequestingFirstResponder; +} + +- (void)reactDidMakeFirstResponder +{ + _jsRequestingFirstResponder = NO; +} + @end @implementation RCTTextView { RCTEventDispatcher *_eventDispatcher; - BOOL _jsRequestingFirstResponder; NSString *_placeholder; UITextView *_placeholderView; UITextView *_textView; @@ -360,7 +377,7 @@ - (void)textViewDidBeginEditing:(UITextView *)textView [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus reactTag:self.reactTag - text:textView.text + text:nil key:nil eventCount:_nativeEventCount]; } @@ -385,28 +402,27 @@ - (void)textViewDidEndEditing:(UITextView *)textView text:textView.text key:nil eventCount:_nativeEventCount]; + + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur + reactTag:self.reactTag + text:nil + key:nil + eventCount:_nativeEventCount]; +} + +- (void)reactWillMakeFirstResponder +{ + [_textView reactWillMakeFirstResponder]; } - (BOOL)becomeFirstResponder { - _jsRequestingFirstResponder = YES; - BOOL result = [_textView becomeFirstResponder]; - _jsRequestingFirstResponder = NO; - return result; + return [_textView becomeFirstResponder]; } -- (BOOL)resignFirstResponder +- (void)reactDidMakeFirstResponder { - [super resignFirstResponder]; - BOOL result = [_textView resignFirstResponder]; - if (result) { - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur - reactTag:self.reactTag - text:_textView.text - key:nil - eventCount:_nativeEventCount]; - } - return result; + [_textView reactDidMakeFirstResponder]; } - (void)layoutSubviews @@ -415,11 +431,6 @@ - (void)layoutSubviews [self updateFrames]; } -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - - (UIFont *)defaultPlaceholderFont { return [UIFont systemFontOfSize:17]; From 710b443af1173b927d16efa9aa3f82020d4dfb8f Mon Sep 17 00:00:00 2001 From: Bram Date: Fri, 27 Nov 2015 07:15:28 -0800 Subject: [PATCH 0147/1411] bugfix #3997: react-native bundle not working on windows 10 Summary: fixes https://github.com/facebook/react-native/issues/3997 the root cause is in Mon, 09 Nov 2015 13:22:47 GMT ReactNativePackager:SocketServer uncaught error Error: listen EACCES C:\Users\donald\AppData\Local\Temp\react-packager-9248a9803ac72b509b389b456696850d This means that the socket server cannot create the socket. cfr https://gist.github.com/domenic/2790533 the socket name is not a valid windows socket name. Closes https://github.com/facebook/react-native/pull/4071 Reviewed By: mkonicek Differential Revision: D2699546 Pulled By: davidaurelio fb-gh-sync-id: 6c6494c14c42bb17506b8559001419c9f85e91e3 --- local-cli/bundle/saveAssets.js | 4 ++-- package.json | 1 + packager/react-packager/src/SocketInterface/index.js | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/local-cli/bundle/saveAssets.js b/local-cli/bundle/saveAssets.js index 1dd88512b4f2..f95cd4c166ae 100644 --- a/local-cli/bundle/saveAssets.js +++ b/local-cli/bundle/saveAssets.js @@ -8,11 +8,11 @@ */ 'use strict'; -const execFile = require('child_process').execFile; const fs = require('fs'); const getAssetDestPathAndroid = require('./getAssetDestPathAndroid'); const getAssetDestPathIOS = require('./getAssetDestPathIOS'); const log = require('../util/log').out('bundle'); +const mkdirp = require('mkdirp'); const path = require('path'); function saveAssets( @@ -70,7 +70,7 @@ function copyAll(filesToCopy) { function copy(src, dest, callback) { const destDir = path.dirname(dest); - execFile('mkdir', ['-p', destDir], err => { + mkdirp(destDir, err => { if (err) { return callback(err); } diff --git a/package.json b/package.json index b3f5727f70c1..5be50c910962 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "joi": "^6.6.1", "json5": "^0.4.0", "jstransform": "^11.0.3", + "mkdirp": "^0.5.0", "module-deps": "^3.9.1", "node-fetch": "^1.3.3", "opn": "^3.0.2", diff --git a/packager/react-packager/src/SocketInterface/index.js b/packager/react-packager/src/SocketInterface/index.js index c56026ead4c3..32e3688c333f 100644 --- a/packager/react-packager/src/SocketInterface/index.js +++ b/packager/react-packager/src/SocketInterface/index.js @@ -35,10 +35,17 @@ const SocketInterface = { } }); - const sockPath = path.join( + let sockPath = path.join( tmpdir, 'react-packager-' + hash.digest('hex') ); + if (process.platform === 'win32'){ + // on Windows, use a named pipe, convert sockPath into a valid pipe name + // based on https://gist.github.com/domenic/2790533 + sockPath = sockPath.replace(/^\//, '') + sockPath = sockPath.replace(/\//g, '-') + sockPath = '\\\\.\\pipe\\' + sockPath + } if (fs.existsSync(sockPath)) { var sock = net.connect(sockPath); From 599a130c9ac341796f14e4622cd610c4d5f6620e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Sagnes?= Date: Fri, 27 Nov 2015 08:36:00 -0800 Subject: [PATCH 0148/1411] Add perf tests to avoid decoding the bundle completely Reviewed By: jspahrsummers Differential Revision: D2574965 fb-gh-sync-id: 7bea6b3af04ba7e15471cc94a324a6c4a1d3614d --- React/Base/RCTBatchedBridge.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 89861dedaf53..8923d85ef6a7 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -198,6 +198,9 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad RCTPerformanceLoggerStart(RCTPLScriptDownload); NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil); + // Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out + (void)cookie; + RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source) { RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil); RCTPerformanceLoggerEnd(RCTPLScriptDownload); From fd0d9877685b5135c73fe5f6ba8fbed0722a81ab Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Sat, 28 Nov 2015 11:00:43 -0800 Subject: [PATCH 0149/1411] ShadowNodeHierarchyManager should be supplied externally to UIManagerModule Summary: public UIManagerModule should not be creating UIImplementation. Instead, UIImplementation instance should be supplied to it to allow plugging in different implementations. No functional changes. Reviewed By: astreet Differential Revision: D2464632 fb-gh-sync-id: e7372977c93ceb7ef5e8658e5ee7e8e87f52d851 --- .../facebook/react/CoreModulesPackage.java | 6 ++++- .../react/uimanager/UIImplementation.java | 7 +++--- .../react/uimanager/UIManagerModule.java | 22 ++++++++++++++----- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 02bd96d0dc0f..5dd7b1959e88 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -28,6 +28,7 @@ import com.facebook.react.modules.systeminfo.AndroidInfoModule; import com.facebook.react.uimanager.AppRegistry; import com.facebook.react.uimanager.ReactNative; +import com.facebook.react.uimanager.UIImplementation; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.debug.DebugComponentOwnershipModule; @@ -57,9 +58,12 @@ public List createNativeModules( Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createUIManagerModule"); UIManagerModule uiManagerModule; try { + List viewManagersList = mReactInstanceManager.createAllViewManagers( + catalystApplicationContext); uiManagerModule = new UIManagerModule( catalystApplicationContext, - mReactInstanceManager.createAllViewManagers(catalystApplicationContext)); + viewManagersList, + new UIImplementation(catalystApplicationContext, viewManagersList)); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 1718fe2bbab1..94b2307b1702 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -11,6 +11,7 @@ import javax.annotation.Nullable; import java.util.Arrays; +import java.util.List; import com.facebook.csslayout.CSSLayoutContext; import com.facebook.infer.annotation.Assertions; @@ -39,11 +40,11 @@ public class UIImplementation { private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer; private final int[] mMeasureBuffer = new int[4]; - public UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) { + public UIImplementation(ReactApplicationContext reactContext, List viewManagers) { + mViewManagers = new ViewManagerRegistry(viewManagers); mOperationsQueue = new UIViewOperationQueue( reactContext, - new NativeViewHierarchyManager(viewManagers)); - mViewManagers = viewManagers; + new NativeViewHierarchyManager(mViewManagers)); mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( mOperationsQueue, mShadowNodeRegistry); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 04724229bed3..59fd688e348b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -1,4 +1,4 @@ -/** + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * @@ -78,17 +78,27 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private int mNextRootViewTag = 1; private int mBatchId = 0; - public UIManagerModule(ReactApplicationContext reactContext, List viewManagerList) { + /** + * This contructor is temporarily here to workaround Sandcastle error. + */ + public UIManagerModule( + ReactApplicationContext reactContext, + List viewManagerList) { + this(reactContext, viewManagerList, new UIImplementation(reactContext, viewManagerList)); + } + + public UIManagerModule( + ReactApplicationContext reactContext, + List viewManagerList, + UIImplementation uiImplementation) { super(reactContext); mEventDispatcher = new EventDispatcher(reactContext); DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics(); DisplayMetricsHolder.setDisplayMetrics(displayMetrics); mModuleConstants = createConstants(displayMetrics, viewManagerList); - reactContext.addLifecycleEventListener(this); + mUIImplementation = uiImplementation; - mUIImplementation = new UIImplementation( - reactContext, - new ViewManagerRegistry(viewManagerList)); + reactContext.addLifecycleEventListener(this); } @Override From 3c3495739d72e93b5ef22635cc2b8dccd2d545f1 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 29 Nov 2015 10:04:07 +0530 Subject: [PATCH 0150/1411] Add docs on building from source --- docs/AndroidBuildingFromSource.md | 106 ++++++++++++++++++++++++++++++ docs/AndroidUIPerformance.md | 4 +- 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 docs/AndroidBuildingFromSource.md diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md new file mode 100644 index 000000000000..303c43dd26bc --- /dev/null +++ b/docs/AndroidBuildingFromSource.md @@ -0,0 +1,106 @@ +--- +id: android-building-from-source +title: Building React Native from source +layout: docs +category: Guides (Android) +permalink: docs/android-building-from-source +next: activityindicatorios +--- + +You will need to build React Native from source if you want to work on a new feature/bug fix, try out the latest features which are not released yet, or maintain your own fork with patches that cannot be merged to the core. + +## Prerequisites + +Assuming you have the Android SDK installed, run `android` to open the Android SDK Manager. + +Make sure you have the following installed: + +1. Android SDK version 23 (compileSdkVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle)) +2. SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle)]) +3. Android Support Repository >= 17 (for Android Support Library) +4. Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html)) + +Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK ` defined, or create a local.properties file in the root of your react-native checkout with the following contents: + +``` +sdk.dir=absolute_path_to_android_sdk +ndk.dir=absolute_path_to_android_ndk +``` + +Example: + +``` +sdk.dir=/Users/your_unix_name/android-sdk-macosx +ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e +``` + + +## Building the source + +1. Install `react-native` from your fork. For example, to install the master branch from the offcial repo, run the following: + +```sh +npm install --save github:facebook/react-native#master +``` + +Alternatively, you can clone the repo to your `node_modules` directory and run `npm install` inside the cloned repo. + +2. Add `gradle-download-task` as dependency in `andoid/build.gradle`: + +```gradle +... + dependencies { + classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'de.undercouch:gradle-download-task:2.0.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +... +``` + +3. Add the `:ReactAndroid` project in `andoid/settings.gradle`: + +```gradle +... +include ':ReactAndroid' + +project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid') +... +``` + +4. Modify your `android/app/build.gradle` to use the `:ReactAndroid` project instead of the pre-compiled library, e.g. - replace `compile 'com.facebook.react:react-native:0.16.+'` with `compile project(':ReactAndroid')`: + +```gradle +... +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.0.1' + + compile project(':ReactAndroid') + + ... +} +... +``` + +5. If you use 3rd-party React Native modules, modify your `android/app/build.gradle` to override their dependencies so that they don't bundle the pre-compiled library, e.g. - replace `compile project(':react-native-custom-module')` with: + +```gradle +compile(project(':react-native-custom-module')) { + exclude group: 'com.facebook.react', module: 'react-native' +} +``` + + +## Additional notes + +Building from source can take a long time, especially for the first build, as it needs to download ~200 MB of files and compile JSC etc. Every time you update the `react-native` version from your repo, the build directory may get deleted, and all the files are re-downloaded. To avoid this, you might want to change your build directory path by editing the `~/.gradle/init.gradle ` file: + +```gradle +gradle.projectsLoaded { + rootProject.allprojects { + buildDir = "/path/to/build/directory/${rootProject.name}/${project.name}" + } +} +``` diff --git a/docs/AndroidUIPerformance.md b/docs/AndroidUIPerformance.md index 927ba2131b68..399bd09eefe0 100644 --- a/docs/AndroidUIPerformance.md +++ b/docs/AndroidUIPerformance.md @@ -4,7 +4,7 @@ title: Profiling Android UI Performance layout: docs category: Guides (Android) permalink: docs/android-ui-performance.html -next: activityindicatorios +next: android-building-from-source --- We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve (and many times it's not native code's fault at all!). @@ -12,7 +12,7 @@ We try our best to deliver buttery-smooth UI performance by default, but sometim The first step for debugging this jank is to answer the fundamental question of where your time is being spent during each 16ms frame. For that, we'll be using a standard Android profiling tool called systrace. But first... > Make sure that JS dev mode is OFF! -> +> > You should see `__DEV__ === false, development-level warning are OFF, performance optimizations are ON` in your application logs (which you can view using `adb logcat`) ## Profiling with Systrace From c4f882a59fc0c2ef9a15e5d955f74899fef89e87 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 29 Nov 2015 10:13:06 +0530 Subject: [PATCH 0151/1411] [Docs] Syntax highlight gradle code --- docs/SignedAPKAndroid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/SignedAPKAndroid.md b/docs/SignedAPKAndroid.md index 231c82ac89d0..d35e95d82d9d 100644 --- a/docs/SignedAPKAndroid.md +++ b/docs/SignedAPKAndroid.md @@ -41,7 +41,7 @@ _Note: Once you publish the app on the Play Store, you will need to republish yo Edit the file `android/app/build.gradle` in your project folder and add the signing config, -``` +```gradle ... android { ... @@ -110,7 +110,7 @@ _**IMPORTANT**: Make sure to thoroughly test your app if you've enabled Proguard To enable Proguard, set `minifyEnabled` to `true`: -``` +```gradle ... android { ... From 489042456225e1a24a0555ca44fc651574c24941 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Sun, 29 Nov 2015 03:53:49 -0800 Subject: [PATCH 0152/1411] Allow to pass in `ImagePipelineConfig` to `FrescoModule` Summary: This allows an app to configure much more options in Fresco. public Reviewed By: astreet Differential Revision: D2700598 fb-gh-sync-id: e1ffff18bff270e14ef82c14f7bfeef984605738 --- .../react/modules/fresco/FrescoModule.java | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index 666acc2fe159..3f2302ab9ab4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -39,25 +39,26 @@ public class FrescoModule extends ReactContextBaseJavaModule implements ModuleDataCleaner.Cleanable { - @Nullable private RequestListener mRequestListener; - @Nullable private DiskCacheConfig mDiskCacheConfig; + private @Nullable ImagePipelineConfig mConfig; public FrescoModule(ReactApplicationContext reactContext) { - super(reactContext); + this(reactContext, getDefaultConfig(reactContext, null, null)); } public FrescoModule(ReactApplicationContext reactContext, RequestListener listener) { - super(reactContext); - mRequestListener = listener; + this(reactContext, getDefaultConfig(reactContext, listener, null)); } public FrescoModule( ReactApplicationContext reactContext, RequestListener listener, DiskCacheConfig diskCacheConfig) { + this(reactContext, getDefaultConfig(reactContext, listener, diskCacheConfig)); + } + + public FrescoModule(ReactApplicationContext reactContext, ImagePipelineConfig config) { super(reactContext); - mRequestListener = listener; - mDiskCacheConfig = diskCacheConfig; + mConfig = config; } @Override @@ -67,27 +68,9 @@ public void initialize() { // This code can be removed if using Fresco from Maven rather than from source SoLoaderShim.setHandler(new FrescoHandler()); - HashSet requestListeners = new HashSet<>(); - requestListeners.add(new SystraceRequestListener()); - if (mRequestListener != null) { - requestListeners.add(mRequestListener); - } - - Context context = this.getReactApplicationContext().getApplicationContext(); - OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); - ImagePipelineConfig.Builder builder = - OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient); - - builder - .setDownsampleEnabled(false) - .setRequestListeners(requestListeners); - - if (mDiskCacheConfig != null) { - builder.setMainDiskCacheConfig(mDiskCacheConfig); - } - - ImagePipelineConfig config = builder.build(); - Fresco.initialize(context, config); + Context context = getReactApplicationContext().getApplicationContext(); + Fresco.initialize(context, mConfig); + mConfig = null; } @Override @@ -105,6 +88,31 @@ public void clearSensitiveData() { imagePipelineFactory.getSmallImageDiskStorageCache().clearAll(); } + private static ImagePipelineConfig getDefaultConfig( + Context context, + @Nullable RequestListener listener, + @Nullable DiskCacheConfig diskCacheConfig) { + HashSet requestListeners = new HashSet<>(); + requestListeners.add(new SystraceRequestListener()); + if (listener != null) { + requestListeners.add(listener); + } + + OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); + ImagePipelineConfig.Builder builder = + OkHttpImagePipelineConfigFactory.newBuilder(context.getApplicationContext(), okHttpClient); + + builder + .setDownsampleEnabled(false) + .setRequestListeners(requestListeners); + + if (diskCacheConfig != null) { + builder.setMainDiskCacheConfig(diskCacheConfig); + } + + return builder.build(); + } + private static class FrescoHandler implements SoLoaderShim.Handler { @Override public void loadLibrary(String libraryName) { From 593a45e31966120318102024615549b337066b13 Mon Sep 17 00:00:00 2001 From: olivier notteghem Date: Sun, 29 Nov 2015 16:26:13 -0800 Subject: [PATCH 0153/1411] LayoutAnimation support for Android RN Reviewed By: astreet Differential Revision: D2217731 fb-gh-sync-id: d990af4b630995f95433690d5dcf510382dc34d2 --- Examples/UIExplorer/ListViewPagingExample.js | 10 ++ .../uimanager/NativeViewHierarchyManager.java | 28 ++++- .../react/uimanager/UIManagerModule.java | 39 ++++++- .../react/uimanager/UIViewOperationQueue.java | 42 +++++++ .../AbstractLayoutAnimation.java | 108 ++++++++++++++++++ .../layoutanimation/AnimatedPropertyType.java | 32 ++++++ .../layoutanimation/BaseLayoutAnimation.java | 48 ++++++++ .../layoutanimation/HandlesLayout.java | 10 ++ .../layoutanimation/InterpolatorType.java | 34 ++++++ .../LayoutAnimationController.java | 94 +++++++++++++++ .../layoutanimation/LayoutAnimationType.java | 22 ++++ .../LayoutCreateAnimation.java | 15 +++ .../LayoutUpdateAnimation.java | 42 +++++++ .../layoutanimation/OpacityAnimation.java | 66 +++++++++++ .../PositionAndSizeAnimation.java | 52 +++++++++ .../SimpleSpringInterpolator.java | 20 ++++ .../react/views/view/ReactViewGroup.java | 11 +- 17 files changed, 668 insertions(+), 5 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js index b82f272c9dbb..7d0ac98f2bfd 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/ListViewPagingExample.js @@ -11,6 +11,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + * @provides ListViewPagingExample * @flow */ 'use strict'; @@ -26,6 +27,11 @@ var { View, } = React; +var NativeModules = require('NativeModules'); +var { + UIManager, +} = NativeModules; + var PAGE_SIZE = 4; var THUMB_URLS = [ 'Thumbnails/like.png', @@ -48,6 +54,10 @@ var Thumb = React.createClass({ getInitialState: function() { return {thumbIndex: this._getThumbIdx(), dir: 'row'}; }, + componentWillMount: function() { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); + }, _getThumbIdx: function() { return Math.floor(Math.random() * THUMB_URLS.length); }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index fddd7047820f..744279880b53 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -29,9 +29,11 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.JSResponderHandler; +import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between @@ -66,6 +68,9 @@ private final ViewManagerRegistry mViewManagers; private final JSResponderHandler mJSResponderHandler = new JSResponderHandler(); private final RootViewManager mRootViewManager = new RootViewManager(); + private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController(); + + private boolean mLayoutAnimationEnabled; public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { mAnimationRegistry = new AnimationRegistry(); @@ -80,6 +85,10 @@ public AnimationRegistry getAnimationRegistry() { return mAnimationRegistry; } + public void setLayoutAnimationEnabled(boolean enabled) { + mLayoutAnimationEnabled = enabled; + } + public void updateProperties(int tag, CatalystStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); @@ -154,8 +163,17 @@ public void updateLayout( } if (parentViewGroupManager != null && !parentViewGroupManager.needsCustomLayoutForChildren()) { - viewToUpdate.layout(x, y, x + width, y + height); + updateLayout(viewToUpdate, x, y, width, height); } + } else { + updateLayout(viewToUpdate, x, y, width, height); + } + } + + private void updateLayout(View viewToUpdate, int x, int y, int width, int height) { + if (mLayoutAnimationEnabled && + mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) { + mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height); } else { viewToUpdate.layout(x, y, x + width, y + height); } @@ -470,6 +488,14 @@ public void clearJSResponder() { mJSResponderHandler.clearJSResponder(); } + void configureLayoutAnimation(final ReadableMap config) { + mLayoutAnimator.initializeFromConfig(config); + } + + void clearLayoutAnimation() { + mLayoutAnimator.reset(); + } + /* package */ void startAnimationForNativeView( int reactTag, Animation animation, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 59fd688e348b..adfd7f3ff906 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -383,12 +383,45 @@ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Cal mUIImplementation.showPopupMenu(reactTag, items, error, success); } + @ReactMethod + public void setMainScrollViewTag(int reactTag) { + // TODO(6588266): Implement if required + } + + /** + * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled + * explicitly in order to avoid regression in existing application written for iOS using this API. + * + * Warning : This method will be removed in future version of React Native, and layout animation + * will be enabled by default, so always check for its existence before invoking it. + * + * TODO(9139831) : remove this method once layout animation is fully stable. + * + * @param enabled whether layout animation is enabled or not + */ + @ReactMethod + public void setLayoutAnimationEnabledExperimental(boolean enabled) { + mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled); + } + + /** + * Configure an animation to be used for the native layout changes, and native views + * creation. The animation will only apply during the current batch operations. + * + * TODO(7728153) : animating view deletion is currently not supported. + * TODO(7613721) : callbacks are not supported, this feature will likely be killed. + * + * @param config the configuration of the animation for view addition/removal/update. + * @param success will be called when the animation completes, or when the animation get + * interrupted. In this case, callback parameter will be false. + * @param error will be called if there was an error processing the animation + */ @ReactMethod public void configureNextLayoutAnimation( ReadableMap config, - Callback successCallback, - Callback errorCallback) { - // TODO(6588266): Implement if required + Callback success, + Callback error) { + mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index ed507fd3ec63..a8dc4aefdeeb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; @@ -322,6 +323,32 @@ public void execute() { } } + private class SetLayoutAnimationEnabledOperation implements UIOperation { + private final boolean mEnabled; + + private SetLayoutAnimationEnabledOperation(final boolean enabled) { + mEnabled = enabled; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled); + } + } + + private class ConfigureLayoutAnimationOperation implements UIOperation { + private final ReadableMap mConfig; + + private ConfigureLayoutAnimationOperation(final ReadableMap config) { + mConfig = config; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.configureLayoutAnimation(mConfig); + } + } + private final class MeasureOperation implements UIOperation { private final int mReactTag; @@ -576,6 +603,18 @@ public void enqueueRemoveAnimation(int animationID) { mOperations.add(new RemoveAnimationOperation(animationID)); } + public void enqueueSetLayoutAnimationEnabled( + final boolean enabled) { + mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); + } + + public void enqueueConfigureLayoutAnimation( + final ReadableMap config, + final Callback onSuccess, + final Callback onError) { + mOperations.add(new ConfigureLayoutAnimationOperation(config)); + } + public void enqueueMeasure( final int reactTag, final Callback callback) { @@ -672,6 +711,9 @@ public void doFrameGuarded(long frameTimeNanos) { mDispatchUIRunnables.get(i).run(); } mDispatchUIRunnables.clear(); + + // Clear layout animation, as animation only apply to current UI operations batch. + mNativeViewHierarchyManager.clearLayoutAnimation(); } ReactChoreographer.getInstance().postFrameCallback( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java new file mode 100644 index 000000000000..c29d7ae0bef9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java @@ -0,0 +1,108 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; + +import java.util.Map; + +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.IllegalViewOperationException; + +/** + * Class responsible for parsing and converting layout animation data into native {@link Animation} + * in order to animate layout when a valid configuration has been supplied by the application. + */ +/* package */ abstract class AbstractLayoutAnimation { + + // Forces animation to be playing 10x slower, used for debug purposes. + private static final boolean SLOWDOWN_ANIMATION_MODE = false; + + abstract boolean isValid(); + + /** + * Create an animation object for the current animation type, based on the view and final screen + * coordinates. If the application-supplied configuraiton does not specify an animation definition + * for this types, or if the animation definition is invalid, returns null. + */ + abstract @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height); + + private static final Map INTERPOLATOR = MapBuilder.of( + InterpolatorType.LINEAR, new LinearInterpolator(), + InterpolatorType.EASE_IN, new AccelerateInterpolator(), + InterpolatorType.EASE_OUT, new DecelerateInterpolator(), + InterpolatorType.EASE_IN_EASE_OUT, new AccelerateDecelerateInterpolator(), + InterpolatorType.SPRING, new SimpleSpringInterpolator()); + + private @Nullable Interpolator mInterpolator; + private int mDelayMs; + + protected @Nullable AnimatedPropertyType mAnimatedProperty; + protected int mDurationMs; + + public void reset() { + mAnimatedProperty = null; + mDurationMs = 0; + mDelayMs = 0; + mInterpolator = null; + } + + public void initializeFromConfig(ReadableMap data, int globalDuration) { + mAnimatedProperty = data.hasKey("property") ? + AnimatedPropertyType.fromString(data.getString("property")) : null; + mDurationMs = data.hasKey("duration") ? data.getInt("duration") : globalDuration; + mDelayMs = data.hasKey("delay") ? data.getInt("delay") : 0; + mInterpolator = data.hasKey("type") ? + getInterpolator(InterpolatorType.fromString(data.getString("type"))) : null; + + if (!isValid()) { + throw new IllegalViewOperationException("Invalid layout animation : " + data); + } + } + + /** + * Create an animation object to be used to animate the view, based on the animation config + * supplied at initialization time and the new view position and size. + * + * @param view the view to create the animation for + * @param x the new X position for the view + * @param y the new Y position for the view + * @param width the new width value for the view + * @param height the new height value for the view + */ + public final @Nullable Animation createAnimation( + View view, + int x, + int y, + int width, + int height) { + if (!isValid()) { + return null; + } + Animation animation = createAnimationImpl(view, x, y, width, height); + if (animation != null) { + int slowdownFactor = SLOWDOWN_ANIMATION_MODE ? 10 : 1; + animation.setDuration(mDurationMs * slowdownFactor); + animation.setStartOffset(mDelayMs * slowdownFactor); + animation.setInterpolator(mInterpolator); + } + return animation; + } + + private static Interpolator getInterpolator(InterpolatorType type) { + Interpolator interpolator = INTERPOLATOR.get(type); + if (interpolator == null) { + throw new IllegalArgumentException("Missing interpolator for type : " + type); + } + return interpolator; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java new file mode 100644 index 000000000000..51a9d246c061 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java @@ -0,0 +1,32 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different view properties that can be used when animating layout for + * view creation. + */ +/* package */ enum AnimatedPropertyType { + OPACITY("opacity"), + SCALE_XY("scaleXY"); + + private final String mName; + + private AnimatedPropertyType(String name) { + mName = name; + } + + public static AnimatedPropertyType fromString(String name) { + for (AnimatedPropertyType property : AnimatedPropertyType.values()) { + if (property.toString().equalsIgnoreCase(name)) { + return property; + } + } + throw new IllegalArgumentException("Unsupported animated property : " + name); + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java new file mode 100644 index 000000000000..9d3d0f45aa79 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.ScaleAnimation; + +import com.facebook.react.uimanager.IllegalViewOperationException; + +/** + * Class responsible for default layout animation, i.e animation of view creation and deletion. + */ +/* package */ abstract class BaseLayoutAnimation extends AbstractLayoutAnimation { + + abstract boolean isReverse(); + + @Override + boolean isValid() { + return mDurationMs > 0 && mAnimatedProperty != null; + } + + @Override + Animation createAnimationImpl(View view, int x, int y, int width, int height) { + float fromValue = isReverse() ? 1.0f : 0.0f; + float toValue = isReverse() ? 0.0f : 1.0f; + if (mAnimatedProperty != null) { + switch (mAnimatedProperty) { + case OPACITY: + return new OpacityAnimation(view, fromValue, toValue); + case SCALE_XY: + return new ScaleAnimation( + fromValue, + toValue, + fromValue, + toValue, + Animation.RELATIVE_TO_PARENT, + .5f, + Animation.RELATIVE_TO_PARENT, + .5f); + default: + throw new IllegalViewOperationException( + "Missing animation for property : " + mAnimatedProperty); + } + } + throw new IllegalViewOperationException("Missing animated property from animation config"); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java new file mode 100644 index 000000000000..9f3c907e97d0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java @@ -0,0 +1,10 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Marker interface to indicate a given animation type takes care of updating the view layout. + */ +/* package */ interface HandleLayout { + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java new file mode 100644 index 000000000000..f59675a8fbe1 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different interpolators that can be used in layout animation configuration. + */ +/* package */ enum InterpolatorType { + LINEAR("linear"), + EASE_IN("easeIn"), + EASE_OUT("easeOut"), + EASE_IN_EASE_OUT("easeInEaseOut"), + SPRING("spring"); + + private final String mName; + + private InterpolatorType(String name) { + mName = name; + } + + public static InterpolatorType fromString(String name) { + for (InterpolatorType type : InterpolatorType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + throw new IllegalArgumentException("Unsupported interpolation type : " + name); + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java new file mode 100644 index 000000000000..d3522947fd36 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java @@ -0,0 +1,94 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +import android.view.View; +import android.view.animation.Animation; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UiThreadUtil; + +/** + * Class responsible for animation layout changes, if a valid layout animation config has been + * supplied. If not animation is available, layout change is applied immediately instead of + * performing an animation. + * + * TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled. + */ +@NotThreadSafe +public class LayoutAnimationController { + + private static final boolean ENABLED = true; + + private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation(); + private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation(); + private boolean mShouldAnimateLayout; + + public void initializeFromConfig(final @Nullable ReadableMap config) { + if (!ENABLED) { + return; + } + + if (config == null) { + reset(); + return; + } + + mShouldAnimateLayout = false; + int globalDuration = config.hasKey("duration") ? config.getInt("duration") : 0; + if (config.hasKey(LayoutAnimationType.CREATE.toString())) { + mLayoutCreateAnimation.initializeFromConfig( + config.getMap(LayoutAnimationType.CREATE.toString()), globalDuration); + mShouldAnimateLayout = true; + } + if (config.hasKey(LayoutAnimationType.UPDATE.toString())) { + mLayoutUpdateAnimation.initializeFromConfig( + config.getMap(LayoutAnimationType.UPDATE.toString()), globalDuration); + mShouldAnimateLayout = true; + } + } + + public void reset() { + mLayoutCreateAnimation.reset(); + mLayoutUpdateAnimation.reset(); + mShouldAnimateLayout = false; + } + + public boolean shouldAnimateLayout(View viewToAnimate) { + // if view parent is null, skip animation: view have been clipped, we don't want animation to + // resume when view is re-attached to parent, which is the standard android animation behavior. + return mShouldAnimateLayout && viewToAnimate.getParent() != null; + } + + /** + * Update layout of given view, via immediate update or animation depending on the current batch + * layout animation configuration supplied during initialization. + * + * @param view the view to update layout of + * @param x the new X position for the view + * @param y the new Y position for the view + * @param width the new width value for the view + * @param height the new height value for the view + */ + public void applyLayoutUpdate(View view, int x, int y, int width, int height) { + UiThreadUtil.assertOnUiThread(); + + // Determine which animation to use : if view is initially invisible, use create animation. + // If view is becoming invisible, use delete animation. Otherwise, use update animation. + // This approach is easier than maintaining a list of tags for recently created/deleted views. + AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ? + mLayoutCreateAnimation : + mLayoutUpdateAnimation; + + Animation animation = layoutAnimation.createAnimation(view, x, y, width, height); + if (animation == null || !(animation instanceof HandleLayout)) { + view.layout(x, y, x + width, y + height); + } + if (animation != null) { + view.startAnimation(animation); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java new file mode 100644 index 000000000000..0f3d9d7fdb2b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java @@ -0,0 +1,22 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different animation type that can be specified in layout animation config. + */ +/* package */ enum LayoutAnimationType { + CREATE("create"), + UPDATE("update"); + + private final String mName; + + private LayoutAnimationType(String name) { + mName = name; + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java new file mode 100644 index 000000000000..a989e625c675 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java @@ -0,0 +1,15 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Class responsible for handling layout view creation animation, applied to view whenever a + * valid config was supplied for the layout animation of CREATE type. + */ +/* package */ class LayoutCreateAnimation extends BaseLayoutAnimation { + + @Override + boolean isReverse() { + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java new file mode 100644 index 000000000000..8189716f729d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java @@ -0,0 +1,42 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; + +/** + * Class responsible for handling layout update animation, applied to view whenever a valid config + * was supplied for the layout animation of UPDATE type. + */ +/* package */ class LayoutUpdateAnimation extends AbstractLayoutAnimation { + + // We are currently not enabling translation GPU-accelerated animated, as it creates odd + // artifacts with native react scrollview. This needs to be investigated. + private static final boolean USE_TRANSLATE_ANIMATION = false; + + @Override + boolean isValid() { + return mDurationMs > 0; + } + + @Override + @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height) { + boolean animateLocation = view.getX() != x || view.getY() != y; + boolean animateSize = view.getWidth() != width || view.getHeight() != height; + if (!animateLocation && !animateSize) { + return null; + } else if (animateLocation && !animateSize && USE_TRANSLATE_ANIMATION) { + // Use GPU-accelerated animation, however we loose the ability to resume interrupted + // animation where it was left off. We may be able to listen to animation interruption + // and set the layout manually in this case, so that next animation kicks off smoothly. + return new TranslateAnimation(view.getX() - x, 0, view.getY() - y, 0); + } else { + // Animation is sub-optimal for perf, but scale transformation can't be use in this case. + return new PositionAndSizeAnimation(view, x, y, width, height); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java new file mode 100644 index 000000000000..a7ad690023c5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java @@ -0,0 +1,66 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Animation responsible for updating opacity of a view. It should ideally use hardware texture + * to optimize rendering performances. + */ +/* package */ class OpacityAnimation extends Animation { + + static class OpacityAnimationListener implements AnimationListener { + + private final View mView; + private boolean mLayerTypeChanged = false; + + public OpacityAnimationListener(View view) { + mView = view; + } + + @Override + public void onAnimationStart(Animation animation) { + if (mView.hasOverlappingRendering() && + mView.getLayerType() == View.LAYER_TYPE_NONE) { + mLayerTypeChanged = true; + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + } + + @Override + public void onAnimationEnd(Animation animation) { + if (mLayerTypeChanged) { + mView.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + // do nothing + } + } + + private final View mView; + private final float mStartOpacity, mDeltaOpacity; + + public OpacityAnimation(View view, float startOpacity, float endOpacity) { + mView = view; + mStartOpacity = startOpacity; + mDeltaOpacity = endOpacity - startOpacity; + + setAnimationListener(new OpacityAnimationListener(view)); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + mView.setAlpha(mStartOpacity + mDeltaOpacity * interpolatedTime); + } + + @Override + public boolean willChangeBounds() { + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java new file mode 100644 index 000000000000..2adf81a6bb6c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java @@ -0,0 +1,52 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Animation responsible for updating size and position of a view. We can't use scaling as view + * content may not necessarily stretch. As a result, this approach is inefficient because of + * layout passes occurring on every frame. + * What we might want to try to do instead is use a combined ScaleAnimation and TranslateAnimation. + */ +/* package */ class PositionAndSizeAnimation extends Animation implements HandleLayout { + + private final View mView; + private final float mStartX, mStartY, mDeltaX, mDeltaY; + private final int mStartWidth, mStartHeight, mDeltaWidth, mDeltaHeight; + + public PositionAndSizeAnimation(View view, int x, int y, int width, int height) { + mView = view; + + mStartX = view.getX(); + mStartY = view.getY(); + mStartWidth = view.getWidth(); + mStartHeight = view.getHeight(); + + mDeltaX = x - mStartX; + mDeltaY = y - mStartY; + mDeltaWidth = width - mStartWidth; + mDeltaHeight = height - mStartHeight; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float newX = mStartX + mDeltaX * interpolatedTime; + float newY = mStartY + mDeltaY * interpolatedTime; + float newWidth = mStartWidth + mDeltaWidth * interpolatedTime; + float newHeight = mStartHeight + mDeltaHeight * interpolatedTime; + mView.layout( + Math.round(newX), + Math.round(newY), + Math.round(newX + newWidth), + Math.round(newY + newHeight)); + } + + @Override + public boolean willChangeBounds() { + return true; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java new file mode 100644 index 000000000000..9696fe90a1a3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java @@ -0,0 +1,20 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.animation.Interpolator; + +/** + * Simple spring interpolator + */ +//TODO(7613736): Improve spring interpolator with friction and damping variable support +/* package */ class SimpleSpringInterpolator implements Interpolator { + + private static final float FACTOR = 0.5f; + + @Override + public float getInterpolation(float input) { + return (float) + (1 + Math.pow(2, -10 * input) * Math.sin((input - FACTOR / 4) * Math.PI * 2 / FACTOR)); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 134b6492b9bf..9e2728db4e1e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -16,6 +16,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.view.animation.Animation; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -286,7 +287,15 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa boolean intersects = clippingRect .intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom); boolean needUpdateClippingRecursive = false; - if (!intersects && child.getParent() != null) { + // We never want to clip children that are being animated, as this can easily break layout : + // when layout animation changes size and/or position of views contained inside a listview that + // clips offscreen children, we need to ensure that, when view exits the viewport, final size + // and position is set prior to removing the view from its listview parent. + // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll, + // it won't be size and located properly. + Animation animation = child.getAnimation(); + boolean isAnimating = animation != null && !animation.hasEnded(); + if (!intersects && child.getParent() != null && !isAnimating) { // We can try saving on invalidate call here as the view that we remove is out of visible area // therefore invalidation is not necessary. super.removeViewsInLayout(idx - clippedSoFar, 1); From e8e7a2db57c26923522390deb9200fbb678b4cce Mon Sep 17 00:00:00 2001 From: Der-Nien Lee Date: Sun, 29 Nov 2015 17:22:04 -0800 Subject: [PATCH 0154/1411] @build-break revert of D2217731 Differential Revision: D2702368 fb-gh-sync-id: 64f53168610c5bf5f3dc22cd7e4dd6b4bb620b4c --- Examples/UIExplorer/ListViewPagingExample.js | 10 -- .../uimanager/NativeViewHierarchyManager.java | 28 +---- .../react/uimanager/UIManagerModule.java | 39 +------ .../react/uimanager/UIViewOperationQueue.java | 42 ------- .../AbstractLayoutAnimation.java | 108 ------------------ .../layoutanimation/AnimatedPropertyType.java | 32 ------ .../layoutanimation/BaseLayoutAnimation.java | 48 -------- .../layoutanimation/HandlesLayout.java | 10 -- .../layoutanimation/InterpolatorType.java | 34 ------ .../LayoutAnimationController.java | 94 --------------- .../layoutanimation/LayoutAnimationType.java | 22 ---- .../LayoutCreateAnimation.java | 15 --- .../LayoutUpdateAnimation.java | 42 ------- .../layoutanimation/OpacityAnimation.java | 66 ----------- .../PositionAndSizeAnimation.java | 52 --------- .../SimpleSpringInterpolator.java | 20 ---- .../react/views/view/ReactViewGroup.java | 11 +- 17 files changed, 5 insertions(+), 668 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js index 7d0ac98f2bfd..b82f272c9dbb 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/ListViewPagingExample.js @@ -11,7 +11,6 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * @provides ListViewPagingExample * @flow */ 'use strict'; @@ -27,11 +26,6 @@ var { View, } = React; -var NativeModules = require('NativeModules'); -var { - UIManager, -} = NativeModules; - var PAGE_SIZE = 4; var THUMB_URLS = [ 'Thumbnails/like.png', @@ -54,10 +48,6 @@ var Thumb = React.createClass({ getInitialState: function() { return {thumbIndex: this._getThumbIdx(), dir: 'row'}; }, - componentWillMount: function() { - UIManager.setLayoutAnimationEnabledExperimental && - UIManager.setLayoutAnimationEnabledExperimental(true); - }, _getThumbIdx: function() { return Math.floor(Math.random() * THUMB_URLS.length); }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 744279880b53..fddd7047820f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -29,11 +29,9 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.JSResponderHandler; -import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between @@ -68,9 +66,6 @@ private final ViewManagerRegistry mViewManagers; private final JSResponderHandler mJSResponderHandler = new JSResponderHandler(); private final RootViewManager mRootViewManager = new RootViewManager(); - private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController(); - - private boolean mLayoutAnimationEnabled; public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { mAnimationRegistry = new AnimationRegistry(); @@ -85,10 +80,6 @@ public AnimationRegistry getAnimationRegistry() { return mAnimationRegistry; } - public void setLayoutAnimationEnabled(boolean enabled) { - mLayoutAnimationEnabled = enabled; - } - public void updateProperties(int tag, CatalystStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); @@ -163,17 +154,8 @@ public void updateLayout( } if (parentViewGroupManager != null && !parentViewGroupManager.needsCustomLayoutForChildren()) { - updateLayout(viewToUpdate, x, y, width, height); + viewToUpdate.layout(x, y, x + width, y + height); } - } else { - updateLayout(viewToUpdate, x, y, width, height); - } - } - - private void updateLayout(View viewToUpdate, int x, int y, int width, int height) { - if (mLayoutAnimationEnabled && - mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) { - mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height); } else { viewToUpdate.layout(x, y, x + width, y + height); } @@ -488,14 +470,6 @@ public void clearJSResponder() { mJSResponderHandler.clearJSResponder(); } - void configureLayoutAnimation(final ReadableMap config) { - mLayoutAnimator.initializeFromConfig(config); - } - - void clearLayoutAnimation() { - mLayoutAnimator.reset(); - } - /* package */ void startAnimationForNativeView( int reactTag, Animation animation, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index adfd7f3ff906..59fd688e348b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -383,45 +383,12 @@ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Cal mUIImplementation.showPopupMenu(reactTag, items, error, success); } - @ReactMethod - public void setMainScrollViewTag(int reactTag) { - // TODO(6588266): Implement if required - } - - /** - * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled - * explicitly in order to avoid regression in existing application written for iOS using this API. - * - * Warning : This method will be removed in future version of React Native, and layout animation - * will be enabled by default, so always check for its existence before invoking it. - * - * TODO(9139831) : remove this method once layout animation is fully stable. - * - * @param enabled whether layout animation is enabled or not - */ - @ReactMethod - public void setLayoutAnimationEnabledExperimental(boolean enabled) { - mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled); - } - - /** - * Configure an animation to be used for the native layout changes, and native views - * creation. The animation will only apply during the current batch operations. - * - * TODO(7728153) : animating view deletion is currently not supported. - * TODO(7613721) : callbacks are not supported, this feature will likely be killed. - * - * @param config the configuration of the animation for view addition/removal/update. - * @param success will be called when the animation completes, or when the animation get - * interrupted. In this case, callback parameter will be false. - * @param error will be called if there was an error processing the animation - */ @ReactMethod public void configureNextLayoutAnimation( ReadableMap config, - Callback success, - Callback error) { - mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error); + Callback successCallback, + Callback errorCallback) { + // TODO(6588266): Implement if required } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index a8dc4aefdeeb..ed507fd3ec63 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -23,7 +23,6 @@ import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; @@ -323,32 +322,6 @@ public void execute() { } } - private class SetLayoutAnimationEnabledOperation implements UIOperation { - private final boolean mEnabled; - - private SetLayoutAnimationEnabledOperation(final boolean enabled) { - mEnabled = enabled; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled); - } - } - - private class ConfigureLayoutAnimationOperation implements UIOperation { - private final ReadableMap mConfig; - - private ConfigureLayoutAnimationOperation(final ReadableMap config) { - mConfig = config; - } - - @Override - public void execute() { - mNativeViewHierarchyManager.configureLayoutAnimation(mConfig); - } - } - private final class MeasureOperation implements UIOperation { private final int mReactTag; @@ -603,18 +576,6 @@ public void enqueueRemoveAnimation(int animationID) { mOperations.add(new RemoveAnimationOperation(animationID)); } - public void enqueueSetLayoutAnimationEnabled( - final boolean enabled) { - mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); - } - - public void enqueueConfigureLayoutAnimation( - final ReadableMap config, - final Callback onSuccess, - final Callback onError) { - mOperations.add(new ConfigureLayoutAnimationOperation(config)); - } - public void enqueueMeasure( final int reactTag, final Callback callback) { @@ -711,9 +672,6 @@ public void doFrameGuarded(long frameTimeNanos) { mDispatchUIRunnables.get(i).run(); } mDispatchUIRunnables.clear(); - - // Clear layout animation, as animation only apply to current UI operations batch. - mNativeViewHierarchyManager.clearLayoutAnimation(); } ReactChoreographer.getInstance().postFrameCallback( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java deleted file mode 100644 index c29d7ae0bef9..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import javax.annotation.Nullable; - -import java.util.Map; - -import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; - -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.IllegalViewOperationException; - -/** - * Class responsible for parsing and converting layout animation data into native {@link Animation} - * in order to animate layout when a valid configuration has been supplied by the application. - */ -/* package */ abstract class AbstractLayoutAnimation { - - // Forces animation to be playing 10x slower, used for debug purposes. - private static final boolean SLOWDOWN_ANIMATION_MODE = false; - - abstract boolean isValid(); - - /** - * Create an animation object for the current animation type, based on the view and final screen - * coordinates. If the application-supplied configuraiton does not specify an animation definition - * for this types, or if the animation definition is invalid, returns null. - */ - abstract @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height); - - private static final Map INTERPOLATOR = MapBuilder.of( - InterpolatorType.LINEAR, new LinearInterpolator(), - InterpolatorType.EASE_IN, new AccelerateInterpolator(), - InterpolatorType.EASE_OUT, new DecelerateInterpolator(), - InterpolatorType.EASE_IN_EASE_OUT, new AccelerateDecelerateInterpolator(), - InterpolatorType.SPRING, new SimpleSpringInterpolator()); - - private @Nullable Interpolator mInterpolator; - private int mDelayMs; - - protected @Nullable AnimatedPropertyType mAnimatedProperty; - protected int mDurationMs; - - public void reset() { - mAnimatedProperty = null; - mDurationMs = 0; - mDelayMs = 0; - mInterpolator = null; - } - - public void initializeFromConfig(ReadableMap data, int globalDuration) { - mAnimatedProperty = data.hasKey("property") ? - AnimatedPropertyType.fromString(data.getString("property")) : null; - mDurationMs = data.hasKey("duration") ? data.getInt("duration") : globalDuration; - mDelayMs = data.hasKey("delay") ? data.getInt("delay") : 0; - mInterpolator = data.hasKey("type") ? - getInterpolator(InterpolatorType.fromString(data.getString("type"))) : null; - - if (!isValid()) { - throw new IllegalViewOperationException("Invalid layout animation : " + data); - } - } - - /** - * Create an animation object to be used to animate the view, based on the animation config - * supplied at initialization time and the new view position and size. - * - * @param view the view to create the animation for - * @param x the new X position for the view - * @param y the new Y position for the view - * @param width the new width value for the view - * @param height the new height value for the view - */ - public final @Nullable Animation createAnimation( - View view, - int x, - int y, - int width, - int height) { - if (!isValid()) { - return null; - } - Animation animation = createAnimationImpl(view, x, y, width, height); - if (animation != null) { - int slowdownFactor = SLOWDOWN_ANIMATION_MODE ? 10 : 1; - animation.setDuration(mDurationMs * slowdownFactor); - animation.setStartOffset(mDelayMs * slowdownFactor); - animation.setInterpolator(mInterpolator); - } - return animation; - } - - private static Interpolator getInterpolator(InterpolatorType type) { - Interpolator interpolator = INTERPOLATOR.get(type); - if (interpolator == null) { - throw new IllegalArgumentException("Missing interpolator for type : " + type); - } - return interpolator; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java deleted file mode 100644 index 51a9d246c061..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -/** - * Enum representing the different view properties that can be used when animating layout for - * view creation. - */ -/* package */ enum AnimatedPropertyType { - OPACITY("opacity"), - SCALE_XY("scaleXY"); - - private final String mName; - - private AnimatedPropertyType(String name) { - mName = name; - } - - public static AnimatedPropertyType fromString(String name) { - for (AnimatedPropertyType property : AnimatedPropertyType.values()) { - if (property.toString().equalsIgnoreCase(name)) { - return property; - } - } - throw new IllegalArgumentException("Unsupported animated property : " + name); - } - - @Override - public String toString() { - return mName; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java deleted file mode 100644 index 9d3d0f45aa79..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.ScaleAnimation; - -import com.facebook.react.uimanager.IllegalViewOperationException; - -/** - * Class responsible for default layout animation, i.e animation of view creation and deletion. - */ -/* package */ abstract class BaseLayoutAnimation extends AbstractLayoutAnimation { - - abstract boolean isReverse(); - - @Override - boolean isValid() { - return mDurationMs > 0 && mAnimatedProperty != null; - } - - @Override - Animation createAnimationImpl(View view, int x, int y, int width, int height) { - float fromValue = isReverse() ? 1.0f : 0.0f; - float toValue = isReverse() ? 0.0f : 1.0f; - if (mAnimatedProperty != null) { - switch (mAnimatedProperty) { - case OPACITY: - return new OpacityAnimation(view, fromValue, toValue); - case SCALE_XY: - return new ScaleAnimation( - fromValue, - toValue, - fromValue, - toValue, - Animation.RELATIVE_TO_PARENT, - .5f, - Animation.RELATIVE_TO_PARENT, - .5f); - default: - throw new IllegalViewOperationException( - "Missing animation for property : " + mAnimatedProperty); - } - } - throw new IllegalViewOperationException("Missing animated property from animation config"); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java deleted file mode 100644 index 9f3c907e97d0..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -/** - * Marker interface to indicate a given animation type takes care of updating the view layout. - */ -/* package */ interface HandleLayout { - -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java deleted file mode 100644 index f59675a8fbe1..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -/** - * Enum representing the different interpolators that can be used in layout animation configuration. - */ -/* package */ enum InterpolatorType { - LINEAR("linear"), - EASE_IN("easeIn"), - EASE_OUT("easeOut"), - EASE_IN_EASE_OUT("easeInEaseOut"), - SPRING("spring"); - - private final String mName; - - private InterpolatorType(String name) { - mName = name; - } - - public static InterpolatorType fromString(String name) { - for (InterpolatorType type : InterpolatorType.values()) { - if (type.toString().equalsIgnoreCase(name)) { - return type; - } - } - throw new IllegalArgumentException("Unsupported interpolation type : " + name); - } - - @Override - public String toString() { - return mName; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java deleted file mode 100644 index d3522947fd36..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - -import android.view.View; -import android.view.animation.Animation; - -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.UiThreadUtil; - -/** - * Class responsible for animation layout changes, if a valid layout animation config has been - * supplied. If not animation is available, layout change is applied immediately instead of - * performing an animation. - * - * TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled. - */ -@NotThreadSafe -public class LayoutAnimationController { - - private static final boolean ENABLED = true; - - private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation(); - private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation(); - private boolean mShouldAnimateLayout; - - public void initializeFromConfig(final @Nullable ReadableMap config) { - if (!ENABLED) { - return; - } - - if (config == null) { - reset(); - return; - } - - mShouldAnimateLayout = false; - int globalDuration = config.hasKey("duration") ? config.getInt("duration") : 0; - if (config.hasKey(LayoutAnimationType.CREATE.toString())) { - mLayoutCreateAnimation.initializeFromConfig( - config.getMap(LayoutAnimationType.CREATE.toString()), globalDuration); - mShouldAnimateLayout = true; - } - if (config.hasKey(LayoutAnimationType.UPDATE.toString())) { - mLayoutUpdateAnimation.initializeFromConfig( - config.getMap(LayoutAnimationType.UPDATE.toString()), globalDuration); - mShouldAnimateLayout = true; - } - } - - public void reset() { - mLayoutCreateAnimation.reset(); - mLayoutUpdateAnimation.reset(); - mShouldAnimateLayout = false; - } - - public boolean shouldAnimateLayout(View viewToAnimate) { - // if view parent is null, skip animation: view have been clipped, we don't want animation to - // resume when view is re-attached to parent, which is the standard android animation behavior. - return mShouldAnimateLayout && viewToAnimate.getParent() != null; - } - - /** - * Update layout of given view, via immediate update or animation depending on the current batch - * layout animation configuration supplied during initialization. - * - * @param view the view to update layout of - * @param x the new X position for the view - * @param y the new Y position for the view - * @param width the new width value for the view - * @param height the new height value for the view - */ - public void applyLayoutUpdate(View view, int x, int y, int width, int height) { - UiThreadUtil.assertOnUiThread(); - - // Determine which animation to use : if view is initially invisible, use create animation. - // If view is becoming invisible, use delete animation. Otherwise, use update animation. - // This approach is easier than maintaining a list of tags for recently created/deleted views. - AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ? - mLayoutCreateAnimation : - mLayoutUpdateAnimation; - - Animation animation = layoutAnimation.createAnimation(view, x, y, width, height); - if (animation == null || !(animation instanceof HandleLayout)) { - view.layout(x, y, x + width, y + height); - } - if (animation != null) { - view.startAnimation(animation); - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java deleted file mode 100644 index 0f3d9d7fdb2b..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -/** - * Enum representing the different animation type that can be specified in layout animation config. - */ -/* package */ enum LayoutAnimationType { - CREATE("create"), - UPDATE("update"); - - private final String mName; - - private LayoutAnimationType(String name) { - mName = name; - } - - @Override - public String toString() { - return mName; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java deleted file mode 100644 index a989e625c675..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -/** - * Class responsible for handling layout view creation animation, applied to view whenever a - * valid config was supplied for the layout animation of CREATE type. - */ -/* package */ class LayoutCreateAnimation extends BaseLayoutAnimation { - - @Override - boolean isReverse() { - return false; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java deleted file mode 100644 index 8189716f729d..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import javax.annotation.Nullable; - -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; - -/** - * Class responsible for handling layout update animation, applied to view whenever a valid config - * was supplied for the layout animation of UPDATE type. - */ -/* package */ class LayoutUpdateAnimation extends AbstractLayoutAnimation { - - // We are currently not enabling translation GPU-accelerated animated, as it creates odd - // artifacts with native react scrollview. This needs to be investigated. - private static final boolean USE_TRANSLATE_ANIMATION = false; - - @Override - boolean isValid() { - return mDurationMs > 0; - } - - @Override - @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height) { - boolean animateLocation = view.getX() != x || view.getY() != y; - boolean animateSize = view.getWidth() != width || view.getHeight() != height; - if (!animateLocation && !animateSize) { - return null; - } else if (animateLocation && !animateSize && USE_TRANSLATE_ANIMATION) { - // Use GPU-accelerated animation, however we loose the ability to resume interrupted - // animation where it was left off. We may be able to listen to animation interruption - // and set the layout manually in this case, so that next animation kicks off smoothly. - return new TranslateAnimation(view.getX() - x, 0, view.getY() - y, 0); - } else { - // Animation is sub-optimal for perf, but scale transformation can't be use in this case. - return new PositionAndSizeAnimation(view, x, y, width, height); - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java deleted file mode 100644 index a7ad690023c5..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.Transformation; - -/** - * Animation responsible for updating opacity of a view. It should ideally use hardware texture - * to optimize rendering performances. - */ -/* package */ class OpacityAnimation extends Animation { - - static class OpacityAnimationListener implements AnimationListener { - - private final View mView; - private boolean mLayerTypeChanged = false; - - public OpacityAnimationListener(View view) { - mView = view; - } - - @Override - public void onAnimationStart(Animation animation) { - if (mView.hasOverlappingRendering() && - mView.getLayerType() == View.LAYER_TYPE_NONE) { - mLayerTypeChanged = true; - mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - } - - @Override - public void onAnimationEnd(Animation animation) { - if (mLayerTypeChanged) { - mView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - // do nothing - } - } - - private final View mView; - private final float mStartOpacity, mDeltaOpacity; - - public OpacityAnimation(View view, float startOpacity, float endOpacity) { - mView = view; - mStartOpacity = startOpacity; - mDeltaOpacity = endOpacity - startOpacity; - - setAnimationListener(new OpacityAnimationListener(view)); - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - mView.setAlpha(mStartOpacity + mDeltaOpacity * interpolatedTime); - } - - @Override - public boolean willChangeBounds() { - return false; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java deleted file mode 100644 index 2adf81a6bb6c..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.Transformation; - -/** - * Animation responsible for updating size and position of a view. We can't use scaling as view - * content may not necessarily stretch. As a result, this approach is inefficient because of - * layout passes occurring on every frame. - * What we might want to try to do instead is use a combined ScaleAnimation and TranslateAnimation. - */ -/* package */ class PositionAndSizeAnimation extends Animation implements HandleLayout { - - private final View mView; - private final float mStartX, mStartY, mDeltaX, mDeltaY; - private final int mStartWidth, mStartHeight, mDeltaWidth, mDeltaHeight; - - public PositionAndSizeAnimation(View view, int x, int y, int width, int height) { - mView = view; - - mStartX = view.getX(); - mStartY = view.getY(); - mStartWidth = view.getWidth(); - mStartHeight = view.getHeight(); - - mDeltaX = x - mStartX; - mDeltaY = y - mStartY; - mDeltaWidth = width - mStartWidth; - mDeltaHeight = height - mStartHeight; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float newX = mStartX + mDeltaX * interpolatedTime; - float newY = mStartY + mDeltaY * interpolatedTime; - float newWidth = mStartWidth + mDeltaWidth * interpolatedTime; - float newHeight = mStartHeight + mDeltaHeight * interpolatedTime; - mView.layout( - Math.round(newX), - Math.round(newY), - Math.round(newX + newWidth), - Math.round(newY + newHeight)); - } - - @Override - public boolean willChangeBounds() { - return true; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java deleted file mode 100644 index 9696fe90a1a3..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.uimanager.layoutanimation; - -import android.view.animation.Interpolator; - -/** - * Simple spring interpolator - */ -//TODO(7613736): Improve spring interpolator with friction and damping variable support -/* package */ class SimpleSpringInterpolator implements Interpolator { - - private static final float FACTOR = 0.5f; - - @Override - public float getInterpolation(float input) { - return (float) - (1 + Math.pow(2, -10 * input) * Math.sin((input - FACTOR / 4) * Math.PI * 2 / FACTOR)); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 9e2728db4e1e..134b6492b9bf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -16,7 +16,6 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; -import android.view.animation.Animation; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -287,15 +286,7 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa boolean intersects = clippingRect .intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom); boolean needUpdateClippingRecursive = false; - // We never want to clip children that are being animated, as this can easily break layout : - // when layout animation changes size and/or position of views contained inside a listview that - // clips offscreen children, we need to ensure that, when view exits the viewport, final size - // and position is set prior to removing the view from its listview parent. - // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll, - // it won't be size and located properly. - Animation animation = child.getAnimation(); - boolean isAnimating = animation != null && !animation.hasEnded(); - if (!intersects && child.getParent() != null && !isAnimating) { + if (!intersects && child.getParent() != null) { // We can try saving on invalidate call here as the view that we remove is out of visible area // therefore invalidation is not necessary. super.removeViewsInLayout(idx - clippedSoFar, 1); From 3dca8cf9fdd864b1359fc7276bf98cb154c3e360 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Sun, 29 Nov 2015 22:24:36 -0800 Subject: [PATCH 0155/1411] Add UIImplementationProvider and allow overriding it in AbstractReactActivity Summary: public UIImplementationProvider allows plugging in an alternative UIImplementation. A follow up diff adds a toggle under FB Dev Settings and uses this class to control an implementation. This allows us experimenting with other ways of generating UI hierarchy from JavaScript components. Reviewed By: astreet Differential Revision: D2554774 fb-gh-sync-id: 6574a893020e3519bd2ab00b9620a6dbdfaed595 --- .../facebook/react/CoreModulesPackage.java | 11 ++++++--- .../facebook/react/ReactInstanceManager.java | 21 ++++++++++++++-- .../react/ReactInstanceManagerImpl.java | 8 +++++-- .../uimanager/UIImplementationProvider.java | 24 +++++++++++++++++++ .../react/uimanager/UIManagerModule.java | 9 ------- 5 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 5dd7b1959e88..b770b3b756d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -28,7 +28,7 @@ import com.facebook.react.modules.systeminfo.AndroidInfoModule; import com.facebook.react.uimanager.AppRegistry; import com.facebook.react.uimanager.ReactNative; -import com.facebook.react.uimanager.UIImplementation; +import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.debug.DebugComponentOwnershipModule; @@ -44,12 +44,15 @@ private final ReactInstanceManager mReactInstanceManager; private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler; + private final UIImplementationProvider mUIImplementationProvider; CoreModulesPackage( ReactInstanceManager reactInstanceManager, - DefaultHardwareBackBtnHandler hardwareBackBtnHandler) { + DefaultHardwareBackBtnHandler hardwareBackBtnHandler, + UIImplementationProvider uiImplementationProvider) { mReactInstanceManager = reactInstanceManager; mHardwareBackBtnHandler = hardwareBackBtnHandler; + mUIImplementationProvider = uiImplementationProvider; } @Override @@ -63,7 +66,9 @@ public List createNativeModules( uiManagerModule = new UIManagerModule( catalystApplicationContext, viewManagersList, - new UIImplementation(catalystApplicationContext, viewManagersList)); + mUIImplementationProvider.createUIImplementation( + catalystApplicationContext, + viewManagersList)); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 609b320fc9e8..2449c28ea491 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -25,6 +25,7 @@ import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.ViewManager; /** @@ -140,13 +141,23 @@ public static class Builder { protected @Nullable Application mApplication; protected boolean mUseDeveloperSupport; protected @Nullable LifecycleState mInitialLifecycleState; + protected @Nullable UIImplementationProvider mUIImplementationProvider; protected Builder() { } + /** + * Sets a provider of {@link UIImplementation}. + * Uses default provider if null is passed. + */ + public Builder setUIImplementationProvider( + @Nullable UIImplementationProvider uiImplementationProvider) { + mUIImplementationProvider = uiImplementationProvider; + return this; + } + /** * Name of the JS bundle file to be loaded from application's raw assets. - * * Example: {@code "index.android.js"} */ public Builder setBundleAssetName(String bundleAssetName) { @@ -231,6 +242,11 @@ public ReactInstanceManager build() { mJSMainModuleName != null || mJSBundleFile != null, "Either MainModuleName or JS Bundle File needs to be provided"); + if (mUIImplementationProvider == null) { + // create default UIImplementationProvider if the provided one is null. + mUIImplementationProvider = new UIImplementationProvider(); + } + return new ReactInstanceManagerImpl( Assertions.assertNotNull( mApplication, @@ -240,7 +256,8 @@ public ReactInstanceManager build() { mPackages, mUseDeveloperSupport, mBridgeIdleDebugListener, - Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set")); + Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), + mUIImplementationProvider); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index deb6bf21c29b..c97a55b067fe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -52,6 +52,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.uimanager.AppRegistry; import com.facebook.react.uimanager.ReactNative; +import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import com.facebook.soloader.SoLoader; @@ -95,6 +96,7 @@ private String mSourceUrl; private @Nullable Activity mCurrentActivity; private volatile boolean mHasStartedCreatingInitialContext = false; + private final UIImplementationProvider mUIImplementationProvider; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -189,7 +191,8 @@ protected void onPostExecute(ReactApplicationContext reactContext) { List packages, boolean useDeveloperSupport, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, - LifecycleState initialLifecycleState) { + LifecycleState initialLifecycleState, + UIImplementationProvider uiImplementationProvider) { initializeSoLoaderIfNecessary(applicationContext); mApplicationContext = applicationContext; @@ -208,6 +211,7 @@ protected void onPostExecute(ReactApplicationContext reactContext) { useDeveloperSupport); mBridgeIdleDebugListener = bridgeIdleDebugListener; mLifecycleState = initialLifecycleState; + mUIImplementationProvider = uiImplementationProvider; } @Override @@ -597,7 +601,7 @@ private ReactApplicationContext createReactContext( "createAndProcessCoreModulesPackage"); try { CoreModulesPackage coreModulesPackage = - new CoreModulesPackage(this, mBackBtnHandler); + new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider); processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java new file mode 100644 index 000000000000..3d1a4a2f39cd --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.facebook.react.uimanager; + +import java.util.List; + +import com.facebook.react.bridge.ReactApplicationContext; + +/** + * Provides UIImplementation to use in {@link UIManagerModule}. + */ +public class UIImplementationProvider { + public UIImplementation createUIImplementation( + ReactApplicationContext reactContext, + List viewManagers) { + return new UIImplementation(reactContext, viewManagers); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 59fd688e348b..b60598569bf0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -78,15 +78,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements private int mNextRootViewTag = 1; private int mBatchId = 0; - /** - * This contructor is temporarily here to workaround Sandcastle error. - */ - public UIManagerModule( - ReactApplicationContext reactContext, - List viewManagerList) { - this(reactContext, viewManagerList, new UIImplementation(reactContext, viewManagerList)); - } - public UIManagerModule( ReactApplicationContext reactContext, List viewManagerList, From c9dd4015f1ff50ce433b036be8459ab1366eb07b Mon Sep 17 00:00:00 2001 From: mkonicek pr tester Date: Mon, 30 Nov 2015 04:25:55 -0800 Subject: [PATCH 0156/1411] Better error handling in Android Geolocation module (PR 2) Summary: Just a testing PR for the shipit bot (please don't close :)) Closes https://github.com/facebook/react-native/pull/4355 Reviewed By: svcscm Differential Revision: D2702784 Pulled By: mkonicek fb-gh-sync-id: 867c65dcea486750ca65a8437b37a0f658538111 --- .../modules/location/LocationModule.java | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java index 188a45905642..9973563fc120 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java @@ -107,28 +107,25 @@ public void getCurrentPosition( Callback error) { LocationOptions locationOptions = LocationOptions.fromReactMap(options); - LocationManager locationManager = - (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); - String provider = getValidProvider(locationManager, locationOptions.highAccuracy); - if (provider == null) { - error.invoke("No available location provider."); - return; - } - - Location location = null; try { - location = locationManager.getLastKnownLocation(provider); + LocationManager locationManager = + (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); + String provider = getValidProvider(locationManager, locationOptions.highAccuracy); + if (provider == null) { + error.invoke("No available location provider."); + return; + } + Location location = locationManager.getLastKnownLocation(provider); + if (location != null && + SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) { + success.invoke(locationToMap(location)); + return; + } + new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error) + .invoke(); } catch (SecurityException e) { throwLocationPermissionMissing(e); } - if (location != null && - SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) { - success.invoke(locationToMap(location)); - return; - } - - new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error) - .invoke(); } /** @@ -143,24 +140,23 @@ public void startObserving(ReadableMap options) { return; } LocationOptions locationOptions = LocationOptions.fromReactMap(options); - LocationManager locationManager = - (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); - String provider = getValidProvider(locationManager, locationOptions.highAccuracy); - if (provider == null) { - emitError("No location provider available."); - return; - } try { + LocationManager locationManager = + (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); + String provider = getValidProvider(locationManager, locationOptions.highAccuracy); + if (provider == null) { + emitError("No location provider available."); + return; + } if (!provider.equals(mWatchedProvider)) { locationManager.removeUpdates(mLocationListener); locationManager.requestLocationUpdates(provider, 1000, 0, mLocationListener); } + mWatchedProvider = provider; } catch (SecurityException e) { throwLocationPermissionMissing(e); } - - mWatchedProvider = provider; } /** From c63de5e2a58df1ecf16b4be62a9c314b2770b627 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 30 Nov 2015 05:07:43 -0800 Subject: [PATCH 0157/1411] Fixed MapView crash on iOS 8 and earlier Summary: public [MKPinAnnotationView redPinColor] is only supported on iOS 9 and later. This caused React Native to crash on iOS 8 and earlier. This fixes the crash by providing a forked implementation for different OS versions. Reviewed By: tadeuzagallo, javache Differential Revision: D2702737 fb-gh-sync-id: cd8984f1f3d42989001f3c571e325f1b4ba09ac8 --- Libraries/Components/MapView/MapView.js | 10 ++++- React/Views/RCTMapManager.m | 56 +++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 9324a2aeca43..2dc4fdf30895 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -219,6 +219,10 @@ var MapView = React.createClass({ * The pin color. This can be any valid color string, or you can use one * of the predefined PinColors constants. Applies to both standard pins * and custom pin images. + * + * Note that on iOS 8 and earlier, only the standard PinColor constants + * are supported for regualr pins. For custom pin images, any tintColor + * value is supported on all iOS versions. * @platform ios */ tintColor: React.PropTypes.string, @@ -373,8 +377,10 @@ var MapView = React.createClass({ /** * Standard iOS MapView pin color constants, to be used with the - * `annotation.tintColor` property. You are not obliged to use these, - * but they are useful for matching the standard iOS look and feel. + * `annotation.tintColor` property. On iOS 8 and earlier these are the + * only supported values when using regular pins. On iOS 9 and later + * you are not obliged to use these, but they are useful for matching + * the standard iOS look and feel. */ let PinColors = RCTMapConstants && RCTMapConstants.PinColors; MapView.PinColors = PinColors && { diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 6c22c6b16c4a..e662d486829f 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -23,6 +23,24 @@ static NSString *const RCTMapViewKey = @"MapView"; +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 + +static NSString *const RCTMapPinRed = @"#ff3b30"; +static NSString *const RCTMapPinGreen = @"#4cd964"; +static NSString *const RCTMapPinPurple = @"#c969e0"; + +@implementation RCTConvert (MKPinAnnotationColor) + +RCT_ENUM_CONVERTER(MKPinAnnotationColor, (@{ + RCTMapPinRed: @(MKPinAnnotationColorRed), + RCTMapPinGreen: @(MKPinAnnotationColorGreen), + RCTMapPinPurple: @(MKPinAnnotationColorPurple) +}), MKPinAnnotationColorRed, unsignedIntegerValue) + +@end + +#endif + @interface RCTMapManager() @end @@ -60,11 +78,29 @@ - (UIView *)view - (NSDictionary *)constantsToExport { + NSString *red, *green, *purple; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 + + if (![MKPinAnnotationView respondsToSelector:@selector(redPinColor)]) { + red = RCTMapPinRed; + green = RCTMapPinGreen; + purple = RCTMapPinPurple; + } else + +#endif + + { + red = RCTColorToHexString([MKPinAnnotationView redPinColor].CGColor); + green = RCTColorToHexString([MKPinAnnotationView greenPinColor].CGColor); + purple = RCTColorToHexString([MKPinAnnotationView purplePinColor].CGColor); + } + return @{ @"PinColors": @{ - @"RED": RCTColorToHexString([MKPinAnnotationView redPinColor].CGColor), - @"GREEN": RCTColorToHexString([MKPinAnnotationView greenPinColor].CGColor), - @"PURPLE": RCTColorToHexString([MKPinAnnotationView purplePinColor].CGColor), + @"RED": red, + @"GREEN": green, + @"PURPLE": purple, } }; } @@ -127,7 +163,19 @@ - (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(R NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]); annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; ((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop; - ((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor]; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 + + if (![annotationView respondsToSelector:@selector(pinTintColor)]) { + NSString *hexColor = annotation.tintColor ? RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed; + ((MKPinAnnotationView *)annotationView).pinColor = [RCTConvert MKPinAnnotationColor:hexColor]; + } else + +#endif + + { + ((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor]; + } } annotationView.canShowCallout = true; From c852e73f5a488a4c560df47a69f198730f1834c8 Mon Sep 17 00:00:00 2001 From: Pawel Sienkowski Date: Fri, 20 Nov 2015 15:18:43 -0500 Subject: [PATCH 0158/1411] [docs] A guide coverin communication between native and RN on IOS. WARNING: I was not able to run the website locally because of a bug, so please test it before merging --- docs/CommunicationIOS.md | 218 +++++++++++++++++++++++++++++++++++++++ docs/EmbeddedAppIOS.md | 2 +- 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 docs/CommunicationIOS.md diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md new file mode 100644 index 000000000000..686cf0059055 --- /dev/null +++ b/docs/CommunicationIOS.md @@ -0,0 +1,218 @@ +--- +id: communication-ios +title: Communication between native and React Native +layout: docs +category: Guides (iOS) +permalink: docs/communication-ios.html +next: native-modules-android +--- + +In [Integrating with Existing Apps guide](http://facebook.github.io/react-native/docs/embedded-app-ios.html) and [Native UI Components guide](https://facebook.github.io/react-native/docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques. + +## Introduction + +React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner.If we have an ancestor component that rely on the state of its descendant, the recommended solution would be to pass down a callback that would be used by the descendant to update the ancestor. + +The same concept applies to React Native. As long as we are building our application purely within the framework, we can drive our app with properties and callbacks. But, when we mix React Native and native components, we need some special, cross-language mechanisms that would allow us to pass information between them. + +## Properties + +Properties are the simplest way of cross-component communication. So we need a way to pass properties both from native to React Native, and from React Native to native. + +### Passing properties from native to React Native + +In order to embed a React Native view in a native component, we use `RCTRootView`. `RCTRootView` is a `UIView` that holds a React Native app. It also provides an interface between native side and the hosted app. + +`RCTRootView` has an initializer that allows you to pass arbitrary properties down to the React Native app. The `initialProperties` parameter has to be an instance of `NSDictionary`. The dictionary is internally converted into a JSON object that the top-level JS component can reference. + +``` +NSArray *imageList = @[@"http://foo.com/bar1.png", + @"http://foo.com/bar2.png"]; + +NSDictionary *props = @{@"images" : imageList}; + +RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"ImageBrowserApp" + initialProperties:props]; +``` + +``` +'use strict'; + +var React = require('react-native'); + var { + View, + Image +} = React; + +class ImageBrowserApp extends React.Component { + renderImage: function(imgURI) { + return ( + + ); + }, + render() { + return ( + + {this.props.imageList.map(this.renderImage)} + + ); + } +} + +React.AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp); +``` + +`RCTRootView` also provides a read-write property `appProperties`. After `appProperties` is set, the React Native app is re-rendered with new properties. The update is only performed when the new updated properties differ from the previous ones. + +``` +NSArray *imageList = @[@"http://foo.com/bar3.png", + @"http://foo.com/bar4.png"]; + +rootView.appProperties = @{@"images" : imageList}; +``` + +It is fine to update properties anytime. However, updates have to be performed on the main thread. You use the getter on any thread. + +There is no way to update only a few properties at a time. We suggest that you build it into your own wrapper instead. + +> ***Note:*** +> Currently, JS functions `componentWillReceiveProps` and `componentWillUpdateProps` of the top level RN component will not be called after a prop update. However, you can access the new props in `componentWillMount` function. + +### Passing properties from React Native to native +The problem exposing properties of native components is covered in detail in [this article](https://facebook.github.io/react-native/docs/native-components-ios.html#properties). In short, export properties with `RCT_CUSTOM_VIEW_PROPERTY` macro in your custom native component, then just use them in React Native as if the component was an ordinary React Native component. + +### Limits of properties + +The main drawback of cross-language properties is that they do not support callbacks, which would allow us to handle bottom-up data bindings. Imagine you have a small RN view that you want to be removed from the native parent view as a result of a JS action. There is no way to do that with props, as the information would need to go bottom-up. + +Although we have a flavor of cross-language callbacks ([described here](https://facebook.github.io/react-native/docs/native-modules-ios.html#callbacks)), these callbacks are not always the thing we need. The main problem is that they are not intended to be passed as properties. Rather, this mechanism allows us to trigger a native action from JS, and handle the result of that action in JS. + +## Other ways of cross-language interaction (events and native modules) + +As stated in the previous chapter, using properties comes with some limitations. Sometimes properties are not enough to drive the logic of our app and we need a solution that gives more flexibility. This chapter covers other communication techniques available in React Native. They can be used for internal communication (between JS and native layers in RN) as well as for external communication (between RN and the 'pure native' part of your app). + +React Native enables you to perform cross-language function calls. You can execute custom native code from JS and vice versa. Unfortunately, depending on the side we are working on, we achieve the same goal in different ways. For native - we use events mechanism to schedule an execution of a handler function in JS, while for React Native we directly call methods exported by native modules. + +### Calling React Native functions from native (events) + +Events are described in detail in [this article](http://facebook.github.io/react-native/docs/native-components-ios.html#events). Note that using events gives us no guarantees about execution time, as the event is handled on a separate thread. + +Events are powerful, because they allow us to change React Native components without needing a reference to them. However, there are some pitfalls that you can fall into while using them: + +* As events can be sent from anywhere, they can introduce spaghetti-style dependencies into your project. +* Events share namespace, which means that you may encounter some name collisions. Collisions will not be detected statically, what makes them hard to debug. +* If you use several instances of the same React Native component and you want to distinguish them from the perspective of your event, you'll likely need to introduce some kind of identifiers and pass them along with events (you can use the native view's `reactTag` as an identifier). + +The common pattern we use when embedding native in React Native is to make the native component's RCTViewManager a delegate for the views, sending events back to JavaScript via the bridge. This keeps related event calls in one place. + +### Calling native functions from React Native (native modules) + +Native modules are Objective-C classes that are available in JS. Typically one instance of each module is created per JS bridge. They can export arbitrary functions and constants to React Native. They have been covered in detail in [this article](https://facebook.github.io/react-native/docs/native-modules-ios.html#content). + +The fact that native modules are singletons limits the mechanism in context of embedding. Let's say we have a React Native component embedded in a native view and we want to update the native, parent view. Using the native module mechanism, we would export a function that not only takes expected arguments, but also an identifier of the parent native view. The identifier would be used to retrieve a reference to the parent view to update. That said, we would need to keep a mapping from identifiers to native views in the module. + +Although this solution is complex, it is used in `RCTUIManager`, which is an internal React Native class that manages all React Native views. + +Native modules can also be used to expose existing native libraries to JS. [Geolocation library](https://github.com/facebook/react-native/tree/master/Libraries/Geolocation) is a living example of the idea. + +> ***Warning***: +> All native modules share the same namespace. Watch out for name collisions when creating new ones. + +## Layout computation flow + +When integrating native and React Native, we also need a way to consolidate two different layout systems. This section covers common layouting problems and provides a brief description of mechanisms that are intended to address them. + +### Layout of a native component embedded in React Native + +This case is covered in [this article](https://facebook.github.io/react-native/docs/native-components-ios.html#styles). Basically, as all our native react views are subclasses of `UIView`, most style and size attributes will work like you would expect out of the box. + +### Layout of a React Native component embedded in native + +#### React Native content with fixed size +The simplest scenario is when we have a React Native app with a fixed size, which is known to the native side. In particular, a full-screen React Native view falls into this case. If we want a smaller root view, we can explicitly set RCTRootView's frame. + +For instance, to make an RN app 200 (logical) pixels high, and the hosting view's width wide, we could do: + +``` +// SomeViewController.m + +- (void)viewDidLoad +{ + [...] + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:appName + initialProperties:props]; + rootView.frame = CGMakeRect(0, 0, self.view.width, 200); + [self.view addSubview:rootView]; +} +``` + +When we have a fixed size root view, we need to respect its bounds on the JS side. In other words, we need to ensure that the React Native content can be contained within the fixed-size root view. The easiest way to ensure this is to use flexbox layout. If you use absolute positioning, and React components are visible outside the root view's bounds, you'll get overlap with native views, causing some features to behave unexpectedly. For instance, 'TouchableHighlight' will not highlight your touches outside the root view's bounds. + +It's totally fine to update root view's size dynamically by re-setting its frame property. React Native will take care of the content's layout. + +#### React Native content with flexible size + +In some cases we'd like to render content of initially unknown size. Let's say the size will be defined dynamically in JS. We have two solutions to this problem. + + +1. You can wrap your React Native view in `ScrollView` component. This guarantees that your content will always be available and it won't overlap with native views. +2. React Native allows you to determine, in JS, the size of the RN app and provide it to the owner of the hosting `RCTRootView`. The owner is then responsible for re-laying out the subviews and keeping the UI consistent. We achieve this with `RCTRootView`'s flexibility modes. + + +`RCTRootView` supports 4 different size flexibility modes: + +``` +// RCTRootView.h + +typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) { + RCTRootViewSizeFlexibilityNone = 0, + RCTRootViewSizeFlexibilityWidth, + RCTRootViewSizeFlexibilityHeight, + RCTRootViewSizeFlexibilityWidthAndHeight, +}; +``` + +`RCTRootViewSizeFlexibilityNone` is the default value, which makes a root view's size fixed (but it still can be updated with `setFrame:`). The other three modes allow us to track React Native content's size updates. For instance, setting mode to `RCTRootViewSizeFlexibilityHeight` will cause React Native to measure the content's height and pass that information back to `RCTRootView`'s delegate. An arbitrary action can be performed within the delegate, including setting the root view's frame, so the content fits. The delegate is called only when the size of the content has changed. + +> ***Warning:*** +> Making a dimension flexible in both JS and native leads to undefined behavior. For example - don't make a top-level React component's width flexible (with `flexbox`) while you're using `RCTRootViewSizeFlexibilityWidth` on the hosting `RCTRootView`. + +Let's look at an example. + +``` +// FlexibleSizeExampleView.m + +- (instancetype)initWithFrame:(CGRect)frame +{ + [...] + + _rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"FlexibilityExampleApp" + initialProperties:@{}]; + + _rootView.delegate = self; + _rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight; + _rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0); +} + +#pragma mark - RCTRootViewDelegate +- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView +{ + CGRect newFrame = rootView.frame; + newFrame.size = rootView.intrinsicSize; + + rootView.frame = newFrame; +} +``` + +In the example we have a `FlexibleSizeExampleView` view that holds a root view. We create the root view, initialize it and set the delegate. The delegate will handle size updates. Then, we set the root view's size flexibility to `RCTRootViewSizeFlexibilityHeight`, which means that `rootViewDidChangeIntrinsicSize:` method will be called every time the React Native content changes its height. Finally, we set the root view's width and position. Note that we set there height as well, but it has no effect as we made the height RN-dependent. + +You can checkout full source code of the example [here](https://phabricator.fb.com/diffusion/FBOBJC/browse/master/Libraries/FBReactKit/js/react-native-github/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m). + +It's fine to change root view's size flexibility mode dynamically. Changing flexibility mode of a root view will schedule a layout recalculation and the delegate `rootViewDidChangeIntrinsicSize:` method will be called once the content size is known. + +> ***Note:*** React Native layout calculation is performed on a special thread, while native UI view updates are done on the main thread. This may cause temporary UI inconsistencies between native and React Native. This is a known problem and our team is working on synchronizing UI updates coming from different sources. + +> ***Note:*** React Native does not perform any layout calculations until the root view becomes a subview of some other views. If you want to hide React Native view until its dimensions are known, add the root view as a subview and make it initially hidden (use `UIView`'s `hidden` property). Then change its visibility in the delegate method. diff --git a/docs/EmbeddedAppIOS.md b/docs/EmbeddedAppIOS.md index d913d0beb0d4..655809fa044a 100644 --- a/docs/EmbeddedAppIOS.md +++ b/docs/EmbeddedAppIOS.md @@ -4,7 +4,7 @@ title: Integrating with Existing Apps layout: docs category: Guides (iOS) permalink: docs/embedded-app-ios.html -next: native-modules-android +next: communication-ios --- Since React makes no assumptions about the rest of your technology stack – it’s commonly noted as simply the `V` in `MVC` – it’s easily embeddable within an existing non-React Native app. In fact, it integrates with other best practice community tools like [CocoaPods](http://cocoapods.org/). From c929e15523c2c33297b1e9895e9bea49442e4f73 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Mon, 30 Nov 2015 05:24:13 -0800 Subject: [PATCH 0159/1411] Update touch handling to properly handle transformed Views Summary: public Our view handling for determining if a touch was in a view was not transform aware. This updates it to be transform aware (by borrowing the code from ViewGroup). Now, touches will be correctly translated to the view if the view is transformed. They will also have the correct local touch point. This fixes https://github.com/facebook/react-native/issues/3557 Reviewed By: andreicoman11 Differential Revision: D2696063 fb-gh-sync-id: 291f6b9884c610c29f8f8b9992c98d59863ab481 --- .../react/uimanager/TouchTargetHelper.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 409a9f91f402..24a4d286946f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -11,6 +11,8 @@ import javax.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.PointF; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -25,6 +27,9 @@ public class TouchTargetHelper { private static final float[] mEventCoords = new float[2]; + private static final PointF mTempPoint = new PointF(); + private static final float[] mMatrixTransformCoords = new float[2]; + private static final Matrix mInverseMatrix = new Matrix(); /** * Find touch event target view within the provided container given the coordinates provided @@ -94,31 +99,60 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup int childrenCount = viewGroup.getChildCount(); for (int i = childrenCount - 1; i >= 0; i--) { View child = viewGroup.getChildAt(i); - if (isTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child)) { - // Apply offset to event coordinates to transform them into the coordinate space of the - // child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}. - eventCoords[0] += viewGroup.getScrollY() - child.getTop(); - eventCoords[1] += viewGroup.getScrollX() - child.getLeft(); + PointF childPoint = mTempPoint; + if (isTransformedTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child, childPoint)) { + // If it is contained within the child View, the childPoint value will contain the view + // coordinates relative to the child + // We need to store the existing X,Y for the viewGroup away as it is possible this child + // will not actually be the target and so we restore them if not + float restoreY = eventCoords[0]; + float restoreX = eventCoords[1]; + eventCoords[0] = childPoint.y; + eventCoords[1] = childPoint.x; View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child); if (targetView != null) { return targetView; } - eventCoords[0] -= viewGroup.getScrollY() - child.getTop(); - eventCoords[1] -= viewGroup.getScrollX() - child.getLeft(); + eventCoords[0] = restoreY; + eventCoords[1] = restoreX; } } return viewGroup; } - // Taken from {@link ViewGroup#isTransformedTouchPointInView()} - private static boolean isTouchPointInView(float y, float x, ViewGroup parent, View child) { - float localY = y + parent.getScrollY() - child.getTop(); + /** + * Returns whether the touch point is within the child View + * It is transform aware and will invert the transform Matrix to find the true local points + * This code is taken from {@link ViewGroup#isTransformedTouchPointInView()} + */ + private static boolean isTransformedTouchPointInView( + float y, + float x, + ViewGroup parent, + View child, + PointF outLocalPoint) { float localX = x + parent.getScrollX() - child.getLeft(); - // Taken from {@link View#pointInView()}. - return localY >= 0 && localY < (child.getBottom() - child.getTop()) - && localX >= 0 && localX < (child.getRight() - child.getLeft()); + float localY = y + parent.getScrollY() - child.getTop(); + Matrix matrix = child.getMatrix(); + if (!matrix.isIdentity()) { + float[] localXY = mMatrixTransformCoords; + localXY[0] = localX; + localXY[1] = localY; + Matrix inverseMatrix = mInverseMatrix; + matrix.invert(inverseMatrix); + inverseMatrix.mapPoints(localXY); + localX = localXY[0]; + localY = localXY[1]; + } + if ((localX >= 0 && localX < (child.getRight() - child.getLeft())) + && (localY >= 0 && localY < (child.getBottom() - child.getTop()))) { + outLocalPoint.set(localX, localY); + return true; + } + return false; } + /** * Returns the touch target View of the event given, or null if neither the given View nor any of * its descendants are the touch target. From 0f0b57880fe5b2565cc247a2e0c0b7611330b5d1 Mon Sep 17 00:00:00 2001 From: Andreas Amsenius Date: Mon, 30 Nov 2015 06:18:55 -0800 Subject: [PATCH 0160/1411] ActionSheetIOS sharing exclude activity types Summary: Closes https://github.com/facebook/react-native/pull/4427 Reviewed By: svcscm Differential Revision: D2702825 Pulled By: nicklockwood fb-gh-sync-id: f12e83332f2083cee2c04625b5113774c8a907e0 --- Examples/UIExplorer/ActionSheetIOSExample.js | 3 +++ Libraries/ActionSheetIOS/RCTActionSheetManager.m | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 548fe3bd1583..1ef41c1edef7 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -90,6 +90,9 @@ var ShareActionSheetExample = React.createClass({ url: 'https://code.facebook.com', message: 'message to go with the shared url', subject: 'a subject to go in the email heading', + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] }, (error) => { console.error(error); diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index 1d4e93ba741b..245493b0ceef 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -172,6 +172,11 @@ - (CGRect)sourceRectInView:(UIView *)sourceView [shareController setValue:subject forKey:@"subject"]; } + NSArray *excludedActivityTypes = [RCTConvert NSStringArray:options[@"excludedActivityTypes"]]; + if (excludedActivityTypes) { + shareController.excludedActivityTypes = excludedActivityTypes; + } + UIViewController *controller = RCTKeyWindow().rootViewController; #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 From df70005c12e3be14bfbdf0b32f9ea0a5c052deba Mon Sep 17 00:00:00 2001 From: Danny Arnold Date: Mon, 30 Nov 2015 07:32:24 -0800 Subject: [PATCH 0161/1411] calling navigationBar's overridden ref function Summary: Before that it was not possible to get a ref to a navigation bar (unless using Navigator's internal `_navBar` prop) Closes https://github.com/facebook/react-native/pull/3755 Reviewed By: svcscm Differential Revision: D2674315 Pulled By: nicklockwood fb-gh-sync-id: 26120f7bcbb675e8217b8bd963dcc6ed314d4ba3 --- Libraries/CustomComponents/Navigator/Navigator.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index ff7ff2c632f7..e11ee25055ed 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1087,7 +1087,10 @@ var Navigator = React.createClass({ return null; } return React.cloneElement(this.props.navigationBar, { - ref: (navBar) => { this._navBar = navBar; }, + ref: (navBar) => { + this.props.navigationBar.ref instanceof Function && this.props.navigationBar.ref(navBar); + this._navBar = navBar; + }, navigator: this._navigationBarNavigator, navState: this.state, }); From 32fa0b7fa196ae83e7f65bf1836d672b58f2213c Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 30 Nov 2015 08:31:35 -0800 Subject: [PATCH 0162/1411] Move bundle output formats to their own files Reviewed By: tadeuzagallo Differential Revision: D2702834 fb-gh-sync-id: fca308c5078a11924e071077822fb77d762bd564 --- local-cli/bundle/buildBundle.js | 117 ++++++--------------------- local-cli/bundle/bundle.js | 6 +- local-cli/bundle/output/bundle.js | 59 ++++++++++++++ local-cli/bundle/output/prepack.js | 34 ++++++++ local-cli/bundle/output/writeFile.js | 25 ++++++ 5 files changed, 149 insertions(+), 92 deletions(-) create mode 100644 local-cli/bundle/output/bundle.js create mode 100644 local-cli/bundle/output/prepack.js create mode 100644 local-cli/bundle/output/writeFile.js diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index 5cb752a43b1c..f2f1e3291ec1 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -8,59 +8,13 @@ */ 'use strict'; -const fs = require('fs'); const log = require('../util/log').out('bundle'); +const outputBundle = require('./output/bundle'); const Promise = require('promise'); const ReactPackager = require('../../packager/react-packager'); const saveAssets = require('./saveAssets'); -const sign = require('./sign'); - -function saveBundleAndMap( - bundle, - bundleOutput, - encoding, - sourcemapOutput, - dev -) { - log('start'); - let codeWithMap; - if (!dev) { - codeWithMap = bundle.getMinifiedSourceAndMap(dev); - } else { - codeWithMap = { - code: bundle.getSource({ dev }), - map: JSON.stringify(bundle.getSourceMap({ dev })), - }; - } - log('finish'); - - log('Writing bundle output to:', bundleOutput); - fs.writeFileSync(bundleOutput, sign(codeWithMap.code), encoding); - log('Done writing bundle output'); - - if (sourcemapOutput) { - log('Writing sourcemap output to:', sourcemapOutput); - fs.writeFileSync(sourcemapOutput, codeWithMap.map); - log('Done writing sourcemap output'); - } -} - -function savePrepackBundleAndMap( - bundle, - bundleOutput, - sourcemapOutput, - bridgeConfig -) { - log('Writing prepack bundle output to:', bundleOutput); - const result = bundle.build({ - batchedBridgeConfig: bridgeConfig - }); - fs.writeFileSync(bundleOutput, result, 'ucs-2'); - log('Done writing prepack bundle output'); -} - -function buildBundle(args, config) { +function buildBundle(args, config, output = outputBundle) { return new Promise((resolve, reject) => { // This is used by a bazillion of npm modules we don't control so we don't @@ -82,55 +36,36 @@ function buildBundle(args, config) { platform: args.platform, }; - const prepack = args.prepack; - - const client = ReactPackager.createClientFor(options); - - client.then(() => log('Created ReactPackager')); + const clientPromise = ReactPackager.createClientFor(options); // Build and save the bundle - let bundle; - if (prepack) { - bundle = client.then(c => c.buildPrepackBundle(requestOpts)) - .then(outputBundle => { - savePrepackBundleAndMap( - outputBundle, - args['bundle-output'], - args['sourcemap-output'], - args['bridge-config'] - ); - return outputBundle; - }); - } else { - bundle = client.then(c => c.buildBundle(requestOpts)) - .then(outputBundle => { - saveBundleAndMap( - outputBundle, - args['bundle-output'], - args['bundle-encoding'], - args['sourcemap-output'], - args.dev - ); - return outputBundle; - }); - } + const bundlePromise = clientPromise + .then(client => { + log('Created ReactPackager'); + return output.build(client, requestOpts); + }) + .then(bundle => { + output.save(bundle, args, log); + return bundle; + }); // When we're done bundling, close the client - bundle.then(() => client.then(c => { - log('Closing client'); - c.close(); - })); + Promise.all([clientPromise, bundlePromise]) + .then(([client]) => { + log('Closing client'); + client.close(); + }); // Save the assets of the bundle - const assets = bundle - .then(outputBundle => outputBundle.getAssets()) - .then(outputAssets => saveAssets( - outputAssets, - args.platform, - args['assets-dest'] - )); - - // When we're done saving the assets, we're done. + const assets = bundlePromise + .then(bundle => bundle.getAssets()) + .then(outputAssets => saveAssets( + outputAssets, + args.platform, + args['assets-dest'] + )); + + // When we're done saving bundle output and the assets, we're done. resolve(assets); }); } diff --git a/local-cli/bundle/bundle.js b/local-cli/bundle/bundle.js index 6826de71faa4..e242049f0156 100644 --- a/local-cli/bundle/bundle.js +++ b/local-cli/bundle/bundle.js @@ -11,12 +11,16 @@ const buildBundle = require('./buildBundle'); const bundleCommandLineArgs = require('./bundleCommandLineArgs'); const parseCommandLine = require('../util/parseCommandLine'); +const outputBundle = require('./output/bundle'); +const outputPrepack = require('./output/prepack'); /** * Builds the bundle starting to look for dependencies at the given entry path. */ function bundle(argv, config) { - return buildBundle(parseCommandLine(bundleCommandLineArgs, argv), config); + const args = parseCommandLine(bundleCommandLineArgs, argv); + const output = args.prepack ? outputPrepack : outputBundle; + return buildBundle(args, config, output); } module.exports = bundle; diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js new file mode 100644 index 000000000000..4ffd8a649bc4 --- /dev/null +++ b/local-cli/bundle/output/bundle.js @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const Promise = require('promise'); +const sign = require('../sign'); +const writeFile = require('./writeFile'); + +function buildBundle(packagerClient, requestOptions) { + return packagerClient.buildBundle(requestOptions); +} + +function createCodeWithMap(bundle, dev) { + if (!dev) { + return bundle.getMinifiedSourceAndMap(dev); + } else { + return { + code: bundle.getSource({dev}), + map: JSON.stringify(bundle.getSourceMap({dev})), + }; + } +} + +function saveBundleAndMap(bundle, options, log) { + const { + 'bundle-output': bundleOutput, + 'bundle-encoding': encoding, + dev, + 'sourcemap-output': sourcemapOutput, +} = options; + + log('start'); + const codeWithMap = createCodeWithMap(bundle, dev); + log('finish'); + + log('Writing bundle output to:', bundleOutput); + const writeBundle = writeFile(bundleOutput, sign(codeWithMap.code), encoding); + writeBundle.then(() => log('Done writing bundle output')); + + if (sourcemapOutput) { + log('Writing sourcemap output to:', sourcemapOutput); + const writeMap = writeFile(sourcemapOutput, codeWithMap.map, null); + writeMap.then(() => log('Done writing sourcemap output')); + return Promise.all([writeBundle, writeMap]); + } else { + return writeBundle; + } +} + + +exports.build = buildBundle; +exports.save = saveBundleAndMap; +exports.formatName = 'bundle'; diff --git a/local-cli/bundle/output/prepack.js b/local-cli/bundle/output/prepack.js new file mode 100644 index 000000000000..9165b8763571 --- /dev/null +++ b/local-cli/bundle/output/prepack.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const writeFile = require('./writeFile'); + +function buildPrepackBundle(packagerClient, requestOptions) { + return packagerClient.buildPrepackBundle(requestOptions); +} + +function savePrepackBundle(bundle, options, log) { + const { + 'bundle-output': bundleOutput, + 'bridge-config': bridgeConfig, + } = options; + + const result = bundle.build({ + batchedBridgeConfig: bridgeConfig + }); + + log('Writing prepack bundle output to:', bundleOutput); + const writePrepackBundle = writeFile(bundleOutput, result, 'ucs-2'); + writePrepackBundle.then(() => log('Done writing prepack bundle output')); + return writePrepackBundle; +} + +exports.build = buildPrepackBundle; +exports.save = savePrepackBundle; diff --git a/local-cli/bundle/output/writeFile.js b/local-cli/bundle/output/writeFile.js new file mode 100644 index 000000000000..bb30d941fae5 --- /dev/null +++ b/local-cli/bundle/output/writeFile.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const fs = require('fs'); +const Promise = require('promise'); + +function writeFile(file, data, encoding) { + return new Promise((resolve, reject) => { + fs.writeFile( + file, + data, + encoding, + error => error ? reject(error) : resolve() + ); + }); +} + +module.exports = writeFile; From 015c6b3be273a40b530b9b79bb1f9da3d0ba1cdf Mon Sep 17 00:00:00 2001 From: Aleksei Androsov Date: Mon, 30 Nov 2015 10:28:49 -0800 Subject: [PATCH 0163/1411] ImagePicker always called with cancelCallback Summary: We should pick 2 callbacks from pickerCallbacks and pickerCancelCallbacks arrays and call them depens on args. Closes https://github.com/facebook/react-native/pull/4411 Reviewed By: svcscm Differential Revision: D2703209 Pulled By: javache fb-gh-sync-id: 7d2d7c63b933a66a1ff600663c3a7fcc821c750b --- Libraries/CameraRoll/RCTImagePickerManager.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index 0ad9e8e71ec4..d82d0e9bf554 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -132,7 +132,8 @@ - (void)_presentPicker:(UIImagePickerController *)imagePicker - (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args { NSUInteger index = [_pickers indexOfObject:picker]; - RCTResponseSenderBlock callback = _pickerCancelCallbacks[index]; + RCTResponseSenderBlock successCallback = _pickerCallbacks[index]; + RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index]; [_pickers removeObjectAtIndex:index]; [_pickerCallbacks removeObjectAtIndex:index]; @@ -141,7 +142,11 @@ - (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args UIViewController *rootViewController = RCTKeyWindow().rootViewController; [rootViewController dismissViewControllerAnimated:YES completion:nil]; - callback(args ?: @[]); + if (args) { + successCallback(args); + } else { + cancelCallback(@[]); + } } @end From 8f50ace8b3a6439fec63ea5ed8642d290c843cfd Mon Sep 17 00:00:00 2001 From: mkonicek-pr-test Date: Mon, 30 Nov 2015 11:08:58 -0800 Subject: [PATCH 0164/1411] Remove newline in TicTacToe example Summary: Really just for testing the shipit bot :) Closes https://github.com/facebook/react-native/pull/4434 Reviewed By: svcscm Differential Revision: D2703503 Pulled By: mkonicek fb-gh-sync-id: 4b93638e01eaf86d63c429e6797d08095c4824ee --- Examples/TicTacToe/TicTacToeApp.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Examples/TicTacToe/TicTacToeApp.js b/Examples/TicTacToe/TicTacToeApp.js index 15652ee4de64..6d5c460cbea3 100755 --- a/Examples/TicTacToe/TicTacToeApp.js +++ b/Examples/TicTacToe/TicTacToeApp.js @@ -89,7 +89,6 @@ class Board { } } } - return this.winner() === null; } } From b86b5af1e864b77da48c5c4f43f7c2c53be9999e Mon Sep 17 00:00:00 2001 From: Jake Yang Date: Mon, 30 Nov 2015 11:30:02 -0800 Subject: [PATCH 0165/1411] Add TYDY to the Showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 7d9bbfd816a9..889fd69bd6ee 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -350,6 +350,12 @@ var apps = [ link: 'https://itunes.apple.com/cn/app/tong-xing-wang/id914254459?mt=8', author: 'Ho Yin Tsun Eugene', }, + { + name: 'TYDY', + icon: 'http://d2j2p20zkb3bwp.cloudfront.net/images/appicon76x76_2x.png', + link: 'https://itunes.apple.com/us/app/tydy-buy-sell-free-your-local/id1039965583?mt=8', + author: '99gamers', + }, { name: 'WOOP', icon: 'http://a4.mzstatic.com/us/r30/Purple6/v4/b0/44/f9/b044f93b-dbf3-9ae5-0f36-9b4956628cab/icon350x350.jpeg', From 4e04e3bc28141243ba901789e86cdc8d996f001f Mon Sep 17 00:00:00 2001 From: "Tsung Han (Jake) Yang" Date: Mon, 30 Nov 2015 11:50:56 -0800 Subject: [PATCH 0166/1411] Revert "Add TYDY to the Showcase" --- website/src/react-native/showcase.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 889fd69bd6ee..7d9bbfd816a9 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -350,12 +350,6 @@ var apps = [ link: 'https://itunes.apple.com/cn/app/tong-xing-wang/id914254459?mt=8', author: 'Ho Yin Tsun Eugene', }, - { - name: 'TYDY', - icon: 'http://d2j2p20zkb3bwp.cloudfront.net/images/appicon76x76_2x.png', - link: 'https://itunes.apple.com/us/app/tydy-buy-sell-free-your-local/id1039965583?mt=8', - author: '99gamers', - }, { name: 'WOOP', icon: 'http://a4.mzstatic.com/us/r30/Purple6/v4/b0/44/f9/b044f93b-dbf3-9ae5-0f36-9b4956628cab/icon350x350.jpeg', From dd5bb96e44c8c1a9b5d6783053c053de92455f59 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 30 Nov 2015 12:31:43 -0800 Subject: [PATCH 0167/1411] Provide getters for CallerContext and DraweeControllerBuilder in ReactViewManager Summary: public Right now there is no centralized place to initialize CallerContext for Fresco. It should probably go through FrescoModule, but currently it's going through ReactImageManager, and thus unavailable to FlatUIImplementation, that doesn't use ReactImageManager. So this diff provides public getters for CallerContext (and DraweeControllerBuilder) in ReactViewManager. This is hacky and generally should not be needed, but without it groups don't have correct CallerContext set. Reviewed By: kmagiera, astreet Differential Revision: D2569372 fb-gh-sync-id: 2622e69c64bfebfc575deb330ee77e0139efb7ec --- .../react/views/image/ReactImageManager.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 8803652d1153..b0df41db0b3c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -48,16 +48,23 @@ public ReactImageManager() { mCallerContext = null; } - @Override - public ReactImageView createViewInstance(ThemedReactContext context) { + public AbstractDraweeControllerBuilder getDraweeControllerBuilder() { if (mDraweeControllerBuilder == null) { mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder(); } + return mDraweeControllerBuilder; + } + public Object getCallerContext() { + return mCallerContext; + } + + @Override + public ReactImageView createViewInstance(ThemedReactContext context) { return new ReactImageView( context, - mDraweeControllerBuilder, - mCallerContext); + getDraweeControllerBuilder(), + getCallerContext()); } // In JS this is Image.props.source.uri From 94f126ddbf1e3600ed7d34b8aba444a5df587c58 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 30 Nov 2015 13:37:37 -0800 Subject: [PATCH 0168/1411] Fixed TextInput.blur() Reviewed By: helouree Differential Revision: D2704065 fb-gh-sync-id: a4f5aa9c9b4fcb7980eb85289d16982840a980bd --- Libraries/Text/RCTTextView.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 412dd47acc06..34f3b74970f0 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -425,6 +425,11 @@ - (void)reactDidMakeFirstResponder [_textView reactDidMakeFirstResponder]; } +- (BOOL)resignFirstResponder +{ + return [_textView resignFirstResponder]; +} + - (void)layoutSubviews { [super layoutSubviews]; From 0c8bafe84a07aa9038e438d07fc4d54a69a6f8d1 Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 30 Nov 2015 16:12:44 -0800 Subject: [PATCH 0169/1411] fix library/utility lint warnings Summary: I have disected lint warnings fixes to several PRs. This one fixes lint warnings under Libraries/Utility path. Closes https://github.com/facebook/react-native/pull/4444 Reviewed By: svcscm Differential Revision: D2705303 Pulled By: spicyj fb-gh-sync-id: c745ac62cbff30d6bb9478a1d2465fe56b305f0c --- Libraries/Utilities/MessageQueue.js | 1 - Libraries/Utilities/createStrictShapeTypeChecker.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index faefc7827048..940b3b459997 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -16,7 +16,6 @@ let BridgeProfiling = require('BridgeProfiling'); let ErrorUtils = require('ErrorUtils'); let JSTimersExecution = require('JSTimersExecution'); -let ReactUpdates = require('ReactUpdates'); let invariant = require('invariant'); let keyMirror = require('keyMirror'); diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js index cd0d5757a96d..883da2459f7c 100644 --- a/Libraries/Utilities/createStrictShapeTypeChecker.js +++ b/Libraries/Utilities/createStrictShapeTypeChecker.js @@ -24,7 +24,7 @@ function createStrictShapeTypeChecker( if (isRequired) { invariant( false, - `Required object \`${propName}\` was not specified in `+ + `Required object \`${propName}\` was not specified in ` + `\`${componentName}\`.` ); } From 9f92e916086d91821098abed88da25f67dec82a1 Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 30 Nov 2015 16:20:41 -0800 Subject: [PATCH 0170/1411] fix camera roll lint warnings Summary: fix lint warnings under `Libraries/CameraRoll` directory Closes https://github.com/facebook/react-native/pull/4445 Reviewed By: svcscm Differential Revision: D2705430 Pulled By: nicklockwood fb-gh-sync-id: b5a179750a6696836cf458bc8e646e6b8a23fd98 --- Libraries/CameraRoll/CameraRoll.js | 2 +- Libraries/CameraRoll/ImagePickerIOS.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 1eee46a3edbf..c7e28d9510b3 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -120,7 +120,7 @@ class CameraRoll { * Saves the image to the camera roll / gallery. * * The CameraRoll API is not yet implemented for Android. - * + * * @param {string} tag On Android, this is a local URI, such * as `"file:///sdcard/img.png"`. * diff --git a/Libraries/CameraRoll/ImagePickerIOS.js b/Libraries/CameraRoll/ImagePickerIOS.js index 9b2f75e5b92d..a67884dc8435 100644 --- a/Libraries/CameraRoll/ImagePickerIOS.js +++ b/Libraries/CameraRoll/ImagePickerIOS.js @@ -24,7 +24,7 @@ var ImagePickerIOS = { config = { videoMode: false, ...config, - } + }; return RCTImagePicker.openCameraDialog(config, successCallback, cancelCallback); }, openSelectDialog: function(config: Object, successCallback: Function, cancelCallback: Function) { @@ -32,7 +32,7 @@ var ImagePickerIOS = { showImages: true, showVideos: false, ...config, - } + }; return RCTImagePicker.openSelectDialog(config, successCallback, cancelCallback); }, }; From da0744892c64f9bff128338e863fa45b3fc71986 Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 30 Nov 2015 16:21:44 -0800 Subject: [PATCH 0171/1411] fix Library/ReactNative lint warnings Summary: fix 2 lint warnings under `Libraries/ReactNative` directory Closes https://github.com/facebook/react-native/pull/4446 Reviewed By: svcscm Differential Revision: D2705429 Pulled By: nicklockwood fb-gh-sync-id: a7318ef9d1d194f077b0879fd041c42da5b2dec3 --- Libraries/ReactNative/ReactNative.js | 1 - Libraries/ReactNative/ReactNativeBaseComponent.js | 1 - 2 files changed, 2 deletions(-) diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js index a5554c0f4b51..1b02747532af 100644 --- a/Libraries/ReactNative/ReactNative.js +++ b/Libraries/ReactNative/ReactNative.js @@ -26,7 +26,6 @@ var ReactNativeMount = require('ReactNativeMount'); var ReactPropTypes = require('ReactPropTypes'); var ReactUpdates = require('ReactUpdates'); -var deprecated = require('deprecated'); var findNodeHandle = require('findNodeHandle'); var invariant = require('invariant'); var onlyChild = require('onlyChild'); diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 765c0429063b..266c7b30fbf6 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -14,7 +14,6 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); var UIManager = require('UIManager'); From 8f2023d9617804201293b6226d0e4a58c343f97a Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 30 Nov 2015 17:14:10 -0800 Subject: [PATCH 0172/1411] fix Libraries/Components/Touchable lint warnings Summary: fix 4 lint warnings under Libraries/Components/Touchable directory Closes https://github.com/facebook/react-native/pull/4449 Reviewed By: svcscm Differential Revision: D2705537 Pulled By: spicyj fb-gh-sync-id: 0c573d846a2263819c2a0bffe0a178eee1fe3fca --- Libraries/Components/Touchable/BoundingDimensions.js | 2 +- Libraries/Components/Touchable/Position.js | 2 +- Libraries/Components/Touchable/Touchable.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/Components/Touchable/BoundingDimensions.js b/Libraries/Components/Touchable/BoundingDimensions.js index f4acffc0237e..ca63fd14b08e 100644 --- a/Libraries/Components/Touchable/BoundingDimensions.js +++ b/Libraries/Components/Touchable/BoundingDimensions.js @@ -2,7 +2,7 @@ * @providesModule BoundingDimensions */ -"use strict"; +'use strict'; var PooledClass = require('PooledClass'); diff --git a/Libraries/Components/Touchable/Position.js b/Libraries/Components/Touchable/Position.js index 6175b6b9ea8c..0de7372e9811 100644 --- a/Libraries/Components/Touchable/Position.js +++ b/Libraries/Components/Touchable/Position.js @@ -2,7 +2,7 @@ * @providesModule Position */ -"use strict"; +'use strict'; var PooledClass = require('PooledClass'); diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 7b1f56a551e5..0a2f554c43b0 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -426,12 +426,12 @@ var TouchableMixin = { top: PRESS_EXPAND_PX, bottom: PRESS_EXPAND_PX }; - + var pressExpandLeft = pressRectOffset.left; var pressExpandTop = pressRectOffset.top; var pressExpandRight = pressRectOffset.right; var pressExpandBottom = pressRectOffset.bottom; - + var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); var pageX = touch && touch.pageX; var pageY = touch && touch.pageY; From c4c74215fad0c7169c3001ec7ee76daa4e90daea Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 30 Nov 2015 16:36:54 -0800 Subject: [PATCH 0173/1411] Allow extending UIViewOperationQueue Summary: public I need to extend NativeViewHierarchyManager to support a richer set of operations on Views, and those operations needs to be ran in UI thread. Existing UIViewOperationQueue doesn't know about those operations, so I need to extend it too to allow them. This patch is making the class constructor protected to allow subclassing it from another package. Should not have any functional changes. Reviewed By: astreet Differential Revision: D2554826 fb-gh-sync-id: ad2d44a61beb216d940e20cd1489d3b5da1b398b --- .../react/uimanager/UIViewOperationQueue.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index ed507fd3ec63..f26a295eef4b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -46,7 +46,7 @@ public class UIViewOperationQueue { /** * A mutation or animation operation on the view hierarchy. */ - private interface UIOperation { + protected interface UIOperation { void execute(); } @@ -440,7 +440,7 @@ public void execute() { private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener; - /* package */ UIViewOperationQueue( + protected UIViewOperationQueue( ReactApplicationContext reactContext, NativeViewHierarchyManager nativeViewHierarchyManager) { mNativeViewHierarchyManager = nativeViewHierarchyManager; @@ -484,6 +484,14 @@ public void run() { } } + /** + * Enqueues a UIOperation to be executed in UI thread. This method should only be used by a + * subclass to support UIOperations not provided by UIViewOperationQueue. + */ + protected void enqueueUIOperation(UIOperation operation) { + mOperations.add(operation); + } + public void enqueueRemoveRootView(int rootViewTag) { mOperations.add(new RemoveRootViewOperation(rootViewTag)); } From fe28118a48f2677395fb209feaa21626143c3164 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 30 Nov 2015 16:47:12 -0800 Subject: [PATCH 0174/1411] When creating a View using NativeViewHierarchyManager, pass ThemedReactContext instead of root view tag Summary: public NativeViewHierarchyManager.createView() takes int rootViewTag which is only used to resolve ThemedReactContext, but we already have it resolved before we enqueueCreateView, so we can just pass it directly. This makes mRootViewsContext tag -> to ThemedReactContext mapping unnecessary in NativeViewHierarchyManager. Makes the class simpler. Reviewed By: kmagiera, astreet Differential Revision: D2460280 fb-gh-sync-id: 68c503f4fb37ca0b1dcb2abc9e0c33a5225d1f6a --- .../uimanager/NativeViewHierarchyManager.java | 15 +++------------ .../uimanager/NativeViewHierarchyOptimizer.java | 12 ++++++++---- .../react/uimanager/UIImplementation.java | 2 +- .../react/uimanager/UIViewOperationQueue.java | 12 ++++++------ 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index fddd7047820f..3be9a92e1db1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -62,7 +62,6 @@ private final SparseArray mTagsToViews; private final SparseArray mTagsToViewManagers; private final SparseBooleanArray mRootTags; - private final SparseArray mRootViewsContext; private final ViewManagerRegistry mViewManagers; private final JSResponderHandler mJSResponderHandler = new JSResponderHandler(); private final RootViewManager mRootViewManager = new RootViewManager(); @@ -73,7 +72,6 @@ public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { mTagsToViews = new SparseArray<>(); mTagsToViewManagers = new SparseArray<>(); mRootTags = new SparseBooleanArray(); - mRootViewsContext = new SparseArray<>(); } public AnimationRegistry getAnimationRegistry() { @@ -162,15 +160,14 @@ public void updateLayout( } public void createView( - int rootViewTagForContext, + ThemedReactContext themedContext, int tag, String className, @Nullable CatalystStylesDiffMap initialProps) { UiThreadUtil.assertOnUiThread(); ViewManager viewManager = mViewManagers.get(className); - View view = - viewManager.createView(mRootViewsContext.get(rootViewTagForContext), mJSResponderHandler); + View view = viewManager.createView(themedContext, mJSResponderHandler); mTagsToViews.put(tag, view); mTagsToViewManagers.put(tag, viewManager); @@ -376,7 +373,6 @@ public void addRootView( mTagsToViews.put(tag, view); mTagsToViewManagers.put(tag, mRootViewManager); mRootTags.put(tag, true); - mRootViewsContext.put(tag, themedContext); view.setId(tag); } @@ -416,7 +412,6 @@ public void removeRootView(int rootViewTag) { View rootView = mTagsToViews.get(rootViewTag); dropView(rootView); mRootTags.delete(rootViewTag); - mRootViewsContext.remove(rootViewTag); } /** @@ -585,14 +580,10 @@ public boolean onMenuItemClick(MenuItem item) { } /** - * @return Themed React context for view with a given {@param reactTag} - in the case of root - * view it returns the context from {@link #mRootViewsContext} and all the other cases it gets the + * @return Themed React context for view with a given {@param reactTag} - it gets the * context directly from the view using {@link View#getContext}. */ private ThemedReactContext getReactContextForView(int reactTag) { - if (mRootTags.get(reactTag)) { - return Assertions.assertNotNull(mRootViewsContext.get(reactTag)); - } View view = mTagsToViews.get(reactTag); if (view == null) { throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java index fd3946eeb7c9..ef3c2c8484bb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java @@ -69,11 +69,15 @@ public NativeViewHierarchyOptimizer( */ public void handleCreateView( ReactShadowNode node, - int rootViewTag, + ThemedReactContext themedContext, @Nullable CatalystStylesDiffMap initialProps) { if (!ENABLED) { int tag = node.getReactTag(); - mUIViewOperationQueue.enqueueCreateView(rootViewTag, tag, node.getViewClass(), initialProps); + mUIViewOperationQueue.enqueueCreateView( + themedContext, + tag, + node.getViewClass(), + initialProps); return; } @@ -83,7 +87,7 @@ public void handleCreateView( if (!isLayoutOnly) { mUIViewOperationQueue.enqueueCreateView( - rootViewTag, + themedContext, node.getReactTag(), node.getViewClass(), initialProps); @@ -391,7 +395,7 @@ private void transitionLayoutOnlyViewToNativeView( // Create the view since it doesn't exist in the native hierarchy yet mUIViewOperationQueue.enqueueCreateView( - node.getRootNode().getReactTag(), + node.getRootNode().getThemedContext(), node.getReactTag(), node.getViewClass(), props); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 94b2307b1702..369ffda90f6c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -121,7 +121,7 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p } if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles); + mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootNode.getThemedContext(), styles); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index f26a295eef4b..6f0ebdcc9258 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -122,17 +122,17 @@ public void execute() { private final class CreateViewOperation extends ViewOperation { - private final int mRootViewTagForContext; + private final ThemedReactContext mThemedContext; private final String mClassName; private final @Nullable CatalystStylesDiffMap mInitialProps; public CreateViewOperation( - int rootViewTagForContext, + ThemedReactContext themedContext, int tag, String className, @Nullable CatalystStylesDiffMap initialProps) { super(tag); - mRootViewTagForContext = rootViewTagForContext; + mThemedContext = themedContext; mClassName = className; mInitialProps = initialProps; } @@ -140,7 +140,7 @@ public CreateViewOperation( @Override public void execute() { mNativeViewHierarchyManager.createView( - mRootViewTagForContext, + mThemedContext, mTag, mClassName, mInitialProps); @@ -533,13 +533,13 @@ public void enqueueShowPopupMenu( } public void enqueueCreateView( - int rootViewTagForContext, + ThemedReactContext themedContext, int viewReactTag, String viewClassName, @Nullable CatalystStylesDiffMap initialProps) { mOperations.add( new CreateViewOperation( - rootViewTagForContext, + themedContext, viewReactTag, viewClassName, initialProps)); From da9e9e709b42a8875d38977c4ae37c88de92b084 Mon Sep 17 00:00:00 2001 From: Perry Poon Date: Tue, 1 Dec 2015 10:50:35 +0800 Subject: [PATCH 0175/1411] add mockingbot to showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 7d9bbfd816a9..ab74ad8aad96 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -386,6 +386,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, + { + name: 'MockingBot', + icon: 'https://s3.cn-north-1.amazonaws.com.cn/modao/downloads/images/MockingBot175.png', + link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8', + author: 'YuanYi Zhang (@mockingbot)', + }, ]; var AppList = React.createClass({ From 00046bc832d5b0f87a56d3016d6fe4ab1d6898aa Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 30 Nov 2015 18:03:09 -0800 Subject: [PATCH 0176/1411] Allow extending NativeViewHierarchyManager Summary: public This diff makes a few small changes to NativeViewHierarchyManager to allow extending it: a) makes the class public so it can be constructed from outside of the package b) adds resolveView and resolveViewManager to access mTagsToViews and mTagsToViewManagers c) changes addRootView signature to make root view a ViewGroup instead of SizeMonitoringFrameLayout The reason behind change c) is that in FlatUIImplementation I want to use a root view that does not extend SizeMonitoringFrameLayout. NativeViewHierarchyManager doesn't really use any of the root view properties, so it could be even a View, but ViewGroup seems more fitting. This diff should contain no functional changes or other side-effects. Reviewed By: astreet Differential Revision: D2554841 fb-gh-sync-id: cce748707cf7485d456e4a057dae1db87aa17160 --- .../uimanager/NativeViewHierarchyManager.java | 70 +++++++++---------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 3be9a92e1db1..28b95139ca9a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -56,7 +56,7 @@ * TODO(5483031): Only dispatch updates when shadow views have changed */ @NotThreadSafe -/* package */ final class NativeViewHierarchyManager { +public class NativeViewHierarchyManager { private final AnimationRegistry mAnimationRegistry; private final SparseArray mTagsToViews; @@ -74,39 +74,40 @@ public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { mRootTags = new SparseBooleanArray(); } - public AnimationRegistry getAnimationRegistry() { - return mAnimationRegistry; + protected final View resolveView(int tag) { + View view = mTagsToViews.get(tag); + if (view == null) { + throw new IllegalViewOperationException("Trying to resolve view with tag " + tag + + " which doesn't exist"); + } + return view; } - public void updateProperties(int tag, CatalystStylesDiffMap props) { - UiThreadUtil.assertOnUiThread(); - + protected final ViewManager resolveViewManager(int tag) { ViewManager viewManager = mTagsToViewManagers.get(tag); if (viewManager == null) { throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found"); } + return viewManager; + } - View viewToUpdate = mTagsToViews.get(tag); - if (viewToUpdate == null) { - throw new IllegalViewOperationException("Trying to update view with tag " + tag - + " which doesn't exist"); - } + public AnimationRegistry getAnimationRegistry() { + return mAnimationRegistry; + } + + public void updateProperties(int tag, CatalystStylesDiffMap props) { + UiThreadUtil.assertOnUiThread(); + + ViewManager viewManager = resolveViewManager(tag); + View viewToUpdate = resolveView(tag); viewManager.updateProperties(viewToUpdate, props); } public void updateViewExtraData(int tag, Object extraData) { UiThreadUtil.assertOnUiThread(); - ViewManager viewManager = mTagsToViewManagers.get(tag); - if (viewManager == null) { - throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found"); - } - - View viewToUpdate = mTagsToViews.get(tag); - if (viewToUpdate == null) { - throw new IllegalViewOperationException("Trying to update view with tag " + tag + " which " + - "doesn't exist"); - } + ViewManager viewManager = resolveViewManager(tag); + View viewToUpdate = resolveView(tag); viewManager.updateExtraData(viewToUpdate, extraData); } @@ -119,11 +120,7 @@ public void updateLayout( int height) { UiThreadUtil.assertOnUiThread(); - View viewToUpdate = mTagsToViews.get(tag); - if (viewToUpdate == null) { - throw new IllegalViewOperationException("Trying to update view with tag " + tag + " which " + - "doesn't exist"); - } + View viewToUpdate = resolveView(tag); // Even though we have exact dimensions, we still call measure because some platform views (e.g. // Switch) assume that method will always be called before onLayout and onDraw. They use it to @@ -257,10 +254,7 @@ public void manageChildren( @Nullable ViewAtIndex[] viewsToAdd, @Nullable int[] tagsToDelete) { ViewGroup viewToManage = (ViewGroup) mTagsToViews.get(tag); - ViewGroupManager viewManager = (ViewGroupManager) mTagsToViewManagers.get(tag); - if (viewManager == null) { - throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found"); - } + ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(tag); if (viewToManage == null) { throw new IllegalViewOperationException("Trying to manageChildren view with tag " + tag + " which doesn't exist\n detail: " + @@ -362,6 +356,13 @@ public void addRootView( int tag, SizeMonitoringFrameLayout view, ThemedReactContext themedContext) { + addRootViewGroup(tag, view, themedContext); + } + + protected final void addRootViewGroup( + int tag, + ViewGroup view, + ThemedReactContext themedContext) { UiThreadUtil.assertOnUiThread(); if (view.getId() != View.NO_ID) { throw new IllegalViewOperationException( @@ -383,7 +384,7 @@ private void dropView(View view) { UiThreadUtil.assertOnUiThread(); if (!mRootTags.get(view.getId())) { // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} - Assertions.assertNotNull(mTagsToViewManagers.get(view.getId())).onDropViewInstance( + resolveViewManager(view.getId()).onDropViewInstance( (ThemedReactContext) view.getContext(), view); } @@ -512,12 +513,7 @@ public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray "with tag " + reactTag); } - ViewManager viewManager = mTagsToViewManagers.get(reactTag); - if (viewManager == null) { - throw new IllegalViewOperationException( - "ViewManager for view tag " + reactTag + " could not be found"); - } - + ViewManager viewManager = resolveViewManager(reactTag); viewManager.receiveCommand(view, commandId, args); } From baa97b2e9fd4125df6112573cb793688ed6f63c6 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Mon, 30 Nov 2015 18:18:52 -0800 Subject: [PATCH 0177/1411] Make ViewManagerRegistry class public Summary: public I need to construct this class outside of the uimanager package, and this requires the class (and its constructor) to be public. Reviewed By: astreet Differential Revision: D2554847 fb-gh-sync-id: a73c3236b91a1ed7074521e19b72e1994804cced --- .../com/facebook/react/uimanager/ViewManagerRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java index 2dffc8c26ca7..3dfacf1b387e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java @@ -17,7 +17,7 @@ * Class that stores the mapping between native view name used in JS and the corresponding instance * of {@link ViewManager}. */ -/* package */ class ViewManagerRegistry { +public class ViewManagerRegistry { private final Map mViewManagers = new HashMap<>(); @@ -27,7 +27,7 @@ public ViewManagerRegistry(List viewManagerList) { } } - /* package */ ViewManager get(String className) { + public ViewManager get(String className) { ViewManager viewManager = mViewManagers.get(className); if (viewManager != null) { return viewManager; From 7242efde0ae56545fedc29630fa70158128dca7d Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 30 Nov 2015 19:30:17 -0800 Subject: [PATCH 0178/1411] fix navigator lint warnings Summary: fix some lint warnings under `Libraries/CustomComponents/Navigator/` directory Closes https://github.com/facebook/react-native/pull/4447 Reviewed By: svcscm Differential Revision: D2706390 Pulled By: ericvicenti fb-gh-sync-id: 5bfed687265e07a162ffc528a41fa5b0d384f635 --- Libraries/CustomComponents/Navigator/Navigator.js | 4 ++-- .../Navigator/NavigatorBreadcrumbNavigationBar.js | 10 +++++----- .../NavigatorBreadcrumbNavigationBarStyles.ios.js | 2 +- .../Navigator/NavigatorNavigationBar.js | 1 - .../Navigator/NavigatorNavigationBarStylesIOS.js | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index e11ee25055ed..22bb84365235 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -675,7 +675,7 @@ var Navigator = React.createClass({ this.state.presentedIndex = destIndex; this._transitionTo( transitionBackToPresentedIndex, - - transitionVelocity, + -transitionVelocity, 1 - this.spring.getCurrentValue() ); } @@ -750,7 +750,7 @@ var Navigator = React.createClass({ var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top'; var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top'; var distance = isTravelVertical ? gestureState.dy : gestureState.dx; - distance = isTravelInverted ? - distance : distance; + distance = isTravelInverted ? -distance : distance; var gestureDetectMovement = gesture.gestureDetectMovement; var nextProgress = (distance - gestureDetectMovement) / (gesture.fullDistance - gestureDetectMovement); diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index b205327afa18..ccf28f1f9d1e 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -46,11 +46,11 @@ var PropTypes = React.PropTypes; /** * Reusable props objects. */ -var CRUMB_PROPS = Interpolators.map(() => {return {style: {}};}); -var ICON_PROPS = Interpolators.map(() => {return {style: {}};}); -var SEPARATOR_PROPS = Interpolators.map(() => {return {style: {}};}); -var TITLE_PROPS = Interpolators.map(() => {return {style: {}};}); -var RIGHT_BUTTON_PROPS = Interpolators.map(() => {return {style: {}};}); +var CRUMB_PROPS = Interpolators.map(() => ({style: {}})); +var ICON_PROPS = Interpolators.map(() => ({style: {}})); +var SEPARATOR_PROPS = Interpolators.map(() => ({style: {}})); +var TITLE_PROPS = Interpolators.map(() => ({style: {}})); +var RIGHT_BUTTON_PROPS = Interpolators.map(() => ({style: {}})); var navStatePresentedIndex = function(navState) { diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js index d73cb5661661..eea15eee9bec 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js @@ -136,7 +136,7 @@ CENTER[0] = { Title: merge(FIRST_TITLE_BASE, {opacity: 1}), RightItem: CENTER[0].RightItem, }; -LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: - SCREEN_WIDTH / 4, opacity: 0}); +LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: -SCREEN_WIDTH / 4, opacity: 0}); RIGHT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0}); diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js index 2b6c79def612..b6fbcdc378e5 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js @@ -30,7 +30,6 @@ var React = require('React'); var NavigatorNavigationBarStylesAndroid = require('NavigatorNavigationBarStylesAndroid'); var NavigatorNavigationBarStylesIOS = require('NavigatorNavigationBarStylesIOS'); var Platform = require('Platform'); -var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); var View = require('View'); diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js index fdad315adc81..9e14677519a4 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js +++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js @@ -76,8 +76,8 @@ var BASE_STYLES = { // center-to-left transition on the current navigation item. var Stages = { Left: { - Title: merge(BASE_STYLES.Title, { left: - SCREEN_WIDTH / 2, opacity: 0 }), - LeftButton: merge(BASE_STYLES.LeftButton, { left: - SCREEN_WIDTH / 3, opacity: 0 }), + Title: merge(BASE_STYLES.Title, { left: -SCREEN_WIDTH / 2, opacity: 0 }), + LeftButton: merge(BASE_STYLES.LeftButton, { left: -SCREEN_WIDTH / 3, opacity: 0 }), RightButton: merge(BASE_STYLES.RightButton, { left: SCREEN_WIDTH / 3, opacity: 0 }), }, Center: { From f025049b6cc10fc1c91556a8dd250d94329bf363 Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Mon, 30 Nov 2015 18:44:06 -0800 Subject: [PATCH 0179/1411] Add secure and login-password types to AlertIOS. Summary: Request from issue #3893 * Added support for `secure-text` and `login-password` types to AlertIOS. * Fixed and extended the cancel button highlighting functionality, which was broken at some point * Added localization for default `OK` and `Cancel` labels when using UIAlertController Closes https://github.com/facebook/react-native/pull/4401 Reviewed By: javache Differential Revision: D2702052 Pulled By: nicklockwood fb-gh-sync-id: cce312d7fec949f5fd2a7c656e65c657c4832c8f --- Examples/UIExplorer/AlertIOSExample.js | 88 ++++++++++++- Libraries/Utilities/AlertIOS.js | 69 ++++++----- React/Base/RCTUtils.h | 3 + React/Base/RCTUtils.m | 8 ++ React/Modules/RCTAlertManager.m | 164 +++++++++++++++++-------- 5 files changed, 249 insertions(+), 83 deletions(-) diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js index e09c883c137e..8666d5ba9bcc 100644 --- a/Examples/UIExplorer/AlertIOSExample.js +++ b/Examples/UIExplorer/AlertIOSExample.js @@ -43,7 +43,7 @@ exports.examples = [{ AlertIOS.alert( - null, + 'Foo Title', null, [ {text: 'Button', onPress: () => console.log('Button Pressed!')}, @@ -97,6 +97,87 @@ exports.examples = [{ ); } }, +{ + title: 'Alert Types', + render() { + return ( + + AlertIOS.alert( + 'Hello World', + null, + [ + {text: 'OK', onPress: (text) => console.log('OK pressed')}, + ], + 'default' + )}> + + + + {'default'} + + + + + AlertIOS.alert( + 'Plain Text Entry', + null, + [ + {text: 'Submit', onPress: (text) => console.log('Text: ' + text)}, + ], + 'plain-text' + )}> + + + + plain-text + + + + + AlertIOS.alert( + 'Secure Text Entry', + null, + [ + {text: 'Submit', onPress: (text) => console.log('Password: ' + text)}, + ], + 'secure-text' + )}> + + + + secure-text + + + + + AlertIOS.alert( + 'Login & Password', + null, + [ + {text: 'Submit', onPress: (details) => console.log('Login: ' + details.login + '; Password: ' + details.password)}, + ], + 'login-password' + )}> + + + + login-password + + + + + + ); + } +}, { title: 'Prompt', render(): React.Component { @@ -116,10 +197,11 @@ class PromptExample extends React.Component { this.title = 'Type a value'; this.defaultValue = 'Default value'; this.buttons = [{ - text: 'Custom cancel', - }, { text: 'Custom OK', onPress: this.promptResponse + }, { + text: 'Custom Cancel', + style: 'cancel', }]; } diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js index 7fd15d254ae6..2bd1a3223680 100644 --- a/Libraries/Utilities/AlertIOS.js +++ b/Libraries/Utilities/AlertIOS.js @@ -14,11 +14,18 @@ var RCTAlertManager = require('NativeModules').AlertManager; var invariant = require('invariant'); -var DEFAULT_BUTTON_TEXT = 'OK'; -var DEFAULT_BUTTON = { - text: DEFAULT_BUTTON_TEXT, - onPress: null, -}; +type AlertType = $Enum<{ + 'default': string; + 'plain-text': string; + 'secure-text': string; + 'login-password': string; +}>; + +type AlertButtonStyle = $Enum<{ + 'default': string; + 'cancel': string; + 'destructive': string; +}>; /** * Launches an alert dialog with the specified title and message. @@ -27,16 +34,13 @@ var DEFAULT_BUTTON = { * respective onPress callback and dismiss the alert. By default, the only * button will be an 'OK' button * - * The last button in the list will be considered the 'Primary' button and - * it will appear bold. - * * ``` * AlertIOS.alert( * 'Foo Title', * 'My Alert Msg', * [ - * {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, - * {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, + * {text: 'OK', onPress: () => console.log('OK Pressed!')}, + * {text: 'Cancel', onPress: () => console.log('Cancel Pressed!'), style: 'cancel'}, * ] * ) * ``` @@ -47,29 +51,36 @@ class AlertIOS { title: ?string, message?: ?string, buttons?: Array<{ - text: ?string; + text?: string; onPress?: ?Function; + style?: AlertButtonStyle; }>, - type?: ?string + type?: ?AlertType ): void { var callbacks = []; var buttonsSpec = []; - title = title || ''; - message = message || ''; - buttons = buttons || [DEFAULT_BUTTON]; - type = type || ''; - - buttons.forEach((btn, index) => { + var cancelButtonKey; + var destructiveButtonKey; + buttons && buttons.forEach((btn, index) => { callbacks[index] = btn.onPress; - var btnDef = {}; - btnDef[index] = btn.text || DEFAULT_BUTTON_TEXT; - buttonsSpec.push(btnDef); + if (btn.style == 'cancel') { + cancelButtonKey = String(index); + } else if (btn.style == 'destructive') { + destructiveButtonKey = String(index); + } + if (btn.text || index < (buttons || []).length - 1) { + var btnDef = {}; + btnDef[index] = btn.text || ''; + buttonsSpec.push(btnDef); + } }); RCTAlertManager.alertWithArgs({ - title, - message, + title: title || undefined, + message: message || undefined, buttons: buttonsSpec, - type, + type: type || undefined, + cancelButtonKey, + destructiveButtonKey, }, (id, value) => { var cb = callbacks[id]; cb && cb(value); @@ -80,8 +91,9 @@ class AlertIOS { title: string, value?: string, buttons?: Array<{ - text: ?string; + text?: string; onPress?: ?Function; + style?: AlertButtonStyle; }>, callback?: ?Function ): void { @@ -104,12 +116,7 @@ class AlertIOS { ); if (!buttons) { - buttons = [{ - text: 'Cancel', - }, { - text: 'OK', - onPress: callback - }]; + buttons = [{ onPress: callback }]; } this.alert(title, value, buttons, 'plain-text'); } diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index e8f3781a6c61..251167e0ba34 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -102,3 +102,6 @@ RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL); // Converts a CGColor to a hex string RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color); + +// Get standard localized string (if it exists) +RCT_EXTERN NSString *RCTUIKitLocalizedString(NSString *string); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index b7a751488378..d1254a98abbd 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -575,3 +575,11 @@ static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4]) return [NSString stringWithFormat:@"#%02x%02x%02x", r, g, b]; } } + + +// (https://github.com/0xced/XCDFormInputAccessoryView/blob/master/XCDFormInputAccessoryView/XCDFormInputAccessoryView.m#L10-L14) +RCT_EXTERN NSString *RCTUIKitLocalizedString(NSString *string) +{ + NSBundle *UIKitBundle = [NSBundle bundleForClass:[UIApplication class]]; + return UIKitBundle ? [UIKitBundle localizedStringForKey:string value:string table:nil] : string; +} diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index 74168866af06..d735d54423a2 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -14,6 +14,17 @@ #import "RCTLog.h" #import "RCTUtils.h" +@implementation RCTConvert (UIAlertViewStyle) + +RCT_ENUM_CONVERTER(UIAlertViewStyle, (@{ + @"default": @(UIAlertViewStyleDefault), + @"secure-text": @(UIAlertViewStyleSecureTextInput), + @"plain-text": @(UIAlertViewStylePlainTextInput), + @"login-password": @(UIAlertViewStyleLoginAndPasswordInput), +}), UIAlertViewStyleDefault, integerValue) + +@end + @interface RCTAlertManager() @end @@ -50,58 +61,69 @@ - (void)invalidate * @"message": @"", * @"buttons": @[ * @{@"": @""}, - * @{@"": @""}, - * ] + * @{@"": @""}, + * ], + * @"cancelButtonKey": @"", * } * The key from the `buttons` dictionary is passed back in the callback on click. - * Buttons are displayed in the order they are specified. If "cancel" is used as - * the button key, it will be differently highlighted, according to iOS UI conventions. + * Buttons are displayed in the order they are specified. */ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args callback:(RCTResponseSenderBlock)callback) { NSString *title = [RCTConvert NSString:args[@"title"]]; NSString *message = [RCTConvert NSString:args[@"message"]]; - NSString *type = [RCTConvert NSString:args[@"type"]]; - NSDictionaryArray *buttons = [RCTConvert NSDictionaryArray:args[@"buttons"]]; - BOOL allowsTextInput = [type isEqual:@"plain-text"]; + UIAlertViewStyle type = [RCTConvert UIAlertViewStyle:args[@"type"]]; + NSArray *buttons = [RCTConvert NSDictionaryArray:args[@"buttons"]]; + NSString *cancelButtonKey = [RCTConvert NSString:args[@"cancelButtonKey"]]; + NSString *destructiveButtonKey = [RCTConvert NSString:args[@"destructiveButtonKey"]]; if (!title && !message) { RCTLogError(@"Must specify either an alert title, or message, or both"); return; - } else if (buttons.count == 0) { - RCTLogError(@"Must have at least one button."); - return; + } + + if (buttons.count == 0) { + if (type == UIAlertViewStyleDefault) { + buttons = @[@{@"0": RCTUIKitLocalizedString(@"OK")}]; + cancelButtonKey = @"0"; + } else { + buttons = @[ + @{@"0": RCTUIKitLocalizedString(@"OK")}, + @{@"1": RCTUIKitLocalizedString(@"Cancel")}, + ]; + cancelButtonKey = @"1"; + } } #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - // TODO: we've encountered some bug when presenting alerts on top of a window that is subsequently - // dismissed. As a temporary solution to this, we'll use UIAlertView preferentially if it's available. - BOOL preferAlertView = (!RCTRunningInAppExtension() && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone); + // TODO: we've encountered some bug when presenting alerts on top of a window + // that is subsequently dismissed. As a temporary solution to this, we'll use + // UIAlertView preferentially if it's available and supports our use case. + BOOL preferAlertView = (!RCTRunningInAppExtension() && + !destructiveButtonKey && + UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone); if (preferAlertView || [UIAlertController class] == nil) { UIAlertView *alertView = RCTAlertView(title, nil, self, nil, nil); - NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count]; + alertView.alertViewStyle = type; + alertView.message = message; - if (allowsTextInput) { - alertView.alertViewStyle = UIAlertViewStylePlainTextInput; - [alertView textFieldAtIndex:0].text = message; - } else { - alertView.message = message; - } + NSMutableArray *buttonKeys = + [[NSMutableArray alloc] initWithCapacity:buttons.count]; NSInteger index = 0; - for (NSDictionary *button in buttons) { + 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]; + NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]]; [alertView addButtonWithTitle:buttonTitle]; - if ([buttonKey isEqualToString:@"cancel"]) { - alertView.cancelButtonIndex = index; + if ([buttonKey isEqualToString:cancelButtonKey]) { + alertView.cancelButtonIndex = buttonKeys.count; } [buttonKeys addObject:buttonKey]; index ++; @@ -129,42 +151,74 @@ - (void)invalidate return; } - // Walk the chain up to get the topmost modal view controller. If modals are presented, - // the root view controller's view might not be in the window hierarchy, and presenting from it will fail. + // Walk the chain up to get the topmost modal view controller. If modals are + // presented the root view controller's view might not be in the window + // hierarchy, and presenting from it will fail. while (presentingController.presentedViewController) { presentingController = presentingController.presentedViewController; } UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:title - message:nil - preferredStyle:UIAlertControllerStyleAlert]; - - if (allowsTextInput) { - [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.text = message; - }]; - } else { - alertController.message = message; + [UIAlertController alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + switch (type) { + case UIAlertViewStylePlainTextInput: + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = NO; + }]; + break; + case UIAlertViewStyleSecureTextInput: + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Password"); + textField.secureTextEntry = YES; + }]; + break; + case UIAlertViewStyleLoginAndPasswordInput: + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Login"); + }]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = RCTUIKitLocalizedString(@"Password"); + textField.secureTextEntry = YES; + }]; + case UIAlertViewStyleDefault: + break; } - for (NSDictionary *button in buttons) { + alertController.message = message; + + 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]; - UIAlertActionStyle buttonStyle = [buttonKey isEqualToString:@"cancel"] ? UIAlertActionStyleCancel : UIAlertActionStyleDefault; - UITextField *textField = allowsTextInput ? alertController.textFields.firstObject : nil; + NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]]; + UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault; + if ([buttonKey isEqualToString:cancelButtonKey]) { + buttonStyle = UIAlertActionStyleCancel; + } else if ([buttonKey isEqualToString:destructiveButtonKey]) { + buttonStyle = UIAlertActionStyleDestructive; + } [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle style:buttonStyle handler:^(__unused UIAlertAction *action) { - if (callback) { - if (allowsTextInput) { - callback(@[buttonKey, textField.text]); - } else { - callback(@[buttonKey]); + switch (type) { + case UIAlertViewStylePlainTextInput: + case UIAlertViewStyleSecureTextInput: + callback(@[buttonKey, [alertController.textFields.firstObject text]]); + break; + case UIAlertViewStyleLoginAndPasswordInput: { + NSDictionary *loginCredentials = @{ + @"login": [alertController.textFields.firstObject text], + @"password": [alertController.textFields.lastObject text] + }; + callback(@[buttonKey, loginCredentials]); + break; } + case UIAlertViewStyleDefault: + callback(@[buttonKey]); + break; } }]]; } @@ -188,10 +242,22 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto RCTResponseSenderBlock callback = _alertCallbacks[index]; NSArray *buttonKeys = _alertButtonKeys[index]; - if (alertView.alertViewStyle == UIAlertViewStylePlainTextInput) { - callback(@[buttonKeys[buttonIndex], [alertView textFieldAtIndex:0].text]); - } else { - callback(@[buttonKeys[buttonIndex]]); + switch (alertView.alertViewStyle) { + case UIAlertViewStylePlainTextInput: + case UIAlertViewStyleSecureTextInput: + callback(@[buttonKeys[buttonIndex], [alertView textFieldAtIndex:0].text]); + break; + case UIAlertViewStyleLoginAndPasswordInput: { + NSDictionary *loginCredentials = @{ + @"login": [alertView textFieldAtIndex:0].text, + @"password": [alertView textFieldAtIndex:1].text, + }; + callback(@[buttonKeys[buttonIndex], loginCredentials]); + break; + } + case UIAlertViewStyleDefault: + callback(@[buttonKeys[buttonIndex]]); + break; } [_alerts removeObjectAtIndex:index]; From aad40b8b1d3c53145272fd958a0e9f1cdc4531a0 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 1 Dec 2015 09:55:57 +0100 Subject: [PATCH 0180/1411] Update Android Setup with recommendation to use gradle daemon --- docs/DevelopmentSetupAndroid.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/DevelopmentSetupAndroid.md b/docs/DevelopmentSetupAndroid.md index 2cc0a9159373..6a7b7d87f3c6 100644 --- a/docs/DevelopmentSetupAndroid.md +++ b/docs/DevelopmentSetupAndroid.md @@ -30,6 +30,10 @@ __IMPORTANT__: Make sure the `ANDROID_HOME` environment variable points to your - **On Windows**, go to `Control Panel` -> `System and Security` -> `System` -> `Change settings` -> `Advanced` -> `Environment variables` -> `New` +### Use gradle daemon + +React Native Android use [gradle](https://docs.gradle.org) as a build system. We recommend to enable gradle daemon functionailty which may result in up to 50% improvement in incremental build times for changes in java code. Learn [here](https://docs.gradle.org/2.9/userguide/gradle_daemon.html) how to enable it for your platform. + ### Configure your SDK 1. Open the Android SDK Manager (**on Mac** start a new shell and run `android`); in the window that appears make sure you check: From d08727d99fa07caabcb1fb37cf91de9a47e13b82 Mon Sep 17 00:00:00 2001 From: Aleksei Androsov Date: Tue, 1 Dec 2015 02:10:20 -0800 Subject: [PATCH 0181/1411] RCTImagePickerManager crash on image from CameraRoll Summary: Image from CameraRoll haven't UIImagePickerControllerReferenceURL. So we need to save it to PhotoAlbums first. To save image I've used the same method from RCTCameraRollManager. Closes https://github.com/facebook/react-native/pull/4412 Reviewed By: svcscm Differential Revision: D2707249 Pulled By: nicklockwood fb-gh-sync-id: eee683bd4179700bed46ebf45e569197f3ad2077 --- Libraries/CameraRoll/RCTImagePickerManager.m | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index d82d0e9bf554..0f97099ad851 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -9,6 +9,7 @@ */ #import "RCTImagePickerManager.h" +#import "RCTImageStoreManager.h" #import "RCTConvert.h" #import "RCTRootView.h" @@ -32,6 +33,8 @@ @implementation RCTImagePickerManager RCT_EXPORT_MODULE(ImagePickerIOS); +@synthesize bridge = _bridge; + - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -101,9 +104,26 @@ - (dispatch_queue_t)methodQueue - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - [self _dismissPicker:picker args:@[ - [info[UIImagePickerControllerReferenceURL] absoluteString] - ]]; + // Image from PhotoLibrary + NSString *imageUri = [info[UIImagePickerControllerReferenceURL] absoluteString]; + if (imageUri) { + [self _dismissPicker:picker args:@[imageUri]]; + + } else { + // Image from CameraRoll hasn't uri. + // We need to save it to the store first. + UIImage *originalImage = info[UIImagePickerControllerOriginalImage]; + + // WARNING: Using imageStoreManager causes memory leak + // because image isn't removed from store once we're done using it + [_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) { + if (!tempImageTag) { + [self _dismissPicker:picker args:nil]; + return; + } + [self _dismissPicker:picker args:@[tempImageTag]]; + }]; + } } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker From 7377fdcc70b25eb023e7c6d1b37eeae2a700cb88 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Tue, 1 Dec 2015 04:41:04 -0800 Subject: [PATCH 0182/1411] Fix XY coords to be XY instead of YX Summary: public This fixes the ordering of methods in touch handling to take their arguments as X,Y instead of Y,X. This is really just internal cleanup of native touch handling. Reviewed By: andreicoman11 Differential Revision: D2703003 fb-gh-sync-id: d169436d21fd11c1a9cb251e7e0b57b2094699e4 --- .../com/facebook/react/ReactRootView.java | 28 ++++++++-------- .../uimanager/NativeViewHierarchyManager.java | 2 +- .../react/uimanager/TouchTargetHelper.java | 32 +++++++++---------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 0f51cd86d4bb..3450f883aa55 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -61,8 +61,6 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView private @Nullable String mJSModuleName; private @Nullable Bundle mLaunchOptions; private int mTargetTag = -1; - // Note mTargetCoordinates are Y,X - // TODO: t9136625 tracks moving to X,Y private final float[] mTargetCoordinates = new float[2]; private boolean mChildIsHandlingNativeGesture = false; private boolean mWasMeasured = false; @@ -147,8 +145,8 @@ private void handleTouchEvent(MotionEvent ev) { // this gesture mChildIsHandlingNativeGesture = false; mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch( - ev.getY(), ev.getX(), + ev.getY(), this, mTargetCoordinates); eventDispatcher.dispatchEvent( @@ -157,8 +155,8 @@ private void handleTouchEvent(MotionEvent ev) { SystemClock.uptimeMillis(), TouchEventType.START, ev, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); } else if (mChildIsHandlingNativeGesture) { // If the touch was intercepted by a child, we've already sent a cancel event to JS for this // gesture, so we shouldn't send any more touches related to it. @@ -179,8 +177,8 @@ private void handleTouchEvent(MotionEvent ev) { SystemClock.uptimeMillis(), TouchEventType.END, ev, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); mTargetTag = -1; } else if (action == MotionEvent.ACTION_MOVE) { // Update pointer position for current gesture @@ -190,8 +188,8 @@ private void handleTouchEvent(MotionEvent ev) { SystemClock.uptimeMillis(), TouchEventType.MOVE, ev, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); } else if (action == MotionEvent.ACTION_POINTER_DOWN) { // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer eventDispatcher.dispatchEvent( @@ -200,8 +198,8 @@ private void handleTouchEvent(MotionEvent ev) { SystemClock.uptimeMillis(), TouchEventType.START, ev, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); } else if (action == MotionEvent.ACTION_POINTER_UP) { // Exactly onw of the pointers goes up eventDispatcher.dispatchEvent( @@ -210,8 +208,8 @@ private void handleTouchEvent(MotionEvent ev) { SystemClock.uptimeMillis(), TouchEventType.END, ev, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); } else if (action == MotionEvent.ACTION_CANCEL) { dispatchCancelEvent(ev); mTargetTag = -1; @@ -261,8 +259,8 @@ private void dispatchCancelEvent(MotionEvent androidEvent) { SystemClock.uptimeMillis(), TouchEventType.CANCEL, androidEvent, - mTargetCoordinates[1], - mTargetCoordinates[0])); + mTargetCoordinates[0], + mTargetCoordinates[1])); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 28b95139ca9a..ac6ae4e46d8c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -437,7 +437,7 @@ public int findTargetTagForTouch(int reactTag, float touchX, float touchY) { if (view == null) { throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag); } - return TouchTargetHelper.findTargetTagForTouch(touchY, touchX, (ViewGroup) view); + return TouchTargetHelper.findTargetTagForTouch(touchX, touchY, (ViewGroup) view); } public void setJSResponder(int reactTag, int initialReactTag, boolean blockNativeResponder) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 24a4d286946f..8564258babe7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -35,38 +35,38 @@ public class TouchTargetHelper { * Find touch event target view within the provided container given the coordinates provided * via {@link MotionEvent}. * - * @param eventY the Y screen coordinate of the touch location * @param eventX the X screen coordinate of the touch location + * @param eventY the Y screen coordinate of the touch location * @param viewGroup the container view to traverse * @return the react tag ID of the child view that should handle the event */ public static int findTargetTagForTouch( - float eventY, float eventX, + float eventY, ViewGroup viewGroup) { - return findTargetTagAndCoordinatesForTouch(eventY, eventX, viewGroup, mEventCoords); + return findTargetTagAndCoordinatesForTouch(eventX, eventY, viewGroup, mEventCoords); } /** * Find touch event target view within the provided container given the coordinates provided * via {@link MotionEvent}. * - * @param eventY the Y screen coordinate of the touch location * @param eventX the X screen coordinate of the touch location + * @param eventY the Y screen coordinate of the touch location * @param viewGroup the container view to traverse - * @param viewCoords an out parameter that will return the Y,X value in the target view + * @param viewCoords an out parameter that will return the X,Y value in the target view * @return the react tag ID of the child view that should handle the event */ public static int findTargetTagAndCoordinatesForTouch( - float eventY, float eventX, + float eventY, ViewGroup viewGroup, float[] viewCoords) { UiThreadUtil.assertOnUiThread(); int targetTag = viewGroup.getId(); // Store eventCoords in array so that they are modified to be relative to the targetView found. - viewCoords[0] = eventY; - viewCoords[1] = eventX; + viewCoords[0] = eventX; + viewCoords[1] = eventY; View nativeTargetView = findTouchTargetView(viewCoords, viewGroup); if (nativeTargetView != null) { View reactTargetView = findClosestReactAncestor(nativeTargetView); @@ -105,16 +105,16 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup // coordinates relative to the child // We need to store the existing X,Y for the viewGroup away as it is possible this child // will not actually be the target and so we restore them if not - float restoreY = eventCoords[0]; - float restoreX = eventCoords[1]; - eventCoords[0] = childPoint.y; - eventCoords[1] = childPoint.x; + float restoreX = eventCoords[0]; + float restoreY = eventCoords[1]; + eventCoords[0] = childPoint.x; + eventCoords[1] = childPoint.y; View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child); if (targetView != null) { return targetView; } - eventCoords[0] = restoreY; - eventCoords[1] = restoreX; + eventCoords[0] = restoreX; + eventCoords[1] = restoreY; } } return viewGroup; @@ -126,8 +126,8 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup * This code is taken from {@link ViewGroup#isTransformedTouchPointInView()} */ private static boolean isTransformedTouchPointInView( - float y, float x, + float y, ViewGroup parent, View child, PointF outLocalPoint) { @@ -190,7 +190,7 @@ private static boolean isTransformedTouchPointInView( } } - private static int getTouchTargetForView(View targetView, float eventY, float eventX) { + private static int getTouchTargetForView(View targetView, float eventX, float eventY) { if (targetView instanceof ReactCompoundView) { // Use coordinates relative to the view, which have been already computed by // {@link #findTouchTargetView()}. From 8ec052a727c45e98e23fac581cf0bc31c2271c9b Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 1 Dec 2015 05:06:45 -0800 Subject: [PATCH 0183/1411] Expose JS hooks to profile async events Summary: public Expose JS hooks to add async events to Systrace from JS. Reviewed By: mikearmstrong001 Differential Revision: D2702739 fb-gh-sync-id: 906849d3819a914c521b9315e396c75946c9211d --- React/Executors/RCTContextExecutor.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 163718799f87..b666690f165d 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -352,7 +352,7 @@ - (void)setUp [bridge handleBuffer:calls batchEnded:NO]; }; - strongSelf->_context.context[@"RCTPerformanceNow"] = ^(){ + strongSelf->_context.context[@"RCTPerformanceNow"] = ^{ return CACurrentMediaTime() * 1000 * 1000; }; @@ -361,6 +361,20 @@ - (void)setUp strongSelf->_context.context[@"__RCTProfileIsProfiling"] = @YES; } + CFMutableDictionaryRef cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); + strongSelf->_context.context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil); + CFDictionarySetValue(cookieMap, (const void *)cookie, (const void *)newCookie); + return; + }; + + strongSelf->_context.context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(cookieMap, (const void *)cookie); + RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, nil); + CFDictionaryRemoveValue(cookieMap, (const void *)cookie); + return; + }; + [strongSelf _addNativeHook:RCTNativeTraceBeginSection withName:"nativeTraceBeginSection"]; [strongSelf _addNativeHook:RCTNativeTraceEndSection withName:"nativeTraceEndSection"]; From 16b9c7d6f61b49917ccdb30335d1190e3fdb8247 Mon Sep 17 00:00:00 2001 From: Perry Poon Date: Tue, 1 Dec 2015 23:01:48 +0800 Subject: [PATCH 0184/1411] change order in showcase --- website/src/react-native/showcase.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index ab74ad8aad96..439ec56c9d7e 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -248,6 +248,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/mintrain/id1015739031?mt=8', author: 'Peter Cottle', }, + { + name: 'MockingBot', + icon: 'https://s3.cn-north-1.amazonaws.com.cn/modao/downloads/images/MockingBot175.png', + link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8', + author: 'YuanYi Zhang (@mockingbot)', + }, { name: 'Mr. Dapper', icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple4/v4/e8/3f/7c/e83f7cb3-2602-f8e8-de9a-ce0a775a4a14/mzl.hmdjhfai.png', @@ -386,12 +392,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, - { - name: 'MockingBot', - icon: 'https://s3.cn-north-1.amazonaws.com.cn/modao/downloads/images/MockingBot175.png', - link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8', - author: 'YuanYi Zhang (@mockingbot)', - }, ]; var AppList = React.createClass({ From 0a3694ce48be8991839c42aab2586202a12d43aa Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Tue, 1 Dec 2015 07:24:14 -0800 Subject: [PATCH 0185/1411] fix several lint warnings Summary: fix some ling warnings from several files Closes https://github.com/facebook/react-native/pull/4450 Reviewed By: svcscm Differential Revision: D2707606 Pulled By: mkonicek fb-gh-sync-id: 410ccacf061ac7b0f6e44d1a5e4621a9d1d606fc --- Libraries/Components/MapView/MapView.js | 2 +- Libraries/Components/View/ViewStylePropTypes.js | 2 +- .../Initialization/InitializeJavaScriptAppEngine.js | 2 +- Libraries/LinkingIOS/LinkingIOS.js | 2 +- Libraries/Network/XMLHttpRequest.ios.js | 1 - Libraries/Picker/PickerIOS.ios.js | 2 -- Libraries/RCTTest/SnapshotViewIOS.ios.js | 1 - 7 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 2dc4fdf30895..74f2774d68fc 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -232,7 +232,7 @@ var MapView = React.createClass({ * @platform ios */ image: Image.propTypes.source, - + /** * annotation id */ diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index 9f7ba776aab7..95767970510d 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -48,7 +48,7 @@ var ViewStylePropTypes = { shadowOpacity: ReactPropTypes.number, shadowRadius: ReactPropTypes.number, /** - * (Android-only) Sets the elevation of a view, using Android's underlying + * (Android-only) Sets the elevation of a view, using Android's underlying * [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation). * This adds a drop shadow to the item and affects z-order for overlapping views. * Only supported on Android 5.0+, has no effect on earlier versions. diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 9c2b7f58438b..141ea7045cd7 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -53,7 +53,7 @@ function setUpConsole() { * For more info on that particular case, see: * https://github.com/facebook/react-native/issues/934 */ -function polyfillGlobal(name, newValue, scope=GLOBAL) { +function polyfillGlobal(name, newValue, scope = GLOBAL) { var descriptor = Object.getOwnPropertyDescriptor(scope, name) || { // jest for some bad reasons runs the polyfill code multiple times. In jest // environment, XmlHttpRequest doesn't exist so getOwnPropertyDescriptor diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js index 0ee8367df073..7ee6ace38ce0 100644 --- a/Libraries/LinkingIOS/LinkingIOS.js +++ b/Libraries/LinkingIOS/LinkingIOS.js @@ -130,7 +130,7 @@ class LinkingIOS { * Determine whether or not an installed app can handle a given URL. * The callback function will be called with `bool supported` as the only argument * - * NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key + * NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key * inside `Info.plist`. */ static canOpenURL(url: string, callback: Function) { diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 56b319a254d8..21614ff36163 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -13,7 +13,6 @@ var FormData = require('FormData'); var RCTNetworking = require('RCTNetworking'); -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var XMLHttpRequestBase = require('XMLHttpRequestBase'); diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index 924f13573907..5c245397d0b0 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -15,13 +15,11 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); var ReactChildren = require('ReactChildren'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants; var StyleSheet = require('StyleSheet'); var View = require('View'); var requireNativeComponent = require('requireNativeComponent'); -var merge = require('merge'); var PICKER = 'picker'; diff --git a/Libraries/RCTTest/SnapshotViewIOS.ios.js b/Libraries/RCTTest/SnapshotViewIOS.ios.js index 7b6294be1ce7..3ecefdc3dea1 100644 --- a/Libraries/RCTTest/SnapshotViewIOS.ios.js +++ b/Libraries/RCTTest/SnapshotViewIOS.ios.js @@ -11,7 +11,6 @@ */ 'use strict'; -var Platform = require('Platform'); var React = require('React'); var StyleSheet = require('StyleSheet'); var { TestModule, UIManager } = require('NativeModules'); From b6f5c7fa041fb5e13b07876beed117d8a4de0d17 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Tue, 1 Dec 2015 07:37:52 -0800 Subject: [PATCH 0186/1411] Fix systrace profile handling for relay async calls Reviewed By: astreet Differential Revision: D2700239 fb-gh-sync-id: eaa29d63ee4f7688dd70b0cdc12564a9d479f9ef --- Libraries/Utilities/BridgeProfiling.js | 36 ++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 526775b20793..8e60354f2a50 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -22,6 +22,7 @@ var GLOBAL = GLOBAL || this; var TRACE_TAG_REACT_APPS = 1 << 17; var _enabled; +var _asyncCookie = 0; var _ReactPerf = null; function ReactPerf() { if (!_ReactPerf) { @@ -37,6 +38,9 @@ var BridgeProfiling = { ReactPerf().enableMeasure = enabled; }, + /** + * profile/profileEnd for starting and then ending a profile within the same call stack frame + **/ profile(profileName?: any) { if (_enabled) { profileName = typeof profileName === 'function' ? @@ -51,6 +55,30 @@ var BridgeProfiling = { } }, + /** + * profileAsync/profileAsyncEnd for starting and then ending a profile where the end can either + * occur on another thread or out of the current stack frame, eg await + * the returned cookie variable should be used as input into the asyncEnd call to end the profile + **/ + profileAsync(profileName?: any): any { + var cookie = _asyncCookie; + if (_enabled) { + _asyncCookie++; + profileName = typeof profileName === 'function' ? + profileName() : profileName; + global.nativeTraceBeginAsyncSection(TRACE_TAG_REACT_APPS, profileName, cookie, 0); + } + return cookie; + }, + + profileAsyncEnd(profileName?: any, cookie?: any) { + if (_enabled) { + profileName = typeof profileName === 'function' ? + profileName() : profileName; + global.nativeTraceEndAsyncSection(TRACE_TAG_REACT_APPS, profileName, cookie, 0); + } + }, + reactPerfMeasure(objName: string, fnName: string, func: any): any { return function (component) { if (!_enabled) { @@ -69,11 +97,15 @@ var BridgeProfiling = { ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure); }, + /** + * Relay profiles use await calls, so likely occur out of current stack frame + * therefore async variant of profiling is used + **/ attachToRelayProfiler(relayProfiler: RelayProfiler) { relayProfiler.attachProfileHandler('*', (name) => { - BridgeProfiling.profile(name); + var cookie = BridgeProfiling.profileAsync(name); return () => { - BridgeProfiling.profileEnd(); + BridgeProfiling.profileAsyncEnd(name, cookie); }; }); }, From cc4a5d39db23c5c18b562cb0273d5edae65a287b Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 1 Dec 2015 07:42:44 -0800 Subject: [PATCH 0187/1411] Add unbundling to packager Reviewed By: tadeuzagallo Differential Revision: D2707409 fb-gh-sync-id: 30216c36066dae68d83622dba2d598e9dc0a29db --- local-cli/bundle/bundle.js | 12 +- local-cli/bundle/output/bundle.js | 2 +- local-cli/bundle/output/unbundle.js | 126 ++++++++++++++++++ local-cli/bundle/unbundle.js | 21 +++ local-cli/cli.js | 2 + packager/react-packager/src/Bundler/Bundle.js | 39 ++++++ .../src/Bundler/__tests__/Bundler-test.js | 10 +- packager/react-packager/src/Bundler/index.js | 10 +- .../DependencyGraph/ResolutionResponse.js | 2 + .../src/Resolver/__tests__/Resolver-test.js | 3 +- packager/react-packager/src/Resolver/index.js | 27 ++-- .../Resolver/polyfills/require-unbundle.js | 73 ++++++++++ .../src/Server/__tests__/Server-test.js | 4 + packager/react-packager/src/Server/index.js | 4 + .../react-packager/src/lib/ModuleTransport.js | 2 + 15 files changed, 318 insertions(+), 19 deletions(-) create mode 100644 local-cli/bundle/output/unbundle.js create mode 100644 local-cli/bundle/unbundle.js create mode 100644 packager/react-packager/src/Resolver/polyfills/require-unbundle.js diff --git a/local-cli/bundle/bundle.js b/local-cli/bundle/bundle.js index e242049f0156..debbb62ecfc1 100644 --- a/local-cli/bundle/bundle.js +++ b/local-cli/bundle/bundle.js @@ -17,10 +17,18 @@ const outputPrepack = require('./output/prepack'); /** * Builds the bundle starting to look for dependencies at the given entry path. */ -function bundle(argv, config) { +function bundleWithOutput(argv, config, output) { const args = parseCommandLine(bundleCommandLineArgs, argv); - const output = args.prepack ? outputPrepack : outputBundle; + if (!output) { + output = args.prepack ? outputPrepack : outputBundle; + } return buildBundle(args, config, output); + +} + +function bundle(argv, config) { + return bundleWithOutput(argv, config); } module.exports = bundle; +module.exports.withOutput = bundleWithOutput; diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js index 4ffd8a649bc4..6f40e5baccde 100644 --- a/local-cli/bundle/output/bundle.js +++ b/local-cli/bundle/output/bundle.js @@ -33,7 +33,7 @@ function saveBundleAndMap(bundle, options, log) { 'bundle-encoding': encoding, dev, 'sourcemap-output': sourcemapOutput, -} = options; + } = options; log('start'); const codeWithMap = createCodeWithMap(bundle, dev); diff --git a/local-cli/bundle/output/unbundle.js b/local-cli/bundle/output/unbundle.js new file mode 100644 index 000000000000..65156dcca031 --- /dev/null +++ b/local-cli/bundle/output/unbundle.js @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const fs = require('fs'); +const Promise = require('promise'); +const writeFile = require('./writeFile'); + +const MAGIC_STARTUP_MODULE_ID = ''; + +function buildBundle(packagerClient, requestOptions) { + return packagerClient.buildBundle({...requestOptions, unbundle: true}); +} + +function saveUnbundle(bundle, options, log) { + const { + 'bundle-output': bundleOutput, + 'bundle-encoding': encoding, + dev, + 'sourcemap-output': sourcemapOutput, + } = options; + + log('start'); + const {startupCode, modules} = bundle.getUnbundle({minify: !dev}); + log('finish'); + + log('Writing unbundle output to:', bundleOutput); + const writeUnbundle = writeBuffers( + fs.createWriteStream(bundleOutput), + buildTableAndContents(startupCode, modules, encoding) + ); + + writeUnbundle.then(() => log('Done writing unbundle output')); + + if (sourcemapOutput) { + log('Writing sourcemap output to:', sourcemapOutput); + const writeMap = writeFile(sourcemapOutput, '', null); + writeMap.then(() => log('Done writing sourcemap output')); + return Promise.all([writeUnbundle, writeMap]); + } else { + return writeUnbundle; + } +} + +/* global Buffer: true */ +const nullByteBuffer = Buffer(1).fill(0); + +const moduleToBuffer = ({name, code}, encoding) => ({ + name, + buffer: Buffer.concat([ + Buffer(code, encoding), + nullByteBuffer // create \0-terminated strings + ]) +}); + +function buildModuleBuffers(startupCode, modules, encoding) { + return ( + [moduleToBuffer({name: '', code: startupCode}, encoding)] + .concat(modules.map(module => moduleToBuffer(module, encoding))) + ); +} + +function uInt32Buffer(n) { + const buffer = Buffer(4); + buffer.writeUInt32LE(n, 0); // let's assume LE for now :) + return buffer; +} + +function buildModuleTable(buffers) { + // table format: + // - table_length: uint_32 length of all table entries in bytes + // - entries: entry... + // + // entry: + // - module_id: NUL terminated utf8 string + // - module_offset: uint_32 offset into the module string + // - module_length: uint_32 length of the module string, including terminating NUL byte + + const numBuffers = buffers.length; + + const tableLengthBuffer = uInt32Buffer(0); + let tableLength = 4; // the table length itself, 4 == tableLengthBuffer.length + let currentOffset = 0; + + const offsetTable = [tableLengthBuffer]; + for (let i = 0; i < numBuffers; i++) { + const {name, buffer: {length}} = buffers[i]; + const entry = Buffer.concat([ + Buffer(i === 0 ? MAGIC_STARTUP_MODULE_ID : name, 'utf8'), + nullByteBuffer, + uInt32Buffer(currentOffset), + uInt32Buffer(length) + ]); + currentOffset += length; + tableLength += entry.length; + offsetTable.push(entry); + } + + tableLengthBuffer.writeUInt32LE(tableLength, 0); + return Buffer.concat(offsetTable); +} + +function buildTableAndContents(startupCode, modules, encoding) { + const buffers = buildModuleBuffers(startupCode, modules, encoding); + const table = buildModuleTable(buffers, encoding); + return [table].concat(buffers.map(({buffer}) => buffer)); +} + +function writeBuffers(stream, buffers) { + buffers.forEach(buffer => stream.write(buffer)); + return new Promise((resolve, reject) => { + stream.on('error', reject); + stream.on('finish', () => resolve()); + stream.end(); + }); +} + +exports.build = buildBundle; +exports.save = saveUnbundle; +exports.formatName = 'bundle'; diff --git a/local-cli/bundle/unbundle.js b/local-cli/bundle/unbundle.js new file mode 100644 index 000000000000..042093ced897 --- /dev/null +++ b/local-cli/bundle/unbundle.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const bundleWithOutput = require('./bundle').withOutput; +const outputUnbundle = require('./output/unbundle'); + +/** + * Builds the bundle starting to look for dependencies at the given entry path. + */ +function unbundle(argv, config) { + return bundleWithOutput(argv, config, outputUnbundle); +} + +module.exports = unbundle; diff --git a/local-cli/cli.js b/local-cli/cli.js index a7b6a2cf5ef6..9fd1fda7f1a5 100644 --- a/local-cli/cli.js +++ b/local-cli/cli.js @@ -28,6 +28,7 @@ var runAndroid = require('./runAndroid/runAndroid'); var server = require('./server/server'); var TerminalAdapter = require('yeoman-environment/lib/adapter.js'); var yeoman = require('yeoman-environment'); +var unbundle = require('./bundle/unbundle'); var upgrade = require('./upgrade/upgrade'); var fs = require('fs'); @@ -40,6 +41,7 @@ gracefulFs.gracefulify(fs); var documentedCommands = { 'start': [server, 'starts the webserver'], 'bundle': [bundle, 'builds the javascript bundle for offline use'], + 'unbundle': [unbundle, 'builds javascript as "unbundle" for offline use'], 'new-library': [library, 'generates a native library bridge'], 'link': [link, 'Adds a third-party library to your project. Example: react-native link awesome-camera'], 'android': [generateWrapper, 'generates an Android project for your app'], diff --git a/packager/react-packager/src/Bundler/Bundle.js b/packager/react-packager/src/Bundler/Bundle.js index b6635be9eacf..f94c8ff581f5 100644 --- a/packager/react-packager/src/Bundler/Bundle.js +++ b/packager/react-packager/src/Bundler/Bundle.js @@ -16,6 +16,14 @@ const Activity = require('../Activity'); const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; +const minifyCode = code => + UglifyJS.minify(code, {fromString: true, ascii_only: true}).code; +const getCode = x => x.code; +const getMinifiedCode = x => minifyCode(x.code); +const getNameAndCode = ({name, code}) => ({name, code}); +const getNameAndMinifiedCode = + ({name, code}) => ({name, code: minifyCode(code)}); + class Bundle { constructor(sourceMapUrl) { this._finalized = false; @@ -24,6 +32,8 @@ class Bundle { this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; + this._numPrependedModules = 0; + this._numRequireCalls = 0; } setMainModuleId(moduleId) { @@ -48,6 +58,10 @@ class Bundle { return this._modules; } + setNumPrependedModules(n) { + this._numPrependedModules = n; + } + addAsset(asset) { this._assets.push(asset); } @@ -76,6 +90,7 @@ class Bundle { sourceCode: code, sourcePath: name + '.js', })); + this._numRequireCalls += 1; } _assertFinalized() { @@ -141,6 +156,26 @@ class Bundle { return source; } + getUnbundle({minify}) { + const allModules = this._modules.slice(); + const prependedModules = this._numPrependedModules; + const requireCalls = this._numRequireCalls; + + const modules = + allModules + .splice(prependedModules, allModules.length - requireCalls - prependedModules); + const startupCode = + allModules + .map(minify ? getMinifiedCode : getCode) + .join('\n'); + + return { + startupCode, + modules: + modules.map(minify ? getNameAndMinifiedCode : getNameAndCode) + }; + } + getMinifiedSourceAndMap(dev) { this._assertFinalized(); @@ -336,6 +371,8 @@ class Bundle { assets: this._assets, sourceMapUrl: this._sourceMapUrl, mainModuleId: this._mainModuleId, + numPrependedModules: this._numPrependedModules, + numRequireCalls: this._numRequireCalls, }; } @@ -345,6 +382,8 @@ class Bundle { bundle._assets = json.assets; bundle._modules = json.modules; bundle._sourceMapUrl = json.sourceMapUrl; + bundle._numPrependedModules = json.numPrependedModules; + bundle._numRequireCalls = json.numRequireCalls; Object.freeze(bundle._modules); Object.seal(bundle._modules); diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js index 9ce3f7c46da4..ccdc84961d4c 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -130,7 +130,10 @@ describe('Bundler', function() { }); wrapModule.mockImpl(function(response, module, code) { - return Promise.resolve('lol ' + code + ' lol'); + return module.getName().then(name => ({ + name, + code: 'lol ' + code + ' lol' + })); }); sizeOf.mockImpl(function(path, cb) { @@ -160,6 +163,7 @@ describe('Bundler', function() { sourceMapUrl: 'source_map_url', }).then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ + name: 'foo', code: 'lol transformed /root/foo.js lol', map: 'sourcemap /root/foo.js', sourceCode: 'source /root/foo.js', @@ -167,6 +171,7 @@ describe('Bundler', function() { }); expect(p.addModule.mock.calls[1][0]).toEqual({ + name: 'bar', code: 'lol transformed /root/bar.js lol', map: 'sourcemap /root/bar.js', sourceCode: 'source /root/bar.js', @@ -183,6 +188,7 @@ describe('Bundler', function() { }; expect(p.addModule.mock.calls[2][0]).toEqual({ + name: 'image!img', code: 'lol module.exports = ' + JSON.stringify(imgModule_DEPRECATED) + '; lol', @@ -212,6 +218,7 @@ describe('Bundler', function() { }; expect(p.addModule.mock.calls[3][0]).toEqual({ + name: 'new_image.png', code: 'lol module.exports = require("AssetRegistry").registerAsset(' + JSON.stringify(imgModule) + '); lol', @@ -224,6 +231,7 @@ describe('Bundler', function() { }); expect(p.addModule.mock.calls[4][0]).toEqual({ + name: 'package/file.json', code: 'lol module.exports = {"json":true}; lol', sourceCode: 'module.exports = {"json":true};', sourcePath: '/root/file.json', diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index e097667ff58c..ffe17735626d 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -140,6 +140,7 @@ class Bundler { sourceMapUrl, dev: isDev, platform, + unbundle: isUnbundle, }) { // Const cannot have the same name as the method (babel/babel#2834) const bbundle = new Bundle(sourceMapUrl); @@ -147,7 +148,7 @@ class Bundler { let transformEventId; const moduleSystem = this._resolver.getModuleSystemDependencies( - { dev: isDev, platform } + { dev: isDev, platform, isUnbundle } ); return this.getDependencies(entryFile, isDev, platform).then((response) => { @@ -168,6 +169,8 @@ class Bundler { } bbundle.setMainModuleId(response.mainModuleId); + bbundle.setNumPrependedModules( + response.numPrependedDependencies + moduleSystem.length); return Promise.all( dependencies.map( module => this._transformModule( @@ -317,8 +320,9 @@ class Bundler { module, transformed.code ).then( - code => new ModuleTransport({ - code: code, + ({code, name}) => new ModuleTransport({ + code, + name, map: transformed.map, sourceCode: transformed.sourceCode, sourcePath: transformed.sourcePath, diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js index aa454a9f5fdd..7fbafde84982 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js @@ -14,6 +14,7 @@ class ResolutionResponse { this.asyncDependencies = []; this.mainModuleId = null; this.mocks = null; + this.numPrependedDependencies = 0; this._mappings = Object.create(null); this._finalized = false; } @@ -50,6 +51,7 @@ class ResolutionResponse { prependDependency(module) { this._assertNotFinalized(); this.dependencies.unshift(module); + this.numPrependedDependencies += 1; } pushAsyncDependency(dependency) { diff --git a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js index bf6363c520da..1de9905b9a40 100644 --- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -627,7 +627,8 @@ describe('Resolver', function() { createModule('test module', ['x', 'y']), code ).then(processedCode => { - expect(processedCode).toEqual([ + expect(processedCode.name).toEqual('test module'); + expect(processedCode.code).toEqual([ '__d(\'test module\',function(global, require,' + ' module, exports) { ' + // single line import diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js index a4e546e07bbe..8e8dd315c529 100644 --- a/packager/react-packager/src/Resolver/index.js +++ b/packager/react-packager/src/Resolver/index.js @@ -60,6 +60,10 @@ const getDependenciesValidateOpts = declareOpts({ type: 'string', required: false, }, + isUnbundle: { + type: 'boolean', + default: false + }, }); class Resolver { @@ -115,7 +119,9 @@ class Resolver { ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'); - const moduleSystem = path.join(__dirname, 'polyfills/require.js'); + const moduleSystem = opts.isUnbundle + ? path.join(__dirname, 'polyfills/require-unbundle.js') + : path.join(__dirname, 'polyfills/require.js'); return [ prelude, @@ -152,7 +158,7 @@ class Resolver { wrapModule(resolutionResponse, module, code) { return Promise.resolve().then(() => { if (module.isPolyfill()) { - return Promise.resolve(code); + return Promise.resolve({code}); } const resolvedDeps = Object.create(null); @@ -179,14 +185,13 @@ class Resolver { } }; - return module.getName().then( - name => defineModuleCode({ - code: code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - moduleName: name, - }) - ); + code = code + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.EXPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode); + + return module.getName().then(name => + ({name, code: defineModuleCode(name, code)})); }); }); } @@ -197,7 +202,7 @@ class Resolver { } -function defineModuleCode({moduleName, code}) { +function defineModuleCode(moduleName, code) { return [ `__d(`, `'${moduleName}',`, diff --git a/packager/react-packager/src/Resolver/polyfills/require-unbundle.js b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js new file mode 100644 index 000000000000..1ea71705d401 --- /dev/null +++ b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js @@ -0,0 +1,73 @@ +'use strict'; + +((global) => { + const {ErrorUtils, __nativeRequire} = global; + global.require = require; + global.__d = define; + + const modules = Object.create(null); + + const loadModule = ErrorUtils ? + guardedLoadModule : loadModuleImplementation; + + function define(moduleId, factory) { + modules[moduleId] = { + factory, + hasError: false, + exports: undefined, + }; + } + + function require(moduleId) { + const module = modules[moduleId]; + return module && module.exports || loadModule(moduleId, module); + } + + function guardedLoadModule(moduleId, module) { + try { + return loadModuleImplementation(moduleId, module); + } catch (e) { + ErrorUtils.reportFatalError(e); + } + } + + function loadModuleImplementation(moduleId, module) { + if (!module) { + __nativeRequire(moduleId); + module = modules[moduleId]; + } + + if (!module) { + throw unknownModuleError(moduleId); + } + + if (module.hasError) { + throw moduleThrewError(moduleId); + } + + const exports = module.exports = {}; + const {factory} = module; + try { + const moduleObject = {exports}; + factory(global, require, moduleObject, exports); + return (module.exports = moduleObject.exports); + } catch(e) { + module.hasError = true; + module.exports = undefined; + } + } + + function unknownModuleError(id) { + let message = 'Requiring unknown module "' + id + '".'; + if (__DEV__) { + message += + 'If you are sure the module is there, try restarting the packager.'; + } + return Error(message); + } + + function moduleThrewError(id) { + return Error('Requiring module "' + id + '", which threw an exception.'); + } + +})(this); diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index d68e04105613..b8fee5fad049 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -115,6 +115,7 @@ describe('processRequest', () => { dev: true, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }); }); }); @@ -134,6 +135,7 @@ describe('processRequest', () => { dev: true, platform: 'ios', runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }); }); }); @@ -274,6 +276,7 @@ describe('processRequest', () => { dev: true, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }) ); }); @@ -292,6 +295,7 @@ describe('processRequest', () => { dev: false, platform: undefined, runBeforeMainModule: ['InitializeJavaScriptAppEngine'], + unbundle: false, }) ); }); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 28c5b4d1bc80..b2ca6d73b66d 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -102,6 +102,10 @@ const bundleOpts = declareOpts({ 'InitializeJavaScriptAppEngine' ], }, + unbundle: { + type: 'boolean', + default: false, + } }); const dependencyOpts = declareOpts({ diff --git a/packager/react-packager/src/lib/ModuleTransport.js b/packager/react-packager/src/lib/ModuleTransport.js index a5f1d5689106..afb660d386dd 100644 --- a/packager/react-packager/src/lib/ModuleTransport.js +++ b/packager/react-packager/src/lib/ModuleTransport.js @@ -9,6 +9,8 @@ 'use strict'; function ModuleTransport(data) { + this.name = data.name; + assertExists(data, 'code'); this.code = data.code; From 765801dfc283b90c7054a64d182b359e859dbb50 Mon Sep 17 00:00:00 2001 From: Satish Sampath Date: Tue, 1 Dec 2015 08:04:19 -0800 Subject: [PATCH 0188/1411] Improve JSC GC implementation on android Reviewed By: astreet Differential Revision: D2657643 fb-gh-sync-id: a049ee745de9a066a4a2da2762ec6a2f1517c78b --- ReactAndroid/src/main/jni/react/JSCExecutor.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 52eb369e77b9..5e6caae2630e 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -26,6 +26,10 @@ using fbsystrace::FbSystraceSection; // Add native performance markers support #include +#ifdef WITH_FB_JSC_TUNING +#include +#endif + using namespace facebook::jni; namespace facebook { @@ -82,6 +86,11 @@ JSCExecutor::JSCExecutor(FlushImmediateCallback cb) : s_globalContextRefToJSCExecutor[m_context] = this; installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook); + + #ifdef WITH_FB_JSC_TUNING + configureJSCForAndroid(); + #endif + #ifdef WITH_JSC_EXTRA_TRACING addNativeTracingHooks(m_context); addNativeProfilingHooks(m_context); From a26b89f8bd7d6db66614095afeaf0ce6a1982378 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 1 Dec 2015 07:41:20 -0800 Subject: [PATCH 0189/1411] Add property mapping for `borderStyle` from JS to `RCTView` (iOS) Reviewed By: nicklockwood Differential Revision: D2540321 fb-gh-sync-id: 57316f7b8dc734ee784179dbfde83e6034827819 --- React/Base/RCTConvert.h | 2 ++ React/Base/RCTConvert.m | 6 ++++++ React/React.xcodeproj/project.pbxproj | 2 ++ React/Views/RCTBorderStyle.h | 17 +++++++++++++++++ React/Views/RCTView.h | 6 ++++++ React/Views/RCTView.m | 24 +++++++++++++++++++++--- React/Views/RCTViewManager.m | 7 +++++++ 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 React/Views/RCTBorderStyle.h diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 35106a6940bc..3d1c4291d11a 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -12,6 +12,7 @@ #import "Layout.h" #import "RCTAnimationType.h" +#import "RCTBorderStyle.h" #import "RCTTextDecorationLineType.h" #import "RCTDefines.h" #import "RCTLog.h" @@ -140,6 +141,7 @@ typedef BOOL css_clip_t, css_backface_visibility_t; + (RCTPointerEvents)RCTPointerEvents:(id)json; + (RCTAnimationType)RCTAnimationType:(id)json; ++ (RCTBorderStyle)RCTBorderStyle:(id)json; + (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json; @end diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 8fc14e102fb0..f5f6033432ce 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -229,6 +229,12 @@ + (NSDate *)NSDate:(id)json @"dashed": @(NSUnderlinePatternDash | NSUnderlineStyleSingle), }), NSUnderlineStyleSingle, integerValue) +RCT_ENUM_CONVERTER(RCTBorderStyle, (@{ + @"solid": @(RCTBorderStyleSolid), + @"dotted": @(RCTBorderStyleDotted), + @"dashed": @(RCTBorderStyleDashed), +}), RCTBorderStyleSolid, integerValue) + RCT_ENUM_CONVERTER(RCTTextDecorationLineType, (@{ @"none": @(RCTTextDecorationLineTypeNone), @"underline": @(RCTTextDecorationLineTypeUnderline), diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 92b4cf7f6a8d..6d671d58b4b4 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -269,6 +269,7 @@ 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; 83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = ""; }; + ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderStyle.h; sourceTree = ""; }; E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = ""; }; E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAccessibilityManager.h; sourceTree = ""; }; E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAccessibilityManager.m; sourceTree = ""; }; @@ -414,6 +415,7 @@ 137327E51AA5CF210034F82E /* RCTTabBarManager.h */, 137327E61AA5CF210034F82E /* RCTTabBarManager.m */, E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */, + ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */, 13E0674F1A70F44B002CDEE1 /* RCTView.h */, 13E067501A70F44B002CDEE1 /* RCTView.m */, 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */, diff --git a/React/Views/RCTBorderStyle.h b/React/Views/RCTBorderStyle.h new file mode 100644 index 000000000000..470d832314b7 --- /dev/null +++ b/React/Views/RCTBorderStyle.h @@ -0,0 +1,17 @@ +/** + * 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 + +typedef NS_ENUM(NSInteger, RCTBorderStyle) { + RCTBorderStyleUnset = 0, + RCTBorderStyleSolid, + RCTBorderStyleDotted, + RCTBorderStyleDashed, +}; diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index 1222dc9cf7d0..3f312abd1d87 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -11,6 +11,7 @@ #import +#import "RCTBorderStyle.h" #import "RCTComponent.h" #import "RCTPointerEvents.h" @@ -84,4 +85,9 @@ @property (nonatomic, assign) CGFloat borderLeftWidth; @property (nonatomic, assign) CGFloat borderWidth; +/** + * Border styles. + */ +@property (nonatomic, assign) RCTBorderStyle borderStyle; + @end diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 8f143d8a9ff8..3a3ea732548a 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -108,6 +108,7 @@ - (instancetype)initWithFrame:(CGRect)frame _borderTopRightRadius = -1; _borderBottomLeftRadius = -1; _borderBottomRightRadius = -1; + _borderStyle = RCTBorderStyleSolid; _backgroundColor = super.backgroundColor; } @@ -509,6 +510,7 @@ - (void)displayLayer:(CALayer *)layer RCTCornerRadiiAreEqual(cornerRadii) && RCTBorderInsetsAreEqual(borderInsets) && RCTBorderColorsAreEqual(borderColors) && + _borderStyle == RCTBorderStyleSolid && // iOS draws borders in front of the content whereas CSS draws them behind // the content. For this reason, only use iOS border drawing when clipping @@ -531,9 +533,9 @@ - (void)displayLayer:(CALayer *)layer return; } - UIImage *image = RCTGetBorderImage([self cornerRadii], - [self bordersAsInsets], - [self borderColors], + UIImage *image = RCTGetBorderImage(cornerRadii, + borderInsets, + borderColors, _backgroundColor.CGColor, self.clipsToBounds); @@ -630,6 +632,8 @@ - (void)setBorder##side##Width:(CGFloat)width \ setBorderWidth(Bottom) setBorderWidth(Left) +#pragma mark - Border Radius + #define setBorderRadius(side) \ - (void)setBorder##side##Radius:(CGFloat)radius \ { \ @@ -646,6 +650,20 @@ - (void)setBorder##side##Radius:(CGFloat)radius \ setBorderRadius(BottomLeft) setBorderRadius(BottomRight) +#pragma mark - Border Style + +#define setBorderStyle(side) \ + - (void)setBorder##side##Style:(RCTBorderStyle)style \ + { \ + if (_border##side##Style == style) { \ + return; \ + } \ + _border##side##Style = style; \ + [self.layer setNeedsDisplay]; \ + } + +setBorderStyle() + - (void)dealloc { CGColorRelease(_borderColor); diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index aaac6d27987d..379adecdc02f 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -10,6 +10,7 @@ #import "RCTViewManager.h" #import "RCTBridge.h" +#import "RCTBorderStyle.h" #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTLog.h" @@ -186,6 +187,12 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth; } } +RCT_CUSTOM_VIEW_PROPERTY(borderStyle, RCTBorderStyle, RCTView) +{ + if ([view respondsToSelector:@selector(setBorderStyle:)]) { + view.borderStyle = json ? [RCTConvert RCTBorderStyle:json] : defaultView.borderStyle; + } +} RCT_EXPORT_VIEW_PROPERTY(onAccessibilityTap, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onMagicTap, RCTDirectEventBlock) From b6f662d6b602f45f70a1c3f7f3809e271d340a0d Mon Sep 17 00:00:00 2001 From: Aaron Chiu Date: Tue, 1 Dec 2015 08:54:34 -0800 Subject: [PATCH 0190/1411] init Timing.java in a paused state Summary: It hasn't been resumed yet when first constructed. public Reviewed By: astreet Differential Revision: D2705635 fb-gh-sync-id: fa90a89524d56fd5e349ebad2820b42deba7f26f --- .../src/main/java/com/facebook/react/modules/core/Timing.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java index 8b0888e0882d..a29b2632b479 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java @@ -90,7 +90,7 @@ public void doFrame(long frameTimeNanos) { private final Object mTimerGuard = new Object(); private final PriorityQueue mTimers; private final SparseArray mTimerIdsToTimers; - private final AtomicBoolean isPaused = new AtomicBoolean(false); + private final AtomicBoolean isPaused = new AtomicBoolean(true); private final FrameCallback mFrameCallback = new FrameCallback(); private @Nullable ReactChoreographer mReactChoreographer; private @Nullable JSTimersExecution mJSTimersModule; From b0e39d26aecce2b5aa33888ca3172205a879ed98 Mon Sep 17 00:00:00 2001 From: Daniel Hugenroth Date: Tue, 1 Dec 2015 09:10:46 -0800 Subject: [PATCH 0191/1411] implemented tracing async flow events (s, t and f) Reviewed By: jkeljo Differential Revision: D2699625 fb-gh-sync-id: 1d1b41fac157650e98a49307b701b6da63589c4b --- .../java/com/facebook/systrace/Systrace.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java index 4ffaa3eb4a26..042a1668ee0d 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java +++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java @@ -83,4 +83,22 @@ public static void traceCounter( final String counterName, final int counterValue) { } + + public static void startAsyncFlow( + long tag, + final String sectionName, + final int cookie){ + } + + public static void stepAsyncFlow( + long tag, + final String sectionName, + final int cookie){ + } + + public static void endAsyncFlow( + long tag, + final String sectionName, + final int cookie){ + } } From b828ae4200c6e2650d1ae608982e945cfb1470ec Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Tue, 1 Dec 2015 12:07:36 -0800 Subject: [PATCH 0192/1411] Initial FlatUIImplemenatation Summary: public This patch adds an alternative UIImplementation based on an idea of creating UI hierarchy off-the-main-thread (everything but Views), flattening ReactShadowNode hierarchy and displaying it within a single View when possible. While NativeViewHierarchyOptimizer allows removing layout-only RCTViews, this allows removing RCTView, RCTText and RCTImage. This is an initial bare-bones implementation that doesn't really draw anything, only lays out the shadow nodes. Followup diffs will add missing features. Reviewed By: astreet Differential Revision: D2564309 fb-gh-sync-id: 2dda4c8cfc2bac3eb39c5c15e97bd23a57550a1d --- .../react/uimanager/UIImplementation.java | 85 ++++++++++++++----- .../react/uimanager/UIViewOperationQueue.java | 2 +- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 369ffda90f6c..608e33792198 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -41,15 +41,40 @@ public class UIImplementation { private final int[] mMeasureBuffer = new int[4]; public UIImplementation(ReactApplicationContext reactContext, List viewManagers) { - mViewManagers = new ViewManagerRegistry(viewManagers); - mOperationsQueue = new UIViewOperationQueue( - reactContext, - new NativeViewHierarchyManager(mViewManagers)); + this(reactContext, new ViewManagerRegistry(viewManagers)); + } + + private UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) { + this( + viewManagers, + new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(viewManagers))); + } + + protected UIImplementation( + ViewManagerRegistry viewManagers, + UIViewOperationQueue operationsQueue) { + mViewManagers = viewManagers; + mOperationsQueue = operationsQueue; mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( mOperationsQueue, mShadowNodeRegistry); } + protected ReactShadowNode createRootShadowNode() { + ReactShadowNode rootCSSNode = new ReactShadowNode(); + rootCSSNode.setViewClassName("Root"); + return rootCSSNode; + } + + protected ReactShadowNode createShadowNode(String className) { + ViewManager viewManager = mViewManagers.get(className); + return viewManager.createShadowNodeInstance(); + } + + protected final ReactShadowNode resolveShadowNode(int reactTag) { + return mShadowNodeRegistry.getNode(reactTag); + } + /** * Registers a root node with a given tag, size and ThemedReactContext * and adds it to a node registry. @@ -60,12 +85,11 @@ public void registerRootView( int width, int height, ThemedReactContext context) { - final ReactShadowNode rootCSSNode = new ReactShadowNode(); + final ReactShadowNode rootCSSNode = createRootShadowNode(); rootCSSNode.setReactTag(tag); rootCSSNode.setThemedContext(context); rootCSSNode.setStyleWidth(width); rootCSSNode.setStyleHeight(height); - rootCSSNode.setViewClassName("Root"); mShadowNodeRegistry.addRootNode(rootCSSNode); // register it within NativeViewHierarchyManager @@ -104,8 +128,7 @@ public void updateRootNodeSize( * Invoked by React to create a new node with a given tag, class name and properties. */ public void createView(int tag, String className, int rootViewTag, ReadableMap props) { - ViewManager viewManager = mViewManagers.get(className); - ReactShadowNode cssNode = viewManager.createShadowNodeInstance(); + ReactShadowNode cssNode = createShadowNode(className); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); cssNode.setReactTag(tag); cssNode.setViewClassName(className); @@ -120,8 +143,15 @@ public void createView(int tag, String className, int rootViewTag, ReadableMap p cssNode.updateProperties(styles); } + handleCreateView(cssNode, rootViewTag, styles); + } + + protected void handleCreateView( + ReactShadowNode cssNode, + int rootViewTag, + @Nullable CatalystStylesDiffMap styles) { if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootNode.getThemedContext(), styles); + mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles); } } @@ -141,9 +171,16 @@ public void updateView(int tag, String className, ReadableMap props) { if (props != null) { CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props); cssNode.updateProperties(styles); - if (!cssNode.isVirtual()) { - mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); - } + handleUpdateView(cssNode, className, styles); + } + } + + protected void handleUpdateView( + ReactShadowNode cssNode, + String className, + CatalystStylesDiffMap styles) { + if (!cssNode.isVirtual()) { + mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); } } @@ -401,14 +438,7 @@ public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) { ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); notifyOnBeforeLayoutRecursive(cssRoot); - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") - .arg("rootTag", tag) - .flush(); - try { - cssRoot.calculateLayout(mLayoutContext); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } + calculateRootLayout(cssRoot); applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher); } @@ -492,7 +522,7 @@ public void setViewHierarchyUpdateDebugListener( mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); } - private void removeShadowNode(ReactShadowNode nodeToRemove) { + protected final void removeShadowNode(ReactShadowNode nodeToRemove) { mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { @@ -597,7 +627,18 @@ private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) { cssNode.onBeforeLayout(); } - private void applyUpdatesRecursive( + protected void calculateRootLayout(ReactShadowNode cssRoot) { + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") + .arg("rootTag", cssRoot.getReactTag()) + .flush(); + try { + cssRoot.calculateLayout(mLayoutContext); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + protected void applyUpdatesRecursive( ReactShadowNode cssNode, float absoluteX, float absoluteY, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 6f0ebdcc9258..8f79a72081f3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -440,7 +440,7 @@ public void execute() { private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener; - protected UIViewOperationQueue( + public UIViewOperationQueue( ReactApplicationContext reactContext, NativeViewHierarchyManager nativeViewHierarchyManager) { mNativeViewHierarchyManager = nativeViewHierarchyManager; From cf892a96d693af778679278549fc8fa354326f43 Mon Sep 17 00:00:00 2001 From: Nathan Spaun Date: Tue, 1 Dec 2015 14:17:59 -0800 Subject: [PATCH 0193/1411] Fix NetInfo for Android Reviewed By: oli Differential Revision: D2708102 fb-gh-sync-id: f6a12c49c89c46d31114fefaf3e2b1ba72dabdee --- Libraries/Network/NetInfo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 6c811f9256c4..41eb9390d708 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -17,7 +17,7 @@ var Platform = require('Platform'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var RCTNetInfo = NativeModules.NetInfo; -var DEVICE_REACHABILITY_EVENT = 'networkDidChange'; +var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange'; type ChangeEventName = $Enum<{ change: string; @@ -163,7 +163,7 @@ var NetInfo = { var listener = RCTDeviceEventEmitter.addListener( DEVICE_REACHABILITY_EVENT, (appStateData) => { - handler(appStateData.network_info); + handler(appStateData.network_reachability); } ); _subscriptions.set(handler, listener); From 098fcb3a277f0c363e00aeeb49880db52ab03424 Mon Sep 17 00:00:00 2001 From: Olivier Notteghem Date: Tue, 1 Dec 2015 18:03:58 -0800 Subject: [PATCH 0194/1411] LayoutAnimation support for Android RN Reviewed By: dernienl Differential Revision: D2710141 fb-gh-sync-id: 28d6af84441b7c2dbc423b73eb05e71f62f7cdea --- Examples/UIExplorer/ListViewPagingExample.js | 10 ++ .../uimanager/NativeViewHierarchyManager.java | 28 ++++- .../react/uimanager/UIImplementation.java | 35 ++++++ .../react/uimanager/UIManagerModule.java | 34 +++++- .../react/uimanager/UIViewOperationQueue.java | 42 +++++++ .../AbstractLayoutAnimation.java | 108 ++++++++++++++++++ .../layoutanimation/AnimatedPropertyType.java | 32 ++++++ .../layoutanimation/BaseLayoutAnimation.java | 48 ++++++++ .../layoutanimation/HandlesLayout.java | 10 ++ .../layoutanimation/InterpolatorType.java | 34 ++++++ .../LayoutAnimationController.java | 94 +++++++++++++++ .../layoutanimation/LayoutAnimationType.java | 22 ++++ .../LayoutCreateAnimation.java | 15 +++ .../LayoutUpdateAnimation.java | 42 +++++++ .../layoutanimation/OpacityAnimation.java | 66 +++++++++++ .../PositionAndSizeAnimation.java | 52 +++++++++ .../SimpleSpringInterpolator.java | 20 ++++ .../react/views/view/ReactViewGroup.java | 11 +- 18 files changed, 698 insertions(+), 5 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js index b82f272c9dbb..7d0ac98f2bfd 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/ListViewPagingExample.js @@ -11,6 +11,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + * @provides ListViewPagingExample * @flow */ 'use strict'; @@ -26,6 +27,11 @@ var { View, } = React; +var NativeModules = require('NativeModules'); +var { + UIManager, +} = NativeModules; + var PAGE_SIZE = 4; var THUMB_URLS = [ 'Thumbnails/like.png', @@ -48,6 +54,10 @@ var Thumb = React.createClass({ getInitialState: function() { return {thumbIndex: this._getThumbIdx(), dir: 'row'}; }, + componentWillMount: function() { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); + }, _getThumbIdx: function() { return Math.floor(Math.random() * THUMB_URLS.length); }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index ac6ae4e46d8c..752ffaf291bc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -29,9 +29,11 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.JSResponderHandler; +import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between @@ -65,6 +67,9 @@ public class NativeViewHierarchyManager { private final ViewManagerRegistry mViewManagers; private final JSResponderHandler mJSResponderHandler = new JSResponderHandler(); private final RootViewManager mRootViewManager = new RootViewManager(); + private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController(); + + private boolean mLayoutAnimationEnabled; public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { mAnimationRegistry = new AnimationRegistry(); @@ -95,6 +100,10 @@ public AnimationRegistry getAnimationRegistry() { return mAnimationRegistry; } + public void setLayoutAnimationEnabled(boolean enabled) { + mLayoutAnimationEnabled = enabled; + } + public void updateProperties(int tag, CatalystStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); @@ -149,8 +158,17 @@ public void updateLayout( } if (parentViewGroupManager != null && !parentViewGroupManager.needsCustomLayoutForChildren()) { - viewToUpdate.layout(x, y, x + width, y + height); + updateLayout(viewToUpdate, x, y, width, height); } + } else { + updateLayout(viewToUpdate, x, y, width, height); + } + } + + private void updateLayout(View viewToUpdate, int x, int y, int width, int height) { + if (mLayoutAnimationEnabled && + mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) { + mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height); } else { viewToUpdate.layout(x, y, x + width, y + height); } @@ -466,6 +484,14 @@ public void clearJSResponder() { mJSResponderHandler.clearJSResponder(); } + void configureLayoutAnimation(final ReadableMap config) { + mLayoutAnimator.initializeFromConfig(config); + } + + void clearLayoutAnimation() { + mLayoutAnimator.reset(); + } + /* package */ void startAnimationForNativeView( int reactTag, Animation animation, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 608e33792198..aef2ebc29317 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -469,6 +469,41 @@ public void removeAnimation(int reactTag, int animationID) { mOperationsQueue.enqueueRemoveAnimation(animationID); } + /** + * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled + * explicitly in order to avoid regression in existing application written for iOS using this API. + * + * Warning : This method will be removed in future version of React Native, and layout animation + * will be enabled by default, so always check for its existence before invoking it. + * + * TODO(9139831) : remove this method once layout animation is fully stable. + * + * @param enabled whether layout animation is enabled or not + */ + public void setLayoutAnimationEnabledExperimental(boolean enabled) { + mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled); + } + + /** + * Configure an animation to be used for the native layout changes, and native views + * creation. The animation will only apply during the current batch operations. + * + * TODO(7728153) : animating view deletion is currently not supported. + * TODO(7613721) : callbacks are not supported, this feature will likely be killed. + * + * @param config the configuration of the animation for view addition/removal/update. + * @param success will be called when the animation completes, or when the animation get + * interrupted. In this case, callback parameter will be false. + * @param error will be called if there was an error processing the animation + */ + public void configureNextLayoutAnimation( + ReadableMap config, + Callback success, + Callback error) { + mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error); + } + + public void setJSResponder(int reactTag, boolean blockNativeResponder) { assertViewExists(reactTag, "setJSResponder"); ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index b60598569bf0..b352f4d53e61 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -374,12 +374,40 @@ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Cal mUIImplementation.showPopupMenu(reactTag, items, error, success); } + /** + * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled + * explicitly in order to avoid regression in existing application written for iOS using this API. + * + * Warning : This method will be removed in future version of React Native, and layout animation + * will be enabled by default, so always check for its existence before invoking it. + * + * TODO(9139831) : remove this method once layout animation is fully stable. + * + * @param enabled whether layout animation is enabled or not + */ + @ReactMethod + public void setLayoutAnimationEnabledExperimental(boolean enabled) { + mUIImplementation.setLayoutAnimationEnabledExperimental(enabled); + } + + /** + * Configure an animation to be used for the native layout changes, and native views + * creation. The animation will only apply during the current batch operations. + * + * TODO(7728153) : animating view deletion is currently not supported. + * TODO(7613721) : callbacks are not supported, this feature will likely be killed. + * + * @param config the configuration of the animation for view addition/removal/update. + * @param success will be called when the animation completes, or when the animation get + * interrupted. In this case, callback parameter will be false. + * @param error will be called if there was an error processing the animation + */ @ReactMethod public void configureNextLayoutAnimation( ReadableMap config, - Callback successCallback, - Callback errorCallback) { - // TODO(6588266): Implement if required + Callback success, + Callback error) { + mUIImplementation.configureNextLayoutAnimation(config, success, error); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 8f79a72081f3..5fc65cd64a23 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.systrace.Systrace; @@ -322,6 +323,32 @@ public void execute() { } } + private class SetLayoutAnimationEnabledOperation implements UIOperation { + private final boolean mEnabled; + + private SetLayoutAnimationEnabledOperation(final boolean enabled) { + mEnabled = enabled; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled); + } + } + + private class ConfigureLayoutAnimationOperation implements UIOperation { + private final ReadableMap mConfig; + + private ConfigureLayoutAnimationOperation(final ReadableMap config) { + mConfig = config; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.configureLayoutAnimation(mConfig); + } + } + private final class MeasureOperation implements UIOperation { private final int mReactTag; @@ -584,6 +611,18 @@ public void enqueueRemoveAnimation(int animationID) { mOperations.add(new RemoveAnimationOperation(animationID)); } + public void enqueueSetLayoutAnimationEnabled( + final boolean enabled) { + mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); + } + + public void enqueueConfigureLayoutAnimation( + final ReadableMap config, + final Callback onSuccess, + final Callback onError) { + mOperations.add(new ConfigureLayoutAnimationOperation(config)); + } + public void enqueueMeasure( final int reactTag, final Callback callback) { @@ -680,6 +719,9 @@ public void doFrameGuarded(long frameTimeNanos) { mDispatchUIRunnables.get(i).run(); } mDispatchUIRunnables.clear(); + + // Clear layout animation, as animation only apply to current UI operations batch. + mNativeViewHierarchyManager.clearLayoutAnimation(); } ReactChoreographer.getInstance().postFrameCallback( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java new file mode 100644 index 000000000000..c29d7ae0bef9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java @@ -0,0 +1,108 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; + +import java.util.Map; + +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.IllegalViewOperationException; + +/** + * Class responsible for parsing and converting layout animation data into native {@link Animation} + * in order to animate layout when a valid configuration has been supplied by the application. + */ +/* package */ abstract class AbstractLayoutAnimation { + + // Forces animation to be playing 10x slower, used for debug purposes. + private static final boolean SLOWDOWN_ANIMATION_MODE = false; + + abstract boolean isValid(); + + /** + * Create an animation object for the current animation type, based on the view and final screen + * coordinates. If the application-supplied configuraiton does not specify an animation definition + * for this types, or if the animation definition is invalid, returns null. + */ + abstract @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height); + + private static final Map INTERPOLATOR = MapBuilder.of( + InterpolatorType.LINEAR, new LinearInterpolator(), + InterpolatorType.EASE_IN, new AccelerateInterpolator(), + InterpolatorType.EASE_OUT, new DecelerateInterpolator(), + InterpolatorType.EASE_IN_EASE_OUT, new AccelerateDecelerateInterpolator(), + InterpolatorType.SPRING, new SimpleSpringInterpolator()); + + private @Nullable Interpolator mInterpolator; + private int mDelayMs; + + protected @Nullable AnimatedPropertyType mAnimatedProperty; + protected int mDurationMs; + + public void reset() { + mAnimatedProperty = null; + mDurationMs = 0; + mDelayMs = 0; + mInterpolator = null; + } + + public void initializeFromConfig(ReadableMap data, int globalDuration) { + mAnimatedProperty = data.hasKey("property") ? + AnimatedPropertyType.fromString(data.getString("property")) : null; + mDurationMs = data.hasKey("duration") ? data.getInt("duration") : globalDuration; + mDelayMs = data.hasKey("delay") ? data.getInt("delay") : 0; + mInterpolator = data.hasKey("type") ? + getInterpolator(InterpolatorType.fromString(data.getString("type"))) : null; + + if (!isValid()) { + throw new IllegalViewOperationException("Invalid layout animation : " + data); + } + } + + /** + * Create an animation object to be used to animate the view, based on the animation config + * supplied at initialization time and the new view position and size. + * + * @param view the view to create the animation for + * @param x the new X position for the view + * @param y the new Y position for the view + * @param width the new width value for the view + * @param height the new height value for the view + */ + public final @Nullable Animation createAnimation( + View view, + int x, + int y, + int width, + int height) { + if (!isValid()) { + return null; + } + Animation animation = createAnimationImpl(view, x, y, width, height); + if (animation != null) { + int slowdownFactor = SLOWDOWN_ANIMATION_MODE ? 10 : 1; + animation.setDuration(mDurationMs * slowdownFactor); + animation.setStartOffset(mDelayMs * slowdownFactor); + animation.setInterpolator(mInterpolator); + } + return animation; + } + + private static Interpolator getInterpolator(InterpolatorType type) { + Interpolator interpolator = INTERPOLATOR.get(type); + if (interpolator == null) { + throw new IllegalArgumentException("Missing interpolator for type : " + type); + } + return interpolator; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java new file mode 100644 index 000000000000..51a9d246c061 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java @@ -0,0 +1,32 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different view properties that can be used when animating layout for + * view creation. + */ +/* package */ enum AnimatedPropertyType { + OPACITY("opacity"), + SCALE_XY("scaleXY"); + + private final String mName; + + private AnimatedPropertyType(String name) { + mName = name; + } + + public static AnimatedPropertyType fromString(String name) { + for (AnimatedPropertyType property : AnimatedPropertyType.values()) { + if (property.toString().equalsIgnoreCase(name)) { + return property; + } + } + throw new IllegalArgumentException("Unsupported animated property : " + name); + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java new file mode 100644 index 000000000000..9d3d0f45aa79 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.ScaleAnimation; + +import com.facebook.react.uimanager.IllegalViewOperationException; + +/** + * Class responsible for default layout animation, i.e animation of view creation and deletion. + */ +/* package */ abstract class BaseLayoutAnimation extends AbstractLayoutAnimation { + + abstract boolean isReverse(); + + @Override + boolean isValid() { + return mDurationMs > 0 && mAnimatedProperty != null; + } + + @Override + Animation createAnimationImpl(View view, int x, int y, int width, int height) { + float fromValue = isReverse() ? 1.0f : 0.0f; + float toValue = isReverse() ? 0.0f : 1.0f; + if (mAnimatedProperty != null) { + switch (mAnimatedProperty) { + case OPACITY: + return new OpacityAnimation(view, fromValue, toValue); + case SCALE_XY: + return new ScaleAnimation( + fromValue, + toValue, + fromValue, + toValue, + Animation.RELATIVE_TO_PARENT, + .5f, + Animation.RELATIVE_TO_PARENT, + .5f); + default: + throw new IllegalViewOperationException( + "Missing animation for property : " + mAnimatedProperty); + } + } + throw new IllegalViewOperationException("Missing animated property from animation config"); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java new file mode 100644 index 000000000000..9f3c907e97d0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java @@ -0,0 +1,10 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Marker interface to indicate a given animation type takes care of updating the view layout. + */ +/* package */ interface HandleLayout { + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java new file mode 100644 index 000000000000..f59675a8fbe1 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different interpolators that can be used in layout animation configuration. + */ +/* package */ enum InterpolatorType { + LINEAR("linear"), + EASE_IN("easeIn"), + EASE_OUT("easeOut"), + EASE_IN_EASE_OUT("easeInEaseOut"), + SPRING("spring"); + + private final String mName; + + private InterpolatorType(String name) { + mName = name; + } + + public static InterpolatorType fromString(String name) { + for (InterpolatorType type : InterpolatorType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + throw new IllegalArgumentException("Unsupported interpolation type : " + name); + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java new file mode 100644 index 000000000000..d3522947fd36 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java @@ -0,0 +1,94 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +import android.view.View; +import android.view.animation.Animation; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UiThreadUtil; + +/** + * Class responsible for animation layout changes, if a valid layout animation config has been + * supplied. If not animation is available, layout change is applied immediately instead of + * performing an animation. + * + * TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled. + */ +@NotThreadSafe +public class LayoutAnimationController { + + private static final boolean ENABLED = true; + + private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation(); + private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation(); + private boolean mShouldAnimateLayout; + + public void initializeFromConfig(final @Nullable ReadableMap config) { + if (!ENABLED) { + return; + } + + if (config == null) { + reset(); + return; + } + + mShouldAnimateLayout = false; + int globalDuration = config.hasKey("duration") ? config.getInt("duration") : 0; + if (config.hasKey(LayoutAnimationType.CREATE.toString())) { + mLayoutCreateAnimation.initializeFromConfig( + config.getMap(LayoutAnimationType.CREATE.toString()), globalDuration); + mShouldAnimateLayout = true; + } + if (config.hasKey(LayoutAnimationType.UPDATE.toString())) { + mLayoutUpdateAnimation.initializeFromConfig( + config.getMap(LayoutAnimationType.UPDATE.toString()), globalDuration); + mShouldAnimateLayout = true; + } + } + + public void reset() { + mLayoutCreateAnimation.reset(); + mLayoutUpdateAnimation.reset(); + mShouldAnimateLayout = false; + } + + public boolean shouldAnimateLayout(View viewToAnimate) { + // if view parent is null, skip animation: view have been clipped, we don't want animation to + // resume when view is re-attached to parent, which is the standard android animation behavior. + return mShouldAnimateLayout && viewToAnimate.getParent() != null; + } + + /** + * Update layout of given view, via immediate update or animation depending on the current batch + * layout animation configuration supplied during initialization. + * + * @param view the view to update layout of + * @param x the new X position for the view + * @param y the new Y position for the view + * @param width the new width value for the view + * @param height the new height value for the view + */ + public void applyLayoutUpdate(View view, int x, int y, int width, int height) { + UiThreadUtil.assertOnUiThread(); + + // Determine which animation to use : if view is initially invisible, use create animation. + // If view is becoming invisible, use delete animation. Otherwise, use update animation. + // This approach is easier than maintaining a list of tags for recently created/deleted views. + AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ? + mLayoutCreateAnimation : + mLayoutUpdateAnimation; + + Animation animation = layoutAnimation.createAnimation(view, x, y, width, height); + if (animation == null || !(animation instanceof HandleLayout)) { + view.layout(x, y, x + width, y + height); + } + if (animation != null) { + view.startAnimation(animation); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java new file mode 100644 index 000000000000..0f3d9d7fdb2b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java @@ -0,0 +1,22 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Enum representing the different animation type that can be specified in layout animation config. + */ +/* package */ enum LayoutAnimationType { + CREATE("create"), + UPDATE("update"); + + private final String mName; + + private LayoutAnimationType(String name) { + mName = name; + } + + @Override + public String toString() { + return mName; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java new file mode 100644 index 000000000000..a989e625c675 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java @@ -0,0 +1,15 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +/** + * Class responsible for handling layout view creation animation, applied to view whenever a + * valid config was supplied for the layout animation of CREATE type. + */ +/* package */ class LayoutCreateAnimation extends BaseLayoutAnimation { + + @Override + boolean isReverse() { + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java new file mode 100644 index 000000000000..8189716f729d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java @@ -0,0 +1,42 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import javax.annotation.Nullable; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; + +/** + * Class responsible for handling layout update animation, applied to view whenever a valid config + * was supplied for the layout animation of UPDATE type. + */ +/* package */ class LayoutUpdateAnimation extends AbstractLayoutAnimation { + + // We are currently not enabling translation GPU-accelerated animated, as it creates odd + // artifacts with native react scrollview. This needs to be investigated. + private static final boolean USE_TRANSLATE_ANIMATION = false; + + @Override + boolean isValid() { + return mDurationMs > 0; + } + + @Override + @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height) { + boolean animateLocation = view.getX() != x || view.getY() != y; + boolean animateSize = view.getWidth() != width || view.getHeight() != height; + if (!animateLocation && !animateSize) { + return null; + } else if (animateLocation && !animateSize && USE_TRANSLATE_ANIMATION) { + // Use GPU-accelerated animation, however we loose the ability to resume interrupted + // animation where it was left off. We may be able to listen to animation interruption + // and set the layout manually in this case, so that next animation kicks off smoothly. + return new TranslateAnimation(view.getX() - x, 0, view.getY() - y, 0); + } else { + // Animation is sub-optimal for perf, but scale transformation can't be use in this case. + return new PositionAndSizeAnimation(view, x, y, width, height); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java new file mode 100644 index 000000000000..a7ad690023c5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java @@ -0,0 +1,66 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Animation responsible for updating opacity of a view. It should ideally use hardware texture + * to optimize rendering performances. + */ +/* package */ class OpacityAnimation extends Animation { + + static class OpacityAnimationListener implements AnimationListener { + + private final View mView; + private boolean mLayerTypeChanged = false; + + public OpacityAnimationListener(View view) { + mView = view; + } + + @Override + public void onAnimationStart(Animation animation) { + if (mView.hasOverlappingRendering() && + mView.getLayerType() == View.LAYER_TYPE_NONE) { + mLayerTypeChanged = true; + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + } + + @Override + public void onAnimationEnd(Animation animation) { + if (mLayerTypeChanged) { + mView.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + // do nothing + } + } + + private final View mView; + private final float mStartOpacity, mDeltaOpacity; + + public OpacityAnimation(View view, float startOpacity, float endOpacity) { + mView = view; + mStartOpacity = startOpacity; + mDeltaOpacity = endOpacity - startOpacity; + + setAnimationListener(new OpacityAnimationListener(view)); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + mView.setAlpha(mStartOpacity + mDeltaOpacity * interpolatedTime); + } + + @Override + public boolean willChangeBounds() { + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java new file mode 100644 index 000000000000..2adf81a6bb6c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java @@ -0,0 +1,52 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Animation responsible for updating size and position of a view. We can't use scaling as view + * content may not necessarily stretch. As a result, this approach is inefficient because of + * layout passes occurring on every frame. + * What we might want to try to do instead is use a combined ScaleAnimation and TranslateAnimation. + */ +/* package */ class PositionAndSizeAnimation extends Animation implements HandleLayout { + + private final View mView; + private final float mStartX, mStartY, mDeltaX, mDeltaY; + private final int mStartWidth, mStartHeight, mDeltaWidth, mDeltaHeight; + + public PositionAndSizeAnimation(View view, int x, int y, int width, int height) { + mView = view; + + mStartX = view.getX(); + mStartY = view.getY(); + mStartWidth = view.getWidth(); + mStartHeight = view.getHeight(); + + mDeltaX = x - mStartX; + mDeltaY = y - mStartY; + mDeltaWidth = width - mStartWidth; + mDeltaHeight = height - mStartHeight; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float newX = mStartX + mDeltaX * interpolatedTime; + float newY = mStartY + mDeltaY * interpolatedTime; + float newWidth = mStartWidth + mDeltaWidth * interpolatedTime; + float newHeight = mStartHeight + mDeltaHeight * interpolatedTime; + mView.layout( + Math.round(newX), + Math.round(newY), + Math.round(newX + newWidth), + Math.round(newY + newHeight)); + } + + @Override + public boolean willChangeBounds() { + return true; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java new file mode 100644 index 000000000000..9696fe90a1a3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java @@ -0,0 +1,20 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager.layoutanimation; + +import android.view.animation.Interpolator; + +/** + * Simple spring interpolator + */ +//TODO(7613736): Improve spring interpolator with friction and damping variable support +/* package */ class SimpleSpringInterpolator implements Interpolator { + + private static final float FACTOR = 0.5f; + + @Override + public float getInterpolation(float input) { + return (float) + (1 + Math.pow(2, -10 * input) * Math.sin((input - FACTOR / 4) * Math.PI * 2 / FACTOR)); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 134b6492b9bf..9e2728db4e1e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -16,6 +16,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.view.animation.Animation; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -286,7 +287,15 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa boolean intersects = clippingRect .intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom); boolean needUpdateClippingRecursive = false; - if (!intersects && child.getParent() != null) { + // We never want to clip children that are being animated, as this can easily break layout : + // when layout animation changes size and/or position of views contained inside a listview that + // clips offscreen children, we need to ensure that, when view exits the viewport, final size + // and position is set prior to removing the view from its listview parent. + // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll, + // it won't be size and located properly. + Animation animation = child.getAnimation(); + boolean isAnimating = animation != null && !animation.hasEnded(); + if (!intersects && child.getParent() != null && !isAnimating) { // We can try saving on invalidate call here as the view that we remove is out of visible area // therefore invalidation is not necessary. super.removeViewsInLayout(idx - clippedSoFar, 1); From 892dd5b86ab6e90337ccfb3f78e5ac5a4ef17839 Mon Sep 17 00:00:00 2001 From: Gabe Levi Date: Tue, 1 Dec 2015 19:09:01 -0800 Subject: [PATCH 0195/1411] Fix errors uncovered by v0.19.0 Reviewed By: mroch Differential Revision: D2706663 fb-gh-sync-id: 017c91bab849bf18767cacd2ebe32d1a1b10c715 --- Examples/UIExplorer/GeolocationExample.js | 12 ++- Examples/UIExplorer/ImageExample.js | 1 + Examples/UIExplorer/LayoutEventsExample.js | 25 ++++-- Examples/UIExplorer/MapViewExample.js | 87 ++++++++++++++++--- Examples/UIExplorer/PanResponderExample.js | 8 +- Examples/UIExplorer/PickerIOSExample.js | 2 +- Examples/UIExplorer/UIExplorerListBase.js | 2 +- Examples/UIExplorer/XHRExampleHeaders.js | 3 +- IntegrationTests/AppEventsTest.js | 9 +- IntegrationTests/IntegrationTestsApp.js | 4 +- IntegrationTests/LayoutEventsTest.js | 33 +++++-- .../Components/Navigation/NavigatorIOS.ios.js | 4 +- .../Components/TabBarIOS/TabBarItemIOS.ios.js | 1 + Libraries/Components/TextInput/TextInput.js | 8 +- .../ToastAndroid/ToastAndroid.ios.js | 1 + .../Components/Touchable/TouchableBounce.js | 1 + .../Touchable/TouchableHighlight.js | 1 + .../Components/Touchable/TouchableOpacity.js | 1 + .../Navigator/Navigation/NavigationContext.js | 1 + Libraries/ReactIOS/renderApplication.ios.js | 1 + Libraries/Storage/AsyncStorage.js | 1 + Libraries/Utilities/MatrixMath.js | 1 + .../vendor/emitter/EmitterSubscription.js | 1 + Libraries/vendor/emitter/EventEmitter.js | 1 + 24 files changed, 168 insertions(+), 41 deletions(-) diff --git a/Examples/UIExplorer/GeolocationExample.js b/Examples/UIExplorer/GeolocationExample.js index d9dd4e842b74..61d7d3778bda 100644 --- a/Examples/UIExplorer/GeolocationExample.js +++ b/Examples/UIExplorer/GeolocationExample.js @@ -49,11 +49,15 @@ var GeolocationExample = React.createClass({ componentDidMount: function() { navigator.geolocation.getCurrentPosition( - (initialPosition) => this.setState({initialPosition}), + (position) => { + var initialPosition = JSON.stringify(position); + this.setState({initialPosition}); + }, (error) => alert(error.message), {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000} ); - this.watchID = navigator.geolocation.watchPosition((lastPosition) => { + this.watchID = navigator.geolocation.watchPosition((position) => { + var lastPosition = JSON.stringify(position); this.setState({lastPosition}); }); }, @@ -67,11 +71,11 @@ var GeolocationExample = React.createClass({ Initial position: - {JSON.stringify(this.state.initialPosition)} + {this.state.initialPosition} Current position: - {JSON.stringify(this.state.lastPosition)} + {this.state.lastPosition} ); diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 68b16b2d103e..50c96ffd285e 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -32,6 +32,7 @@ var NetworkImageCallbackExample = React.createClass({ getInitialState: function() { return { events: [], + mountTime: new Date(), }; }, diff --git a/Examples/UIExplorer/LayoutEventsExample.js b/Examples/UIExplorer/LayoutEventsExample.js index bcbb5cbb7f93..a6b0cea89c0f 100644 --- a/Examples/UIExplorer/LayoutEventsExample.js +++ b/Examples/UIExplorer/LayoutEventsExample.js @@ -24,19 +24,30 @@ var { View, } = React; +type Layout = { + x: number; + y: number; + width: number; + height: number; +}; + type LayoutEvent = { nativeEvent: { - layout: { - x: number; - y: number; - width: number; - height: number; - }; + layout: Layout, }; }; +type State = { + containerStyle?: { width: number }, + extraText?: string, + imageLayout?: Layout, + textLayout?: Layout, + viewLayout?: Layout, + viewStyle: { margin: number }, +}; + var LayoutEventExample = React.createClass({ - getInitialState: function() { + getInitialState(): State { return { viewStyle: { margin: 20, diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 863187edab61..44142a4af160 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -31,6 +31,17 @@ var regionText = { longitudeDelta: '0', }; +type MapRegion = { + latitude: number, + longitude: number, + latitudeDelta: number, + longitudeDelta: number, +}; + +type MapRegionInputState = { + region: MapRegion, +}; + var MapRegionInput = React.createClass({ propTypes: { @@ -43,7 +54,7 @@ var MapRegionInput = React.createClass({ onChange: React.PropTypes.func.isRequired, }, - getInitialState: function() { + getInitialState(): MapRegionInputState { return { region: { latitude: 0, @@ -135,19 +146,42 @@ var MapRegionInput = React.createClass({ _change: function() { this.setState({ - latitude: parseFloat(regionText.latitude), - longitude: parseFloat(regionText.longitude), - latitudeDelta: parseFloat(regionText.latitudeDelta), - longitudeDelta: parseFloat(regionText.longitudeDelta), + region: { + latitude: parseFloat(regionText.latitude), + longitude: parseFloat(regionText.longitude), + latitudeDelta: parseFloat(regionText.latitudeDelta), + longitudeDelta: parseFloat(regionText.longitudeDelta), + }, }); this.props.onChange(this.state.region); }, }); +type Annotations = Array<{ + animateDrop?: boolean, + latitude: number, + longitude: number, + title?: string, + subtitle?: string, + hasLeftCallout?: boolean, + hasRightCallout?: boolean, + onLeftCalloutPress?: Function, + onRightCalloutPress?: Function, + tintColor?: string, + image?: any, + id?: string, +}>; +type MapViewExampleState = { + isFirstLoad: boolean, + mapRegion?: MapRegion, + mapRegionInput?: MapRegion, + annotations?: Annotations, +}; + var MapViewExample = React.createClass({ - getInitialState() { + getInitialState(): MapViewExampleState { return { isFirstLoad: true, }; @@ -171,7 +205,7 @@ var MapViewExample = React.createClass({ ); }, - _getAnnotations(region) { + _getAnnotations(region): Annotations { return [{ longitude: region.longitude, latitude: region.latitude, @@ -205,9 +239,14 @@ var MapViewExample = React.createClass({ }); +type CalloutMapViewExampleState = { + isFirstLoad: boolean, + annotations?: Annotations, + mapRegion?: MapRegion, +}; var CalloutMapViewExample = React.createClass({ - getInitialState() { + getInitialState(): CalloutMapViewExampleState { return { isFirstLoad: true, }; @@ -243,9 +282,14 @@ var CalloutMapViewExample = React.createClass({ }); +type CustomPinColorMapViewExampleState = { + isFirstLoad: boolean, + annotations?: Annotations, + mapRegion?: MapRegion, +}; var CustomPinColorMapViewExample = React.createClass({ - getInitialState() { + getInitialState(): CustomPinColorMapViewExampleState { return { isFirstLoad: true, }; @@ -278,9 +322,14 @@ var CustomPinColorMapViewExample = React.createClass({ }); +type CustomPinImageMapViewExampleState = { + isFirstLoad: boolean, + annotations?: Annotations, + mapRegion?: MapRegion, +}; var CustomPinImageMapViewExample = React.createClass({ - getInitialState() { + getInitialState(): CustomPinImageMapViewExampleState { return { isFirstLoad: true, }; @@ -313,9 +362,25 @@ var CustomPinImageMapViewExample = React.createClass({ }); +type Overlays = Array<{ + coordinates?: Array<{ + latitude: number, + longitude: number, + }>, + lineWidth?: number, + strokeColor?: string, + fillColor?: string, + id?: string, +}>; +type CustomOverlayMapViewExampleState = { + isFirstLoad: boolean, + overlays?: Overlays, + annotations?: Annotations, + mapRegion?: MapRegion, +}; var CustomOverlayMapViewExample = React.createClass({ - getInitialState() { + getInitialState(): CustomOverlayMapViewExampleState { return { isFirstLoad: true, }; diff --git a/Examples/UIExplorer/PanResponderExample.js b/Examples/UIExplorer/PanResponderExample.js index c2e6a667ce0d..47833af3f5a8 100644 --- a/Examples/UIExplorer/PanResponderExample.js +++ b/Examples/UIExplorer/PanResponderExample.js @@ -11,7 +11,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * @flow-weak + * @flow weak */ 'use strict'; @@ -79,7 +79,8 @@ var PanResponderExample = React.createClass({ }, _highlight: function() { - this.circle && this.circle.setNativeProps({ + const circle = this.circle; + circle && circle.setNativeProps({ style: { backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR) } @@ -87,7 +88,8 @@ var PanResponderExample = React.createClass({ }, _unHighlight: function() { - this.circle && this.circle.setNativeProps({ + const circle = this.circle; + circle && circle.setNativeProps({ style: { backgroundColor: processColor(CIRCLE_COLOR) } diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js index 31c81ccccdad..b0cc5cb9729d 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/PickerIOSExample.js @@ -99,7 +99,7 @@ var PickerExample = React.createClass({ {CAR_MAKES_AND_MODELS[this.state.carMake].models.map( (modelName, modelIndex) => ( diff --git a/Examples/UIExplorer/UIExplorerListBase.js b/Examples/UIExplorer/UIExplorerListBase.js index 4b26a6976dc6..c4b55f6d0ab3 100644 --- a/Examples/UIExplorer/UIExplorerListBase.js +++ b/Examples/UIExplorer/UIExplorerListBase.js @@ -116,7 +116,7 @@ class UIExplorerListBase extends React.Component { search(text: mixed): void { this.props.search && this.props.search(text); - var regex = new RegExp(text, 'i'); + var regex = new RegExp(String(text), 'i'); var filter = (component) => regex.test(component.title); this.setState({ diff --git a/Examples/UIExplorer/XHRExampleHeaders.js b/Examples/UIExplorer/XHRExampleHeaders.js index e9f5e2c4c974..e83c1b071d95 100644 --- a/Examples/UIExplorer/XHRExampleHeaders.js +++ b/Examples/UIExplorer/XHRExampleHeaders.js @@ -11,6 +11,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + * @noflow */ 'use strict'; @@ -113,4 +114,4 @@ var styles = StyleSheet.create({ }, }); -module.exports = XHRExampleHeaders; \ No newline at end of file +module.exports = XHRExampleHeaders; diff --git a/IntegrationTests/AppEventsTest.js b/IntegrationTests/AppEventsTest.js index 7362072ffd48..c003b014d1ea 100644 --- a/IntegrationTests/AppEventsTest.js +++ b/IntegrationTests/AppEventsTest.js @@ -24,8 +24,15 @@ var deepDiffer = require('deepDiffer'); var TEST_PAYLOAD = {foo: 'bar'}; +type AppEvent = { data: Object, ts: number, }; +type State = { + sent: 'none' | AppEvent, + received: 'none' | AppEvent, + elapsed?: string, +}; + var AppEventsTest = React.createClass({ - getInitialState: function() { + getInitialState(): State { return {sent: 'none', received: 'none'}; }, componentDidMount: function() { diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js index ce136f7fd6fa..54fca9a4bd49 100644 --- a/IntegrationTests/IntegrationTestsApp.js +++ b/IntegrationTests/IntegrationTestsApp.js @@ -39,10 +39,12 @@ TESTS.forEach( // Modules required for integration tests require('LoggingTestModule'); +type Test = any; + var IntegrationTestsApp = React.createClass({ getInitialState: function() { return { - test: null, + test: (null: ?Test), }; }, render: function() { diff --git a/IntegrationTests/LayoutEventsTest.js b/IntegrationTests/LayoutEventsTest.js index 9f7eb7d54b35..2cfe13f9b9dd 100644 --- a/IntegrationTests/LayoutEventsTest.js +++ b/IntegrationTests/LayoutEventsTest.js @@ -27,19 +27,38 @@ function debug() { // console.log.apply(null, arguments); } +type Layout = { + x: number; + y: number; + width: number; + height: number; +}; type LayoutEvent = { nativeEvent: { - layout: { - x: number; - y: number; - width: number; - height: number; - }; + layout: Layout; }; }; +type Style = { + margin?: number, + padding?: number, + borderColor?: string, + borderWidth?: number, + backgroundColor?: string, + width?: number, +}; + +type State = { + didAnimation: boolean, + extraText?: string, + imageLayout?: Layout, + textLayout?: Layout, + viewLayout?: Layout, + viewStyle?: Style, + containerStyle?: Style, +}; var LayoutEventsTest = React.createClass({ - getInitialState: function() { + getInitialState(): State { return { didAnimation: false, }; diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index a0f2199b295d..6d86930b7a6f 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -74,7 +74,7 @@ type State = { fromIndex: number; toIndex: number; makingNavigatorRequest: boolean; - updatingAllIndicesAtOrBeyond: number; + updatingAllIndicesAtOrBeyond: ?number; } type Event = Object; @@ -592,7 +592,7 @@ var NavigatorIOS = React.createClass({ _routeToStackItem: function(route: Route, i: number) { var Component = route.component; - var shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond !== null && + var shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond != null && this.state.updatingAllIndicesAtOrBeyond >= i; return ( diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index a4c883bc900e..f220249c76c7 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TabBarItemIOS + * @noflow */ 'use strict'; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index e7416e833bf0..e7049d2e07d9 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -335,8 +335,12 @@ var TextInput = React.createClass({ */ mixins: [NativeMethodsMixin, TimerMixin], - viewConfig: ((Platform.OS === 'ios' ? RCTTextField.viewConfig : - (Platform.OS === 'android' ? AndroidTextInput.viewConfig : {})) : Object), + viewConfig: + ((Platform.OS === 'ios' && RCTTextField ? + RCTTextField.viewConfig : + (Platform.OS === 'android' && AndroidTextInput ? + AndroidTextInput.viewConfig : + {})) : Object), isFocused: function(): boolean { return TextInputState.currentlyFocusedField() === diff --git a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js index bf42aae640e8..087786e08937 100644 --- a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js +++ b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ToastAndroid + * @noflow */ 'use strict'; diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 0b7d91152c76..d3c1696769e8 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -21,6 +21,7 @@ type Event = Object; type State = { animationID: ?number; + scale: Animated.Value; }; var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index b7dc706eb6a0..67c1e55a0b85 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TouchableHighlight + * @noflow */ 'use strict'; diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 5d7d8f3fbb28..627dd1205bd6 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TouchableOpacity + * @noflow */ 'use strict'; diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js index 5a0c35f37e07..6d17fe1256df 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js @@ -23,6 +23,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @providesModule NavigationContext + * @noflow */ 'use strict'; diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js index 0b0f306d6be3..ad000f643051 100644 --- a/Libraries/ReactIOS/renderApplication.ios.js +++ b/Libraries/ReactIOS/renderApplication.ios.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule renderApplication + * @noflow */ 'use strict'; diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index c5be992e2aba..9135088f6163 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule AsyncStorage + * @noflow * @flow-weak */ 'use strict'; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index 23db73811f18..e248ba75dc87 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -2,6 +2,7 @@ * Copyright 2004-present Facebook. All Rights Reserved. * * @providesModule MatrixMath + * @noflow */ /* eslint-disable space-infix-ops */ 'use strict'; diff --git a/Libraries/vendor/emitter/EmitterSubscription.js b/Libraries/vendor/emitter/EmitterSubscription.js index a4d52a802ee5..bbd1d414c431 100644 --- a/Libraries/vendor/emitter/EmitterSubscription.js +++ b/Libraries/vendor/emitter/EmitterSubscription.js @@ -13,6 +13,7 @@ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * * @providesModule EmitterSubscription + * @noflow * @typechecks */ 'use strict'; diff --git a/Libraries/vendor/emitter/EventEmitter.js b/Libraries/vendor/emitter/EventEmitter.js index cb3725ebb989..b5c14a66a7c7 100644 --- a/Libraries/vendor/emitter/EventEmitter.js +++ b/Libraries/vendor/emitter/EventEmitter.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule EventEmitter + * @noflow * @typechecks */ From c11dce36e6488c77eb16c121ea7cfe497c26ff4a Mon Sep 17 00:00:00 2001 From: Gabe Levi Date: Tue, 1 Dec 2015 19:10:06 -0800 Subject: [PATCH 0196/1411] Deploy 0.19.0 Reviewed By: mroch Differential Revision: D2711202 fb-gh-sync-id: b889cc9fe13f676aa4e7a5672556174792ece365 --- .flowconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.flowconfig b/.flowconfig index 7f4891b33340..08acf4e61aeb 100644 --- a/.flowconfig +++ b/.flowconfig @@ -55,9 +55,9 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.18.1 +0.19.0 From 0571250521c6fc21c3112481f335daa7dea1b18b Mon Sep 17 00:00:00 2001 From: Wenjing Wang Date: Tue, 1 Dec 2015 22:04:55 -0800 Subject: [PATCH 0197/1411] 8/n Fix warnings at startup Summary: public - Add missing required `key` prop for array of child components Reviewed By: zjj010104 Differential Revision: D2711271 fb-gh-sync-id: b9848abffd3c458cf45b1231be5eff0a5de805f1 --- .../Navigator/NavigatorBreadcrumbNavigationBar.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index ccf28f1f9d1e..b142b18fbbae 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -205,7 +205,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); var breadcrumbDescriptor = ( - + {navBarRouteMapper.iconForRoute(route, this.props.navigator)} @@ -231,7 +234,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); var titleDescriptor = ( - + {titleContent} ); @@ -253,7 +259,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ } var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState)); var rightButtonDescriptor = ( - + {rightContent} ); From 04187536d15d8946084aa24cda41dd5705028104 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 01:27:56 -0800 Subject: [PATCH 0198/1411] Fixed threading bug in RCTNetworking Summary: public When uploading images, RCTHTTPFormDataHelper was sometimes accessed on the wrong thread. Reviewed By: helouree Differential Revision: D2709186 fb-gh-sync-id: d0a14926927d1d41f602f78a9f6892dfbdfc6ff9 --- Libraries/Network/RCTNetworking.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 74b58972ccac..9afb0914ee80 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -59,6 +59,8 @@ @implementation RCTHTTPFormDataHelper - (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData callback:(RCTHTTPQueryResult)callback { + RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue"); + if (formData.count == 0) { return callback(nil, nil); } @@ -76,6 +78,8 @@ - (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData - (RCTURLRequestCancellationBlock)handleResult:(NSDictionary *)result error:(NSError *)error { + RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on method queue"); + if (error) { return _callback(error, nil); } @@ -256,7 +260,7 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary *)query callback: (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback { - RCTAssertThread(_methodQueue, @"processData: must be called on method queue"); + RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on method queue"); if (!query) { return callback(nil, nil); @@ -270,7 +274,9 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary __block RCTURLRequestCancellationBlock cancellationBlock = nil; RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { - cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil); + dispatch_async(_methodQueue, ^{ + cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil); + }); }]; [task start]; From 5dc40afbf681839947cbab691a9f4d2d1ccb7bc0 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 2 Dec 2015 03:10:42 -0800 Subject: [PATCH 0199/1411] Add for-of transform to babel configurations Summary: Adds babel-plugin-transform-es2015-for-of to babel configuration. public Reviewed By: tadeuzagallo Differential Revision: D2712220 fb-gh-sync-id: cc6dd9e6e70946607ef9bb9f659eb628cf2eb555 --- package.json | 1 + packager/react-packager/.babelrc | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5be50c910962..d2249d02e799 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "babel-plugin-transform-es2015-computed-properties": "^6.0.14", "babel-plugin-transform-es2015-constants": "^6.0.15", "babel-plugin-transform-es2015-destructuring": "^6.0.18", + "babel-plugin-transform-es2015-for-of": "^6.0.14", "babel-plugin-transform-es2015-modules-commonjs": "^6.1.3", "babel-plugin-transform-es2015-parameters": "^6.0.18", "babel-plugin-transform-es2015-shorthand-properties": "^6.0.14", diff --git a/packager/react-packager/.babelrc b/packager/react-packager/.babelrc index c72259dac08c..e1c61b11f7df 100644 --- a/packager/react-packager/.babelrc +++ b/packager/react-packager/.babelrc @@ -23,7 +23,8 @@ "transform-object-rest-spread", "transform-react-display-name", "transform-react-jsx", - "transform-regenerator" + "transform-regenerator", + "transform-es2015-for-of" ], "sourceMaps": false } From 873efa2e972169c6b787158d31b63617888f2a75 Mon Sep 17 00:00:00 2001 From: sercanov Date: Wed, 2 Dec 2015 14:27:18 +0200 Subject: [PATCH 0200/1411] added Mobabuild app --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 439ec56c9d7e..fe6e00741c31 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -392,6 +392,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, + { + name: 'Mobabuild', + icon: 'http://mobabuild.co/images/applogo.png', + link: 'http://mobabuild.co', + author: 'Sercan Demircan ( @sercanov )', + }, ]; var AppList = React.createClass({ From edb26072c397180840de54738235f064402ad75c Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Wed, 2 Dec 2015 20:31:05 +0800 Subject: [PATCH 0201/1411] add git requirement and note for environment variables --- docs/DevelopmentSetupAndroid.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/DevelopmentSetupAndroid.md b/docs/DevelopmentSetupAndroid.md index 6a7b7d87f3c6..e7d1f8b36ac2 100644 --- a/docs/DevelopmentSetupAndroid.md +++ b/docs/DevelopmentSetupAndroid.md @@ -7,8 +7,18 @@ permalink: docs/android-setup.html next: linux-windows-support --- -This guide describes basic steps of the Android development environment setup that are required to run React Native android apps on an android emulator. We don't discuss developer tool configuration such as IDEs here. +This guide describes basic steps of the Android development environment setup that are required to run React Native android apps on an android emulator. We don't discuss developer tool configuration such as IDEs here. +### Install Git + + - **On Mac**, if you have installed [XCode](https://developer.apple.com/xcode/), Git is already installed, otherwise run the following: + + brew install git + + - **On Linux**, install Git [via your package manager](https://git-scm.com/download/linux). + + - **On Windows**, download and install [Git for Windows](https://git-for-windows.github.io/). During the setup process, choose "Run Git from Windows Command Prompt", which will add Git to your `PATH` environment variable. + ### Install the Android SDK (unless you have it) 1. [Install the latest JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) @@ -28,7 +38,10 @@ __IMPORTANT__: Make sure the `ANDROID_HOME` environment variable points to your export ANDROID_HOME= - - **On Windows**, go to `Control Panel` -> `System and Security` -> `System` -> `Change settings` -> `Advanced` -> `Environment variables` -> `New` + - **On Windows**, go to `Control Panel` -> `System and Security` -> `System` -> `Change settings` -> `Advanced` -> `Environment variables` -> `New` + +__NOTE__: You need to restart the Command Prompt (Windows) / Terminal Emulator (Mac OS X, Linux) to apply the new Environment variables. + ### Use gradle daemon From 7e4c8b2d2d2b27ecfc96a5594ae162f939a4f4f1 Mon Sep 17 00:00:00 2001 From: sercanov Date: Wed, 2 Dec 2015 14:43:01 +0200 Subject: [PATCH 0202/1411] sorted alphabetically --- website/src/react-native/showcase.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index fe6e00741c31..89b74d7033f2 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -248,6 +248,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/mintrain/id1015739031?mt=8', author: 'Peter Cottle', }, + { + name: 'Mobabuild', + icon: 'http://mobabuild.co/images/applogo.png', + link: 'http://mobabuild.co', + author: 'Sercan Demircan ( @sercanov )', + }, { name: 'MockingBot', icon: 'https://s3.cn-north-1.amazonaws.com.cn/modao/downloads/images/MockingBot175.png', @@ -392,12 +398,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, - { - name: 'Mobabuild', - icon: 'http://mobabuild.co/images/applogo.png', - link: 'http://mobabuild.co', - author: 'Sercan Demircan ( @sercanov )', - }, ]; var AppList = React.createClass({ From 802aef95097ce76a452fb70086a38075ca37c9ae Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Wed, 2 Dec 2015 05:53:38 -0800 Subject: [PATCH 0203/1411] Update argument passed for assetRoots. Summary: Fixes #4388 Closes https://github.com/facebook/react-native/pull/4431 Reviewed By: svcscm Differential Revision: D2704076 Pulled By: mkonicek fb-gh-sync-id: 2b788eb15787334bf7309fde730566092ff63832 --- local-cli/server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-cli/server/server.js b/local-cli/server/server.js index 0648d937c2de..3abffb5beef4 100644 --- a/local-cli/server/server.js +++ b/local-cli/server/server.js @@ -79,7 +79,7 @@ function _server(argv, config, resolve, reject) { } args.assetRoots = args.assetRoots - ? argToArray(args.projectRoots).map(dir => + ? argToArray(args.assetRoots).map(dir => path.resolve(process.cwd(), dir) ) : config.getAssetRoots(); From c25c98c00c8c195f85c9fb17eae3cb0c36b465f5 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 2 Dec 2015 05:12:17 -0800 Subject: [PATCH 0204/1411] Flush UI blocks as soon as they're accumulated Summary: public Currently, we wait to invoke `-flushUIBlocks` until the JavaScript batch to native has completed. This means we may be waiting an unnecessarily long time to perform view hierarchy changes and prop changes. By instead invoking this after each chunk of enqueued UI blocks, we can perform some updates more eagerly, increasing our utilization of the main thread while splitting up the amount of time we spend running upon it. This shouldn't affect layout, which is still tied to `-batchDidComplete`, so any visual inconsistencies should be limited to prop changes, which seems acceptable for the dramatic improvement in performance. Reviewed By: javache Differential Revision: D2658552 fb-gh-sync-id: 6d4560e21d7da1b02d2f30d1860d60735f11c4b5 --- React/Base/RCTBatchedBridge.m | 12 ++++++++++++ React/Base/RCTBridgeModule.h | 8 ++++++++ React/Modules/RCTUIManager.h | 11 +++++++++++ React/Modules/RCTUIManager.m | 7 +++++++ 4 files changed, 38 insertions(+) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 8923d85ef6a7..bbb8fe24a465 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -720,6 +720,7 @@ - (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded if (buffer != nil && buffer != (id)kCFNull) { _wasBatchActive = YES; [self handleBuffer:buffer]; + [self partialBatchDidFlush]; } if (batchEnded) { @@ -800,6 +801,17 @@ - (void)handleBuffer:(NSArray *)buffer } } +- (void)partialBatchDidFlush +{ + for (RCTModuleData *moduleData in _moduleDataByID) { + if (moduleData.hasInstance && [moduleData.instance respondsToSelector:@selector(partialBatchDidFlush)]) { + [self dispatchBlock:^{ + [moduleData.instance partialBatchDidFlush]; + } queue:moduleData.methodQueue]; + } + } +} + - (void)batchDidComplete { // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 436b26c80bd0..dd6dda34cf18 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -235,4 +235,12 @@ RCT_EXTERN void RCTRegisterModule(Class); \ */ - (void)batchDidComplete; +/** + * Notifies the module that the active batch of JS method invocations has been + * partially flushed. + * + * This occurs before -batchDidComplete, and more frequently. + */ +- (void)partialBatchDidFlush; + @end diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index daaf40ac2e91..dc0bcd9d985b 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -77,6 +77,17 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ + (UIView *)JSResponder; +/** + * Normally, UI changes are not applied until the complete batch of method + * invocations from JavaScript to native has completed. + * + * Setting this to YES will flush UI changes sooner, which could potentially + * result in inconsistent UI updates. + * + * The default is NO (recommended). + */ +@property (atomic, assign) BOOL unsafeFlushUIChangesBeforeBatchEnds; + @end /** diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index e4f569e5e1bf..c3c1ea58917b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -880,6 +880,13 @@ - (void)_manageChildren:(NSNumber *)containerReactTag }]; } +- (void)partialBatchDidFlush +{ + if (self.unsafeFlushUIChangesBeforeBatchEnds) { + [self flushUIBlocks]; + } +} + - (void)batchDidComplete { // Gather blocks to be executed now that all view hierarchy manipulations have From 30a5eb51f8efea5cb5ccc75edbefc9575eadba3b Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Wed, 2 Dec 2015 05:30:33 -0800 Subject: [PATCH 0205/1411] Generate module definition on demand Reviewed By: astreet Differential Revision: D2707977 fb-gh-sync-id: fb2baa464a23df82e8b48a91a84c98370517d311 --- .../react/bridge/CatalystInstanceImpl.java | 5 +- .../react/bridge/JavaScriptModulesConfig.java | 23 ++--- .../react/bridge/NativeModuleRegistry.java | 84 ++++++++----------- 3 files changed, 44 insertions(+), 68 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 08f5a702100b..6929ea3d7e75 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -361,16 +361,15 @@ public void stopProfiler(String title, String filename) { private String buildModulesConfigJSONProperty( NativeModuleRegistry nativeModuleRegistry, JavaScriptModulesConfig jsModulesConfig) { - // TODO(5300733): Serialize config using single json generator JsonFactory jsonFactory = new JsonFactory(); StringWriter writer = new StringWriter(); try { JsonGenerator jg = jsonFactory.createGenerator(writer); jg.writeStartObject(); jg.writeFieldName("remoteModuleConfig"); - jg.writeRawValue(nativeModuleRegistry.moduleDescriptions()); + nativeModuleRegistry.writeModuleDescriptions(jg); jg.writeFieldName("localModulesConfig"); - jg.writeRawValue(jsModulesConfig.moduleDescriptions()); + jsModulesConfig.writeModuleDescriptions(jg); jg.writeEndObject(); jg.close(); } catch (IOException ioe) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java index bc75da277911..abeba7230c2f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java @@ -10,12 +10,10 @@ package com.facebook.react.bridge; import java.io.IOException; -import java.io.StringWriter; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; /** @@ -33,23 +31,14 @@ private JavaScriptModulesConfig(List modules) { return mModules; } - /*package*/ String moduleDescriptions() { - JsonFactory jsonFactory = new JsonFactory(); - StringWriter writer = new StringWriter(); - try { - JsonGenerator jg = jsonFactory.createGenerator(writer); - jg.writeStartObject(); - for (JavaScriptModuleRegistration registration : mModules) { - jg.writeObjectFieldStart(registration.getName()); - appendJSModuleToJSONObject(jg, registration); - jg.writeEndObject(); - } + /*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException { + jg.writeStartObject(); + for (JavaScriptModuleRegistration registration : mModules) { + jg.writeObjectFieldStart(registration.getName()); + appendJSModuleToJSONObject(jg, registration); jg.writeEndObject(); - jg.close(); - } catch (IOException ioe) { - throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe); } - return writer.getBuffer().toString(); + jg.writeEndObject(); } private void appendJSModuleToJSONObject( diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java index 50bdd69bbe2d..389fd66b4616 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java @@ -10,17 +10,16 @@ package com.facebook.react.bridge; import java.io.IOException; -import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.facebook.react.common.MapBuilder; import com.facebook.infer.annotation.Assertions; import com.facebook.systrace.Systrace; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; /** @@ -28,18 +27,15 @@ */ public class NativeModuleRegistry { - private final ArrayList mModuleTable; + private final List mModuleTable; private final Map, NativeModule> mModuleInstances; - private final String mModuleDescriptions; private final ArrayList mBatchCompleteListenerModules; private NativeModuleRegistry( - ArrayList moduleTable, - Map, NativeModule> moduleInstances, - String moduleDescriptions) { + List moduleTable, + Map, NativeModule> moduleInstances) { mModuleTable = moduleTable; mModuleInstances = moduleInstances; - mModuleDescriptions = moduleDescriptions; mBatchCompleteListenerModules = new ArrayList(mModuleTable.size()); for (int i = 0; i < mModuleTable.size(); i++) { @@ -62,8 +58,29 @@ private NativeModuleRegistry( definition.call(catalystInstance, methodId, parameters); } - /* package */ String moduleDescriptions() { - return mModuleDescriptions; + /* package */ void writeModuleDescriptions(JsonGenerator jg) throws IOException { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateJSON"); + try { + jg.writeStartObject(); + for (ModuleDefinition moduleDef : mModuleTable) { + jg.writeObjectFieldStart(moduleDef.name); + jg.writeNumberField("moduleID", moduleDef.id); + jg.writeObjectFieldStart("methods"); + for (int i = 0; i < moduleDef.methods.size(); i++) { + MethodRegistration method = moduleDef.methods.get(i); + jg.writeObjectFieldStart(method.name); + jg.writeNumberField("methodID", i); + jg.writeStringField("type", method.method.getType()); + jg.writeEndObject(); + } + jg.writeEndObject(); + moduleDef.target.writeConstantsField(jg, "constants"); + jg.writeEndObject(); + } + jg.writeEndObject(); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } } /* package */ void notifyCatalystInstanceDestroy() { @@ -174,45 +191,16 @@ public Builder add(NativeModule module) { } public NativeModuleRegistry build() { - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateJSON"); - ArrayList moduleTable = new ArrayList<>(); - Map, NativeModule> moduleInstances = MapBuilder.newHashMap(); - String moduleDefinitionJson; - try { - JsonFactory jsonFactory = new JsonFactory(); - StringWriter writer = new StringWriter(); - try { - JsonGenerator jg = jsonFactory.createGenerator(writer); - jg.writeStartObject(); - int idx = 0; - for (NativeModule module : mModules.values()) { - ModuleDefinition moduleDef = new ModuleDefinition(idx++, module.getName(), module); - moduleTable.add(moduleDef); - moduleInstances.put(module.getClass(), module); - jg.writeObjectFieldStart(moduleDef.name); - jg.writeNumberField("moduleID", moduleDef.id); - jg.writeObjectFieldStart("methods"); - for (int i = 0; i < moduleDef.methods.size(); i++) { - MethodRegistration method = moduleDef.methods.get(i); - jg.writeObjectFieldStart(method.name); - jg.writeNumberField("methodID", i); - jg.writeStringField("type", method.method.getType()); - jg.writeEndObject(); - } - jg.writeEndObject(); - moduleDef.target.writeConstantsField(jg, "constants"); - jg.writeEndObject(); - } - jg.writeEndObject(); - jg.close(); - } catch (IOException ioe) { - throw new RuntimeException("Unable to serialize Java module configuration", ioe); - } - moduleDefinitionJson = writer.getBuffer().toString(); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + List moduleTable = new ArrayList<>(); + Map, NativeModule> moduleInstances = new HashMap<>(); + + int idx = 0; + for (NativeModule module : mModules.values()) { + ModuleDefinition moduleDef = new ModuleDefinition(idx++, module.getName(), module); + moduleTable.add(moduleDef); + moduleInstances.put(module.getClass(), module); } - return new NativeModuleRegistry(moduleTable, moduleInstances, moduleDefinitionJson); + return new NativeModuleRegistry(moduleTable, moduleInstances); } } } From f69ac1eaefd35821623f68622fa2f1e10921f725 Mon Sep 17 00:00:00 2001 From: Alon Schwarz Date: Wed, 2 Dec 2015 05:50:34 -0800 Subject: [PATCH 0206/1411] Revert D2679408 (Only send layout update operation to nativehierarchymanager when layout actually changes) Reviewed By: andreicoman11 Differential Revision: D2712224 fb-gh-sync-id: e6aebe6fcf54e9f36cac092cab801bb97a65dbfd --- .../react/uimanager/ReactShadowNode.java | 38 ++++++------------- .../react/uimanager/UIImplementation.java | 14 ++++++- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index bbf33b7b2ed2..73134194fb1b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -18,7 +18,6 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.uimanager.events.EventDispatcher; /** * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily @@ -203,37 +202,18 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { float absoluteX, float absoluteY, UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer, - EventDispatcher eventDispatcher) { + NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { if (mNodeUpdated) { onCollectExtraUpdates(uiViewOperationQueue); } if (hasNewLayout()) { - float absoluteLeft = Math.round(absoluteX + getLayoutX()); - float absoluteTop = Math.round(absoluteY + getLayoutY()); - float absoluteRight = Math.round(absoluteX + getLayoutX() + getLayoutWidth()); - float absoluteBottom = Math.round(absoluteY + getLayoutY() + getLayoutHeight()); - // If the layout didn't change this should calculate exactly same values, it's fine to compare - // floats with "==" in this case - if (absoluteLeft != mAbsoluteLeft || absoluteTop != mAbsoluteTop || - absoluteRight != mAbsoluteRight || absoluteBottom != mAbsoluteBottom) { - mAbsoluteLeft = absoluteLeft; - mAbsoluteTop = absoluteTop; - mAbsoluteRight = absoluteRight; - mAbsoluteBottom = absoluteBottom; - - nativeViewHierarchyOptimizer.handleUpdateLayout(this); - if (mShouldNotifyOnLayout) { - eventDispatcher.dispatchEvent( - OnLayoutEvent.obtain( - getReactTag(), - getScreenX(), - getScreenY(), - getScreenWidth(), - getScreenHeight())); - } - } + mAbsoluteLeft = Math.round(absoluteX + getLayoutX()); + mAbsoluteTop = Math.round(absoluteY + getLayoutY()); + mAbsoluteRight = Math.round(absoluteX + getLayoutX() + getLayoutWidth()); + mAbsoluteBottom = Math.round(absoluteY + getLayoutY() + getLayoutHeight()); + + nativeViewHierarchyOptimizer.handleUpdateLayout(this); } } @@ -284,6 +264,10 @@ public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) { mShouldNotifyOnLayout = shouldNotifyOnLayout; } + /* package */ boolean shouldNotifyOnLayout() { + return mShouldNotifyOnLayout; + } + /** * Adds a child that the native view hierarchy will have at this index in the native view * corresponding to this node. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index aef2ebc29317..633c8dd43c9d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -698,8 +698,18 @@ protected void applyUpdatesRecursive( absoluteX, absoluteY, mOperationsQueue, - mNativeViewHierarchyOptimizer, - eventDispatcher); + mNativeViewHierarchyOptimizer); + + // notify JS about layout event if requested + if (cssNode.shouldNotifyOnLayout()) { + eventDispatcher.dispatchEvent( + OnLayoutEvent.obtain( + tag, + cssNode.getScreenX(), + cssNode.getScreenY(), + cssNode.getScreenWidth(), + cssNode.getScreenHeight())); + } } cssNode.markUpdateSeen(); } From 807e0d931097042c581d11b64d047e0dd338f9e2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 07:08:29 -0800 Subject: [PATCH 0207/1411] Fixed Xcode warnings Summary: public Fixed some Xcode warnings, and added some missing UIResponder methods to make the behavior of RCTTextView more self-consistent. Reviewed By: javache Differential Revision: D2712250 fb-gh-sync-id: d30038500194d7a5262d9e77d516c65d836a4420 --- Libraries/Text/RCTTextField.m | 10 +++++----- Libraries/Text/RCTTextView.m | 12 +++++++++++- React/Views/RCTShadowView.m | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index de4fd867bd35..8a2bbb5a67c4 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -239,6 +239,11 @@ - (void)sendSelectionEvent } } +- (BOOL)canBecomeFirstResponder +{ + return _jsRequestingFirstResponder; +} + - (void)reactWillMakeFirstResponder { _jsRequestingFirstResponder = YES; @@ -249,11 +254,6 @@ - (void)reactDidMakeFirstResponder _jsRequestingFirstResponder = NO; } -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - - (BOOL)resignFirstResponder { BOOL result = [super resignFirstResponder]; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 34f3b74970f0..e7cf180d4976 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -410,6 +410,16 @@ - (void)textViewDidEndEditing:(UITextView *)textView eventCount:_nativeEventCount]; } +- (BOOL)isFirstResponder +{ + return [_textView isFirstResponder]; +} + +- (BOOL)canBecomeFirstResponder +{ + return [_textView canBecomeFirstResponder]; +} + - (void)reactWillMakeFirstResponder { [_textView reactWillMakeFirstResponder]; @@ -427,7 +437,7 @@ - (void)reactDidMakeFirstResponder - (BOOL)resignFirstResponder { - return [_textView resignFirstResponder]; + return [super resignFirstResponder] && [_textView resignFirstResponder]; } - (void)layoutSubviews diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 75625fd7d3e0..f6c775f2ad75 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -580,7 +580,7 @@ - (void)setBackgroundColor:(UIColor *)color [self dirtyPropagation]; } -- (void)didSetProps:(NSArray *)changedProps +- (void)didSetProps:(__unused NSArray *)changedProps { if (_recomputePadding) { RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding); From 37042573b82e1c56ffabf80a505de80dee12c553 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 07:11:20 -0800 Subject: [PATCH 0208/1411] Added blurOnSubmit support to multine TextInput (aka RCTTextView) Summary: public Setting `blurOnSubmit=true` on a multiline `` now causes it to behave like a single-line input with respect to the return key: With the default value of `false`, pressing return will enter a newline character into the field. If you set the value to `true`, pressing return will now blur the field and trigger the onSubmitEditing event. The newline character will *not* be added to the text. (See associated github task for dicussion: https://github.com/facebook/react-native/pull/2149) Reviewed By: javache Differential Revision: D2710448 fb-gh-sync-id: c9706ae11f8b399932d3400ceb4c7558e455570d --- Examples/UIExplorer/TextInputExample.ios.js | 17 +++++++++++++ Libraries/Components/TextInput/TextInput.js | 28 ++++----------------- Libraries/Text/RCTTextField.m | 1 + Libraries/Text/RCTTextView.h | 1 + Libraries/Text/RCTTextView.m | 25 ++++++++++++++++++ Libraries/Text/RCTTextViewManager.m | 1 + 6 files changed, 50 insertions(+), 23 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 826139048cb5..9efde2ea0aeb 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -548,6 +548,23 @@ exports.examples = [ title: 'Blur on submit', render: function(): ReactElement { return ; }, }, + { + title: 'Multiline blur on submit', + render: function() { + return ( + + alert(event.nativeEvent.text)} + /> + + ); + } + }, { title: 'Multiline', render: function() { diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index e7049d2e07d9..9f8f44ed75e9 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -37,7 +37,7 @@ var onlyMultiline = { }; var notMultiline = { - onSubmitEditing: true, + // nothing yet }; if (Platform.OS === 'android') { @@ -47,10 +47,6 @@ if (Platform.OS === 'android') { var RCTTextField = requireNativeComponent('RCTTextField', null); } -type DefaultProps = { - blurOnSubmit: boolean; -}; - type Event = Object; /** @@ -73,17 +69,6 @@ type Event = Object; * ``` * * Note that some props are only available with `multiline={true/false}`: - * ``` - * var onlyMultiline = { - * onSelectionChange: true, // not supported in Open Source yet - * onTextInput: true, // not supported in Open Source yet - * children: true, - * }; - * - * var notMultiline = { - * onSubmitEditing: true, - * }; - * ``` */ var TextInput = React.createClass({ statics: { @@ -304,7 +289,10 @@ var TextInput = React.createClass({ selectTextOnFocus: PropTypes.bool, /** * If true, the text field will blur when submitted. - * The default value is true. + * The default value is true for single-line fields and false for + * multiline fields. Note that for multiline fields, setting blurOnSubmit + * to true means that pressing return will blur the field and trigger the + * onSubmitEditing event instead of inserting a newline into the field. * @platform ios */ blurOnSubmit: PropTypes.bool, @@ -323,12 +311,6 @@ var TextInput = React.createClass({ underlineColorAndroid: PropTypes.string, }, - getDefaultProps: function(): DefaultProps { - return { - blurOnSubmit: true, - }; - }, - /** * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We * make `this` look like an actual native component class. diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index 8a2bbb5a67c4..b8d172cc2894 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -36,6 +36,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher [self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit]; [self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil]; _reactSubviews = [NSMutableArray new]; + _blurOnSubmit = YES; } return self; } diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index 9305442abe91..5279eafb6731 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -17,6 +17,7 @@ @interface RCTTextView : RCTView @property (nonatomic, assign) BOOL autoCorrect; +@property (nonatomic, assign) BOOL blurOnSubmit; @property (nonatomic, assign) BOOL clearTextOnFocus; @property (nonatomic, assign) BOOL selectTextOnFocus; @property (nonatomic, assign) UIEdgeInsets contentInset; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index e7cf180d4976..9a33f7e4696f 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -72,6 +72,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _contentInset = UIEdgeInsetsZero; _eventDispatcher = eventDispatcher; _placeholderTextColor = [self defaultPlaceholderTextColor]; + _blurOnSubmit = NO; _textView = [[RCTUITextView alloc] initWithFrame:CGRectZero]; _textView.backgroundColor = [UIColor clearColor]; @@ -276,11 +277,35 @@ - (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)rang if (textView.textWasPasted) { textView.textWasPasted = NO; } else { + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress reactTag:self.reactTag text:nil key:text eventCount:_nativeEventCount]; + + if (_blurOnSubmit && [text isEqualToString:@"\n"]) { + + // TODO: the purpose of blurOnSubmit on RCTextField is to decide if the + // field should lose focus when return is pressed or not. We're cheating a + // bit here by using it on RCTextView to decide if return character should + // submit the form, or be entered into the field. + // + // The reason this is cheating is because there's no way to specify that + // you want the return key to be swallowed *and* have the field retain + // focus (which was what blurOnSubmit was originally for). For the case + // where _blurOnSubmit = YES, this is still the correct and expected + // behavior though, so we'll leave the don't-blur-or-add-newline problem + // to be solved another day. + + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit + reactTag:self.reactTag + text:self.text + key:nil + eventCount:_nativeEventCount]; + [self resignFirstResponder]; + return NO; + } } if (_maxLength == nil) { diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index 94ba4609c94a..f32369c4a3ce 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -25,6 +25,7 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) +RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL) RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL) RCT_REMAP_VIEW_PROPERTY(color, textView.textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL) From 7424af6078159e83a83c61b64f7bc1cb2eb05259 Mon Sep 17 00:00:00 2001 From: mkonicek-pr-test Date: Wed, 2 Dec 2015 09:02:39 -0800 Subject: [PATCH 0209/1411] Test PR 6: Tweak colors in 2048 example Summary: Just testing the shipit bot Closes https://github.com/facebook/react-native/pull/4503 Reviewed By: svcscm Differential Revision: D2712738 Pulled By: androidtrunkagent fb-gh-sync-id: 267f55991ca8267cb651734093bc62fb9c77a056 --- Examples/2048/Game2048.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 90111d38b871..3bc49217b7a2 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -237,7 +237,7 @@ var styles = StyleSheet.create({ marginBottom: 20, }, tryAgain: { - backgroundColor: '#887766', + backgroundColor: '#887761', padding: 20, borderRadius: 5, }, From 7ab17e5ef30d9b2fc4d639972855bb8f2074ef7a Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 09:05:39 -0800 Subject: [PATCH 0210/1411] Fix for long press state transition error in Touchable.js Summary: public This diff fixes an occasional JS exception thrown by Touchable.js when it attempts to transitions to the RESPONDER_ACTIVE_LONG_PRESS_IN state from the RESPONDER_INACTIVE_PRESS_IN state. Although I wasn't able to reproduce the error while testing, I was able to identify the likely cause: the LONG_PRESS_DETECTED state transition is triggered by a timer that is started on touch-down. This timer should be cancelled if the gesture is interrupted, however I identified a code path where the state can be changed to RESPONDER_INACTIVE_PRESS_IN without the longPressDelayTimeout being cancelled. To fix this, I've added some logic to cancel the timer in that case. I've also added a test for the error scenario that will display a redbox in __DEV__ mode, but will fail gracefully in production mode. Reviewed By: jingc Differential Revision: D2709750 fb-gh-sync-id: aeea1a31de5e92eb394c2ea177f556b131d50790 --- Libraries/Components/Touchable/Touchable.js | 24 ++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 0a2f554c43b0..0493311c5c2d 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -456,6 +456,11 @@ var TouchableMixin = { pressExpandBottom; if (isTouchWithinActive) { this._receiveSignal(Signals.ENTER_PRESS_RECT, e); + var curState = this.state.touchable.touchState; + if (curState === States.RESPONDER_INACTIVE_PRESS_IN) { + // fix for t7967420 + this._cancelLongPressDelayTimeout(); + } } else { this._cancelLongPressDelayTimeout(); this._receiveSignal(Signals.LEAVE_PRESS_RECT, e); @@ -564,7 +569,20 @@ var TouchableMixin = { _handleLongDelay: function(e) { this.longPressDelayTimeout = null; - this._receiveSignal(Signals.LONG_PRESS_DETECTED, e); + var curState = this.state.touchable.touchState; + if (curState !== States.RESPONDER_ACTIVE_PRESS_IN && + curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) { + if (__DEV__) { + throw new Error( + 'Attempted to transition from state `' + curState + '` to `' + + States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. ' + + 'This is most likely due to `Touchable.longPressDelayTimeout` not ' + + 'being cancelled.' + ); + } + } else { + this._receiveSignal(Signals.LONG_PRESS_DETECTED, e); + } }, /** @@ -577,13 +595,13 @@ var TouchableMixin = { */ _receiveSignal: function(signal, e) { var curState = this.state.touchable.touchState; - if (!(Transitions[curState] && Transitions[curState][signal])) { + var nextState = Transitions[curState] && Transitions[curState][signal]; + if (!nextState) { throw new Error( 'Unrecognized signal `' + signal + '` or state `' + curState + '` for Touchable responder `' + this.state.touchable.responderID + '`' ); } - var nextState = Transitions[curState][signal]; if (nextState === States.ERROR) { throw new Error( 'Touchable cannot transition from `' + curState + '` to `' + signal + From c06efc0831b9156ee6261d7ddd2086ed87cbc0aa Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Wed, 2 Dec 2015 09:10:28 -0800 Subject: [PATCH 0211/1411] remove activities from module constructors Summary: Refactor modules that take activities (or activities that implement some interface) as constructor args to not do that. Expose `getCurrentActivity()` in `ReactContext` and use that wherever the activity is needed. public Reviewed By: astreet Differential Revision: D2680462 fb-gh-sync-id: f263b3fe5b422b7aab9fdadd051cef4e82797b0a --- .../facebook/react/ReactInstanceManager.java | 17 +++++++++++++++++ .../react/ReactInstanceManagerImpl.java | 15 ++++++++++++++- .../com/facebook/react/bridge/ReactContext.java | 11 ++++++++++- .../bridge/ReactContextBaseJavaModule.java | 17 +++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 2449c28ea491..bdffdf261a53 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -47,6 +47,18 @@ * have a static method, and so cannot (in Java < 8), be one. */ public abstract class ReactInstanceManager { + + /** + * Listener interface for react instance events. + */ + public interface ReactInstanceEventListener { + /** + * Called when the react context is initialized (all modules registered). Always called on the + * UI thread. + */ + void onReactContextInitialized(ReactContext context); + } + public abstract DevSupportManager getDevSupportManager(); /** @@ -118,6 +130,11 @@ public abstract void onResume( public abstract List createAllViewManagers( ReactApplicationContext catalystApplicationContext); + /** + * Add a listener to be notified of react instance events. + */ + public abstract void addReactInstanceEventListener(ReactInstanceEventListener listener); + @VisibleForTesting public abstract @Nullable ReactContext getCurrentReactContext(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index c97a55b067fe..1eecb403269b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -12,10 +12,11 @@ import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; import android.app.Activity; -import android.app.Application; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -95,6 +96,8 @@ private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl; private String mSourceUrl; private @Nullable Activity mCurrentActivity; + private final Collection mReactInstanceEventListeners = + new ConcurrentLinkedQueue<>(); private volatile boolean mHasStartedCreatingInitialContext = false; private final UIImplementationProvider mUIImplementationProvider; @@ -406,6 +409,7 @@ public void onDestroy() { mCurrentReactContext = null; mHasStartedCreatingInitialContext = false; } + mCurrentActivity = null; } @Override @@ -482,6 +486,11 @@ public List createAllViewManagers( } } + @Override + public void addReactInstanceEventListener(ReactInstanceEventListener listener) { + mReactInstanceEventListeners.add(listener); + } + @VisibleForTesting @Override public @Nullable ReactContext getCurrentReactContext() { @@ -535,6 +544,10 @@ private void setupReactContext(ReactApplicationContext reactContext) { for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } + + for (ReactInstanceEventListener listener : mReactInstanceEventListeners) { + listener.onReactContextInitialized(reactContext); + } } private void attachMeasuredRootViewToInstance( diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index ca2d40c73434..e1632401f460 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -146,7 +146,6 @@ public void onResume(@Nullable Activity activity) { */ public void onPause() { UiThreadUtil.assertOnUiThread(); - mCurrentActivity = null; for (LifecycleEventListener listener : mLifecycleEventListeners) { listener.onHostPause(); } @@ -163,6 +162,7 @@ public void onDestroy() { if (mCatalystInstance != null) { mCatalystInstance.destroy(); } + mCurrentActivity = null; } /** @@ -239,4 +239,13 @@ public boolean startActivityForResult(Intent intent, int code, Bundle bundle) { mCurrentActivity.startActivityForResult(intent, code, bundle); return true; } + + /** + * Get the activity to which this context is currently attached, or {@code null} if not attached. + * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE + * MEMORY LEAKS. + */ + /* package */ @Nullable Activity getCurrentActivity() { + return mCurrentActivity; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java index 4d0470f46b86..6a73f73ccb25 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java @@ -9,6 +9,10 @@ package com.facebook.react.bridge; +import javax.annotation.Nullable; + +import android.app.Activity; + /** * Base class for Catalyst native modules that require access to the {@link ReactContext} * instance. @@ -27,4 +31,17 @@ public ReactContextBaseJavaModule(ReactApplicationContext reactContext) { protected final ReactApplicationContext getReactApplicationContext() { return mReactApplicationContext; } + + /** + * Get the activity to which this context is currently attached, or {@code null} if not attached. + * + * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE + * MEMORY LEAKS. + * + * For example, never store the value returned by this method in a member variable. Instead, call + * this method whenever you actually need the Activity and make sure to check for {@code null}. + */ + protected @Nullable final Activity getCurrentActivity() { + return mReactApplicationContext.getCurrentActivity(); + } } From 6d29f710e1e22e9a6a605b0b64713d85313f074a Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 2 Dec 2015 09:33:50 -0800 Subject: [PATCH 0212/1411] Fixed TextInput on iOS 8 and earlier Summary: public The scrolling fix I added to RCTTextView doesn't work on iOS 8 because the underlying UITextField doesn't resize correctly, which breaks text input functionality. This diff fixes it. Reviewed By: tadeuzagallo Differential Revision: D2712618 fb-gh-sync-id: 1d0282df3a16f1cb6ddf9d005d640738bb1b5659 --- Libraries/Text/RCTTextView.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 9a33f7e4696f..eda22a421e0c 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -201,10 +201,10 @@ - (void)updateFrames - (void)updateContentSize { - _textView.scrollEnabled = YES; - _scrollView.contentSize = _textView.contentSize; - _textView.frame = (CGRect){CGPointZero, _scrollView.contentSize}; - _textView.scrollEnabled = NO; + CGSize size = (CGSize){_scrollView.frame.size.width, INFINITY}; + size.height = [_textView sizeThatFits:size].height; + _scrollView.contentSize = size; + _textView.frame = (CGRect){CGPointZero, size}; } - (void)updatePlaceholder @@ -462,7 +462,8 @@ - (void)reactDidMakeFirstResponder - (BOOL)resignFirstResponder { - return [super resignFirstResponder] && [_textView resignFirstResponder]; + [super resignFirstResponder]; + return [_textView resignFirstResponder]; } - (void)layoutSubviews From 0779dd1e87f6c0c9481f4063e72aafe7805383c6 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Wed, 2 Dec 2015 10:50:09 -0800 Subject: [PATCH 0213/1411] Open source the Android NetInfo module Reviewed By: mkonicek Differential Revision: D2703432 fb-gh-sync-id: 4a85844f1734ec433df543c89f0fdd56fe5db13c --- Examples/UIExplorer/NetInfoExample.android.js | 158 ++++++++++++++++++ ...etInfoExample.js => NetInfoExample.ios.js} | 0 Examples/UIExplorer/UIExplorerList.android.js | 1 + Examples/UIExplorer/UIExplorerList.ios.js | 2 +- .../android/app/src/main/AndroidManifest.xml | 1 + Libraries/Network/NetInfo.js | 52 +++--- Libraries/Network/RCTNetInfo.m | 4 +- .../modules/netinfo/ConnectivityModule.java | 154 +++++++++++++++++ .../react/shell/MainReactPackage.java | 2 + 9 files changed, 347 insertions(+), 27 deletions(-) create mode 100644 Examples/UIExplorer/NetInfoExample.android.js rename Examples/UIExplorer/{NetInfoExample.js => NetInfoExample.ios.js} (100%) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java diff --git a/Examples/UIExplorer/NetInfoExample.android.js b/Examples/UIExplorer/NetInfoExample.android.js new file mode 100644 index 000000000000..41d77232195f --- /dev/null +++ b/Examples/UIExplorer/NetInfoExample.android.js @@ -0,0 +1,158 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const { + NetInfo, // requires android.permission.ACCESS_NETWORK_STATE + Text, + View +} = React; +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); + +const ConnectionSubscription = React.createClass({ + getInitialState() { + return { + connectionHistory: [], + }; + }, + componentDidMount: function() { + NetInfo.addEventListener( + 'change', + this._handleConnectionChange + ); + }, + componentWillUnmount: function() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionChange + ); + }, + _handleConnectionChange: function(netInfo) { + var connectionHistory = this.state.connectionHistory.slice(); + connectionHistory.push(netInfo); + this.setState({ + connectionHistory, + }); + }, + render() { + return ( + {JSON.stringify(this.state.connectionHistory)} + ); + } +}); + +const ConnectionCurrent = React.createClass({ + getInitialState() { + return { + netInfo: null, + }; + }, + componentDidMount: function() { + NetInfo.addEventListener( + 'change', + this._handleConnectionChange + ); + NetInfo.fetch().done( + (netInfo) => { this.setState({netInfo}); } + ); + }, + componentWillUnmount: function() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionChange + ); + }, + _handleConnectionChange: function(netInfo) { + this.setState({ + netInfo, + }); + }, + render() { + return ( + {JSON.stringify(this.state.netInfo)} + ); + } +}); + +const IsConnected = React.createClass({ + getInitialState() { + return { + isConnected: null, + }; + }, + componentDidMount: function() { + NetInfo.isConnected.addEventListener( + 'change', + this._handleConnectivityChange + ); + NetInfo.isConnected.fetch().done( + (isConnected) => { this.setState({isConnected}); } + ); + }, + componentWillUnmount: function() { + NetInfo.isConnected.removeEventListener( + 'change', + this._handleConnectivityChange + ); + }, + _handleConnectivityChange: function(isConnected) { + this.setState({ + isConnected, + }); + }, + render() { + return ( + {this.state.isConnected ? 'Online' : 'Offline'} + ); + } +}); + +const NetInfoExample = React.createClass({ + statics: { + title: '', + description: 'Monitor network status.' + }, + + getInitialState() { + return { + isMetered: null, + }; + }, + render() { + return ( + + Is Connected: + Current Connection Type: + Connection History: + + + Click to see if connection is metered: {this.state.isMetered} + + + + ); + }, + isConnectionMetered: function() { + NetInfo.isConnectionMetered((isConnectionMetered) => { + this.setState({ + isMetered: isConnectionMetered ? 'Is Metered' : 'Is Not Metered', + }); + }); + } +}); + +module.exports = NetInfoExample; diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.ios.js similarity index 100% rename from Examples/UIExplorer/NetInfoExample.js rename to Examples/UIExplorer/NetInfoExample.ios.js diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 7d57332c2318..b3b0fc4d50ff 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -43,6 +43,7 @@ var APIS = [ require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), require('./LayoutExample'), + require('./NetInfoExample.android'), require('./PanResponderExample'), require('./PointerEventsExample'), require('./TimerExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 940bd79ba42e..2f937cbeb6be 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -68,7 +68,7 @@ var APIS = [ require('./CameraRollExample.ios'), require('./GeolocationExample'), require('./LayoutExample'), - require('./NetInfoExample'), + require('./NetInfoExample.ios'), require('./PanResponderExample'), require('./PointerEventsExample'), require('./PushNotificationIOSExample'), diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml index b69776f9a5d4..40bbd20f538f 100644 --- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml +++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.facebook.react.uiapp" > + ; + +var _subscriptions = new Map(); + +if (Platform.OS === 'ios') { + var _isConnected = function( + reachability: ReachabilityStateIOS + ): bool { + return reachability !== 'none' && + reachability !== 'unknown'; + }; +} else if (Platform.OS === 'android') { + var _isConnected = function( + connectionType: ConnectivityStateAndroid + ): bool { + return connectionType !== 'NONE' && connectionType !== 'UNKNOWN'; + }; +} + +var _isConnectedSubscriptions = new Map(); + /** * NetInfo exposes info about online/offline status * @@ -84,6 +104,10 @@ type ConnectivityStateAndroid = $Enum<{ * * ### Android * + * To request network info, you need to add the following line to your + * app's `AndroidManifest.xml`: + * + * `` * Asynchronously determine if the device is connected and details about that connection. * * Android Connectivity Types @@ -135,35 +159,15 @@ type ConnectivityStateAndroid = $Enum<{ * ); * ``` */ - -var _subscriptions = new Map(); - -if (Platform.OS === 'ios') { - var _isConnected = function( - reachability: ReachabilityStateIOS - ): bool { - return reachability !== 'none' && - reachability !== 'unknown'; - }; -} else if (Platform.OS === 'android') { - var _isConnected = function( - connectionType: ConnectivityStateAndroid - ): bool { - return connectionType !== 'NONE' && connectionType !== 'UNKNOWN'; - }; -} - -var _isConnectedSubscriptions = new Map(); - var NetInfo = { addEventListener: function ( eventName: ChangeEventName, handler: Function ): void { var listener = RCTDeviceEventEmitter.addListener( - DEVICE_REACHABILITY_EVENT, + DEVICE_CONNECTIVITY_EVENT, (appStateData) => { - handler(appStateData.network_reachability); + handler(appStateData.network_info); } ); _subscriptions.set(handler, listener); @@ -183,7 +187,7 @@ var NetInfo = { fetch: function(): Promise { return new Promise((resolve, reject) => { - RCTNetInfo.getCurrentReachability( + RCTNetInfo.getCurrentConnectivity( function(resp) { resolve(resp.network_info); }, diff --git a/Libraries/Network/RCTNetInfo.m b/Libraries/Network/RCTNetInfo.m index 5aab64432488..78e1c3ab732d 100644 --- a/Libraries/Network/RCTNetInfo.m +++ b/Libraries/Network/RCTNetInfo.m @@ -51,7 +51,7 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC if (![status isEqualToString:self->_status]) { self->_status = status; - [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkDidChange" + [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkStatusDidChange" body:@{@"network_info": status}]; } } @@ -87,7 +87,7 @@ - (void)dealloc #pragma mark - Public API // TODO: remove error callback - not needed except by Subscribable interface -RCT_EXPORT_METHOD(getCurrentReachability:(RCTResponseSenderBlock)getSuccess +RCT_EXPORT_METHOD(getCurrentConnectivity:(RCTResponseSenderBlock)getSuccess withErrorCallback:(__unused RCTResponseSenderBlock)getError) { getSuccess(@[@{@"network_info": _status}]); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java new file mode 100644 index 000000000000..00414d86a688 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java @@ -0,0 +1,154 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.modules.netinfo; + +import javax.annotation.Nullable; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.support.v4.net.ConnectivityManagerCompat; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; + +import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; + +/** + * Module that monitors and provides information about the connectivity state of the device. + */ +public class ConnectivityModule extends ReactContextBaseJavaModule + implements LifecycleEventListener { + + private static final String CONNECTION_TYPE_NONE = "NONE"; + private static final String CONNECTION_TYPE_UNKNOWN = "UNKNOWN"; + + private final ConnectivityManager mConnectivityManager; + private final ConnectivityManagerCompat mConnectivityManagerCompat; + + private String mConnectivity; + private @Nullable ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver; + + public ConnectivityModule(ReactApplicationContext reactContext) { + super(reactContext); + mConnectivityManager = + (ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityManagerCompat = new ConnectivityManagerCompat(); + mConnectivity = ""; + } + + @Override + public void onHostResume() { + maybeRegisterReceiver(); + updateAndSendConnectionType(); + } + + @Override + public void onHostPause() { + maybeUnregisterReceiver(); + } + + @Override + public void onHostDestroy() { + } + + @Override + public void initialize() { + getReactApplicationContext().addLifecycleEventListener(this); + maybeRegisterReceiver(); + updateAndSendConnectionType(); + } + + @Override + public void onCatalystInstanceDestroy() { + maybeUnregisterReceiver(); + } + + @Override + public String getName() { + return "NetInfo"; + } + + @ReactMethod + public void getCurrentConnectivity(Callback successCallback, Callback errorCallback) { + successCallback.invoke(createConnectivityEventMap()); + } + + @ReactMethod + public void isConnectionMetered(Callback successCallback) { + successCallback.invoke(mConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager)); + } + + private void maybeRegisterReceiver() { + if (mConnectivityBroadcastReceiver != null) { + return; + } + mConnectivityBroadcastReceiver = new ConnectivityBroadcastReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + getReactApplicationContext().registerReceiver(mConnectivityBroadcastReceiver, filter); + } + + private void maybeUnregisterReceiver() { + if (mConnectivityBroadcastReceiver == null) { + return; + } + getReactApplicationContext().unregisterReceiver(mConnectivityBroadcastReceiver); + mConnectivityBroadcastReceiver = null; + mConnectivity = ""; + } + + private void updateAndSendConnectionType() { + String currentConnectivity = getCurrentConnectionType(); + // It is possible to get multiple broadcasts for the same connectivity change, so we only + // update and send an event when the connectivity has indeed changed. + if (!currentConnectivity.equalsIgnoreCase(mConnectivity)) { + mConnectivity = currentConnectivity; + sendConnectivityChangedEvent(); + } + } + + private String getCurrentConnectionType() { + NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected()) { + return CONNECTION_TYPE_NONE; + } else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) { + return networkInfo.getTypeName().toUpperCase(); + } else { + return CONNECTION_TYPE_UNKNOWN; + } + } + private void sendConnectivityChangedEvent() { + getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) + .emit("networkStatusDidChange", createConnectivityEventMap()); + } + + private WritableMap createConnectivityEventMap() { + WritableMap event = new WritableNativeMap(); + event.putString("network_info", mConnectivity); + return event; + } + + /** + * Class that receives intents whenever the connection type changes. + * NB: It is possible on some devices to receive certain connection type changes multiple times. + */ + private class ConnectivityBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + updateAndSendConnectionType(); + } + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index a0e90db20613..bf91bade6ccd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -20,6 +20,7 @@ import com.facebook.react.modules.fresco.FrescoModule; import com.facebook.react.modules.intent.IntentModule; import com.facebook.react.modules.location.LocationModule; +import com.facebook.react.modules.netinfo.ConnectivityModule; import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.toast.ToastModule; @@ -54,6 +55,7 @@ public List createNativeModules(ReactApplicationContext reactConte new IntentModule(reactContext), new LocationModule(reactContext), new NetworkingModule(reactContext), + new ConnectivityModule(reactContext), new WebSocketModule(reactContext), new ToastModule(reactContext)); } From 0058b0806bf64f4f1c1277bca46198399b67c8ba Mon Sep 17 00:00:00 2001 From: Wenjing Wang Date: Wed, 2 Dec 2015 11:23:22 -0800 Subject: [PATCH 0214/1411] propTypes could be undefined Summary: public This causes a warning since propType not exist Reviewed By: vjeux Differential Revision: D2713358 fb-gh-sync-id: 58406d1dc969e6f1d40bee958c28cc87036b30c2 --- Libraries/Animated/src/AnimatedImplementation.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index b8b3aee77b96..3039f887b47c 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -1094,6 +1094,10 @@ function createAnimatedComponent(Component: any): any { } AnimatedComponent.propTypes = { style: function(props, propName, componentName) { + if (!Component.propTypes) { + return; + } + for (var key in ViewStylePropTypes) { if (!Component.propTypes[key] && props[key] !== undefined) { console.error( From 2b7d65fdc9cb4ebe41df2ff184a771a7a031409d Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Thu, 3 Dec 2015 08:27:19 +0800 Subject: [PATCH 0215/1411] remove packager.js way --- docs/LinuxWindowsSupport.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/LinuxWindowsSupport.md b/docs/LinuxWindowsSupport.md index 2f6d59bcdbce..18c4ea9db68c 100644 --- a/docs/LinuxWindowsSupport.md +++ b/docs/LinuxWindowsSupport.md @@ -19,12 +19,6 @@ As of **version 0.14** Android development with React native is mostly possible On Windows the packager won't be started automatically when you run `react-native run-android`. You can start it manually using: - #For React Native version < 0.14 - cd MyAwesomeApp - node node_modules/react-native/packager/packager.js - - - #For React Native version >= 0.14 (which had removed packager.js) cd MyAwesomeApp react-native start From d691091530c925cbfe528825b391ec44a4bbd112 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Thu, 3 Dec 2015 07:47:11 +0530 Subject: [PATCH 0216/1411] Fix typo in permalink --- docs/AndroidBuildingFromSource.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index 303c43dd26bc..bf3d8289b930 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -3,7 +3,7 @@ id: android-building-from-source title: Building React Native from source layout: docs category: Guides (Android) -permalink: docs/android-building-from-source +permalink: docs/android-building-from-source.html next: activityindicatorios --- From 1c57291faae4cd13a4686dd18cb59c4fb4d40560 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 2 Dec 2015 18:57:20 -0800 Subject: [PATCH 0217/1411] RN: Support Multiple Packager Servers Summary: Adds support for multiple packager servers, which could be used to hot-reload dependencies in response to a change in the `cacheVersion`. Currently, there is no way to: - Create multiple `ReactPackager` servers. - Instantiate more than one `FileWatcher` constructor (due to the "single instance" invariant). public Reviewed By: martinbigio Differential Revision: D2713455 fb-gh-sync-id: 9be0f0cb2b846baf088d0cf14650cc8b9e950815 --- packager/react-packager/index.js | 1 + packager/react-packager/src/FileWatcher/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index e1a294f4097a..5b28e7741a2b 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -16,6 +16,7 @@ var debug = require('debug'); var omit = require('underscore').omit; var Activity = require('./src/Activity'); +exports.createServer = createServer; exports.middleware = function(options) { var server = createServer(options); return server.processRequest.bind(server); diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index d9ed7f32fd25..e32c36963ae5 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -72,6 +72,7 @@ class FileWatcher extends EventEmitter { } end() { + inited = false; return this._loading.then( (watchers) => watchers.map( watcher => Promise.denodeify(watcher.close).call(watcher) From 8e5585839728320e8ec259b0d03e0c329247cd17 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Thu, 3 Dec 2015 03:19:45 -0800 Subject: [PATCH 0218/1411] Precompute whether modules respond to batch notification methods Summary: public Looping through every `RCTModuleData` to check whether the module responds to `-batchDidComplete` or `-partialBatchDidFlush` is unnecessarily expensive. We can cache the answer at the time that the module instance is actually initialized. Reviewed By: tadeuzagallo Differential Revision: D2717594 fb-gh-sync-id: 274a59ec2d6014ce18c93404ce6b9940c1dc9c32 --- React/Base/RCTBatchedBridge.m | 4 ++-- React/Base/RCTModuleData.h | 11 +++++++++++ React/Base/RCTModuleData.m | 10 ++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index bbb8fe24a465..bb6aff8e4fee 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -804,7 +804,7 @@ - (void)handleBuffer:(NSArray *)buffer - (void)partialBatchDidFlush { for (RCTModuleData *moduleData in _moduleDataByID) { - if (moduleData.hasInstance && [moduleData.instance respondsToSelector:@selector(partialBatchDidFlush)]) { + if (moduleData.implementsPartialBatchDidFlush) { [self dispatchBlock:^{ [moduleData.instance partialBatchDidFlush]; } queue:moduleData.methodQueue]; @@ -816,7 +816,7 @@ - (void)batchDidComplete { // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? for (RCTModuleData *moduleData in _moduleDataByID) { - if (moduleData.hasInstance && [moduleData.instance respondsToSelector:@selector(batchDidComplete)]) { + if (moduleData.implementsBatchDidComplete) { [self dispatchBlock:^{ [moduleData.instance batchDidComplete]; } queue:moduleData.methodQueue]; diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index 540059842035..79752ba292ab 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -63,4 +63,15 @@ */ @property (nonatomic, copy, readonly) NSArray *config; +/** + * Whether the receiver has a valid `instance` which implements -batchDidComplete. + */ +@property (nonatomic, assign, readonly) BOOL implementsBatchDidComplete; + +/** + * Whether the receiver has a valid `instance` which implements + * -partialBatchDidFlush. + */ +@property (nonatomic, assign, readonly) BOOL implementsPartialBatchDidFlush; + @end diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index 11603850f73b..011159db829e 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -39,6 +39,8 @@ - (instancetype)initWithModuleInstance:(id)instance if ((self = [super init])) { _instance = instance; _moduleClass = [instance class]; + + [self cacheImplementedSelectors]; } return self; } @@ -62,10 +64,18 @@ - (BOOL)hasInstance // Initialize queue [self methodQueue]; + + [self cacheImplementedSelectors]; } return _instance; } +- (void)cacheImplementedSelectors +{ + _implementsBatchDidComplete = [_instance respondsToSelector:@selector(batchDidComplete)]; + _implementsPartialBatchDidFlush = [_instance respondsToSelector:@selector(partialBatchDidFlush)]; +} + - (void)setBridgeForInstance:(RCTBridge *)bridge { if ([_instance respondsToSelector:@selector(bridge)]) { From 1a748f5ed6c291888494addefa64adac38df2901 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 3 Dec 2015 03:26:08 -0800 Subject: [PATCH 0219/1411] Fix profile build Reviewed By: jspahrsummers Differential Revision: D2717604 fb-gh-sync-id: 9817bf743de0d6b5bd22f12ae50a8e1f99c1c0c3 --- React/Base/RCTConvert.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index f5f6033432ce..b2c033b38dbc 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -172,7 +172,7 @@ + (NSDate *)NSDate:(id)json } if ([json isKindOfClass:[NSNumber class]]) { NSArray *allValues = mapping.allValues; - if ([mapping.allValues containsObject:json] || [json isEqual:defaultValue]) { + if ([allValues containsObject:json] || [json isEqual:defaultValue]) { return json; } RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues); From a68c731aca86a9dfdfda678776ce4f23e463fcf5 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 3 Dec 2015 03:55:14 -0800 Subject: [PATCH 0220/1411] Android: Accept Throwables in Promise.reject() Summary: public Fixes #4309 This adds the possibility to reject `Promise` instances with `Throwable`s in java, instead of strings. For now, it only reads the message, but we can add more features on top of this, e.g. forwarding the error stack. Reviewed By: andreicoman11 Differential Revision: D2708192 fb-gh-sync-id: ca5ff584eca29370a9f9b780fa9825b17863a7e9 --- .../src/main/java/com/facebook/react/bridge/Promise.java | 1 + .../src/main/java/com/facebook/react/bridge/PromiseImpl.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java index c19907af83cd..92ce60699ce8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java @@ -18,5 +18,6 @@ */ public interface Promise { void resolve(Object value); + void reject(Throwable reason); void reject(String reason); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java index 688b081028e8..d6e25b694fb9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java @@ -30,6 +30,11 @@ public void resolve(Object value) { } } + @Override + public void reject(Throwable reason) { + reject(reason.getMessage()); + } + @Override public void reject(String reason) { if (mReject != null) { From c8eed6a3619ac45f129da426b4cc2d01a877d923 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Thu, 3 Dec 2015 04:38:07 -0800 Subject: [PATCH 0221/1411] RTL support added for ToolbarAndroid via "rtl" property Summary: Closes https://github.com/facebook/react-native/pull/4378 Reviewed By: svcscm Differential Revision: D2715052 Pulled By: mkonicek fb-gh-sync-id: e24f5db230cd7329911797794e5ef150f6195c4a --- .../ToolbarAndroid/ToolbarAndroid.android.js | 12 ++++++++++++ .../react/views/toolbar/ReactToolbarManager.java | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js index eb904d867af9..2658361a9403 100644 --- a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js +++ b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js @@ -125,6 +125,17 @@ var ToolbarAndroid = React.createClass({ * Sets the toolbar title color. */ titleColor: ReactPropTypes.string, + /** + * Used to set the toolbar direction to RTL. + * In addition to this property you need to add + * + * android:supportsRtl="true" + * + * to your application AndroidManifest.xml and then call + * `setLayoutDirection(LayoutDirection.RTL)` in your MainActivity + * `onCreate` method. + */ + rtl: ReactPropTypes.bool, /** * Used to locate this view in end-to-end tests. */ @@ -180,6 +191,7 @@ var toolbarAttributes = { logo: true, navIcon: true, overflowIcon: true, + rtl: true, subtitle: true, subtitleColor: true, title: true, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java index f55d6d8694c9..36aac87e73ef 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java @@ -18,6 +18,7 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.os.SystemClock; +import android.util.LayoutDirection; import android.view.MenuItem; import android.view.View; @@ -64,6 +65,11 @@ public void setOverflowIcon(ReactToolbar view, @Nullable ReadableMap overflowIco view.setOverflowIconSource(overflowIcon); } + @ReactProp(name = "rtl") + public void setRtl(ReactToolbar view, boolean rtl) { + view.setLayoutDirection(rtl ? LayoutDirection.LTR : LayoutDirection.RTL); + } + @ReactProp(name = "subtitle") public void setSubtitle(ReactToolbar view, @Nullable String subtitle) { view.setSubtitle(subtitle); From 5ad7dd3cac1a827beec81991088834b31279e996 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 3 Dec 2015 05:49:00 -0800 Subject: [PATCH 0222/1411] pre add new systrace TRACE_TAG_REACT_VIEW Reviewed By: foghina Differential Revision: D2717692 fb-gh-sync-id: 572b9286bd5be9f15ba3295e29770c4d33ee7590 --- ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java index 042a1668ee0d..a3d7699ce7d4 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java +++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java @@ -21,6 +21,7 @@ public class Systrace { public static final long TRACE_TAG_REACT_JAVA_BRIDGE = 0L; public static final long TRACE_TAG_REACT_FRESCO = 0L; public static final long TRACE_TAG_REACT_APPS = 0L; + public static final long TRACE_TAG_REACT_VIEW = 0L; public enum EventScope { THREAD('t'), From 67bf0f1a70aa6efc667a6fe4499ae7c2212332ff Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 3 Dec 2015 06:07:23 -0800 Subject: [PATCH 0223/1411] Avoid interrupting scrolling in nested scrollviews Summary: When we nest scrollviews, the hack in handleCustomPan: will cause the scroll to stall and break. There's still some weird behaviour inside ScrollResponder.js but this does not seem the affect the scrollview's scrolling. public Reviewed By: fionaf, jingc, majak Differential Revision: D2713639 fb-gh-sync-id: ad898ead62415bc14c91bc84fdfdb8c0fbb32b06 --- React/Views/RCTScrollView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index f1ef89572119..5ae7fb05a6b5 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -182,7 +182,7 @@ - (BOOL)_shouldDisableScrollInteraction - (void)handleCustomPan:(__unused UIPanGestureRecognizer *)sender { - if ([self _shouldDisableScrollInteraction]) { + if ([self _shouldDisableScrollInteraction] && ![[RCTUIManager JSResponder] isKindOfClass:[RCTScrollView class]]) { self.panGestureRecognizer.enabled = NO; self.panGestureRecognizer.enabled = YES; // TODO: If mid bounce, animate the scroll view to a non-bounced position From ff808fa4e0169f7a7aa9d51066b63fbc4d538249 Mon Sep 17 00:00:00 2001 From: sathis Date: Thu, 3 Dec 2015 19:52:02 +0530 Subject: [PATCH 0224/1411] Fix typos --- docs/AndroidBuildingFromSource.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index bf3d8289b930..b0fd0896a590 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -45,7 +45,7 @@ npm install --save github:facebook/react-native#master Alternatively, you can clone the repo to your `node_modules` directory and run `npm install` inside the cloned repo. -2. Add `gradle-download-task` as dependency in `andoid/build.gradle`: +2. Add `gradle-download-task` as dependency in `android/build.gradle`: ```gradle ... @@ -59,7 +59,7 @@ Alternatively, you can clone the repo to your `node_modules` directory and run ` ... ``` -3. Add the `:ReactAndroid` project in `andoid/settings.gradle`: +3. Add the `:ReactAndroid` project in `android/settings.gradle`: ```gradle ... From 5030cae757a520886c6378be0d5a46216e9bc4b7 Mon Sep 17 00:00:00 2001 From: Qiao Liang Date: Thu, 3 Dec 2015 05:59:57 -0800 Subject: [PATCH 0225/1411] add permission for android geolocation example Summary: screen shot 2015-12-03 at 3 38 02 pm Closes https://github.com/facebook/react-native/pull/4524 Reviewed By: svcscm Differential Revision: D2717843 Pulled By: mkonicek fb-gh-sync-id: 7828a7f28bb95be3d292d0ae3351c9d9678bb964 --- Examples/UIExplorer/android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml index 40bbd20f538f..abb275d7055d 100644 --- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml +++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + Date: Thu, 3 Dec 2015 07:09:09 -0800 Subject: [PATCH 0226/1411] Test PR 12 Summary: Testing the shipit bot Closes https://github.com/facebook/react-native/pull/4536 Reviewed By: svcscm Differential Revision: D2717972 Pulled By: mkonicek fb-gh-sync-id: 6cb1fe6a054d050d4bc9d693adda44381d6ad6e2 --- Examples/2048/Game2048.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 3bc49217b7a2..239d1dadeca4 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -279,7 +279,7 @@ var styles = StyleSheet.create({ backgroundColor: '#eeeecc', }, tile8: { - backgroundColor: '#ffbb88', + backgroundColor: '#ffbb87', }, tile16: { backgroundColor: '#ff9966', From 10c8a1c417ce1d91e19312fb75af404c52ad5a55 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Thu, 3 Dec 2015 07:02:33 -0800 Subject: [PATCH 0227/1411] fixed mardown formatting in NetInfo code to allow proper JS Docs Summary: Current docs look bad https://facebook.github.io/react-native/docs/netinfo.html#content Closes https://github.com/facebook/react-native/pull/4531 Reviewed By: svcscm Differential Revision: D2717974 Pulled By: androidtrunkagent fb-gh-sync-id: cd2cadfb782f8ce6a418ca7af5fcefb54504059b --- Libraries/Network/NetInfo.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 1414b363d01e..c2099c9e3eac 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -110,7 +110,8 @@ var _isConnectedSubscriptions = new Map(); * `` * Asynchronously determine if the device is connected and details about that connection. * - * Android Connectivity Types + * Android Connectivity Types. + * * - `NONE` - device is offline * - `BLUETOOTH` - The Bluetooth data connection. * - `DUMMY` - Dummy data connection. @@ -124,6 +125,7 @@ var _isConnectedSubscriptions = new Map(); * - `WIFI` - The WIFI data connection. * - `WIMAX` - The WiMAX data connection. * - `UNKNOWN` - Unknown data connection. + * * The rest ConnectivityStates are hidden by the Android API, but can be used if necessary. * * ### isConnectionMetered @@ -132,6 +134,7 @@ var _isConnectedSubscriptions = new Map(); * classified as metered when the user is sensitive to heavy data usage on that connection due to * monetary costs, data limitations or battery/performance issues. * + * ``` * NetInfo.isConnectionMetered((isConnectionMetered) => { * console.log('Connection is ' + (isConnectionMetered ? 'Metered' : 'Not Metered')); * }); From 87253ca722a47534b8fd33d38ae1dbe96e2c5dbe Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 3 Dec 2015 07:23:58 -0800 Subject: [PATCH 0228/1411] Append JS_require_ to require profile names Reviewed By: tadeuzagallo Differential Revision: D2717939 fb-gh-sync-id: 4648e240eebfb3a7bc1c5041d4f1fba8761264a9 --- packager/react-packager/src/Resolver/polyfills/require.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/Resolver/polyfills/require.js b/packager/react-packager/src/Resolver/polyfills/require.js index d7550ba1b9bb..ebaa8c406d75 100644 --- a/packager/react-packager/src/Resolver/polyfills/require.js +++ b/packager/react-packager/src/Resolver/polyfills/require.js @@ -54,7 +54,7 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && BridgeProfiling().profile(id); + __DEV__ && BridgeProfiling().profile('JS_require_' + id); // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js From 3313f769f5660857487bc9fc012bd51c80056bd3 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 3 Dec 2015 07:43:34 -0800 Subject: [PATCH 0229/1411] Use console.error() for the Touchable.js bug so we can capture it in production logs Reviewed By: javache Differential Revision: D2717874 fb-gh-sync-id: 7c2cac61fe4fbd2c6de6cf1d9059df9ac119f543 --- Libraries/Components/Touchable/Touchable.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 0493311c5c2d..9172739ebc47 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -572,14 +572,9 @@ var TouchableMixin = { var curState = this.state.touchable.touchState; if (curState !== States.RESPONDER_ACTIVE_PRESS_IN && curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) { - if (__DEV__) { - throw new Error( - 'Attempted to transition from state `' + curState + '` to `' + - States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. ' + - 'This is most likely due to `Touchable.longPressDelayTimeout` not ' + - 'being cancelled.' - ); - } + console.error('Attempted to transition from state `' + curState + '` to `' + + States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. This is ' + + 'most likely due to `Touchable.longPressDelayTimeout` not being cancelled.'); } else { this._receiveSignal(Signals.LONG_PRESS_DETECTED, e); } From a64ee7d8c5b717051f0659bf25ec38a8bd583d54 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Thu, 3 Dec 2015 08:08:37 -0800 Subject: [PATCH 0230/1411] Batch AsyncStorage.multiGet calls Reviewed By: javache Differential Revision: D2636553 fb-gh-sync-id: d6351b67c615d8c01c11c10e32321a9764c54c67 --- Libraries/Storage/AsyncStorage.js | 61 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js index 9135088f6163..eb41c8a736f5 100644 --- a/Libraries/Storage/AsyncStorage.js +++ b/Libraries/Storage/AsyncStorage.js @@ -33,6 +33,10 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncSQLiteStorage || RCTAsyn * method returns a `Promise` object. */ var AsyncStorage = { + _getRequests: ([]: Array), + _getKeys: ([]: Array), + _immediate: (null: ?number), + /** * Fetches `key` and passes the result to `callback`, along with an `Error` if * there is any. Returns a `Promise` object. @@ -164,6 +168,32 @@ var AsyncStorage = { * indicate which key caused the error. */ + /** Flushes any pending requests using a single multiget */ + flushGetRequests: function(): void { + const getRequests = this._getRequests; + const getKeys = this._getKeys; + + this._getRequests = []; + this._getKeys = []; + + RCTAsyncStorage.multiGet(getKeys, function(errors, result) { + // Even though the runtime complexity of this is theoretically worse vs if we used a map, + // it's much, much faster in practice for the data sets we deal with (we avoid + // allocating result pair arrays). This was heavily benchmarked. + const reqLength = getRequests.length; + for (let i = 0; i < reqLength; i++) { + const request = getRequests[i]; + const requestKeys = request.keys; + var requestResult = result.filter(function(resultPair) { + return requestKeys.indexOf(resultPair[0]) !== -1; + }); + + request.callback && request.callback(null, requestResult); + request.resolve && request.resolve(requestResult); + } + }); + }, + /** * multiGet invokes callback with an array of key-value pair arrays that * matches the input format of multiSet. Returns a `Promise` object. @@ -174,17 +204,30 @@ var AsyncStorage = { keys: Array, callback?: ?(errors: ?Array, result: ?Array>) => void ): Promise { - return new Promise((resolve, reject) => { - RCTAsyncStorage.multiGet(keys, function(errors, result) { - var error = convertErrors(errors); - callback && callback(error, result); - if (error) { - reject(error); - } else { - resolve(result); - } + if (!this._immediate) { + this._immediate = setImmediate(() => { + this._immediate = null; + this.flushGetRequests(); }); + } + + var getRequest = { + keys: keys, + callback: callback, + keyIndex: this._getKeys.length, + resolve: null, + reject: null, + }; + + var promiseResult = new Promise((resolve, reject) => { + getRequest.resolve = resolve; + getRequest.reject = reject; }); + + this._getRequests.push(getRequest); + this._getKeys.push.apply(this._getKeys, keys); + + return promiseResult; }, /** From 200e69e39f5509a6cd023867104e1db1f3d6295c Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Thu, 3 Dec 2015 10:48:03 -0800 Subject: [PATCH 0231/1411] [Docs] Website - Change port from 8080 to 8079 --- website/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/README.md b/website/README.md index 43d3debf7803..37afeadb9832 100644 --- a/website/README.md +++ b/website/README.md @@ -10,7 +10,7 @@ Then, run the server via ``` npm start -open http://localhost:8080/react-native/index.html +open http://localhost:8079/react-native/index.html ``` Anytime you change the contents, just refresh the page and it's going to be updated. From dd9c1e16ee372f419e9c4e5757dc25cb3e2dcd66 Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Thu, 3 Dec 2015 10:16:03 -0800 Subject: [PATCH 0232/1411] Add string as possible propType for 'systemIcon' Summary: Fixes #2361 Closes https://github.com/facebook/react-native/pull/4523 Reviewed By: svcscm Differential Revision: D2718568 Pulled By: androidtrunkagent fb-gh-sync-id: a15c94acaac899dfdaeb397c8c764053d1430854 --- Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index f220249c76c7..20e0cf624066 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -52,7 +52,10 @@ var TabBarItemIOS = React.createClass({ /** * A custom icon for the tab. It is ignored when a system icon is defined. */ - icon: Image.propTypes.source, + icon: React.PropTypes.oneOfType([ + React.PropTypes.string, + Image.propTypes.source, + ]), /** * A custom icon when the tab is selected. It is ignored when a system * icon is defined. If left empty, the icon will be tinted in blue. From ecdc3429cd25c0d305627897a076910f33368bb8 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 3 Dec 2015 09:24:01 -0800 Subject: [PATCH 0233/1411] Fork v4 support library NestedScrollView to fix fling bug Reviewed By: oli, lexs Differential Revision: D2707733 fb-gh-sync-id: c9b375a6aa1010d60d21ca7500e862a6ff91a49b --- .../facebook/react/views/scroll/ReactScrollViewHelper.java | 2 +- .../facebook/react/views/scroll/ReactScrollViewManager.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index 12f5606fc8a6..c8e7fa22e5cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -24,7 +24,7 @@ public class ReactScrollViewHelper { /** * Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}. */ - /* package */ static void emitScrollEvent(ViewGroup scrollView, int scrollX, int scrollY) { + public static void emitScrollEvent(ViewGroup scrollView, int scrollX, int scrollY) { View contentView = scrollView.getChildAt(0); ReactContext reactContext = (ReactContext) scrollView.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java index 2c3363f8ec66..6de7da6bcf3d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -81,6 +81,10 @@ public void scrollWithoutAnimationTo( @Override public @Nullable Map getExportedCustomDirectEventTypeConstants() { + return createExportedCustomDirectEventTypeConstants(); + } + + public static Map createExportedCustomDirectEventTypeConstants() { return MapBuilder.builder() .put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll")) .put("topScrollBeginDrag", MapBuilder.of("registrationName", "onScrollBeginDrag")) From 9a47ca1cc927d72b5db3795536e4c846c5997706 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 3 Dec 2015 10:33:03 -0800 Subject: [PATCH 0234/1411] Added addChildren() function as a more-optimal replacement for manageChildren for common use cases. Reviewed By: jspahrsummers Differential Revision: D2707930 fb-gh-sync-id: c44219bf9af943cad5b57f370656c1bcac732cd9 --- .../ReactNative/ReactNativeBaseComponent.js | 15 ++- .../ReactNative/ReactNativeDOMIDOperations.js | 1 + Libraries/ReactNative/ReactNativeMount.js | 10 ++ React/Modules/RCTUIManager.m | 101 +++++++++++++----- 4 files changed, 95 insertions(+), 32 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 266c7b30fbf6..c7b4b42a8e1c 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -104,11 +104,11 @@ ReactNativeBaseComponent.Mixin = { // no children - let's avoid calling out to the native bridge for a large // portion of the children. if (mountImages.length) { - var indexes = cachedIndexArray(mountImages.length); + // TODO: Pool these per platform view class. Reusing the `mountImages` // array would likely be a jit deopt. var createdTags = []; - for (var i = 0; i < mountImages.length; i++) { + for (var i = 0, l = mountImages.length; i < l; i++) { var mountImage = mountImages[i]; var childTag = mountImage.tag; var childID = mountImage.rootNodeID; @@ -122,8 +122,15 @@ ReactNativeBaseComponent.Mixin = { ); createdTags[i] = mountImage.tag; } - UIManager - .manageChildren(containerTag, null, null, createdTags, indexes, null); + + // Fast path for iOS + if (UIManager.addChildren) { + UIManager.addChildren(containerTag, createdTags); + return; + } + + var indexes = cachedIndexArray(mountImages.length); + UIManager.manageChildren(containerTag, null, null, createdTags, indexes, null); } }, diff --git a/Libraries/ReactNative/ReactNativeDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js index fbf90db50337..a3ef1cc36f2c 100644 --- a/Libraries/ReactNative/ReactNativeDOMIDOperations.js +++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js @@ -53,6 +53,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { (updates.addChildTags || (updates.addChildTags = [])).push(tag); } } + // Note this enumeration order will be different on V8! Move `byContainerTag` // to a sparse array as soon as we confirm there are not horrible perf // penalties. diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 1f6dab51b8a4..758fe1e5c174 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -188,6 +188,16 @@ var ReactNativeMount = { mountImage.rootNodeID, mountImage.tag ); + + // Fast path for iOS + if (UIManager.addChildren) { + UIManager.addChildren( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), + [mountImage.tag] + ); + return; + } + var addChildTags = [mountImage.tag]; var addAtIndices = [0]; UIManager.manageChildren( diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index c3c1ea58917b..96ed3aa4e9cb 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -720,6 +720,33 @@ - (void)_removeChildren:(NSArray> *)children removeAtIndices:removeAtIndices]; } +RCT_EXPORT_METHOD(addChildren:(nonnull NSNumber *)containerTag + reactTags:(NSNumberArray *)reactTags) +{ + RCTAddChildren(containerTag, reactTags, + (NSDictionary> *)_shadowViewRegistry); + + [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + + RCTAddChildren(containerTag, reactTags, + (NSDictionary> *)viewRegistry); + }]; +} + +static void RCTAddChildren(NSNumber *containerTag, + NSArray *reactTags, + NSDictionary> *registry) +{ + id container = registry[containerTag]; + NSInteger index = [container reactSubviews].count; + for (NSNumber *reactTag in reactTags) { + id view = registry[reactTag]; + if (view) { + [container insertReactSubview:view atIndex:index++]; + } + } +} + RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag moveFromIndices:(NSNumberArray *)moveFromIndices moveToIndices:(NSNumberArray *)moveToIndices @@ -754,39 +781,57 @@ - (void)_manageChildren:(NSNumber *)containerReactTag removeAtIndices:(NSArray *)removeAtIndices registry:(NSMutableDictionary> *)registry { - id container = registry[containerReactTag]; - RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count); - RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add"); + RCTAssert(moveFromIndices.count == moveToIndices.count, + @"moveFromIndices had size %tu, moveToIndices had size %tu", + moveFromIndices.count, moveToIndices.count); - // Removes (both permanent and temporary moves) are using "before" indices - NSArray> *permanentlyRemovedChildren = - [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; - NSArray> *temporarilyRemovedChildren = - [self _childrenToRemoveFromContainer:container atIndices:moveFromIndices]; - [self _removeChildren:permanentlyRemovedChildren fromContainer:container]; - [self _removeChildren:temporarilyRemovedChildren fromContainer:container]; + RCTAssert(addChildReactTags.count == addAtIndices.count, + @"addChildReactTags had size %tu, addAtIndices had size %tu", + addChildReactTags.count, addAtIndices.count); - [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++) { - destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index]; + id container = registry[containerReactTag]; + NSArray> *views = [container reactSubviews]; + + // Get indices to insert/remove + NSUInteger purgeCount = removeAtIndices.count; + NSUInteger moveCount = moveFromIndices.count; + NSUInteger insertCount = addAtIndices.count; + NSUInteger removeCount = purgeCount + moveCount; + NSMutableArray> *toRemove = removeCount ? [NSMutableArray new] : nil; + NSMutableDictionary *toInsert = insertCount ? [NSMutableDictionary new] : nil; + for (NSNumber *index in removeAtIndices) { + id view = views[index.integerValue]; + [toRemove addObject:view]; + RCTTraverseViewNodes(registry[view.reactTag], ^(id subview) { + RCTAssert(![subview isReactRootView], @"Root views should not be unregistered"); + if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) { + [(id)subview invalidate]; + } + [registry removeObjectForKey:subview.reactTag]; + [_bridgeTransactionListeners removeObject:subview]; + }); } - for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) { - id view = registry[addChildReactTags[index]]; - if (view) { - destinationsToChildrenToAdd[addAtIndices[index]] = view; - } + NSInteger i = 0; + for (NSNumber *index in moveFromIndices) { + id view = views[index.integerValue]; + [toRemove addObject:view]; + toInsert[moveToIndices[i]] = view; + i++; + } + i = 0; + for (NSNumber *reactTag in addChildReactTags) { + toInsert[addAtIndices[i]] = registry[reactTag]; + i++; + } + + // Remove old views + for (id view in toRemove) { + [container removeReactSubview:view]; } - NSArray *sortedIndices = - [destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)]; - for (NSNumber *reactIndex in sortedIndices) { - [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] - atIndex:reactIndex.integerValue]; + // Insert new views in ascending order + for (NSNumber *index in [toInsert.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + [container insertReactSubview:toInsert[index] atIndex:index.integerValue]; } } From d1029d1b9d7c432f7cdf2565d5f93529bdd818db Mon Sep 17 00:00:00 2001 From: moschan Date: Thu, 3 Dec 2015 10:53:40 -0800 Subject: [PATCH 0235/1411] fixed mardown formatting of ListView Summary: about renderRow and renderSeparator. insert a new line before description. Closes https://github.com/facebook/react-native/pull/4532 Reviewed By: svcscm Differential Revision: D2718764 Pulled By: androidtrunkagent fb-gh-sync-id: eeefd16617fcb5e5ca21f6fd0cf29d63cb3b1f1c --- Libraries/CustomComponents/ListView/ListView.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 5706ff0f91b2..fa29da5d87ee 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -116,6 +116,7 @@ var ListView = React.createClass({ dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, /** * (sectionID, rowID, adjacentRowHighlighted) => renderable + * * If provided, a renderable component to be rendered as the separator * below each row but not the last row if there is a section header below. * Take a sectionID and rowID of the row above and whether its adjacent row @@ -124,6 +125,7 @@ var ListView = React.createClass({ renderSeparator: PropTypes.func, /** * (rowData, sectionID, rowID, highlightRow) => renderable + * * Takes a data entry from the data source and its ids and should return * a renderable component to be rendered as the row. By default the data * is exactly what was put into the data source, but it's also possible to From 70f0dfc0e67d8f3489ab31deeec12524bfde57fb Mon Sep 17 00:00:00 2001 From: Dan Witte Date: Thu, 3 Dec 2015 16:04:34 -0800 Subject: [PATCH 0236/1411] revert D2707930 Reviewed By: fkgozali Differential Revision: D2720828 fb-gh-sync-id: 53113fb33150b42a7b597a7dfd04bb9885def029 --- .../ReactNative/ReactNativeBaseComponent.js | 15 +-- .../ReactNative/ReactNativeDOMIDOperations.js | 1 - Libraries/ReactNative/ReactNativeMount.js | 10 -- React/Modules/RCTUIManager.m | 101 +++++------------- 4 files changed, 32 insertions(+), 95 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index c7b4b42a8e1c..266c7b30fbf6 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -104,11 +104,11 @@ ReactNativeBaseComponent.Mixin = { // no children - let's avoid calling out to the native bridge for a large // portion of the children. if (mountImages.length) { - + var indexes = cachedIndexArray(mountImages.length); // TODO: Pool these per platform view class. Reusing the `mountImages` // array would likely be a jit deopt. var createdTags = []; - for (var i = 0, l = mountImages.length; i < l; i++) { + for (var i = 0; i < mountImages.length; i++) { var mountImage = mountImages[i]; var childTag = mountImage.tag; var childID = mountImage.rootNodeID; @@ -122,15 +122,8 @@ ReactNativeBaseComponent.Mixin = { ); createdTags[i] = mountImage.tag; } - - // Fast path for iOS - if (UIManager.addChildren) { - UIManager.addChildren(containerTag, createdTags); - return; - } - - var indexes = cachedIndexArray(mountImages.length); - UIManager.manageChildren(containerTag, null, null, createdTags, indexes, null); + UIManager + .manageChildren(containerTag, null, null, createdTags, indexes, null); } }, diff --git a/Libraries/ReactNative/ReactNativeDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js index a3ef1cc36f2c..fbf90db50337 100644 --- a/Libraries/ReactNative/ReactNativeDOMIDOperations.js +++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js @@ -53,7 +53,6 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { (updates.addChildTags || (updates.addChildTags = [])).push(tag); } } - // Note this enumeration order will be different on V8! Move `byContainerTag` // to a sparse array as soon as we confirm there are not horrible perf // penalties. diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 758fe1e5c174..1f6dab51b8a4 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -188,16 +188,6 @@ var ReactNativeMount = { mountImage.rootNodeID, mountImage.tag ); - - // Fast path for iOS - if (UIManager.addChildren) { - UIManager.addChildren( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), - [mountImage.tag] - ); - return; - } - var addChildTags = [mountImage.tag]; var addAtIndices = [0]; UIManager.manageChildren( diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 96ed3aa4e9cb..c3c1ea58917b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -720,33 +720,6 @@ - (void)_removeChildren:(NSArray> *)children removeAtIndices:removeAtIndices]; } -RCT_EXPORT_METHOD(addChildren:(nonnull NSNumber *)containerTag - reactTags:(NSNumberArray *)reactTags) -{ - RCTAddChildren(containerTag, reactTags, - (NSDictionary> *)_shadowViewRegistry); - - [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ - - RCTAddChildren(containerTag, reactTags, - (NSDictionary> *)viewRegistry); - }]; -} - -static void RCTAddChildren(NSNumber *containerTag, - NSArray *reactTags, - NSDictionary> *registry) -{ - id container = registry[containerTag]; - NSInteger index = [container reactSubviews].count; - for (NSNumber *reactTag in reactTags) { - id view = registry[reactTag]; - if (view) { - [container insertReactSubview:view atIndex:index++]; - } - } -} - RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag moveFromIndices:(NSNumberArray *)moveFromIndices moveToIndices:(NSNumberArray *)moveToIndices @@ -781,57 +754,39 @@ - (void)_manageChildren:(NSNumber *)containerReactTag removeAtIndices:(NSArray *)removeAtIndices registry:(NSMutableDictionary> *)registry { - RCTAssert(moveFromIndices.count == moveToIndices.count, - @"moveFromIndices had size %tu, moveToIndices had size %tu", - moveFromIndices.count, moveToIndices.count); + id container = registry[containerReactTag]; + RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count); + RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add"); - RCTAssert(addChildReactTags.count == addAtIndices.count, - @"addChildReactTags had size %tu, addAtIndices had size %tu", - addChildReactTags.count, addAtIndices.count); + // Removes (both permanent and temporary moves) are using "before" indices + NSArray> *permanentlyRemovedChildren = + [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; + NSArray> *temporarilyRemovedChildren = + [self _childrenToRemoveFromContainer:container atIndices:moveFromIndices]; + [self _removeChildren:permanentlyRemovedChildren fromContainer:container]; + [self _removeChildren:temporarilyRemovedChildren fromContainer:container]; - id container = registry[containerReactTag]; - NSArray> *views = [container reactSubviews]; - - // Get indices to insert/remove - NSUInteger purgeCount = removeAtIndices.count; - NSUInteger moveCount = moveFromIndices.count; - NSUInteger insertCount = addAtIndices.count; - NSUInteger removeCount = purgeCount + moveCount; - NSMutableArray> *toRemove = removeCount ? [NSMutableArray new] : nil; - NSMutableDictionary *toInsert = insertCount ? [NSMutableDictionary new] : nil; - for (NSNumber *index in removeAtIndices) { - id view = views[index.integerValue]; - [toRemove addObject:view]; - RCTTraverseViewNodes(registry[view.reactTag], ^(id subview) { - RCTAssert(![subview isReactRootView], @"Root views should not be unregistered"); - if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) { - [(id)subview invalidate]; - } - [registry removeObjectForKey:subview.reactTag]; - [_bridgeTransactionListeners removeObject:subview]; - }); - } - NSInteger i = 0; - for (NSNumber *index in moveFromIndices) { - id view = views[index.integerValue]; - [toRemove addObject:view]; - toInsert[moveToIndices[i]] = view; - i++; - } - i = 0; - for (NSNumber *reactTag in addChildReactTags) { - toInsert[addAtIndices[i]] = registry[reactTag]; - i++; - } + [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry]; + + // TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient - // Remove old views - for (id view in toRemove) { - [container removeReactSubview:view]; + // Figure out what to insert - merge temporary inserts and adds + NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary]; + for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) { + destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index]; + } + for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) { + id view = registry[addChildReactTags[index]]; + if (view) { + destinationsToChildrenToAdd[addAtIndices[index]] = view; + } } - // Insert new views in ascending order - for (NSNumber *index in [toInsert.allKeys sortedArrayUsingSelector:@selector(compare:)]) { - [container insertReactSubview:toInsert[index] atIndex:index.integerValue]; + NSArray *sortedIndices = + [destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)]; + for (NSNumber *reactIndex in sortedIndices) { + [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] + atIndex:reactIndex.integerValue]; } } From 4661e59d1a9639f0a5c62d2c0dc88a6a19591b21 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Fri, 4 Dec 2015 03:22:43 -0800 Subject: [PATCH 0237/1411] For clippedSubViews, pass down the opportunity for children views to update their clipping if they intersect at all Reviewed By: astreet Differential Revision: D2717903 fb-gh-sync-id: 8852a6d3ac3a329c4cad663e7f9f7093f9b6ae16 --- .../java/com/facebook/react/views/view/ReactViewGroup.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 9e2728db4e1e..478208fab215 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -304,8 +304,8 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa super.addViewInLayout(child, idx - clippedSoFar, sDefaultLayoutParam, true); invalidate(); needUpdateClippingRecursive = true; - } else if (intersects && !clippingRect.contains(sHelperRect)) { - // View is partially clipped. + } else if (intersects) { + // If there is any intersection we need to inform the child to update its clipping rect needUpdateClippingRecursive = true; } if (needUpdateClippingRecursive) { From c1849e7f4b0a36692ff45cf8cbcfd7b37aba74c4 Mon Sep 17 00:00:00 2001 From: Matt Revell Date: Fri, 4 Dec 2015 06:56:41 -0800 Subject: [PATCH 0238/1411] Fix an issue with unsafe picker access to setNativeProps. Summary: Fixing an issue where PickerIOS and DatePicker are being accessed unsafely, As a side effect we are also using ref callbacks as oppose to strings. Fixed after spotting an issue in our app where the picker is closed and the callback attempts to update native props for an item that no longer exists. Closes https://github.com/facebook/react-native/pull/3920 Reviewed By: svcscm Differential Revision: D2663634 Pulled By: nicklockwood fb-gh-sync-id: 813b32a038f59864401d5d3985c7ea32f5e13301 --- Libraries/Components/DatePicker/DatePickerIOS.ios.js | 8 +++----- Libraries/Picker/PickerIOS.ios.js | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index ede2500ee5c2..e42b6ac59956 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -22,8 +22,6 @@ var View = require('View'); var requireNativeComponent = require('requireNativeComponent'); -var DATEPICKER = 'datepicker'; - type DefaultProps = { mode: 'date' | 'time' | 'datetime'; }; @@ -108,8 +106,8 @@ var DatePickerIOS = React.createClass({ // certain values. In other words, the embedder of this component should // be the source of truth, not the native component. var propsTimeStamp = this.props.date.getTime(); - if (nativeTimeStamp !== propsTimeStamp) { - this.refs[DATEPICKER].setNativeProps({ + if (this._picker && nativeTimeStamp !== propsTimeStamp) { + this._picker.setNativeProps({ date: propsTimeStamp, }); } @@ -120,7 +118,7 @@ var DatePickerIOS = React.createClass({ return ( this._picker = picker } style={styles.datePickerIOS} date={props.date.getTime()} maximumDate={ diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index 5c245397d0b0..ffda1f57d174 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -21,8 +21,6 @@ var View = require('View'); var requireNativeComponent = require('requireNativeComponent'); -var PICKER = 'picker'; - var PickerIOS = React.createClass({ mixins: [NativeMethodsMixin], @@ -57,7 +55,7 @@ var PickerIOS = React.createClass({ return ( this._picker = picker } style={styles.pickerIOS} items={this.state.items} selectedIndex={this.state.selectedIndex} @@ -81,8 +79,8 @@ var PickerIOS = React.createClass({ // disallow/undo/mutate the selection of certain values. In other // words, the embedder of this component should be the source of // truth, not the native component. - if (this.state.selectedIndex !== event.nativeEvent.newIndex) { - this.refs[PICKER].setNativeProps({ + if (this._picker && this.state.selectedIndex !== event.nativeEvent.newIndex) { + this._picker.setNativeProps({ selectedIndex: this.state.selectedIndex }); } From dff8f53664009f49756c79f5f7c4982eaa39a5c3 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 4 Dec 2015 06:43:03 -0800 Subject: [PATCH 0239/1411] Support plugins that conform to ES6 modules Summary: ES6 modules export an object with a property called `default`. See Babel itself for how this is handled: https://github.com/babel/babel/commit/b5b7e346a04c99da8793e2c65cc3b3c7c720253d Closes https://github.com/facebook/react-native/pull/4513 Reviewed By: svcscm Differential Revision: D2715512 Pulled By: mkonicek fb-gh-sync-id: 40e5ea35adcdb66806a4895578d637cd72538619 --- packager/transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packager/transformer.js b/packager/transformer.js index 4700407c9368..70dde7702c28 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -48,6 +48,7 @@ function transform(src, filename, options) { // Only resolve the plugin if it's a string reference. if (typeof plugin[0] === 'string') { plugin[0] = require(`babel-plugin-${plugin[0]}`); + plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0]; } return plugin; }); From dde55c30eaf549f1bbab9812a469c42863ea082a Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 4 Dec 2015 06:59:14 -0800 Subject: [PATCH 0240/1411] This Fixes scroll to top and resigning first responder Reviewed By: tadeuzagallo Differential Revision: D2713488 fb-gh-sync-id: 99b2646f7bf8a3ee889bbb856c298beed6817321 --- Libraries/Text/RCTTextView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index eda22a421e0c..6c62006927a5 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -81,6 +81,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _textView.delegate = self; _scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; + _scrollView.scrollsToTop = NO; [_scrollView addSubview:_textView]; _previousSelectionRange = _textView.selectedTextRange; From c71811e49100ea12e08e609d90d74868f4b06d5b Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 4 Dec 2015 07:28:09 -0800 Subject: [PATCH 0241/1411] Added Circle CI integration to run Android unit tests Summary: A few caveats before accepting: - Do I need to squash commits? - Need to set up new Circle CI account connected to FB react-native repo - After that replace tokens and links to the new ones Setting up Integration tests should be straight forward next week https://circleci.com/docs/android Closes https://github.com/facebook/react-native/pull/4566 Reviewed By: svcscm Differential Revision: D2723119 Pulled By: androidtrunkagent fb-gh-sync-id: 2c4a46b206f15f36d94d1b10ff9e339f5182782b --- circle.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000000..51feb2c00a29 --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +machine: + node: + version: 5.1.0 + +dependencies: + pre: + - npm install -g npm@3.2 + +test: + override: + # gradle is flaky in CI envs, found a solution here http://stackoverflow.com/questions/28409608/gradle-assembledebug-and-predexdebug-fail-with-circleci + - TERM=dumb ./gradlew cleanTest test -PpreDexEnable=false -Pcom.android.build.threadPoolSize=1 -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xms512m -Xmx512m" -Dorg.gradle.daemon=false From f4c286f7ac0665edc3027e952d3b445703918c9c Mon Sep 17 00:00:00 2001 From: SangYeob Bono Yu Date: Fri, 4 Dec 2015 08:05:11 -0800 Subject: [PATCH 0242/1411] Add support for Universal Links Summary: Adds support for [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html). Closes https://github.com/facebook/react-native/pull/4109 Reviewed By: svcscm Differential Revision: D2658105 Pulled By: nicklockwood fb-gh-sync-id: 7d94564f64cda7d31c79cf8f4c450ed2387057be --- Libraries/LinkingIOS/LinkingIOS.js | 21 +++++++++++++++++---- Libraries/LinkingIOS/RCTLinkingManager.h | 4 ++++ Libraries/LinkingIOS/RCTLinkingManager.m | 17 ++++++++++++++++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js index 7ee6ace38ce0..1d186ccbf21c 100644 --- a/Libraries/LinkingIOS/LinkingIOS.js +++ b/Libraries/LinkingIOS/LinkingIOS.js @@ -43,9 +43,22 @@ var DEVICE_NOTIF_EVENT = 'openURL'; * execution you'll need to add the following lines to you `*AppDelegate.m`: * * ``` - * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { - * return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; + * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + * sourceApplication:(NSString *)sourceApplication annotation:(id)annotation + * { + * return [RCTLinkingManager application:application openURL:url + * sourceApplication:sourceApplication annotation:annotation]; * } + * + * // Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html). + * - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity + * restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler + * { + * return [RCTLinkingManager application:application + * continueUserActivity:userActivity + * restorationHandler:restorationHandler]; + * } + * * ``` * * And then on your React component you'll be able to listen to the events on @@ -71,7 +84,7 @@ var DEVICE_NOTIF_EVENT = 'openURL'; * LinkingIOS.openURL(url) * ``` * - * If you want to check if any installed app can handle a given URL beforehand you can call + * If you want to check if any installed app can handle a given URL beforehand, call * ``` * LinkingIOS.canOpenURL(url, (supported) => { * if (!supported) { @@ -130,7 +143,7 @@ class LinkingIOS { * Determine whether or not an installed app can handle a given URL. * The callback function will be called with `bool supported` as the only argument * - * NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key + * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key * inside `Info.plist`. */ static canOpenURL(url: string, callback: Function) { diff --git a/Libraries/LinkingIOS/RCTLinkingManager.h b/Libraries/LinkingIOS/RCTLinkingManager.h index caa3aa2a3a92..092ca990b8a1 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.h +++ b/Libraries/LinkingIOS/RCTLinkingManager.h @@ -18,4 +18,8 @@ sourceApplication:(NSString *)sourceApplication annotation:(id)annotation; ++ (BOOL)application:(UIApplication *)application +continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray *))restorationHandler; + @end diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 96c01ca2c47c..749d2c27ad30 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -54,6 +54,19 @@ + (BOOL)application:(UIApplication *)application return YES; } ++ (BOOL)application:(UIApplication *)application +continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray *))restorationHandler +{ + if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSDictionary *payload = @{@"url": userActivity.webpageURL.absoluteString}; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification + object:self + userInfo:payload]; + } + return YES; +} + - (void)handleOpenURLNotification:(NSNotification *)notification { [_bridge.eventDispatcher sendDeviceEventWithName:@"openURL" @@ -62,6 +75,7 @@ - (void)handleOpenURLNotification:(NSNotification *)notification RCT_EXPORT_METHOD(openURL:(NSURL *)URL) { + // TODO: we should really return success/failure via a callback here // Doesn't really matter what thread we call this on since it exits the app [RCTSharedApplication() openURL:URL]; } @@ -72,7 +86,8 @@ - (void)handleOpenURLNotification:(NSNotification *)notification if (RCTRunningInAppExtension()) { // Technically Today widgets can open urls, but supporting that would require // a reference to the NSExtensionContext - callback(@[@(NO)]); + callback(@[@NO]); + return; } // This can be expensive, so we deliberately don't call on main thread From 383d991c40d8d4402405f5a427584cc6abf3dd24 Mon Sep 17 00:00:00 2001 From: "glevi@fb.com" Date: Fri, 4 Dec 2015 11:52:05 -0800 Subject: [PATCH 0243/1411] Fix flow errors in fbobjc Reviewed By: jeffmo Differential Revision: D2724169 fb-gh-sync-id: d2736ce2550fe8bf45c18bce7d89a9cb5c98cb87 --- Libraries/Components/DatePicker/DatePickerIOS.ios.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index e42b6ac59956..3516ae35d003 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -36,6 +36,9 @@ type Event = Object; * source of truth. */ var DatePickerIOS = React.createClass({ + // TOOD: Put a better type for _picker + _picker: (undefined: ?$FlowFixMe), + mixins: [NativeMethodsMixin], propTypes: { From fa884ee5e656b8cb03d000d49f1b1456a7b21784 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Fri, 4 Dec 2015 20:42:19 -0800 Subject: [PATCH 0244/1411] Fix Y-coord on touches Summary: public D2670028 updated the x/y positions of touch events to be relative to the window, but measure still uses the location on the screen. Therefore, in Touchable.js, we were seeing taps get inproperly invalidated because they were erroneously considered outside of the touch rect. This diff moves back to the old version of pageX/Y on touch events that's compatible with the current version of measure. Reviewed By: nicklockwood Differential Revision: D2724917 fb-gh-sync-id: 978ae26fcaa23c47a4f619e2b7ff2d078388ae95 --- .../react/uimanager/events/TouchesHelper.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index d312a402026b..e431fa7aeaeb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -35,35 +35,25 @@ * given {@param event} instance. This method use {@param reactTarget} parameter to set as a * target view id associated with current gesture. */ - private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) { + private static WritableArray createsPointersArray(int reactTarget, TouchEvent touchEvent) { + MotionEvent event = touchEvent.getMotionEvent(); + WritableArray touches = Arguments.createArray(); - MotionEvent motionEvent = event.getMotionEvent(); - - // Calculate the coordinates for the target view. - // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view - // The TouchEvent contains the X,Y of the touch in the coordinate space of the target view - // Subtracting them allows us to get the coordinates of the target view's top left corner - // We then use this when computing the view specific touches below - // Since only one view is actually handling even multiple touches, the values are all relative - // to this one target view. - float targetViewCoordinateX = motionEvent.getX() - event.getViewX(); - float targetViewCoordinateY = motionEvent.getY() - event.getViewY(); - - for (int index = 0; index < motionEvent.getPointerCount(); index++) { + + // Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the + // pointer at index 0. We use those value to calculate "raw" coordinates for other pointers + float offsetX = event.getRawX() - event.getX(); + float offsetY = event.getRawY() - event.getY(); + + for (int index = 0; index < event.getPointerCount(); index++) { WritableMap touch = Arguments.createMap(); - // pageX,Y values are relative to the RootReactView - // the motionEvent already contains coordinates in that view - touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index))); - touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index))); - // locationX,Y values are relative to the target view - // To compute the values for the view, we subtract that views location from the event X,Y - float locationX = motionEvent.getX(index) - targetViewCoordinateX; - float locationY = motionEvent.getY(index) - targetViewCoordinateY; - touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX)); - touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY)); + touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX)); + touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY)); + touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index))); + touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index))); touch.putInt(TARGET_KEY, reactTarget); - touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime()); - touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index)); + touch.putDouble(TIMESTAMP_KEY, event.getEventTime()); + touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index)); touches.pushMap(touch); } From b89e14ab95098fb13d87fa69e16576ce104f8ac6 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 5 Dec 2015 10:46:08 +0530 Subject: [PATCH 0245/1411] Improve docs for Building from source --- docs/AndroidBuildingFromSource.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index b0fd0896a590..b09c53811208 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -37,7 +37,9 @@ ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e ## Building the source -1. Install `react-native` from your fork. For example, to install the master branch from the offcial repo, run the following: +#### 1. Installing the fork + +First, you need to install `react-native` from your fork. For example, to install the master branch from the official repo, run the following: ```sh npm install --save github:facebook/react-native#master @@ -45,7 +47,10 @@ npm install --save github:facebook/react-native#master Alternatively, you can clone the repo to your `node_modules` directory and run `npm install` inside the cloned repo. -2. Add `gradle-download-task` as dependency in `android/build.gradle`: + +#### 2. Adding missing dependencies + +Add `gradle-download-task` as dependency in `android/build.gradle`: ```gradle ... @@ -59,7 +64,10 @@ Alternatively, you can clone the repo to your `node_modules` directory and run ` ... ``` -3. Add the `:ReactAndroid` project in `android/settings.gradle`: + +#### 3. Adding the `:ReactAndroid` project + +Add the `:ReactAndroid` project in `android/settings.gradle`: ```gradle ... @@ -69,7 +77,8 @@ project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_ ... ``` -4. Modify your `android/app/build.gradle` to use the `:ReactAndroid` project instead of the pre-compiled library, e.g. - replace `compile 'com.facebook.react:react-native:0.16.+'` with `compile project(':ReactAndroid')`: + +Modify your `android/app/build.gradle` to use the `:ReactAndroid` project instead of the pre-compiled library, e.g. - replace `compile 'com.facebook.react:react-native:0.16.+'` with `compile project(':ReactAndroid')`: ```gradle ... @@ -84,7 +93,12 @@ dependencies { ... ``` -5. If you use 3rd-party React Native modules, modify your `android/app/build.gradle` to override their dependencies so that they don't bundle the pre-compiled library, e.g. - replace `compile project(':react-native-custom-module')` with: + +#### 4. Making 3rd-party modules use your fork + +If you use 3rd-party React Native modules, you need to override their dependencies so that they don't bundle the pre-compiled library. Otherwise you'll get an error while compiling - `Error: more than one library with package name 'com.facebook.react'`. + +Modify your `android/app/build.gradle` and replace `compile project(':react-native-custom-module')` with: ```gradle compile(project(':react-native-custom-module')) { From 55d1db6768c9b39a316b0a6054e768e85cdcf8ee Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Sat, 5 Dec 2015 12:19:58 -0800 Subject: [PATCH 0246/1411] [Docs] Fix broken links for Testing.md - Closes #4582. --- docs/Testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Testing.md b/docs/Testing.md index 56ffd67892e4..436de2c95dca 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -56,12 +56,12 @@ Note: you may have to install/upgrade/link Node.js and other parts of your envir ## Integration Tests (iOS only) -React Native provides facilities to make it easier to test integrated components that require both native and JS components to communicate across the bridge. The two main components are `RCTTestRunner` and `RCTTestModule`. `RCTTestRunner` sets up the ReactNative environment and provides facilities to run the tests as `XCTestCase`s in Xcode (`runTest:module` is the simplest method). `RCTTestModule` is exported to JS as `NativeModules.TestModule`. The tests themselves are written in JS, and must call `TestModule.markTestCompleted()` when they are done, otherwise the test will timeout and fail. Test failures are primarily indicated by throwing a JS exception. It is also possible to test error conditions with `runTest:module:initialProps:expectErrorRegex:` or `runTest:module:initialProps:expectErrorBlock:` which will expect an error to be thrown and verify the error matches the provided criteria. See [`IntegrationTestHarnessTest.js`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestHarnessTest.js), [`IntegrationTests.m`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTests.m), and [IntegrationTestsApp.js](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js) for example usage and integration points. +React Native provides facilities to make it easier to test integrated components that require both native and JS components to communicate across the bridge. The two main components are `RCTTestRunner` and `RCTTestModule`. `RCTTestRunner` sets up the ReactNative environment and provides facilities to run the tests as `XCTestCase`s in Xcode (`runTest:module` is the simplest method). `RCTTestModule` is exported to JS as `NativeModules.TestModule`. The tests themselves are written in JS, and must call `TestModule.markTestCompleted()` when they are done, otherwise the test will timeout and fail. Test failures are primarily indicated by throwing a JS exception. It is also possible to test error conditions with `runTest:module:initialProps:expectErrorRegex:` or `runTest:module:initialProps:expectErrorBlock:` which will expect an error to be thrown and verify the error matches the provided criteria. See [`IntegrationTestHarnessTest.js`](https://github.com/facebook/react-native/blob/master/IntegrationTests/IntegrationTestHarnessTest.js), [`UIExplorerIntegrationTests.m`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m), and [IntegrationTestsApp.js](https://github.com/facebook/react-native/blob/master/IntegrationTests/IntegrationTestsApp.js) for example usage and integration points. You can run integration tests locally with cmd+U in the IntegrationTest and UIExplorer apps in Xcode. ## Snapshot Tests (iOS only) -A common type of integration test is the snapshot test. These tests render a component, and verify snapshots of the screen against reference images using `TestModule.verifySnapshot()`, using the [`FBSnapshotTestCase`](https://github.com/facebook/ios-snapshot-test-case) library behind the scenes. Reference images are recorded by setting `recordMode = YES` on the `RCTTestRunner`, then running the tests. Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration. It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies. See [`SimpleSnapshotTest`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js) for a basic example. +A common type of integration test is the snapshot test. These tests render a component, and verify snapshots of the screen against reference images using `TestModule.verifySnapshot()`, using the [`FBSnapshotTestCase`](https://github.com/facebook/ios-snapshot-test-case) library behind the scenes. Reference images are recorded by setting `recordMode = YES` on the `RCTTestRunner`, then running the tests. Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration. It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies. See [`SimpleSnapshotTest`](https://github.com/facebook/react-native/blob/master/IntegrationTests/SimpleSnapshotTest.js) for a basic example. If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshotshot reference image. To do this, simply change to `_runner.recordMode = YES;` in [UIExplorer/UIExplorerSnapshotTests.m](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m#L42), re-run the failing tests, then flip record back to `NO` and submit/update your PR and wait to see if the Travis build passes. From ba81517150e7770de97be89bbd5b5e78f44eb9ba Mon Sep 17 00:00:00 2001 From: jato Date: Sat, 5 Dec 2015 13:29:07 -0800 Subject: [PATCH 0247/1411] fixes minor typo --- docs/CommunicationIOS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md index 686cf0059055..7b23c80eeb15 100644 --- a/docs/CommunicationIOS.md +++ b/docs/CommunicationIOS.md @@ -11,7 +11,7 @@ In [Integrating with Existing Apps guide](http://facebook.github.io/react-native ## Introduction -React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner.If we have an ancestor component that rely on the state of its descendant, the recommended solution would be to pass down a callback that would be used by the descendant to update the ancestor. +React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner. If we have an ancestor component that rely on the state of its descendant, the recommended solution would be to pass down a callback that would be used by the descendant to update the ancestor. The same concept applies to React Native. As long as we are building our application purely within the framework, we can drive our app with properties and callbacks. But, when we mix React Native and native components, we need some special, cross-language mechanisms that would allow us to pass information between them. From 31125b2ed7be12c51e8b0308b3fecbaee567b884 Mon Sep 17 00:00:00 2001 From: Mihai Giurgeanu Date: Sat, 5 Dec 2015 14:56:41 -0800 Subject: [PATCH 0248/1411] Android - Fixing #4392 Summary: This is just an workaround for making UIExplorer work in Android. Closes https://github.com/facebook/react-native/pull/4405 Reviewed By: svcscm Differential Revision: D2727452 Pulled By: tadeuzagallo fb-gh-sync-id: af48fb8c462ab04874cfdb96c0fd4409bf6d92c3 --- Libraries/Components/MapView/MapView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 74f2774d68fc..012f5790f75a 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -15,7 +15,8 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var Image = require('Image'); var NativeMethodsMixin = require('NativeMethodsMixin'); var Platform = require('Platform'); -var RCTMapConstants = require('NativeModules').UIManager.RCTMap.Constants; +var RCTMap = require('UIManager').RCTMap; +var RCTMapConstants = RCTMap && RCTMap.Constants; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var View = require('View'); From ca9f0adee22280f20ba96563be1553b7a975923f Mon Sep 17 00:00:00 2001 From: Atticus White Date: Sat, 5 Dec 2015 18:46:43 -0800 Subject: [PATCH 0249/1411] Improve Modal docs describing iOS only support Summary: Adds a note that the `Modal` component is only available in iOS. I hit a bit of a pickle this weekend after realizing a handful of components I built will have to be rewritten. Unfortunately there's no notes in the documentation that the support is limited. Hopefully this can contribute towards avoiding those situations Closes https://github.com/facebook/react-native/pull/4592 Reviewed By: svcscm Differential Revision: D2727634 Pulled By: androidtrunkagent fb-gh-sync-id: 2d0efcca8e17d16cf63d592e235261cea63e59ea --- Libraries/Modal/Modal.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index dfbfdb685542..9df6208d7860 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -31,6 +31,8 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null); * Navigator instead of Modal. With a top-level Navigator, you have more control * over how to present the modal scene over the rest of your app by using the * configureScene property. + * + * This component is only available in iOS at this time. */ class Modal extends React.Component { render(): ?ReactElement { From fb4f05ed7b8a9129e97cbe6c25ce4310024816b7 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Sun, 6 Dec 2015 15:09:22 -0800 Subject: [PATCH 0250/1411] Update KnownIssues.md --- docs/KnownIssues.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index ef7a9db61427..42d7528439c0 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -25,8 +25,7 @@ Our provisional plan for common views and modules includes: ART Maps Modal -Spinner -Swipe Refresh +Spinner ([not a loading indicator](http://developer.android.com/guide/topics/ui/controls/spinner.html)) Webview ``` @@ -38,7 +37,6 @@ App State Camera Roll Dialog Media -Net Info Pasteboard PushNotificationIOS ``` From f1a575eb306872757f47b1f4007ad83f26c111f8 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Sun, 6 Dec 2015 15:39:37 -0800 Subject: [PATCH 0251/1411] Update KnownIssues.md --- docs/KnownIssues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index 42d7528439c0..ad112ecce603 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -25,7 +25,7 @@ Our provisional plan for common views and modules includes: ART Maps Modal -Spinner ([not a loading indicator](http://developer.android.com/guide/topics/ui/controls/spinner.html)) +Spinner (http://developer.android.com/guide/topics/ui/controls/spinner.html) Webview ``` From a38ce5c5705a18379d3f54297fad3b5b241611a0 Mon Sep 17 00:00:00 2001 From: Olivier Notteghem Date: Sun, 6 Dec 2015 15:44:47 -0800 Subject: [PATCH 0252/1411] match RN attachment images Feed experience with Native with spinner/fade in Reviewed By: astreet Differential Revision: D2722917 fb-gh-sync-id: a09b9a1a4b9a19b94471d8e93ec5bde53af7da06 --- Libraries/Image/Image.android.js | 16 +++++++++ .../react/views/image/ReactImageManager.java | 6 ++++ .../react/views/image/ReactImageView.java | 36 +++++++++++++++---- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 9d9558c9687f..e73a68b84f0f 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -53,6 +53,7 @@ var resolveAssetSource = require('resolveAssetSource'); var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, { src: true, + loadingIndicatorSrc: true, resizeMode: true, progressiveRenderingEnabled: true, fadeDuration: true, @@ -74,6 +75,18 @@ var Image = React.createClass({ // Opaque type returned by require('./image.jpg') PropTypes.number, ]).isRequired, + /** + * similarly to `source`, this property represents the resource used to render + * the loading indicator for the image, displayed until image is ready to be + * displayed, typically after when it got downloaded from network. + */ + loadingIndicatorSource: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string, + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, + ]), progressiveRenderingEnabled: PropTypes.bool, fadeDuration: PropTypes.number, /** @@ -138,6 +151,7 @@ var Image = React.createClass({ render: function() { var source = resolveAssetSource(this.props.source); + var loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource); // As opposed to the ios version, here it render `null` // when no source or source.uri... so let's not break that. @@ -155,6 +169,7 @@ var Image = React.createClass({ style, shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd), src: source.uri, + loadingIndicatorSrc: loadingIndicatorSource ? loadingIndicatorSource.uri : null, }); if (nativeProps.children) { @@ -197,6 +212,7 @@ var styles = StyleSheet.create({ var cfg = { nativeOnly: { src: true, + loadingIndicatorSrc: true, defaultImageSrc: true, imageTag: true, progressHandlerRegistered: true, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index b0df41db0b3c..35cb706d3c00 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -73,6 +73,12 @@ public void setSource(ReactImageView view, @Nullable String source) { view.setSource(source); } + // In JS this is Image.props.loadingIndicatorSource.uri + @ReactProp(name = "loadingIndicatorSrc") + public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) { + view.setLoadingIndicatorSource(source); + } + @ReactProp(name = "borderColor", customType = "Color") public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { if (borderColor == null) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index c889c81837da..b0c8b74267e0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -15,6 +15,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; @@ -26,6 +27,7 @@ import com.facebook.common.util.UriUtil; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; +import com.facebook.drawee.drawable.AutoRotateDrawable; import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.controller.ControllerListener; import com.facebook.drawee.controller.ForwardingControllerListener; @@ -104,6 +106,7 @@ public void process(Bitmap output, Bitmap source) { } private @Nullable Uri mUri; + private @Nullable Drawable mLoadingImageDrawable; private int mBorderColor; private float mBorderWidth; private float mBorderRadius; @@ -220,6 +223,13 @@ public void setSource(@Nullable String source) { mIsDirty = true; } + public void setLoadingIndicatorSource(@Nullable String name) { + Drawable drawable = getResourceDrawable(getContext(), name); + mLoadingImageDrawable = + drawable != null ? (Drawable) new AutoRotateDrawable(drawable, 1000) : null; + mIsDirty = true; + } + public void setProgressiveRenderingEnabled(boolean enabled) { mProgressiveRenderingEnabled = enabled; // no worth marking as dirty if it already rendered.. @@ -244,6 +254,10 @@ public void maybeUpdateView() { GenericDraweeHierarchy hierarchy = getHierarchy(); hierarchy.setActualImageScaleType(mScaleType); + if (mLoadingImageDrawable != null) { + hierarchy.setPlaceholderImage(mLoadingImageDrawable, ScalingUtils.ScaleType.CENTER); + } + boolean usePostprocessorScaling = mScaleType != ScalingUtils.ScaleType.CENTER_CROP && mScaleType != ScalingUtils.ScaleType.FOCUS_CROP; @@ -322,18 +336,26 @@ private static boolean shouldResize(@Nullable Uri uri) { return uri != null && (UriUtil.isLocalContentUri(uri) || UriUtil.isLocalFileUri(uri)); } - private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) { + private static int getResourceDrawableId(Context context, @Nullable String name) { if (name == null || name.isEmpty()) { - return null; + return 0; } - name = name.toLowerCase().replace("-", "_"); - int resId = context.getResources().getIdentifier( - name, + return context.getResources().getIdentifier( + name.toLowerCase().replace("-", "_"), "drawable", context.getPackageName()); - return new Uri.Builder() + } + + private static @Nullable Drawable getResourceDrawable(Context context, @Nullable String name) { + int resId = getResourceDrawableId(context, name); + return resId > 0 ? context.getResources().getDrawable(resId) : null; + } + + private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) { + int resId = getResourceDrawableId(context, name); + return resId > 0 ? new Uri.Builder() .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) .path(String.valueOf(resId)) - .build(); + .build() : null; } } From 69ce5ab5b822d0f789525772ba387d47e6a695cb Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 7 Dec 2015 03:47:39 -0800 Subject: [PATCH 0253/1411] JSC bindings for flow events Reviewed By: astreet Differential Revision: D2717887 fb-gh-sync-id: 40d03ac140669b8ebeb096917f2aba32fe260a1a --- .../src/main/jni/react/JSCTracing.cpp | 134 ++++++++++++++---- 1 file changed, 109 insertions(+), 25 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/JSCTracing.cpp b/ReactAndroid/src/main/jni/react/JSCTracing.cpp index fa786005ef62..d0bab2c420f0 100644 --- a/ReactAndroid/src/main/jni/react/JSCTracing.cpp +++ b/ReactAndroid/src/main/jni/react/JSCTracing.cpp @@ -93,6 +93,7 @@ static JSValueRef nativeTraceBeginSection( JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 2)) { // Could raise an exception here. + // TODO T9329825 return JSValueMakeUndefined(ctx); } @@ -129,6 +130,7 @@ static JSValueRef nativeTraceEndSection( JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) { // Could raise an exception here. + // TODO T9329825 return JSValueMakeUndefined(ctx); } @@ -160,6 +162,7 @@ static JSValueRef nativeTraceEndSection( static JSValueRef beginOrEndAsync( bool isEnd, + bool isFlow, JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, @@ -168,6 +171,7 @@ static JSValueRef beginOrEndAsync( JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) { // Could raise an exception here. + // TODO T9329825 return JSValueMakeUndefined(ctx); } @@ -181,7 +185,7 @@ static JSValueRef beginOrEndAsync( // This uses an if-then-else instruction in ARMv7, which should be cheaper // than a full branch. - buf[pos++] = (isEnd ? 'F' : 'S'); + buf[pos++] = ((isFlow) ? (isEnd ? 'f' : 's') : (isEnd ? 'F' : 'S')); pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d|", getpid()); // Skip the overflow check here because the int will be small. pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH); @@ -190,7 +194,8 @@ static JSValueRef beginOrEndAsync( // I tried some trickery to avoid a branch here, but gcc did not cooperate. // We could consider changing the implementation to be lest branchy in the // future. - if (!isEnd) { + // This is not required for flow use an or to avoid introducing another branch + if (!(isEnd | isFlow)) { buf[pos++] = '<'; buf[pos++] = '0'; buf[pos++] = '>'; @@ -216,6 +221,44 @@ static JSValueRef beginOrEndAsync( return JSValueMakeUndefined(ctx); } +static JSValueRef stageAsync( + bool isFlow, + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) { + // Could raise an exception here. + // TODO T9329825 + return JSValueMakeUndefined(ctx); + } + + uint64_t tag = tagFromJSValue(ctx, arguments[0], exception); + if (!fbsystrace_is_tracing(tag)) { + return JSValueMakeUndefined(ctx); + } + + char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH]; + size_t pos = 0; + + buf[pos++] = (isFlow ? 't' : 'T'); + pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d", getpid()); + // Skip the overflow check here because the int will be small. + + // Arguments are section name, cookie, and stage name. + // All added together, they still cannot cause an overflow. + for (int i = 1; i < 4; i++) { + buf[pos++] = '|'; + pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH); + } + + fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1)); + + return JSValueMakeUndefined(ctx); +} + static JSValueRef nativeTraceBeginAsyncSection( JSContextRef ctx, JSObjectRef function, @@ -225,6 +268,7 @@ static JSValueRef nativeTraceBeginAsyncSection( JSValueRef* exception) { return beginOrEndAsync( false /* isEnd */, + false /* isFlow */, ctx, function, thisObject, @@ -242,6 +286,7 @@ static JSValueRef nativeTraceEndAsyncSection( JSValueRef* exception) { return beginOrEndAsync( true /* isEnd */, + false /* isFlow */, ctx, function, thisObject, @@ -257,32 +302,67 @@ static JSValueRef nativeTraceAsyncSectionStage( size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { - if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) { - // Could raise an exception here. - return JSValueMakeUndefined(ctx); - } - - uint64_t tag = tagFromJSValue(ctx, arguments[0], exception); - if (!fbsystrace_is_tracing(tag)) { - return JSValueMakeUndefined(ctx); - } - - char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH]; - size_t pos = 0; - - pos += snprintf(buf + pos, sizeof(buf) - pos, "T|%d", getpid()); - // Skip the overflow check here because the int will be small. + return stageAsync( + false /* isFlow */, + ctx, + function, + thisObject, + argumentCount, + arguments, + exception); +} - // Arguments are section name, cookie, and stage name. - // All added together, they still cannot cause an overflow. - for (int i = 1; i < 4; i++) { - buf[pos++] = '|'; - pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH); - } +static JSValueRef nativeTraceBeginAsyncFlow( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + return beginOrEndAsync( + false /* isEnd */, + true /* isFlow */, + ctx, + function, + thisObject, + argumentCount, + arguments, + exception); +} - fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1)); +static JSValueRef nativeTraceEndAsyncFlow( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + return beginOrEndAsync( + true /* isEnd */, + true /* isFlow */, + ctx, + function, + thisObject, + argumentCount, + arguments, + exception); +} - return JSValueMakeUndefined(ctx); +static JSValueRef nativeTraceAsyncFlowStage( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + return stageAsync( + true /* isFlow */, + ctx, + function, + thisObject, + argumentCount, + arguments, + exception); } static JSValueRef nativeTraceCounter( @@ -294,6 +374,7 @@ static JSValueRef nativeTraceCounter( JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) { // Could raise an exception here. + // TODO T9329825 return JSValueMakeUndefined(ctx); } @@ -322,6 +403,9 @@ void addNativeTracingHooks(JSGlobalContextRef ctx) { installGlobalFunction(ctx, "nativeTraceBeginAsyncSection", nativeTraceBeginAsyncSection); installGlobalFunction(ctx, "nativeTraceEndAsyncSection", nativeTraceEndAsyncSection); installGlobalFunction(ctx, "nativeTraceAsyncSectionStage", nativeTraceAsyncSectionStage); + installGlobalFunction(ctx, "nativeTraceBeginAsyncFlow", nativeTraceBeginAsyncFlow); + installGlobalFunction(ctx, "nativeTraceEndAsyncFlow", nativeTraceEndAsyncFlow); + installGlobalFunction(ctx, "nativeTraceAsyncFlowStage", nativeTraceAsyncFlowStage); installGlobalFunction(ctx, "nativeTraceCounter", nativeTraceCounter); } From 02ded6d0bb4f507db2cf637d49d291be3a38a3eb Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 7 Dec 2015 06:39:16 -0800 Subject: [PATCH 0254/1411] Preserve all return registers on x86_64 trampoline Summary: public Only the first quad-word and floating point return registers were being preserved, make sure to preserve the 2nd ones as well (`%rdx` and `%xmm1`) Reviewed By: jspahrsummers Differential Revision: D2727523 fb-gh-sync-id: d8176512d2dfb5f664f634ecaaf34510515506ea --- React/Profiler/RCTProfileTrampoline-x86_64.S | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/React/Profiler/RCTProfileTrampoline-x86_64.S b/React/Profiler/RCTProfileTrampoline-x86_64.S index 02fa010a4814..6d9c93fb7cd0 100644 --- a/React/Profiler/RCTProfileTrampoline-x86_64.S +++ b/React/Profiler/RCTProfileTrampoline-x86_64.S @@ -142,21 +142,14 @@ SYMBOL_NAME(RCTProfileTrampoline): // call the actual function and save the return value callq *%r11 pushq %rax - subq $0x10+8, %rsp //16-bytes xmm register + 8-bytes for alignment - movdqa %xmm0, (%rsp) - - // align stack - pushq %r12 - movq %rsp, %r12 - andq $-0x10, %rsp + pushq %rdx + subq $0x20, %rsp // 2 16-bytes xmm register + movdqa %xmm0, 0x00(%rsp) + movdqa %xmm1, 0x10(%rsp) // void RCTProfileTrampolineEnd(void) in RCTProfile.m - just ends this profile callq SYMBOL_NAME(RCTProfileTrampolineEnd) - // unalign stack and restore %r12 - movq %r12, %rsp - popq %r12 - /** * Restore the initial value of the callee saved registers, saved in the * memory allocated. @@ -167,7 +160,7 @@ SYMBOL_NAME(RCTProfileTrampoline): movq 0x8(%r14), %r14 /** - * Save caller address and actual function return (previously in the allocated + * save caller address and actual function return (previously in the allocated * memory) and align the stack */ pushq %rcx @@ -183,12 +176,14 @@ SYMBOL_NAME(RCTProfileTrampoline): popq %r12 /** - * pop the caller address to %rcx and the actual function return value to - * %rax, so it's the return value of RCTProfileTrampoline + * pop the caller address to %rcx and the actual function return value(s) + * so it's the return value of RCTProfileTrampoline */ popq %rcx - movdqa (%rsp), %xmm0 - addq $0x10+8, %rsp + movdqa 0x00(%rsp), %xmm0 + movdqa 0x10(%rsp), %xmm1 + addq $0x20, %rsp + popq %rdx popq %rax // jump to caller From 611e0619caf7323b5d06f29efbc2febd0ad4eba7 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 7 Dec 2015 08:28:45 -0800 Subject: [PATCH 0255/1411] Rename PullToRefreshLayoutAndroid -> PullToRefreshViewAndroid Summary: The naming "Layout" is an Android-specific thing and not useful in JS. Let's stay consistent with naming like "ScrollView", "MapView" etc. public Reviewed By: bestander Differential Revision: D2723163 fb-gh-sync-id: 6b86e5a649254c41e9d6b0ef6f1fe2ff4b9f3e9a --- ...js => PullToRefreshViewAndroidExample.android.js} | 12 ++++++------ Examples/UIExplorer/UIExplorerList.android.js | 2 +- ...ndroid.js => PullToRefreshViewAndroid.android.js} | 10 +++++----- ...ndroid.ios.js => PullToRefreshViewAndroid.ios.js} | 2 +- Libraries/react-native/react-native.js | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) rename Examples/UIExplorer/{PullToRefreshLayoutAndroidExample.android.js => PullToRefreshViewAndroidExample.android.js} (91%) rename Libraries/PullToRefresh/{PullToRefreshLayoutAndroid.android.js => PullToRefreshViewAndroid.android.js} (90%) rename Libraries/PullToRefresh/{PullToRefreshLayoutAndroid.ios.js => PullToRefreshViewAndroid.ios.js} (89%) diff --git a/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js b/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js similarity index 91% rename from Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js rename to Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js index 06a39c7ccd86..c309c90288b9 100644 --- a/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js +++ b/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js @@ -18,7 +18,7 @@ const React = require('react-native'); const { ScrollView, StyleSheet, - PullToRefreshLayoutAndroid, + PullToRefreshViewAndroid, Text, TouchableWithoutFeedback, View, @@ -61,9 +61,9 @@ const Row = React.createClass({ ); }, }); -const PullToRefreshLayoutAndroidExample = React.createClass({ +const PullToRefreshViewAndroidExample = React.createClass({ statics: { - title: '', + title: '', description: 'Container that adds pull-to-refresh support to its child view.' }, @@ -88,7 +88,7 @@ const PullToRefreshLayoutAndroidExample = React.createClass({ return ; }); return ( - {rows} - + ); }, @@ -124,4 +124,4 @@ const PullToRefreshLayoutAndroidExample = React.createClass({ }); -module.exports = PullToRefreshLayoutAndroidExample; +module.exports = PullToRefreshViewAndroidExample; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index b3b0fc4d50ff..6445eff49905 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -27,7 +27,7 @@ var COMPONENTS = [ require('./ProgressBarAndroidExample'), require('./ScrollViewSimpleExample'), require('./SwitchAndroidExample'), - require('./PullToRefreshLayoutAndroidExample.android'), + require('./PullToRefreshViewAndroidExample.android'), require('./TextExample.android'), require('./TextInputExample.android'), require('./ToolbarAndroidExample'), diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js similarity index 90% rename from Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js rename to Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js index a1d69c353e8d..40842d0a2715 100644 --- a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js +++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js @@ -6,7 +6,7 @@ * 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 PullToRefreshLayoutAndroid + * @providesModule PullToRefreshViewAndroid */ 'use strict'; @@ -24,7 +24,7 @@ var NATIVE_REF = 'native_swiperefreshlayout'; * React view that supports a single scrollable child view (e.g. `ScrollView`). When this child * view is at `scrollY: 0`, swiping down triggers an `onRefresh` event. */ -var PullToRefreshLayoutAndroid = React.createClass({ +var PullToRefreshViewAndroid = React.createClass({ statics: { SIZE: RefreshLayoutConsts.SIZE, }, @@ -48,7 +48,7 @@ var PullToRefreshLayoutAndroid = React.createClass({ */ refreshing: React.PropTypes.bool, /** - * Size of the refresh indicator, see PullToRefreshLayoutAndroid.SIZE + * Size of the refresh indicator, see PullToRefreshViewAndroid.SIZE */ size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE), }, @@ -81,7 +81,7 @@ var PullToRefreshLayoutAndroid = React.createClass({ var NativePullToRefresh = requireNativeComponent( 'AndroidSwipeRefreshLayout', - PullToRefreshLayoutAndroid + PullToRefreshViewAndroid ); -module.exports = PullToRefreshLayoutAndroid; +module.exports = PullToRefreshViewAndroid; diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js similarity index 89% rename from Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js rename to Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js index eea4dbae7695..2a16f851b827 100644 --- a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js +++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js @@ -6,7 +6,7 @@ * 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 PullToRefreshLayoutAndroid + * @providesModule PullToRefreshViewAndroid */ 'use strict'; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 2264fa5c6657..99bda63a9f9b 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -37,7 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { SliderIOS: require('SliderIOS'), SnapshotViewIOS: require('SnapshotViewIOS'), Switch: require('Switch'), - PullToRefreshLayoutAndroid: require('PullToRefreshLayoutAndroid'), + PullToRefreshViewAndroid: require('PullToRefreshViewAndroid'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), From 510d50fc17157f186a593c2c02b3fcf610889c77 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Mon, 7 Dec 2015 08:43:24 -0800 Subject: [PATCH 0256/1411] Trigger GC and drop compiled code on low memory Reviewed By: astreet Differential Revision: D2658693 fb-gh-sync-id: 8cba49b67ac45a2dbf8b4c9c404d6fb9c97693f6 --- .../facebook/react/MemoryPressureRouter.java | 93 +++++++++++++++++++ .../react/ReactInstanceManagerImpl.java | 5 + .../react/bridge/CatalystInstance.java | 4 +- .../react/bridge/CatalystInstanceImpl.java | 5 + .../facebook/react/bridge/MemoryPressure.java | 8 ++ .../facebook/react/bridge/ReactBridge.java | 15 +++ ReactAndroid/src/main/jni/react/Bridge.cpp | 16 ++++ ReactAndroid/src/main/jni/react/Bridge.h | 2 + ReactAndroid/src/main/jni/react/Executor.h | 4 + .../src/main/jni/react/JSCExecutor.cpp | 16 ++++ ReactAndroid/src/main/jni/react/JSCExecutor.h | 4 +- .../src/main/jni/react/jni/OnLoad.cpp | 13 +++ 12 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java new file mode 100644 index 000000000000..58f2833110e2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java @@ -0,0 +1,93 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react; + +import javax.annotation.Nullable; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; + +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.MemoryPressure; +import com.facebook.react.bridge.ReactContext; + +import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + +/** + * Translates and routes memory pressure events to the current catalyst instance. + */ +public class MemoryPressureRouter { + // Trigger this by sending an intent to your activity with adb shell: + // am start -a "com.facebook.catalyst.ACTION_TRIM_MEMORY" --activity-single-top -n + private static final String ACTION_TRIM_MEMORY ="com.facebook.catalyst.ACTION_TRIM_MEMORY"; + + private @Nullable CatalystInstance mCatalystInstance; + private final ComponentCallbacks2 mCallbacks = new ComponentCallbacks2() { + @Override + public void onTrimMemory(int level) { + trimMemory(level); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void onLowMemory() { + } + }; + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static boolean handleDebugIntent(Activity activity, String action) { + switch (action) { + case ACTION_TRIM_MEMORY: + simulateTrimMemory(activity, TRIM_MEMORY_MODERATE); + break; + default: + return false; + } + + return true; + } + + MemoryPressureRouter(Context context) { + context.getApplicationContext().registerComponentCallbacks(mCallbacks); + } + + public void onNewReactContextCreated(ReactContext reactContext) { + mCatalystInstance = reactContext.getCatalystInstance(); + } + + public void onReactInstanceDestroyed() { + mCatalystInstance = null; + } + + public void destroy(Context context) { + context.getApplicationContext().unregisterComponentCallbacks(mCallbacks); + } + + private void trimMemory(int level) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + dispatchMemoryPressure(MemoryPressure.CRITICAL); + } else if (level >= TRIM_MEMORY_BACKGROUND || level == TRIM_MEMORY_RUNNING_CRITICAL) { + dispatchMemoryPressure(MemoryPressure.MODERATE); + } + } + + private void dispatchMemoryPressure(MemoryPressure level) { + if (mCatalystInstance != null) { + mCatalystInstance.handleMemoryPressure(level); + } + } + + private static void simulateTrimMemory(Activity activity, int level) { + activity.getApplication().onTrimMemory(level); + activity.onTrimMemory(level); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 1eecb403269b..1169034ee116 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -100,6 +100,7 @@ new ConcurrentLinkedQueue<>(); private volatile boolean mHasStartedCreatingInitialContext = false; private final UIImplementationProvider mUIImplementationProvider; + private final MemoryPressureRouter mMemoryPressureRouter; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -215,6 +216,7 @@ protected void onPostExecute(ReactApplicationContext reactContext) { mBridgeIdleDebugListener = bridgeIdleDebugListener; mLifecycleState = initialLifecycleState; mUIImplementationProvider = uiImplementationProvider; + mMemoryPressureRouter = new MemoryPressureRouter(applicationContext); } @Override @@ -400,6 +402,7 @@ public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBac public void onDestroy() { UiThreadUtil.assertOnUiThread(); + mMemoryPressureRouter.destroy(mApplicationContext); if (mUseDeveloperSupport) { mDevSupportManager.setDevSupportEnabled(false); } @@ -539,6 +542,7 @@ private void setupReactContext(ReactApplicationContext reactContext) { catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); + mMemoryPressureRouter.onNewReactContextCreated(reactContext); moveReactContextToCurrentLifecycleState(reactContext); for (ReactRootView rootView : mAttachedRootViews) { @@ -591,6 +595,7 @@ private void tearDownReactContext(ReactContext reactContext) { } reactContext.onDestroy(); mDevSupportManager.onReactInstanceDestroyed(reactContext); + mMemoryPressureRouter.onReactInstanceDestroyed(); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 7e5afc3a47ff..e9240eb88815 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -9,8 +9,6 @@ package com.facebook.react.bridge; -import javax.annotation.Nullable; - import java.util.Collection; import com.facebook.react.bridge.queue.CatalystQueueConfiguration; @@ -49,6 +47,8 @@ public interface CatalystInstance { T getNativeModule(Class nativeModuleInterface); Collection getNativeModules(); + void handleMemoryPressure(MemoryPressure level); + /** * Adds a idle listener for this Catalyst instance. The listener will receive notifications * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 6929ea3d7e75..06d53a85efca 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -314,6 +314,11 @@ public Collection getNativeModules() { return mJavaRegistry.getAllModules(); } + @Override + public void handleMemoryPressure(MemoryPressure level) { + Assertions.assertNotNull(mBridge).handleMemoryPressure(level); + } + /** * Adds a idle listener for this Catalyst instance. The listener will receive notifications * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java new file mode 100644 index 000000000000..c947782efe40 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java @@ -0,0 +1,8 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.bridge; + +public enum MemoryPressure { + MODERATE, + CRITICAL +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java index d84c7b844917..b809e680b6a8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java @@ -56,6 +56,19 @@ public void dispose() { super.dispose(); } + public void handleMemoryPressure(MemoryPressure level) { + switch (level) { + case MODERATE: + handleMemoryPressureModerate(); + break; + case CRITICAL: + handleMemoryPressureCritical(); + break; + default: + throw new IllegalArgumentException("Unknown level: " + level); + } + } + private native void initialize( JavaScriptExecutor jsExecutor, ReactCallback callback, @@ -72,4 +85,6 @@ private native void initialize( public native boolean supportsProfiling(); public native void startProfiler(String title); public native void stopProfiler(String title, String filename); + private native void handleMemoryPressureModerate(); + private native void handleMemoryPressureCritical(); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index e3643e6cf41c..84fb525b00b5 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -51,6 +51,14 @@ class JSThreadState { m_jsExecutor->stopProfiler(title, filename); } + void handleMemoryPressureModerate() { + m_jsExecutor->handleMemoryPressureModerate(); + } + + void handleMemoryPressureCritical() { + m_jsExecutor->handleMemoryPressureCritical(); + } + private: std::unique_ptr m_jsExecutor; Bridge::Callback m_callback; @@ -109,4 +117,12 @@ void Bridge::stopProfiler(const std::string& title, const std::string& filename) m_threadState->stopProfiler(title, filename); } +void Bridge::handleMemoryPressureModerate() { + m_threadState->handleMemoryPressureModerate(); +} + +void Bridge::handleMemoryPressureCritical() { + m_threadState->handleMemoryPressureCritical(); +} + } } diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index 9f81f529d9d3..23843de42940 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -38,6 +38,8 @@ class Bridge : public Countable { bool supportsProfiling(); void startProfiler(const std::string& title); void stopProfiler(const std::string& title, const std::string& filename); + void handleMemoryPressureModerate(); + void handleMemoryPressureCritical(); private: Callback m_callback; std::unique_ptr m_threadState; diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 1f4f54cc5a89..6a59bff420a0 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -43,6 +43,10 @@ class JSExecutor { }; virtual void startProfiler(const std::string &titleString) {}; virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}; + virtual void handleMemoryPressureModerate() {}; + virtual void handleMemoryPressureCritical() { + handleMemoryPressureModerate(); + }; virtual ~JSExecutor() {}; }; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 5e6caae2630e..93bb17fbad13 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -18,6 +18,10 @@ #include #endif +#ifdef WITH_JSC_MEMORY_PRESSURE +#include +#endif + #ifdef WITH_FBSYSTRACE #include using fbsystrace::FbSystraceSection; @@ -182,6 +186,18 @@ void JSCExecutor::stopProfiler(const std::string &titleString, const std::string #endif } +void JSCExecutor::handleMemoryPressureModerate() { + #ifdef WITH_JSC_MEMORY_PRESSURE + JSHandleMemoryPressure(this, m_context, JSMemoryPressure::MODERATE); + #endif +} + +void JSCExecutor::handleMemoryPressureCritical() { + #ifdef WITH_JSC_MEMORY_PRESSURE + JSHandleMemoryPressure(this, m_context, JSMemoryPressure::CRITICAL); + #endif +} + void JSCExecutor::flushQueueImmediate(std::string queueJSON) { m_flushImmediateCallback(queueJSON); } diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index 849148bfaec8..a319a9036574 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -31,7 +31,9 @@ class JSCExecutor : public JSExecutor { virtual bool supportsProfiling() override; virtual void startProfiler(const std::string &titleString) override; virtual void stopProfiler(const std::string &titleString, const std::string &filename) override; - + virtual void handleMemoryPressureModerate() override; + virtual void handleMemoryPressureCritical() override; + void flushQueueImmediate(std::string queueJSON); void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback); diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 6afa306da05e..610cd160d4ab 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -729,6 +729,16 @@ static void stopProfiler(JNIEnv* env, jobject obj, jstring title, jstring filena bridge->stopProfiler(fromJString(env, title), fromJString(env, filename)); } +static void handleMemoryPressureModerate(JNIEnv* env, jobject obj) { + auto bridge = extractRefPtr(env, obj); + bridge->handleMemoryPressureModerate(); +} + +static void handleMemoryPressureCritical(JNIEnv* env, jobject obj) { + auto bridge = extractRefPtr(env, obj); + bridge->handleMemoryPressureCritical(); +} + } // namespace bridge namespace executors { @@ -840,6 +850,9 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { makeNativeMethod("supportsProfiling", bridge::supportsProfiling), makeNativeMethod("startProfiler", bridge::startProfiler), makeNativeMethod("stopProfiler", bridge::stopProfiler), + makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate), + makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical), + }); jclass nativeRunnableClass = env->FindClass("com/facebook/react/bridge/queue/NativeRunnable"); From 7600909caf2e3673c688857fa62263674d38ae1d Mon Sep 17 00:00:00 2001 From: Pratham Agrawal Date: Mon, 7 Dec 2015 23:03:12 +0530 Subject: [PATCH 0257/1411] Added Hubhopper to the showcase. --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 89b74d7033f2..4fbcb58ef3a0 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -204,6 +204,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/hsk-level-1-chinese-flashcards/id936639994', author: 'HS Schaaf', }, + { + name: 'Hubhopper', + icon: 'http://hubhopper.com/images/h_logo.jpg', + link: 'https://play.google.com/store/apps/details?id=com.hubhopper', + author: 'Soch Technologies', + }, { name: 'Kakapo', icon: 'http://a2.mzstatic.com/eu/r30/Purple3/v4/12/ab/2a/12ab2a01-3a3c-9482-b8df-ab38ad281165/icon175x175.png', From abea6c30b440b349d072038e756154ade16457fe Mon Sep 17 00:00:00 2001 From: sathis Date: Mon, 7 Dec 2015 10:44:41 -0800 Subject: [PATCH 0258/1411] Make style property available in Image.android.js like Image.ios.js Summary: Thanks ahanriat for pointing out :) Closes https://github.com/facebook/react-native/pull/4618 Reviewed By: svcscm Differential Revision: D2729582 Pulled By: androidtrunkagent fb-gh-sync-id: df7b9af24cc035c7a9618a7850d628961fcd776b --- Libraries/Image/Image.android.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index e73a68b84f0f..3cf5ae1f66eb 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -63,7 +63,8 @@ var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, { var Image = React.createClass({ propTypes: { ...View.propTypes, - /** + style: StyleSheetPropType(ImageStylePropTypes), + /** * `uri` is a string representing the resource identifier for the image, which * could be an http address, a local file path, or the name of a static image * resource (which should be wrapped in the `require('image!name')` function). From 7605d2d8cdf0b5b230d116d39adb371eb404b263 Mon Sep 17 00:00:00 2001 From: Nick Baugh Date: Thu, 3 Dec 2015 01:03:36 -0500 Subject: [PATCH 0259/1411] Switched recommendation from superagent to frisbee, Moved XMLHTTPRequest to bottom of doc page Resolves issues per recommendations by @ide Added axios reference --- docs/Network.md | 64 ++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/Network.md b/docs/Network.md index d0366e646992..ce2755983593 100644 --- a/docs/Network.md +++ b/docs/Network.md @@ -9,45 +9,19 @@ next: timers One of React Native's goals is to be a playground where we can experiment with different architectures and crazy ideas. Since browsers are not flexible enough, we had no choice but to reimplement the entire stack. In the places that we did not intend to change anything, we tried to be as faithful as possible to the browser APIs. The networking stack is a great example. -## XMLHttpRequest - -XMLHttpRequest API is implemented on-top of [iOS networking apis](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html). The notable difference from web is the security model: you can read from arbitrary websites on the internet since there is no concept of [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing). - -```javascript -var request = new XMLHttpRequest(); -request.onreadystatechange = (e) => { - if (request.readyState !== 4) { - return; - } - - if (request.status === 200) { - console.log('success', request.responseText); - } else { - console.warn('error'); - } -}; - -request.open('GET', 'https://mywebsite.com/endpoint.php'); -request.send(); -``` - -Please follow the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) for a complete description of the API. - -As a developer, you're probably not going to use XMLHttpRequest directly as its API is very tedious to work with. But the fact that it is implemented and compatible with the browser API gives you the ability to use third-party libraries such as [Parse]( https://parse.com/products/javascript) or [super-agent](https://github.com/visionmedia/superagent) directly from npm. - ## Fetch [fetch](https://fetch.spec.whatwg.org/) is a better networking API being worked on by the standards committee and is already available in Chrome. It is available in React Native by default. #### Usage -```javascript +```js fetch('https://mywebsite.com/endpoint/') ``` Include a request object as the optional second argument to customize the HTTP request: -```javascript +```js fetch('https://mywebsite.com/endpoint/', { method: 'POST', headers: { @@ -62,11 +36,12 @@ fetch('https://mywebsite.com/endpoint/', { ``` #### Async + `fetch` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that can be processed in two ways: 1. Using `then` and `catch` in synchronous code: -```javascript +```js fetch('https://mywebsite.com/endpoint.php') .then((response) => response.text()) .then((responseText) => { @@ -79,7 +54,7 @@ fetch('https://mywebsite.com/endpoint.php') 2. Called within an asynchronous function using ES7 `async`/`await` syntax: -```javascript +```js async getUsersFromApi() { try { let response = await fetch('https://mywebsite.com/endpoint/'); @@ -92,12 +67,11 @@ async getUsersFromApi() { - Note: Errors thrown by rejected Promises need to be caught, or they will be swallowed silently - ## WebSocket WebSocket is a protocol providing full-duplex communication channels over a single TCP connection. -```javascript +```js var ws = new WebSocket('ws://host.com/path'); ws.on('open', function() { @@ -120,3 +94,29 @@ ws.on('close', function(e) { console.log(e.code, e.reason); }); ``` + +## XMLHttpRequest + +XMLHttpRequest API is implemented on-top of [iOS networking apis](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html). The notable difference from web is the security model: you can read from arbitrary websites on the internet since there is no concept of [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing). + +```js +var request = new XMLHttpRequest(); +request.onreadystatechange = (e) => { + if (request.readyState !== 4) { + return; + } + + if (request.status === 200) { + console.log('success', request.responseText); + } else { + console.warn('error'); + } +}; + +request.open('GET', 'https://mywebsite.com/endpoint.php'); +request.send(); +``` + +Please follow the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) for a complete description of the API. + +As a developer, you're probably not going to use XMLHttpRequest directly as its API is very tedious to work with. But the fact that it is implemented and compatible with the browser API gives you the ability to use third-party libraries such as [Parse](https://parse.com/products/javascript), [frisbee](https://github.com/niftylettuce/frisbee), or [axios](https://github.com/mzabriskie/axios) directly from npm. From 93f9a4a7ee34ec17d61853f8ff7f5ae7a520bc58 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Mon, 7 Dec 2015 11:25:50 -0800 Subject: [PATCH 0260/1411] Fix for flow (https://github.com/facebook/flow/tree/v0.19.0) Summary: From flow release notes (https://github.com/facebook/flow/releases), > import type * as Foo is now disallowed in favor of import type Foo Closes https://github.com/facebook/react-native/pull/4565 Reviewed By: svcscm Differential Revision: D2723280 Pulled By: mkonicek fb-gh-sync-id: 57074ab893c3e2eae3cefc3002853bfdfed91734 --- .../CustomComponents/Navigator/Navigation/NavigationContext.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js index 6d17fe1256df..7024081d2079 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js @@ -36,7 +36,7 @@ var Set = require('Set'); var emptyFunction = require('emptyFunction'); var invariant = require('invariant'); -import type * as EventSubscription from 'EventSubscription'; +import type EventSubscription from 'EventSubscription'; var { AT_TARGET, From 0f98dedefe5769be39a298efd14e92cd7dc472e9 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 7 Dec 2015 15:21:05 -0800 Subject: [PATCH 0261/1411] More markers for view operations Reviewed By: astreet Differential Revision: D2679126 fb-gh-sync-id: 882e815a7551d23b4594fdc2dd257b4f1cdbbab7 --- .../uimanager/NativeViewHierarchyManager.java | 108 +++++++++++------- .../react/uimanager/UIViewOperationQueue.java | 4 + 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 752ffaf291bc..ad195004333d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -34,6 +34,8 @@ import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.JSResponderHandler; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; +import com.facebook.systrace.Systrace; +import com.facebook.systrace.SystraceMessage; /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between @@ -128,40 +130,50 @@ public void updateLayout( int width, int height) { UiThreadUtil.assertOnUiThread(); - - View viewToUpdate = resolveView(tag); - - // Even though we have exact dimensions, we still call measure because some platform views (e.g. - // Switch) assume that method will always be called before onLayout and onDraw. They use it to - // calculate and cache information used in the draw pass. For most views, onMeasure can be - // stubbed out to only call setMeasuredDimensions. For ViewGroups, onLayout should be stubbed - // out to not recursively call layout on its children: React Native already handles doing that. - // - // Also, note measure and layout need to be called *after* all View properties have been updated - // because of caching and calculation that may occur in onMeasure and onLayout. Layout - // operations should also follow the native view hierarchy and go top to bottom for consistency - // with standard layout passes (some views may depend on this). - - viewToUpdate.measure( - View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); - - // Check if the parent of the view has to layout the view, or the child has to lay itself out. - if (!mRootTags.get(parentTag)) { - ViewManager parentViewManager = mTagsToViewManagers.get(parentTag); - ViewGroupManager parentViewGroupManager; - if (parentViewManager instanceof ViewGroupManager) { - parentViewGroupManager = (ViewGroupManager) parentViewManager; + SystraceMessage.beginSection( + Systrace.TRACE_TAG_REACT_VIEW, + "NativeViewHierarchyManager_updateLayout") + .arg("parentTag", parentTag) + .arg("tag", tag) + .flush(); + try { + View viewToUpdate = resolveView(tag); + + // Even though we have exact dimensions, we still call measure because some platform views (e.g. + // Switch) assume that method will always be called before onLayout and onDraw. They use it to + // calculate and cache information used in the draw pass. For most views, onMeasure can be + // stubbed out to only call setMeasuredDimensions. For ViewGroups, onLayout should be stubbed + // out to not recursively call layout on its children: React Native already handles doing that. + // + // Also, note measure and layout need to be called *after* all View properties have been updated + // because of caching and calculation that may occur in onMeasure and onLayout. Layout + // operations should also follow the native view hierarchy and go top to bottom for consistency + // with standard layout passes (some views may depend on this). + + viewToUpdate.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + + // Check if the parent of the view has to layout the view, or the child has to lay itself out. + if (!mRootTags.get(parentTag)) { + ViewManager parentViewManager = mTagsToViewManagers.get(parentTag); + ViewGroupManager parentViewGroupManager; + if (parentViewManager instanceof ViewGroupManager) { + parentViewGroupManager = (ViewGroupManager) parentViewManager; + } else { + throw new IllegalViewOperationException( + "Trying to use view with tag " + tag + + " as a parent, but its Manager doesn't extends ViewGroupManager"); + } + if (parentViewGroupManager != null + && !parentViewGroupManager.needsCustomLayoutForChildren()) { + updateLayout(viewToUpdate, x, y, width, height); + } } else { - throw new IllegalViewOperationException("Trying to use view with tag " + tag + - " as a parent, but its Manager doesn't extends ViewGroupManager"); - } - if (parentViewGroupManager != null - && !parentViewGroupManager.needsCustomLayoutForChildren()) { updateLayout(viewToUpdate, x, y, width, height); } - } else { - updateLayout(viewToUpdate, x, y, width, height); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW); } } @@ -180,18 +192,28 @@ public void createView( String className, @Nullable CatalystStylesDiffMap initialProps) { UiThreadUtil.assertOnUiThread(); - ViewManager viewManager = mViewManagers.get(className); - - View view = viewManager.createView(themedContext, mJSResponderHandler); - mTagsToViews.put(tag, view); - mTagsToViewManagers.put(tag, viewManager); - - // Use android View id field to store React tag. This is possible since we don't inflate - // React views from layout xmls. Thus it is easier to just reuse that field instead of - // creating another (potentially much more expensive) mapping from view to React tag - view.setId(tag); - if (initialProps != null) { - viewManager.updateProperties(view, initialProps); + SystraceMessage.beginSection( + Systrace.TRACE_TAG_REACT_VIEW, + "NativeViewHierarchyManager_createView") + .arg("tag", tag) + .arg("className", className) + .flush(); + try { + ViewManager viewManager = mViewManagers.get(className); + + View view = viewManager.createView(themedContext, mJSResponderHandler); + mTagsToViews.put(tag, view); + mTagsToViewManagers.put(tag, viewManager); + + // Use android View id field to store React tag. This is possible since we don't inflate + // React views from layout xmls. Thus it is easier to just reuse that field instead of + // creating another (potentially much more expensive) mapping from view to React tag + view.setId(tag); + if (initialProps != null) { + viewManager.updateProperties(view, initialProps); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 5fc65cd64a23..ac2798333609 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -113,10 +113,12 @@ public UpdateLayoutOperation( mY = y; mWidth = width; mHeight = height; + Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag); } @Override public void execute() { + Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag); mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight); } } @@ -136,10 +138,12 @@ public CreateViewOperation( mThemedContext = themedContext; mClassName = className; mInitialProps = initialProps; + Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag); } @Override public void execute() { + Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag); mNativeViewHierarchyManager.createView( mThemedContext, mTag, From f9b744d50137de25357994fe2e829f98104e2242 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 7 Dec 2015 16:54:18 -0800 Subject: [PATCH 0262/1411] Use lazy getters to reduce `require('react-native')` overhead Summary: public In the open source React Native implementation, the recommended approach for importing modules is by importing a the `ReactNative` object, which includes all available modules. This is rather inefficient because it ends up initializing all the JS modules, even if you don't use them. This diff switches the properties of the `ReactNative ` object to lazy getter functions, which defers the `require` until the module is actually requested. This doesn't prevent unused modules from being included in the JS bundle, but it will prevent them from being initialized unless/until they are used. Reviewed By: vjeux Differential Revision: D2722993 fb-gh-sync-id: 0e9a2beb3aa6cd087a0592bd59a8f9242040be0c --- Libraries/react-native/react-native.js | 174 +++++++++++++------------ 1 file changed, 89 insertions(+), 85 deletions(-) diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 99bda63a9f9b..9eb675a8425f 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -6,110 +6,114 @@ * 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. * - * @flow + * @noflow - get/set properties not yet supported by flow. also `...require(x)` is broken #6560135 */ 'use strict'; // Export React, plus some native additions. -// -// The use of Object.create/assign is to work around a Flow bug (#6560135). -// Once that is fixed, change this back to -// -// var ReactNative = {...require('React'), /* additions */} -// -var ReactNative = Object.assign(Object.create(require('React')), { +var ReactNative = { // Components - ActivityIndicatorIOS: require('ActivityIndicatorIOS'), - ART: require('ReactNativeART'), - DatePickerIOS: require('DatePickerIOS'), - DrawerLayoutAndroid: require('DrawerLayoutAndroid'), - Image: require('Image'), - ListView: require('ListView'), - MapView: require('MapView'), - Modal: require('Modal'), - Navigator: require('Navigator'), - NavigatorIOS: require('NavigatorIOS'), - PickerIOS: require('PickerIOS'), - ProgressBarAndroid: require('ProgressBarAndroid'), - ProgressViewIOS: require('ProgressViewIOS'), - ScrollView: require('ScrollView'), - SegmentedControlIOS: require('SegmentedControlIOS'), - SliderIOS: require('SliderIOS'), - SnapshotViewIOS: require('SnapshotViewIOS'), - Switch: require('Switch'), - PullToRefreshViewAndroid: require('PullToRefreshViewAndroid'), - SwitchAndroid: require('SwitchAndroid'), - SwitchIOS: require('SwitchIOS'), - TabBarIOS: require('TabBarIOS'), - Text: require('Text'), - TextInput: require('TextInput'), - ToastAndroid: require('ToastAndroid'), - ToolbarAndroid: require('ToolbarAndroid'), - Touchable: require('Touchable'), - TouchableHighlight: require('TouchableHighlight'), - TouchableNativeFeedback: require('TouchableNativeFeedback'), - TouchableOpacity: require('TouchableOpacity'), - TouchableWithoutFeedback: require('TouchableWithoutFeedback'), - View: require('View'), - ViewPagerAndroid: require('ViewPagerAndroid'), - WebView: require('WebView'), + get ActivityIndicatorIOS() { return require('ActivityIndicatorIOS'); }, + get ART() { return require('ReactNativeART'); }, + get DatePickerIOS() { return require('DatePickerIOS'); }, + get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); }, + get Image() { return require('Image'); }, + get ListView() { return require('ListView'); }, + get MapView() { return require('MapView'); }, + get Modal() { return require('Modal'); }, + get Navigator() { return require('Navigator'); }, + get NavigatorIOS() { return require('NavigatorIOS'); }, + get PickerIOS() { return require('PickerIOS'); }, + get ProgressBarAndroid() { return require('ProgressBarAndroid'); }, + get ProgressViewIOS() { return require('ProgressViewIOS'); }, + get ScrollView() { return require('ScrollView'); }, + get SegmentedControlIOS() { return require('SegmentedControlIOS'); }, + get SliderIOS() { return require('SliderIOS'); }, + get SnapshotViewIOS() { return require('SnapshotViewIOS'); }, + get Switch() { return require('Switch'); }, + get PullToRefreshViewAndroid() { return require('PullToRefreshViewAndroid'); }, + get SwitchAndroid() { return require('SwitchAndroid'); }, + get SwitchIOS() { return require('SwitchIOS'); }, + get TabBarIOS() { return require('TabBarIOS'); }, + get Text() { return require('Text'); }, + get TextInput() { return require('TextInput'); }, + get ToastAndroid() { return require('ToastAndroid'); }, + get ToolbarAndroid() { return require('ToolbarAndroid'); }, + get Touchable() { return require('Touchable'); }, + get TouchableHighlight() { return require('TouchableHighlight'); }, + get TouchableNativeFeedback() { return require('TouchableNativeFeedback'); }, + get TouchableOpacity() { return require('TouchableOpacity'); }, + get TouchableWithoutFeedback() { return require('TouchableWithoutFeedback'); }, + get View() { return require('View'); }, + get ViewPagerAndroid() { return require('ViewPagerAndroid'); }, + get WebView() { return require('WebView'); }, // APIs - ActionSheetIOS: require('ActionSheetIOS'), - AdSupportIOS: require('AdSupportIOS'), - AlertIOS: require('AlertIOS'), - Animated: require('Animated'), - AppRegistry: require('AppRegistry'), - AppStateIOS: require('AppStateIOS'), - AsyncStorage: require('AsyncStorage'), - BackAndroid: require('BackAndroid'), - CameraRoll: require('CameraRoll'), - Dimensions: require('Dimensions'), - Easing: require('Easing'), - ImagePickerIOS: require('ImagePickerIOS'), - IntentAndroid: require('IntentAndroid'), - InteractionManager: require('InteractionManager'), - LayoutAnimation: require('LayoutAnimation'), - LinkingIOS: require('LinkingIOS'), - NetInfo: require('NetInfo'), - PanResponder: require('PanResponder'), - PixelRatio: require('PixelRatio'), - PushNotificationIOS: require('PushNotificationIOS'), - Settings: require('Settings'), - StatusBarIOS: require('StatusBarIOS'), - StyleSheet: require('StyleSheet'), - UIManager: require('UIManager'), - VibrationIOS: require('VibrationIOS'), + get ActionSheetIOS() { return require('ActionSheetIOS'); }, + get AdSupportIOS() { return require('AdSupportIOS'); }, + get AlertIOS() { return require('AlertIOS'); }, + get Animated() { return require('Animated'); }, + get AppRegistry() { return require('AppRegistry'); }, + get AppStateIOS() { return require('AppStateIOS'); }, + get AsyncStorage() { return require('AsyncStorage'); }, + get BackAndroid() { return require('BackAndroid'); }, + get CameraRoll() { return require('CameraRoll'); }, + get Dimensions() { return require('Dimensions'); }, + get Easing() { return require('Easing'); }, + get ImagePickerIOS() { return require('ImagePickerIOS'); }, + get IntentAndroid() { return require('IntentAndroid'); }, + get InteractionManager() { return require('InteractionManager'); }, + get LayoutAnimation() { return require('LayoutAnimation'); }, + get LinkingIOS() { return require('LinkingIOS'); }, + get NetInfo() { return require('NetInfo'); }, + get PanResponder() { return require('PanResponder'); }, + get PixelRatio() { return require('PixelRatio'); }, + get PushNotificationIOS() { return require('PushNotificationIOS'); }, + get Settings() { return require('Settings'); }, + get StatusBarIOS() { return require('StatusBarIOS'); }, + get StyleSheet() { return require('StyleSheet'); }, + get UIManager() { return require('UIManager'); }, + get VibrationIOS() { return require('VibrationIOS'); }, // Plugins - DeviceEventEmitter: require('RCTDeviceEventEmitter'), - NativeAppEventEmitter: require('RCTNativeAppEventEmitter'), - NativeModules: require('NativeModules'), - Platform: require('Platform'), - processColor: require('processColor'), - requireNativeComponent: require('requireNativeComponent'), + get DeviceEventEmitter() { return require('RCTDeviceEventEmitter'); }, + get NativeAppEventEmitter() { return require('RCTNativeAppEventEmitter'); }, + get NativeModules() { return require('NativeModules'); }, + get Platform() { return require('Platform'); }, + get processColor() { return require('processColor'); }, + get requireNativeComponent() { return require('requireNativeComponent'); }, // Prop Types - EdgeInsetsPropType: require('EdgeInsetsPropType'), - PointPropType: require('PointPropType'), + get EdgeInsetsPropType() { return require('EdgeInsetsPropType'); }, + get PointPropType() { return require('PointPropType'); }, // See http://facebook.github.io/react/docs/addons.html addons: { - LinkedStateMixin: require('LinkedStateMixin'), + get LinkedStateMixin() { return require('LinkedStateMixin'); }, Perf: undefined, - PureRenderMixin: require('ReactComponentWithPureRenderMixin'), - TestModule: require('NativeModules').TestModule, + get PureRenderMixin() { return require('ReactComponentWithPureRenderMixin'); }, + get TestModule() { return require('NativeModules').TestModule; }, TestUtils: undefined, - batchedUpdates: require('ReactUpdates').batchedUpdates, - cloneWithProps: require('cloneWithProps'), - createFragment: require('ReactFragment').create, - update: require('update'), + get batchedUpdates() { return require('ReactUpdates').batchedUpdates; }, + get cloneWithProps() { return require('cloneWithProps'); }, + get createFragment() { return require('ReactFragment').create; }, + get update() { return require('update'); }, }, -}); + + // Note: this must be placed last to prevent eager + // evaluation of the getter-wrapped submodules above + ...require('React'), +}; if (__DEV__) { - ReactNative.addons.Perf = require('ReactDefaultPerf'); - ReactNative.addons.TestUtils = require('ReactTestUtils'); + Object.defineProperty(ReactNative.addons, 'Perf', { + enumerable: true, + get: () => require('ReactDefaultPerf'), + }); + Object.defineProperty(ReactNative.addons, 'TestUtils', { + enumerable: true, + get: () => require('ReactTestUtils'), + }); } module.exports = ReactNative; From 61272e69e935fd96029c922487d1fac0c463c5af Mon Sep 17 00:00:00 2001 From: Sam Swarr Date: Mon, 7 Dec 2015 19:22:52 -0800 Subject: [PATCH 0263/1411] Fix race condition in packager list dependencies Reviewed By: martinbigio Differential Revision: D2732655 fb-gh-sync-id: 6d3d730b499c183d3f6977a30ec37a799cc17c33 --- local-cli/dependencies/dependencies.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js index 54c1b004f8ab..6a8fbb31091d 100644 --- a/local-cli/dependencies/dependencies.js +++ b/local-cli/dependencies/dependencies.js @@ -104,7 +104,9 @@ function _dependencies(argv, config, resolve, reject) { outStream.write(modulePath + '\n'); } }); - writeToFile && outStream.end(); + return writeToFile + ? Promise.denodeify(outStream.end).bind(outStream)() + : Promise.resolve(); // log('Wrote dependencies to output file'); }); })); From 17428982eb5e584a40f1b19d9b6780ee5e13c604 Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Mon, 7 Dec 2015 23:06:04 -0800 Subject: [PATCH 0264/1411] [Docs] Typo fix for adb reverse command in Debugging.md --- docs/Debugging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Debugging.md b/docs/Debugging.md index 26a7ca44703f..259f6ca7d992 100644 --- a/docs/Debugging.md +++ b/docs/Debugging.md @@ -31,7 +31,7 @@ In Chrome, press `⌘ + option + i` or select `View` → `Developer` → `Develo To debug on a real device: 1. On iOS - open the file `RCTWebSocketExecutor.m` and change `localhost` to the IP address of your computer. Shake the device to open the development menu with the option to start debugging. -2. On Android, if you're running Android 5.0+ device connected via USB you can use `adb` command line tool to setup port forwarding from the device to your computer. For that run: `adb reverse 8081 8081` (see [this link](http://developer.android.com/tools/help/adb.html) for help on `adb` command). Alternatively, you can [open dev menu](#debugging-react-native-apps) on the device and select `Dev Settings`, then update `Debug server host for device` setting to the IP address of your computer. +2. On Android, if you're running Android 5.0+ device connected via USB you can use `adb` command line tool to setup port forwarding from the device to your computer. For that run: `adb reverse tcp:8081 tcp:8081` (see [this link](http://developer.android.com/tools/help/adb.html) for help on `adb` command). Alternatively, you can [open dev menu](#debugging-react-native-apps) on the device and select `Dev Settings`, then update `Debug server host for device` setting to the IP address of your computer. #### React Developer Tools (optional) Install the [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) extension for Google Chrome. This will allow you to navigate the component hierarchy via the `React` in the developer tools (see [facebook/react-devtools](https://github.com/facebook/react-devtools) for more information). From 4ce03582a0013e60417dedbf2f760d00e687e540 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 7 Dec 2015 23:08:26 -0800 Subject: [PATCH 0265/1411] Improve Text perf Summary: public Most apps create tons of text components but they are actually quite heavy because of the the Touchable mixin which requires binding tons of functions for every instance created. This diff makes the binding lazy, so that the main handlers are only bound if there is a valid touch action configured (e.g. onPress), and the Touchable mixin functions are only bound the first time the node is actually touched and becomes the responder. ScanLab testing shows 5-10% win on render time and memory for various products. Reviewed By: sebmarkbage Differential Revision: D2716823 fb-gh-sync-id: 30adb2ed2231c5635c9336369616cf31c776b930 --- Libraries/Text/Text.js | 238 +++++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 118 deletions(-) diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index c9ca8357d952..69dc800ded6f 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -11,22 +11,22 @@ */ 'use strict'; -var NativeMethodsMixin = require('NativeMethodsMixin'); -var Platform = require('Platform'); -var React = require('React'); -var ReactInstanceMap = require('ReactInstanceMap'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); -var StyleSheetPropType = require('StyleSheetPropType'); -var TextStylePropTypes = require('TextStylePropTypes'); -var Touchable = require('Touchable'); - -var createReactNativeComponentClass = +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const React = require('React'); +const ReactInstanceMap = require('ReactInstanceMap'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const StyleSheetPropType = require('StyleSheetPropType'); +const TextStylePropTypes = require('TextStylePropTypes'); +const Touchable = require('Touchable'); + +const createReactNativeComponentClass = require('createReactNativeComponentClass'); -var merge = require('merge'); +const merge = require('merge'); -var stylePropType = StyleSheetPropType(TextStylePropTypes); +const stylePropType = StyleSheetPropType(TextStylePropTypes); -var viewConfig = { +const viewConfig = { validAttributes: merge(ReactNativeViewAttributes.UIView, { isHighlighted: true, numberOfLines: true, @@ -68,10 +68,7 @@ var viewConfig = { * ``` */ -var Text = React.createClass({ - - mixins: [Touchable.Mixin, NativeMethodsMixin], - +const Text = React.createClass({ propTypes: { /** * Used to truncate the text with an elipsis after computing the text @@ -105,121 +102,126 @@ var Text = React.createClass({ */ allowFontScaling: React.PropTypes.bool, }, - - viewConfig: viewConfig, - - getInitialState: function(): Object { - return merge(this.touchableGetInitialState(), { - isHighlighted: false, - }); - }, - getDefaultProps: function(): Object { + getDefaultProps(): Object { return { + accessible: true, allowFontScaling: true, }; }, - - onStartShouldSetResponder: function(): bool { - var shouldSetFromProps = this.props.onStartShouldSetResponder && - this.props.onStartShouldSetResponder(); - return shouldSetFromProps || !!this.props.onPress; - }, - - /* - * Returns true to allow responder termination - */ - handleResponderTerminationRequest: function(): bool { - // Allow touchable or props.onResponderTerminationRequest to deny - // the request - var allowTermination = this.touchableHandleResponderTerminationRequest(); - if (allowTermination && this.props.onResponderTerminationRequest) { - allowTermination = this.props.onResponderTerminationRequest(); - } - return allowTermination; - }, - - handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) { - this.touchableHandleResponderGrant(e, dispatchID); - this.props.onResponderGrant && - this.props.onResponderGrant.apply(this, arguments); - }, - - handleResponderMove: function(e: SyntheticEvent) { - this.touchableHandleResponderMove(e); - this.props.onResponderMove && - this.props.onResponderMove.apply(this, arguments); - }, - - handleResponderRelease: function(e: SyntheticEvent) { - this.touchableHandleResponderRelease(e); - this.props.onResponderRelease && - this.props.onResponderRelease.apply(this, arguments); - }, - - handleResponderTerminate: function(e: SyntheticEvent) { - this.touchableHandleResponderTerminate(e); - this.props.onResponderTerminate && - this.props.onResponderTerminate.apply(this, arguments); - }, - - touchableHandleActivePressIn: function() { - if (this.props.suppressHighlighting || !this.props.onPress) { - return; - } - this.setState({ - isHighlighted: true, - }); - }, - - touchableHandleActivePressOut: function() { - if (this.props.suppressHighlighting || !this.props.onPress) { - return; - } - this.setState({ + getInitialState: function(): Object { + return merge(Touchable.Mixin.touchableGetInitialState(), { isHighlighted: false, }); }, - - touchableHandlePress: function() { - this.props.onPress && this.props.onPress(); - }, - - touchableGetPressRectOffset: function(): RectOffset { - return PRESS_RECT_OFFSET; - }, - - getChildContext: function(): Object { + mixins: [NativeMethodsMixin], + viewConfig: viewConfig, + getChildContext(): Object { return {isInAParentText: true}; }, - childContextTypes: { isInAParentText: React.PropTypes.bool }, - - render: function() { - var props = {}; - for (var key in this.props) { - props[key] = this.props[key]; - } - // Text is accessible by default - if (props.accessible !== false) { - props.accessible = true; + contextTypes: { + isInAParentText: React.PropTypes.bool + }, + /** + * Only assigned if touch is needed. + */ + _handlers: (null: ?Object), + /** + * These are assigned lazily the first time the responder is set to make plain + * text nodes as cheap as possible. + */ + touchableHandleActivePressIn: (null: ?Function), + touchableHandleActivePressOut: (null: ?Function), + touchableHandlePress: (null: ?Function), + touchableGetPressRectOffset: (null: ?Function), + render(): ReactElement { + let newProps = this.props; + if (this.props.onStartShouldSetResponder || this.props.onPress) { + if (!this._handlers) { + this._handlers = { + onStartShouldSetResponder: (): bool => { + const shouldSetFromProps = this.props.onStartShouldSetResponder && + this.props.onStartShouldSetResponder(); + const setResponder = shouldSetFromProps || !!this.props.onPress; + if (setResponder && !this.touchableHandleActivePressIn) { + // Attach and bind all the other handlers only the first time a touch + // actually happens. + for (let key in Touchable.Mixin) { + if (typeof Touchable.Mixin[key] === 'function') { + (this: any)[key] = Touchable.Mixin[key].bind(this); + } + } + this.touchableHandleActivePressIn = () => { + if (this.props.suppressHighlighting || !this.props.onPress) { + return; + } + this.setState({ + isHighlighted: true, + }); + }; + + this.touchableHandleActivePressOut = () => { + if (this.props.suppressHighlighting || !this.props.onPress) { + return; + } + this.setState({ + isHighlighted: false, + }); + }; + + this.touchableHandlePress = () => { + this.props.onPress && this.props.onPress(); + }; + + this.touchableGetPressRectOffset = function(): RectOffset { + return PRESS_RECT_OFFSET; + }; + } + return setResponder; + }, + onResponderGrant: (e: SyntheticEvent, dispatchID: string) => { + this.touchableHandleResponderGrant(e, dispatchID); + this.props.onResponderGrant && + this.props.onResponderGrant.apply(this, arguments); + }, + onResponderMove: (e: SyntheticEvent) => { + this.touchableHandleResponderMove(e); + this.props.onResponderMove && + this.props.onResponderMove.apply(this, arguments); + }, + onResponderRelease: (e: SyntheticEvent) => { + this.touchableHandleResponderRelease(e); + this.props.onResponderRelease && + this.props.onResponderRelease.apply(this, arguments); + }, + onResponderTerminate: (e: SyntheticEvent) => { + this.touchableHandleResponderTerminate(e); + this.props.onResponderTerminate && + this.props.onResponderTerminate.apply(this, arguments); + }, + onResponderTerminationRequest: (): bool => { + // Allow touchable or props.onResponderTerminationRequest to deny + // the request + var allowTermination = this.touchableHandleResponderTerminationRequest(); + if (allowTermination && this.props.onResponderTerminationRequest) { + allowTermination = this.props.onResponderTerminationRequest(); + } + return allowTermination; + }, + }; + } + newProps = { + ...this.props, + ...this._handlers, + isHighlighted: this.state.isHighlighted, + }; } - props.isHighlighted = this.state.isHighlighted; - props.onStartShouldSetResponder = this.onStartShouldSetResponder; - props.onResponderTerminationRequest = - this.handleResponderTerminationRequest; - props.onResponderGrant = this.handleResponderGrant; - props.onResponderMove = this.handleResponderMove; - props.onResponderRelease = this.handleResponderRelease; - props.onResponderTerminate = this.handleResponderTerminate; - - // TODO: Switch to use contextTypes and this.context after React upgrade - var context = ReactInstanceMap.get(this)._context; - if (context.isInAParentText) { - return ; + if (this.context.isInAParentText) { + return ; } else { - return ; + return ; } }, }); From c031ccdc57b7a3321c1753f15b2f44053072a102 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 7 Dec 2015 23:23:26 -0800 Subject: [PATCH 0266/1411] JSX style Wrote some notes on the standard JSX / XHP style --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aebef40cc862..fbb114cc3cb0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,14 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe * Do not use the optional parameters of `setTimeout` and `setInterval` * 80 character line length +#### JSX + +* Prefer `'` over `"` for string literal props +* When wrapping opening tags over multiple lines, place one prop per line +* `{}` of props should hug their values (no spaces) +* Place the closing `>` of opening tags on the same line as the last prop +* Place the closing `/>` of self-closing tags on their own line and left-align them wih the opening `<` + #### Objective-C * Space after `@property` declarations From a659c9346ced1540beca151b049207b958bd3135 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 7 Dec 2015 23:24:23 -0800 Subject: [PATCH 0267/1411] [Docs] Fix up small typo in code style docs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbb114cc3cb0..530c84098e85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe * When wrapping opening tags over multiple lines, place one prop per line * `{}` of props should hug their values (no spaces) * Place the closing `>` of opening tags on the same line as the last prop -* Place the closing `/>` of self-closing tags on their own line and left-align them wih the opening `<` +* Place the closing `/>` of self-closing tags on their own line and left-align them with the opening `<` #### Objective-C From dcebe8cd374d149dd9d870f77967554b0d1f3802 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Tue, 8 Dec 2015 01:35:57 -0800 Subject: [PATCH 0268/1411] Use ascii characters to construct JSStringRef Reviewed By: astreet Differential Revision: D2700781 fb-gh-sync-id: be790600ea3d4c0238553efe69a0979c177ddb2d --- ReactAndroid/src/main/jni/react/JSCExecutor.cpp | 3 +-- ReactAndroid/src/main/jni/react/Value.h | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 93bb17fbad13..5f23c2b880df 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -117,9 +117,8 @@ void JSCExecutor::executeApplicationScript( jstring endStringMarker = env->NewStringUTF("executeApplicationScript_endStringConvert"); env->CallStaticVoidMethod(markerClass, logMarkerMethod, startStringMarker); - String jsScript(script.c_str()); + String jsScript = String::createExpectingAscii(script); env->CallStaticVoidMethod(markerClass, logMarkerMethod, endStringMarker); - env->DeleteLocalRef(startStringMarker); env->DeleteLocalRef(endStringMarker); diff --git a/ReactAndroid/src/main/jni/react/Value.h b/ReactAndroid/src/main/jni/react/Value.h index bb10f9a85713..b39bdef304db 100644 --- a/ReactAndroid/src/main/jni/react/Value.h +++ b/ReactAndroid/src/main/jni/react/Value.h @@ -8,6 +8,9 @@ #include #include #include +#if WITH_FBJSCEXTENSIONS +#include +#endif namespace facebook { namespace react { @@ -53,6 +56,14 @@ class String : public noncopyable { return JSStringIsEqualToUTF8CString(m_string.get(), utf8); } + static String createExpectingAscii(std::string const &utf8) { + #if WITH_FBJSCEXTENSIONS + return String(Adopt, JSStringCreateWithUTF8CStringExpectAscii(utf8.c_str(), utf8.size())); + #else + return String(Adopt, JSStringCreateWithUTF8CString(utf8.c_str())); + #endif + } + static String ref(JSStringRef string) { return String(string); } From b67229485834113674b25bda193a7426bf44cfc8 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 8 Dec 2015 03:29:08 -0800 Subject: [PATCH 0269/1411] Added RCTImageSource Summary: public The +[RCTConvert UIImage:] function, while convenient, is inherently limited by being synchronous, which means that it cannot be used to load remote images, and may not be efficient for local images either. It's also unable to access the bridge, which means that it cannot take advantage of the modular image-loading pipeline. This diff introduces a new RCTImageSource class which can be used to pass image source objects over the bridge and defer loading until later. I've also added automatic application of the `resolveAssetSource()` function based on prop type, and fixed up the image logic in NavigatorIOS and TabBarIOS. Reviewed By: javache Differential Revision: D2631541 fb-gh-sync-id: 6604635e8bb5394425102487f1ee7cd729321877 --- .../Components/Navigation/NavigatorIOS.ios.js | 39 ++--- .../Components/TabBarIOS/TabBarItemIOS.ios.js | 28 +--- Libraries/Image/Image.ios.js | 41 ++--- Libraries/Image/RCTImageUtils.m | 11 +- Libraries/Image/RCTImageView.h | 3 +- Libraries/Image/RCTImageView.m | 72 ++++---- Libraries/Image/RCTImageViewManager.m | 18 +- Libraries/Image/RCTShadowVirtualImage.h | 3 +- Libraries/Image/RCTShadowVirtualImage.m | 36 ++-- Libraries/Image/RCTVirtualImageManager.m | 2 +- Libraries/ReactIOS/requireNativeComponent.js | 5 + React/Base/RCTConvert.h | 15 +- React/Base/RCTConvert.m | 156 ++++++++++-------- React/Base/RCTImageSource.h | 43 +++++ React/Base/RCTImageSource.m | 83 ++++++++++ React/Base/RCTLog.m | 3 + React/React.xcodeproj/project.pbxproj | 8 +- React/Views/RCTNavItem.h | 4 +- React/Views/RCTNavItem.m | 20 +-- React/Views/RCTNavItemManager.m | 4 +- React/Views/RCTTabBarItem.h | 4 +- React/Views/RCTTabBarItem.m | 107 ++++++------ React/Views/RCTTabBarItemManager.m | 5 +- 23 files changed, 434 insertions(+), 276 deletions(-) create mode 100644 React/Base/RCTImageSource.h create mode 100644 React/Base/RCTImageSource.m diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 6d86930b7a6f..87f49232bffe 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -312,6 +312,12 @@ var NavigatorIOS = React.createClass({ this.navigationContext = new NavigationContext(); }, + getDefaultProps: function(): Object { + return { + translucent: true, + }; + }, + getInitialState: function(): State { return { idStack: [getuid()], @@ -591,37 +597,26 @@ var NavigatorIOS = React.createClass({ }, _routeToStackItem: function(route: Route, i: number) { - var Component = route.component; - var shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond != null && + var {component, wrapperStyle, passProps, ...route} = route; + var {itemWrapperStyle, ...props} = this.props; + var shouldUpdateChild = + this.state.updatingAllIndicesAtOrBeyond && this.state.updatingAllIndicesAtOrBeyond >= i; - + var Component = component; return ( + itemWrapperStyle, + wrapperStyle + ]}> diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index 20e0cf624066..f3fb6964ca7f 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -16,7 +16,6 @@ var React = require('React'); var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); var View = require('View'); -var resolveAssetSource = require('resolveAssetSource'); var requireNativeComponent = require('requireNativeComponent'); @@ -52,10 +51,7 @@ var TabBarItemIOS = React.createClass({ /** * A custom icon for the tab. It is ignored when a system icon is defined. */ - icon: React.PropTypes.oneOfType([ - React.PropTypes.string, - Image.propTypes.source, - ]), + icon: Image.propTypes.source, /** * A custom icon when the tab is selected. It is ignored when a system * icon is defined. If left empty, the icon will be tinted in blue. @@ -101,29 +97,23 @@ var TabBarItemIOS = React.createClass({ }, render: function() { - var tabContents = null; + var {style, children, ...props} = this.props; + // if the tab has already been shown once, always continue to show it so we // preserve state between tab transitions if (this.state.hasBeenSelected) { - tabContents = + var tabContents = - {this.props.children} + {children} ; } else { - tabContents = ; + var tabContents = ; } - - var badge = typeof this.props.badge === 'number' ? - '' + this.props.badge : - this.props.badge; - + return ( + {...props} + style={[styles.tab, style]}> {tabContents} ); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 6943bf909e33..2b649bf9cc6f 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -69,13 +69,16 @@ var Image = React.createClass({ PropTypes.number, ]), /** - * A static image to display while downloading the final image off the - * network. + * A static image to display while loading the image source. * @platform ios */ - defaultSource: PropTypes.shape({ - uri: PropTypes.string, - }), + defaultSource: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string, + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, + ]), /** * When true, indicates the image is an accessibility element. * @platform ios @@ -155,23 +158,10 @@ var Image = React.createClass({ }, render: function() { - for (var prop in cfg.nativeOnly) { - if (this.props[prop] !== undefined) { - console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' + - 'not be set directly on Image.'); - } - } var source = resolveAssetSource(this.props.source) || {}; - var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {}; - var {width, height} = source; var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {}; - if (source.uri === '') { - console.warn('source.uri should not be an empty string'); - return ; - } - var isNetwork = source.uri && source.uri.match(/^https?:/); var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView; var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108 @@ -192,8 +182,7 @@ var Image = React.createClass({ style={style} resizeMode={resizeMode} tintColor={tintColor} - src={source.uri} - defaultImageSrc={defaultSource.uri} + source={source} /> ); } @@ -206,16 +195,8 @@ var styles = StyleSheet.create({ }, }); -var cfg = { - nativeOnly: { - src: true, - defaultImageSrc: true, - imageTag: true, - progressHandlerRegistered: true, - }, -}; -var RCTImageView = requireNativeComponent('RCTImageView', Image, cfg); -var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image, cfg) : RCTImageView; +var RCTImageView = requireNativeComponent('RCTImageView', Image); +var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView; var RCTVirtualImage = requireNativeComponent('RCTVirtualImage', Image); module.exports = Image; diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index 3146a5b5611d..a6c2ccd46098 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -231,6 +231,11 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale) if (CGSizeEqualToSize(destSize, CGSizeZero)) { destSize = sourceSize; + if (!destScale) { + destScale = 1; + } + } else if (!destScale) { + destScale = RCTScreenScale(); } // calculate target size @@ -253,13 +258,9 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale) return nil; } - // adjust scale - size_t actualWidth = CGImageGetWidth(imageRef); - CGFloat scale = actualWidth / targetSize.width * destScale; - // return image UIImage *image = [UIImage imageWithCGImage:imageRef - scale:scale + scale:destScale orientation:UIImageOrientationUp]; CGImageRelease(imageRef); return image; diff --git a/Libraries/Image/RCTImageView.h b/Libraries/Image/RCTImageView.h index a561c0f7f938..8b5b993a731a 100644 --- a/Libraries/Image/RCTImageView.h +++ b/Libraries/Image/RCTImageView.h @@ -11,6 +11,7 @@ #import "RCTImageComponent.h" @class RCTBridge; +@class RCTImageSource; @interface RCTImageView : UIImageView @@ -19,6 +20,6 @@ @property (nonatomic, assign) UIEdgeInsets capInsets; @property (nonatomic, strong) UIImage *defaultImage; @property (nonatomic, assign) UIImageRenderingMode renderingMode; -@property (nonatomic, copy) NSString *src; +@property (nonatomic, strong) RCTImageSource *source; @end diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 246220840614..656dd61e896e 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -13,6 +13,7 @@ #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTImageLoader.h" +#import "RCTImageSource.h" #import "RCTImageUtils.h" #import "RCTUtils.h" @@ -107,6 +108,12 @@ - (void)setCapInsets:(UIEdgeInsets)capInsets } } +- (void)setTintColor:(UIColor *)tintColor +{ + super.tintColor = tintColor; + self.renderingMode = tintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal; +} + - (void)setRenderingMode:(UIImageRenderingMode)renderingMode { if (_renderingMode != renderingMode) { @@ -115,28 +122,29 @@ - (void)setRenderingMode:(UIImageRenderingMode)renderingMode } } -- (void)setSrc:(NSString *)src +- (void)setSource:(RCTImageSource *)source { - if (![src isEqual:_src]) { - _src = [src copy]; + if (![source isEqual:_source]) { + _source = source; [self reloadImage]; } } -+ (BOOL)srcNeedsReload:(NSString *)src +- (BOOL)sourceNeedsReload { + NSString *scheme = _source.imageURL.scheme; return - [src hasPrefix:@"http://"] || - [src hasPrefix:@"https://"] || - [src hasPrefix:@"assets-library://"] || - [src hasPrefix:@"ph://"]; + [scheme isEqualToString:@"http"] || + [scheme isEqualToString:@"https"] || + [scheme isEqualToString:@"assets-library"] || + [scheme isEqualToString:@"ph"]; } - (void)setContentMode:(UIViewContentMode)contentMode { if (self.contentMode != contentMode) { super.contentMode = contentMode; - if ([RCTImageView srcNeedsReload:_src]) { + if ([self sourceNeedsReload]) { [self reloadImage]; } } @@ -162,7 +170,7 @@ - (void)reloadImage { [self cancelImageLoad]; - if (_src && self.frame.size.width > 0 && self.frame.size.height > 0) { + if (_source && self.frame.size.width > 0 && self.frame.size.height > 0) { if (_onLoadStart) { _onLoadStart(nil); } @@ -177,31 +185,37 @@ - (void)reloadImage }; } - _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_src + RCTImageSource *source = _source; + __weak RCTImageView *weakSelf = self; + _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString size:self.bounds.size scale:RCTScreenScale() resizeMode:self.contentMode progressBlock:progressHandler completionBlock:^(NSError *error, UIImage *image) { - if (error) { - if (_onError) { - _onError(@{ @"error": error.localizedDescription }); - } - } else { - if (_onLoad) { - _onLoad(nil); - } - } - if (_onLoadEnd) { - _onLoadEnd(nil); - } - dispatch_async(dispatch_get_main_queue(), ^{ + RCTImageView *strongSelf = weakSelf; + if (![source isEqual:strongSelf.source]) { + // Bail out if source has changed since we started loading + return; + } if (image.reactKeyframeAnimation) { - [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"]; + [strongSelf.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"]; } else { - [self.layer removeAnimationForKey:@"contents"]; - self.image = image; + [strongSelf.layer removeAnimationForKey:@"contents"]; + strongSelf.image = image; + } + if (error) { + if (strongSelf->_onError) { + strongSelf->_onError(@{ @"error": error.localizedDescription }); + } + } else { + if (strongSelf->_onLoad) { + strongSelf->_onLoad(nil); + } + } + if (strongSelf->_onLoadEnd) { + strongSelf->_onLoadEnd(nil); } }); }]; @@ -217,13 +231,13 @@ - (void)reactSetFrame:(CGRect)frame if (!self.image || self.image == _defaultImage) { _targetSize = frame.size; [self reloadImage]; - } else if ([RCTImageView srcNeedsReload:_src]) { + } else if ([self sourceNeedsReload]) { CGSize imageSize = self.image.size; CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, RCTScreenScale(), self.contentMode, YES); if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) { if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) { - RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _src, NSStringFromCGSize(idealSize)); + RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.imageURL, NSStringFromCGSize(idealSize)); // If the existing image or an image being loaded are not the right size, reload the asset in case there is a // better size available. diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index bcf2f0bdb4d0..c2067889539d 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -12,6 +12,7 @@ #import #import "RCTConvert.h" +#import "RCTImageSource.h" #import "RCTImageView.h" @implementation RCTImageViewManager @@ -24,23 +25,14 @@ - (UIView *)view } RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) -RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) -RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) -RCT_EXPORT_VIEW_PROPERTY(src, NSString) +RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage) RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock) -RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) -{ - if (json) { - view.renderingMode = UIImageRenderingModeAlwaysTemplate; - view.tintColor = [RCTConvert UIColor:json]; - } else { - view.renderingMode = defaultView.renderingMode; - view.tintColor = defaultView.tintColor; - } -} +RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) +RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource) +RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) @end diff --git a/Libraries/Image/RCTShadowVirtualImage.h b/Libraries/Image/RCTShadowVirtualImage.h index 841c215e2b4f..bd8bb51ae024 100644 --- a/Libraries/Image/RCTShadowVirtualImage.h +++ b/Libraries/Image/RCTShadowVirtualImage.h @@ -9,6 +9,7 @@ #import "RCTShadowView.h" #import "RCTImageComponent.h" +#import "RCTImageSource.h" @class RCTBridge; @@ -20,6 +21,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge; -@property (nonatomic, copy) NSDictionary *source; +@property (nonatomic, strong) RCTImageSource *source; @end diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m index 431fbac07afe..f3486f9a2f7a 100644 --- a/Libraries/Image/RCTShadowVirtualImage.m +++ b/Libraries/Image/RCTShadowVirtualImage.m @@ -16,6 +16,7 @@ @implementation RCTShadowVirtualImage { RCTBridge *_bridge; + RCTImageLoaderCancellationBlock _cancellationBlock; } @synthesize image = _image; @@ -30,23 +31,31 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge RCT_NOT_IMPLEMENTED(-(instancetype)init) -- (void)setSource:(NSDictionary *)source +- (void)setSource:(RCTImageSource *)source { if (![source isEqual:_source]) { - _source = [source copy]; - NSString *imageTag = [RCTConvert NSString:_source[@"uri"]]; - CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1; + + // Cancel previous request + if (_cancellationBlock) { + _cancellationBlock(); + } + + _source = source; __weak RCTShadowVirtualImage *weakSelf = self; - [_bridge.imageLoader loadImageWithTag:imageTag - size:CGSizeZero - scale:scale - resizeMode:UIViewContentModeScaleToFill - progressBlock:nil - completionBlock:^(NSError *error, UIImage *image) { + _cancellationBlock = [_bridge.imageLoader loadImageWithTag:source.imageURL.absoluteString + size:source.size + scale:source.scale + resizeMode:UIViewContentModeScaleToFill + progressBlock:nil + completionBlock:^(NSError *error, UIImage *image) { dispatch_async(_bridge.uiManager.methodQueue, ^{ RCTShadowVirtualImage *strongSelf = weakSelf; + if (![source isEqual:strongSelf.source]) { + // Bail out if source has changed since we started loading + return; + } strongSelf->_image = image; [strongSelf dirtyText]; }); @@ -54,4 +63,11 @@ - (void)setSource:(NSDictionary *)source } } +- (void)dealloc +{ + if (_cancellationBlock) { + _cancellationBlock(); + } +} + @end diff --git a/Libraries/Image/RCTVirtualImageManager.m b/Libraries/Image/RCTVirtualImageManager.m index 2800a92b0d8b..d497026ab740 100644 --- a/Libraries/Image/RCTVirtualImageManager.m +++ b/Libraries/Image/RCTVirtualImageManager.m @@ -19,6 +19,6 @@ - (RCTShadowView *)shadowView return [[RCTShadowVirtualImage alloc] initWithBridge:self.bridge]; } -RCT_EXPORT_SHADOW_PROPERTY(source, NSDictionary) +RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource) @end diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js index a046fcbd1498..84789e6cf61d 100644 --- a/Libraries/ReactIOS/requireNativeComponent.js +++ b/Libraries/ReactIOS/requireNativeComponent.js @@ -16,10 +16,12 @@ var UIManager = require('UIManager'); var UnimplementedView = require('UnimplementedView'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); + var insetsDiffer = require('insetsDiffer'); var pointsDiffer = require('pointsDiffer'); var matricesDiffer = require('matricesDiffer'); var processColor = require('processColor'); +var resolveAssetSource = require('resolveAssetSource'); var sizesDiffer = require('sizesDiffer'); var verifyPropTypes = require('verifyPropTypes'); var warning = require('warning'); @@ -110,6 +112,9 @@ var TypeToProcessorMap = { CGColorArray: processColor, UIColor: processColor, UIColorArray: processColor, + CGImage: resolveAssetSource, + UIImage: resolveAssetSource, + RCTImageSource: resolveAssetSource, // Android Types Color: processColor, }; diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 3d1c4291d11a..6347cc9cab70 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -85,9 +85,6 @@ typedef NSURL RCTFileURL; + (UIColor *)UIColor:(id)json; + (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED; -+ (UIImage *)UIImage:(id)json; -+ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED; - + (UIFont *)UIFont:(id)json; + (UIFont *)UIFont:(UIFont *)font withSize:(id)json; + (UIFont *)UIFont:(UIFont *)font withWeight:(id)json; @@ -146,6 +143,18 @@ typedef BOOL css_clip_t, css_backface_visibility_t; @end +@interface RCTConvert (Deprecated) + +/** + * Synchronous image loading is generally a bad idea for performance reasons. + * If you need to pass image references, try to use `RCTImageSource` and then + * `RCTImageLoader` instead of converting directly to a UIImage. + */ ++ (UIImage *)UIImage:(id)json; ++ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED; + +@end + /** * Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these. */ diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index b2c033b38dbc..244fe6c0af29 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -12,6 +12,7 @@ #import #import "RCTDefines.h" +#import "RCTImageSource.h" #import "RCTUtils.h" @implementation RCTConvert @@ -421,7 +422,7 @@ + (UIColor *)UIColor:(id)json CGFloat b = (argb & 0xFF) / 255.0; return [UIColor colorWithRed:r green:g blue:b alpha:a]; } else { - RCTLogConvertError(json, @"a color"); + RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?"); return nil; } } @@ -431,78 +432,6 @@ + (CGColorRef)CGColor:(id)json return [self UIColor:json].CGColor; } -/* This method is only used when loading images synchronously, e.g. for tabbar icons */ -+ (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) { - return nil; - } - - __block UIImage *image; - if (![NSThread isMainThread]) { - // It seems that none of the UIImage loading methods can be guaranteed - // thread safe, so we'll pick the lesser of two evils here and block rather - // than run the risk of crashing - RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended"); - dispatch_sync(dispatch_get_main_queue(), ^{ - image = [self UIImage:json]; - }); - return image; - } - - NSString *path; - CGFloat scale = 0.0; - BOOL isPackagerAsset = NO; - if ([json isKindOfClass:[NSString class]]) { - path = json; - } else if ([json isKindOfClass:[NSDictionary class]]) { - if (!(path = [self NSString:json[@"uri"]])) { - return nil; - } - scale = [self CGFloat:json[@"scale"]]; - isPackagerAsset = [self BOOL:json[@"__packager_asset"]]; - } else { - RCTLogConvertError(json, @"an image"); - return nil; - } - - NSURL *URL = [self NSURL:path]; - NSString *scheme = URL.scheme.lowercaseString; - if ([scheme isEqualToString:@"file"]) { - NSString *assetName = RCTBundlePathForURL(URL); - image = [UIImage imageNamed:assetName]; - if (!image) { - // Attempt to load from the file system - NSString *filePath = URL.path; - if (filePath.pathExtension.length == 0) { - filePath = [filePath stringByAppendingPathExtension:@"png"]; - } - image = [UIImage imageWithContentsOfFile:filePath]; - } - } else if ([scheme isEqualToString:@"data"]) { - image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; - } else if ([scheme isEqualToString:@"http"] && isPackagerAsset) { - image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; - } else { - RCTLogConvertError(json, @"an image. Only local files or data URIs are supported"); - } - - if (scale > 0) { - image = [UIImage imageWithCGImage:image.CGImage - scale:scale - orientation:image.imageOrientation]; - } - return image; -} - -+ (CGImageRef)CGImage:(id)json -{ - return [self UIImage:json].CGImage; -} - #if !defined(__IPHONE_8_2) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_2 // These constants are defined in iPhone SDK 8.2, but the app cannot run on @@ -860,3 +789,84 @@ + (NSPropertyList)NSPropertyList:(id)json }), RCTAnimationTypeEaseInEaseOut, integerValue) @end + +@interface RCTImageSource (Packager) + +@property (nonatomic, assign) BOOL packagerAsset; + +@end + +@implementation RCTConvert (Deprecated) + +/* This method is only used when loading images synchronously, e.g. for tabbar icons */ ++ (UIImage *)UIImage:(id)json +{ + if (!json) { + return nil; + } + + RCTImageSource *imageSource = [self RCTImageSource:json]; + if (!imageSource) { + return nil; + } + + __block UIImage *image; + if (![NSThread isMainThread]) { + // It seems that none of the UIImage loading methods can be guaranteed + // thread safe, so we'll pick the lesser of two evils here and block rather + // than run the risk of crashing + RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended"); + dispatch_sync(dispatch_get_main_queue(), ^{ + image = [self UIImage:json]; + }); + return image; + } + + NSURL *URL = imageSource.imageURL; + NSString *scheme = URL.scheme.lowercaseString; + if ([scheme isEqualToString:@"file"]) { + NSString *assetName = RCTBundlePathForURL(URL); + image = [UIImage imageNamed:assetName]; + if (!image) { + // Attempt to load from the file system + NSString *filePath = URL.path; + if (filePath.pathExtension.length == 0) { + filePath = [filePath stringByAppendingPathExtension:@"png"]; + } + image = [UIImage imageWithContentsOfFile:filePath]; + } + } else if ([scheme isEqualToString:@"data"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; + } else if ([scheme isEqualToString:@"http"] && imageSource.packagerAsset) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; + } else { + RCTLogConvertError(json, @"an image. Only local files or data URIs are supported"); + } + + CGFloat scale = imageSource.scale; + if (!scale && imageSource.size.width) { + // If no scale provided, set scale to image width / source width + scale = CGImageGetWidth(image.CGImage) / imageSource.size.width; + } + + if (scale) { + image = [UIImage imageWithCGImage:image.CGImage + scale:scale + orientation:image.imageOrientation]; + } + + if (!CGSizeEqualToSize(imageSource.size, CGSizeZero) && + !CGSizeEqualToSize(imageSource.size, image.size)) { + RCTLogError(@"Image source size %@ does not match loaded image size %@.", + NSStringFromCGSize(imageSource.size), NSStringFromCGSize(image.size)); + } + + return image; +} + ++ (CGImageRef)CGImage:(id)json +{ + return [self UIImage:json].CGImage; +} + +@end diff --git a/React/Base/RCTImageSource.h b/React/Base/RCTImageSource.h new file mode 100644 index 000000000000..efcefaa2b89f --- /dev/null +++ b/React/Base/RCTImageSource.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "RCTConvert.h" + +/** + * Object containing an image URL and associated metadata. + */ +@interface RCTImageSource : NSObject + +@property (nonatomic, strong, readonly) NSURL *imageURL; +@property (nonatomic, assign, readonly) CGSize size; +@property (nonatomic, assign, readonly) CGFloat scale; + +/** + * Create a new image source object. + * Pass a size of CGSizeZero if you do not know or wish to specify the image + * size. Pass a scale of zero if you do not know or wish to specify the scale. + */ +- (instancetype)initWithURL:(NSURL *)url + size:(CGSize)size + scale:(CGFloat)scale; + +/** + * Create a copy of the image source with the specified size and scale. + */ +- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale; + +@end + +@interface RCTConvert (ImageSource) + ++ (RCTImageSource *)RCTImageSource:(id)json; + +@end diff --git a/React/Base/RCTImageSource.m b/React/Base/RCTImageSource.m new file mode 100644 index 000000000000..3392385f8134 --- /dev/null +++ b/React/Base/RCTImageSource.m @@ -0,0 +1,83 @@ +/** + * 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 "RCTImageSource.h" + +@interface RCTImageSource () + +@property (nonatomic, assign) BOOL packagerAsset; + +@end + +@implementation RCTImageSource + +- (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale +{ + if ((self = [super init])) { + _imageURL = url; + _size = size; + _scale = scale; + } + return self; +} + +- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale +{ + RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:_imageURL + size:size + scale:scale]; + imageSource.packagerAsset = _packagerAsset; + return imageSource; +} + +- (BOOL)isEqual:(RCTImageSource *)object +{ + if (![object isKindOfClass:[RCTImageSource class]]) { + return NO; + } + return [_imageURL isEqual:object.imageURL] && _scale == object.scale && + (CGSizeEqualToSize(_size, object.size) || CGSizeEqualToSize(object.size, CGSizeZero)); +} + +@end + +@implementation RCTConvert (ImageSource) + ++ (RCTImageSource *)RCTImageSource:(id)json +{ + if (!json) { + return nil; + } + + NSURL *imageURL; + CGSize size = CGSizeZero; + CGFloat scale = 1.0; + BOOL packagerAsset = NO; + if ([json isKindOfClass:[NSDictionary class]]) { + if (!(imageURL = [self NSURL:json[@"uri"]])) { + return nil; + } + size = [self CGSize:json]; + scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0; + packagerAsset = [self BOOL:json[@"__packager_asset"]]; + } else if ([json isKindOfClass:[NSString class]]) { + imageURL = [self NSURL:json]; + } else { + RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?"); + return nil; + } + + RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL + size:size + scale:scale]; + imageSource.packagerAsset = packagerAsset; + return imageSource; +} + +@end diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 86392db1f999..f09aff78b93f 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -206,6 +206,7 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb } #if RCT_DEBUG + // Log to red box in debug mode. if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { NSArray *stackSymbols = [NSThread callStackSymbols]; @@ -233,7 +234,9 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb // Log to JS executor [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"]; + #endif + } } diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 6d671d58b4b4..53a11ed1d6d8 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; }; 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; }; 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; + 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; }; 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; @@ -173,7 +174,9 @@ 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = ""; }; 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; }; 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; }; - 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; }; + 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; }; + 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; + 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; }; 13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = ""; }; 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; }; 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; }; @@ -526,6 +529,8 @@ 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */, 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */, 83CBBA501A601E3B00E9B192 /* RCTUtils.m */, + 13BB3D001BECD54500932C10 /* RCTImageSource.h */, + 13BB3D011BECD54500932C10 /* RCTImageSource.m */, ); path = Base; sourceTree = ""; @@ -645,6 +650,7 @@ 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */, 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */, + 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */, 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */, 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */, 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */, diff --git a/React/Views/RCTNavItem.h b/React/Views/RCTNavItem.h index 418d5c5c97cf..fdfeb66e4f98 100644 --- a/React/Views/RCTNavItem.h +++ b/React/Views/RCTNavItem.h @@ -31,7 +31,7 @@ @property (nonatomic, readonly) UIBarButtonItem *leftButtonItem; @property (nonatomic, readonly) UIBarButtonItem *rightButtonItem; -@property (nonatomic, copy) RCTBubblingEventBlock onNavLeftButtonTap; -@property (nonatomic, copy) RCTBubblingEventBlock onNavRightButtonTap; +@property (nonatomic, copy) RCTBubblingEventBlock onLeftButtonPress; +@property (nonatomic, copy) RCTBubblingEventBlock onRightButtonPress; @end diff --git a/React/Views/RCTNavItem.m b/React/Views/RCTNavItem.m index 5e4043f8495f..5fa8005c94f9 100644 --- a/React/Views/RCTNavItem.m +++ b/React/Views/RCTNavItem.m @@ -67,14 +67,14 @@ - (UIBarButtonItem *)leftButtonItem [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon style:UIBarButtonItemStylePlain target:self - action:@selector(handleNavLeftButtonTapped)]; + action:@selector(handleLeftButtonPress)]; } else if (_leftButtonTitle.length) { _leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle style:UIBarButtonItemStylePlain target:self - action:@selector(handleNavLeftButtonTapped)]; + action:@selector(handleLeftButtonPress)]; } else { _leftButtonItem = nil; } @@ -82,10 +82,10 @@ - (UIBarButtonItem *)leftButtonItem return _leftButtonItem; } -- (void)handleNavLeftButtonTapped +- (void)handleLeftButtonPress { - if (_onNavLeftButtonTap) { - _onNavLeftButtonTap(nil); + if (_onLeftButtonPress) { + _onLeftButtonPress(nil); } } @@ -109,14 +109,14 @@ - (UIBarButtonItem *)rightButtonItem [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon style:UIBarButtonItemStylePlain target:self - action:@selector(handleNavRightButtonTapped)]; + action:@selector(handleRightButtonPress)]; } else if (_rightButtonTitle.length) { _rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle style:UIBarButtonItemStylePlain target:self - action:@selector(handleNavRightButtonTapped)]; + action:@selector(handleRightButtonPress)]; } else { _rightButtonItem = nil; } @@ -124,10 +124,10 @@ - (UIBarButtonItem *)rightButtonItem return _rightButtonItem; } -- (void)handleNavRightButtonTapped +- (void)handleRightButtonPress { - if (_onNavRightButtonTap) { - _onNavRightButtonTap(nil); + if (_onRightButtonPress) { + _onRightButtonPress(nil); } } diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m index 040b7ffceac5..be4f879dde6d 100644 --- a/React/Views/RCTNavItemManager.m +++ b/React/Views/RCTNavItemManager.m @@ -39,7 +39,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage) RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString) -RCT_EXPORT_VIEW_PROPERTY(onNavLeftButtonTap, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onNavRightButtonTap, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onLeftButtonPress, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onRightButtonPress, RCTBubblingEventBlock) @end diff --git a/React/Views/RCTTabBarItem.h b/React/Views/RCTTabBarItem.h index def1abf6c347..cee2df982e47 100644 --- a/React/Views/RCTTabBarItem.h +++ b/React/Views/RCTTabBarItem.h @@ -13,7 +13,9 @@ @interface RCTTabBarItem : UIView -@property (nonatomic, copy) id icon; +@property (nonatomic, copy) id /* NSString or NSNumber */ badge; +@property (nonatomic, strong) UIImage *icon; +@property (nonatomic, assign) UITabBarSystemItem systemIcon; @property (nonatomic, assign, getter=isSelected) BOOL selected; @property (nonatomic, readonly) UITabBarItem *barItem; @property (nonatomic, copy) RCTBubblingEventBlock onPress; diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m index 8699ff96d71a..a05ae75ee0af 100644 --- a/React/Views/RCTTabBarItem.m +++ b/React/Views/RCTTabBarItem.m @@ -13,73 +13,78 @@ #import "RCTLog.h" #import "UIView+React.h" +@implementation RCTConvert (UITabBarSystemItem) + +RCT_ENUM_CONVERTER(UITabBarSystemItem, (@{ + @"bookmarks": @(UITabBarSystemItemBookmarks), + @"contacts": @(UITabBarSystemItemContacts), + @"downloads": @(UITabBarSystemItemDownloads), + @"favorites": @(UITabBarSystemItemFavorites), + @"featured": @(UITabBarSystemItemFeatured), + @"history": @(UITabBarSystemItemHistory), + @"more": @(UITabBarSystemItemMore), + @"most-recent": @(UITabBarSystemItemMostRecent), + @"most-viewed": @(UITabBarSystemItemMostViewed), + @"recents": @(UITabBarSystemItemRecents), + @"search": @(UITabBarSystemItemSearch), + @"top-rated": @(UITabBarSystemItemTopRated), +}), NSNotFound, integerValue) + +@end + @implementation RCTTabBarItem @synthesize barItem = _barItem; +- (instancetype)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + _systemIcon = NSNotFound; + } + return self; +} + - (UITabBarItem *)barItem { if (!_barItem) { _barItem = [UITabBarItem new]; + _systemIcon = NSNotFound; } return _barItem; } -- (void)setIcon:(id)icon +- (void)setBadge:(id)badge { - static NSDictionary *systemIcons; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - systemIcons = @{ - @"bookmarks": @(UITabBarSystemItemBookmarks), - @"contacts": @(UITabBarSystemItemContacts), - @"downloads": @(UITabBarSystemItemDownloads), - @"favorites": @(UITabBarSystemItemFavorites), - @"featured": @(UITabBarSystemItemFeatured), - @"history": @(UITabBarSystemItemHistory), - @"more": @(UITabBarSystemItemMore), - @"most-recent": @(UITabBarSystemItemMostRecent), - @"most-viewed": @(UITabBarSystemItemMostViewed), - @"recents": @(UITabBarSystemItemRecents), - @"search": @(UITabBarSystemItemSearch), - @"top-rated": @(UITabBarSystemItemTopRated), - }; - }); - - // Update icon - BOOL wasSystemIcon = (systemIcons[_icon] != nil); - _icon = [icon copy]; + _badge = [badge copy]; + self.barItem.badgeValue = [badge description]; +} - // Check if string matches any custom images first - UIImage *image = [RCTConvert UIImage:_icon]; - UITabBarItem *oldItem = _barItem; - if (image) { - // Recreate barItem if previous item was a system icon. Calling self.barItem - // creates a new instance if it wasn't set yet. - if (wasSystemIcon) { - _barItem = nil; - self.barItem.image = image; - } else { - self.barItem.image = image; - return; - } - } else if ([icon isKindOfClass:[NSString class]] && [icon length] > 0) { - // Not a custom image, may be a system item? - NSNumber *systemIcon = systemIcons[icon]; - if (!systemIcon) { - RCTLogError(@"The tab bar icon '%@' did not match any known image or system icon", icon); - return; - } - _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemIcon.integerValue tag:oldItem.tag]; - } else { - self.barItem.image = nil; +- (void)setSystemIcon:(UITabBarSystemItem)systemIcon +{ + if (_systemIcon != systemIcon) { + _systemIcon = systemIcon; + UITabBarItem *oldItem = _barItem; + _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:_systemIcon + tag:oldItem.tag]; + _barItem.title = oldItem.title; + _barItem.imageInsets = oldItem.imageInsets; + _barItem.badgeValue = oldItem.badgeValue; } +} - // Reapply previous properties - _barItem.title = oldItem.title; - _barItem.imageInsets = oldItem.imageInsets; - _barItem.selectedImage = oldItem.selectedImage; - _barItem.badgeValue = oldItem.badgeValue; +- (void)setIcon:(UIImage *)icon +{ + _icon = icon; + if (_icon && _systemIcon != NSNotFound) { + _systemIcon = NSNotFound; + UITabBarItem *oldItem = _barItem; + _barItem = [UITabBarItem new]; + _barItem.title = oldItem.title; + _barItem.imageInsets = oldItem.imageInsets; + _barItem.selectedImage = oldItem.selectedImage; + _barItem.badgeValue = oldItem.badgeValue; + } + self.barItem.image = _icon; } - (UIViewController *)reactViewController diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m index a926a54f343f..d92e3a2d95e9 100644 --- a/React/Views/RCTTabBarItemManager.m +++ b/React/Views/RCTTabBarItemManager.m @@ -21,10 +21,11 @@ - (UIView *)view return [RCTTabBarItem new]; } +RCT_EXPORT_VIEW_PROPERTY(badge, id /* NSString or NSNumber */) RCT_EXPORT_VIEW_PROPERTY(selected, BOOL) -RCT_EXPORT_VIEW_PROPERTY(icon, id) +RCT_EXPORT_VIEW_PROPERTY(icon, UIImage) RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage) -RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString) +RCT_EXPORT_VIEW_PROPERTY(systemIcon, UITabBarSystemItem) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem) { From 5aef380609925922fa23f9fa12b46c8af0f9f3ef Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 8 Dec 2015 04:53:17 -0800 Subject: [PATCH 0270/1411] Refactor NetInfo Reviewed By: foghina Differential Revision: D2718059 fb-gh-sync-id: 5c4e946991be27c5ae2bcc3eec32b5bc596545bd --- .../modules/netinfo/ConnectivityModule.java | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java index 00414d86a688..acf6060b1af5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java @@ -2,8 +2,6 @@ package com.facebook.react.modules.netinfo; -import javax.annotation.Nullable; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -13,11 +11,10 @@ import android.support.v4.net.ConnectivityManagerCompat; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; @@ -34,27 +31,26 @@ public class ConnectivityModule extends ReactContextBaseJavaModule private final ConnectivityManager mConnectivityManager; private final ConnectivityManagerCompat mConnectivityManagerCompat; + private final ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver; - private String mConnectivity; - private @Nullable ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver; + private String mConnectivity = ""; public ConnectivityModule(ReactApplicationContext reactContext) { super(reactContext); mConnectivityManager = (ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE); mConnectivityManagerCompat = new ConnectivityManagerCompat(); - mConnectivity = ""; + mConnectivityBroadcastReceiver = new ConnectivityBroadcastReceiver(); } @Override public void onHostResume() { - maybeRegisterReceiver(); - updateAndSendConnectionType(); + registerReceiver(); } @Override public void onHostPause() { - maybeUnregisterReceiver(); + unregisterReceiver(); } @Override @@ -64,13 +60,6 @@ public void onHostDestroy() { @Override public void initialize() { getReactApplicationContext().addLifecycleEventListener(this); - maybeRegisterReceiver(); - updateAndSendConnectionType(); - } - - @Override - public void onCatalystInstanceDestroy() { - maybeUnregisterReceiver(); } @Override @@ -88,23 +77,14 @@ public void isConnectionMetered(Callback successCallback) { successCallback.invoke(mConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager)); } - private void maybeRegisterReceiver() { - if (mConnectivityBroadcastReceiver != null) { - return; - } - mConnectivityBroadcastReceiver = new ConnectivityBroadcastReceiver(); + private void registerReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); getReactApplicationContext().registerReceiver(mConnectivityBroadcastReceiver, filter); } - private void maybeUnregisterReceiver() { - if (mConnectivityBroadcastReceiver == null) { - return; - } + private void unregisterReceiver() { getReactApplicationContext().unregisterReceiver(mConnectivityBroadcastReceiver); - mConnectivityBroadcastReceiver = null; - mConnectivity = ""; } private void updateAndSendConnectionType() { From e7a4b20d7567056ae6e718be90a29b4035158f7e Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Tue, 8 Dec 2015 06:10:58 -0800 Subject: [PATCH 0271/1411] exceptions generated from incorrect arg usage on JSC trace and LegacyProfile functions Reviewed By: astreet Differential Revision: D2729015 fb-gh-sync-id: a3f56554ff6e77170e07aaf93934f93522b3a81b --- .../src/main/jni/react/JSCHelpers.cpp | 9 +++++ ReactAndroid/src/main/jni/react/JSCHelpers.h | 4 +++ .../src/main/jni/react/JSCLegacyProfiler.cpp | 12 +++++-- .../src/main/jni/react/JSCTracing.cpp | 35 +++++++++++++------ 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/JSCHelpers.cpp b/ReactAndroid/src/main/jni/react/JSCHelpers.cpp index b3ef3aa57c73..b1edc73dc2c7 100644 --- a/ReactAndroid/src/main/jni/react/JSCHelpers.cpp +++ b/ReactAndroid/src/main/jni/react/JSCHelpers.cpp @@ -19,4 +19,13 @@ void installGlobalFunction( JSStringRelease(jsName); } +JSValueRef makeJSCException( + JSContextRef ctx, + const char* exception_text) { + JSStringRef message = JSStringCreateWithUTF8CString(exception_text); + JSValueRef exceptionString = JSValueMakeString(ctx, message); + JSStringRelease(message); + return JSValueToObject(ctx, exceptionString, NULL); +} + } } diff --git a/ReactAndroid/src/main/jni/react/JSCHelpers.h b/ReactAndroid/src/main/jni/react/JSCHelpers.h index 7209b2ce3a1b..ba3f8525a341 100644 --- a/ReactAndroid/src/main/jni/react/JSCHelpers.h +++ b/ReactAndroid/src/main/jni/react/JSCHelpers.h @@ -13,4 +13,8 @@ void installGlobalFunction( const char* name, JSObjectCallAsFunctionCallback callback); +JSValueRef makeJSCException( + JSContextRef ctx, + const char* exception_text); + } } diff --git a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp index 5d2a7e499e18..74222c2efe28 100644 --- a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp +++ b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp @@ -17,7 +17,11 @@ static JSValueRef nativeProfilerStart( const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) { - // Could raise an exception here. + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeProfilerStart: requires at least 1 argument"); + } return JSValueMakeUndefined(ctx); } @@ -35,7 +39,11 @@ static JSValueRef nativeProfilerEnd( const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) { - // Could raise an exception here. + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeProfilerEnd: requires at least 1 argument"); + } return JSValueMakeUndefined(ctx); } diff --git a/ReactAndroid/src/main/jni/react/JSCTracing.cpp b/ReactAndroid/src/main/jni/react/JSCTracing.cpp index d0bab2c420f0..945d6fe72bc4 100644 --- a/ReactAndroid/src/main/jni/react/JSCTracing.cpp +++ b/ReactAndroid/src/main/jni/react/JSCTracing.cpp @@ -92,8 +92,11 @@ static JSValueRef nativeTraceBeginSection( const JSValueRef arguments[], JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 2)) { - // Could raise an exception here. - // TODO T9329825 + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeTraceBeginSection: requires at least 2 arguments"); + } return JSValueMakeUndefined(ctx); } @@ -129,8 +132,11 @@ static JSValueRef nativeTraceEndSection( const JSValueRef arguments[], JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) { - // Could raise an exception here. - // TODO T9329825 + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeTraceEndSection: requires at least 1 argument"); + } return JSValueMakeUndefined(ctx); } @@ -170,8 +176,11 @@ static JSValueRef beginOrEndAsync( const JSValueRef arguments[], JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) { - // Could raise an exception here. - // TODO T9329825 + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "beginOrEndAsync: requires at least 3 arguments"); + } return JSValueMakeUndefined(ctx); } @@ -230,8 +239,11 @@ static JSValueRef stageAsync( const JSValueRef arguments[], JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) { - // Could raise an exception here. - // TODO T9329825 + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "stageAsync: requires at least 4 arguments"); + } return JSValueMakeUndefined(ctx); } @@ -373,8 +385,11 @@ static JSValueRef nativeTraceCounter( const JSValueRef arguments[], JSValueRef* exception) { if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) { - // Could raise an exception here. - // TODO T9329825 + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeTraceCounter: requires at least 3 arguments"); + } return JSValueMakeUndefined(ctx); } From e2c35dddbac6ce305f1ed7b445e92fd392b42865 Mon Sep 17 00:00:00 2001 From: Emilio Rodriguez Date: Tue, 8 Dec 2015 07:44:56 -0800 Subject: [PATCH 0272/1411] Added support for styling the PickerIOS Summary: - PickerIOS accepts now a new prop: style - this prop modifies the native style of the RCTPicker allowing to modify the font size of the items (fontSize), color of the items (color, only 6 char HEX values for now) and alignment of the items (textAlign) Closes https://github.com/facebook/react-native/pull/4490 Reviewed By: svcscm Differential Revision: D2723190 Pulled By: nicklockwood fb-gh-sync-id: ab9188192f1d0d087787dfed8c128073bfaa3235 --- Examples/UIExplorer/PickerIOSExample.js | 54 +++++++++++++++++++------ Libraries/Picker/PickerIOS.ios.js | 16 ++++++-- React/Views/RCTPicker.h | 5 +++ React/Views/RCTPicker.m | 37 ++++++++++++----- React/Views/RCTPickerManager.m | 18 +++++++++ 5 files changed, 104 insertions(+), 26 deletions(-) diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js index b0cc5cb9729d..d38ef54e9f3b 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/PickerIOSExample.js @@ -87,24 +87,21 @@ var PickerExample = React.createClass({ key={carMake} value={carMake} label={CAR_MAKES_AND_MODELS[carMake].name} - /> - ) - )} + /> + ))} Please choose a model of {make.name}: this.setState({modelIndex})}> - {CAR_MAKES_AND_MODELS[this.state.carMake].models.map( - (modelName, modelIndex) => ( - - )) - } + {CAR_MAKES_AND_MODELS[this.state.carMake].models.map((modelName, modelIndex) => ( + + ))} You selected: {selectionString} @@ -112,6 +109,33 @@ var PickerExample = React.createClass({ }, }); +var PickerStyleExample = React.createClass({ + getInitialState: function() { + return { + carMake: 'cadillac', + }; + }, + + render: function() { + var make = CAR_MAKES_AND_MODELS[this.state.carMake]; + var selectionString = make.name + ' ' + make.models[this.state.modelIndex]; + return ( + this.setState({carMake, modelIndex: 0})}> + {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => ( + + ))} + + ); + }, +}); + exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Render lists of selectable options with UIPickerView.'; @@ -121,4 +145,10 @@ exports.examples = [ render: function(): ReactElement { return ; }, +}, +{ + title: ' with custom styling', + render: function(): ReactElement { + return ; + }, }]; diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index ffda1f57d174..0aa82bb5f8f8 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -17,8 +17,11 @@ var React = require('React'); var ReactChildren = require('ReactChildren'); var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants; var StyleSheet = require('StyleSheet'); +var StyleSheetPropType = require('StyleSheetPropType'); +var TextStylePropTypes = require('TextStylePropTypes'); var View = require('View'); +var itemStylePropType = StyleSheetPropType(TextStylePropTypes); var requireNativeComponent = require('requireNativeComponent'); var PickerIOS = React.createClass({ @@ -26,6 +29,7 @@ var PickerIOS = React.createClass({ propTypes: { ...View.propTypes, + itemStyle: itemStylePropType, onValueChange: React.PropTypes.func, selectedValue: React.PropTypes.any, // string or integer basically }, @@ -50,13 +54,13 @@ var PickerIOS = React.createClass({ }); return {selectedIndex, items}; }, - + render: function() { return ( this._picker = picker } - style={styles.pickerIOS} + ref={picker => this._picker = picker} + style={[styles.pickerIOS, this.props.itemStyle]} items={this.state.items} selectedIndex={this.state.selectedIndex} onChange={this._onChange} @@ -108,7 +112,11 @@ var styles = StyleSheet.create({ }, }); -var RCTPickerIOS = requireNativeComponent('RCTPicker', PickerIOS, { +var RCTPickerIOS = requireNativeComponent('RCTPicker', { + propTypes: { + style: itemStylePropType, + }, +}, { nativeOnly: { items: true, onChange: true, diff --git a/React/Views/RCTPicker.h b/React/Views/RCTPicker.h index 8fb39b5a5de3..0066d140f8e4 100644 --- a/React/Views/RCTPicker.h +++ b/React/Views/RCTPicker.h @@ -15,6 +15,11 @@ @property (nonatomic, copy) NSArray *items; @property (nonatomic, assign) NSInteger selectedIndex; + +@property (nonatomic, strong) UIColor *color; +@property (nonatomic, strong) UIFont *font; +@property (nonatomic, assign) NSTextAlignment textAlign; + @property (nonatomic, copy) RCTBubblingEventBlock onChange; @end diff --git a/React/Views/RCTPicker.m b/React/Views/RCTPicker.m index f979c2116b77..b83a9667cbf0 100644 --- a/React/Views/RCTPicker.m +++ b/React/Views/RCTPicker.m @@ -9,6 +9,7 @@ #import "RCTPicker.h" +#import "RCTConvert.h" #import "RCTUtils.h" @interface RCTPicker() @@ -19,7 +20,10 @@ @implementation RCTPicker - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { + _color = [UIColor blackColor]; + _font = [UIFont systemFontOfSize:21]; // TODO: selected title default should be 23.5 _selectedIndex = NSNotFound; + _textAlign = NSTextAlignmentCenter; self.delegate = self; } return self; @@ -59,20 +63,33 @@ - (NSInteger)pickerView:(__unused UIPickerView *)pickerView #pragma mark - UIPickerViewDelegate methods -- (NSDictionary *)itemForRow:(NSInteger)row +- (NSString *)pickerView:(__unused UIPickerView *)pickerView + titleForRow:(NSInteger)row + forComponent:(__unused NSInteger)component { - return _items[row]; + return [RCTConvert NSString:_items[row][@"label"]]; } -- (id)valueForRow:(NSInteger)row +- (UIView *)pickerView:(UIPickerView *)pickerView + viewForRow:(NSInteger)row + forComponent:(NSInteger)component + reusingView:(UILabel *)label { - return [self itemForRow:row][@"value"]; -} + if (!label) { + label = [[UILabel alloc] initWithFrame:(CGRect){ + CGPointZero, + { + [pickerView rowSizeForComponent:component].width, + [pickerView rowSizeForComponent:component].height, + } + }]; + } -- (NSString *)pickerView:(__unused UIPickerView *)pickerView - titleForRow:(NSInteger)row forComponent:(__unused NSInteger)component -{ - return [self itemForRow:row][@"label"]; + label.font = _font; + label.textColor = _color; + label.textAlignment = _textAlign; + label.text = [self pickerView:pickerView titleForRow:row forComponent:component]; + return label; } - (void)pickerView:(__unused UIPickerView *)pickerView @@ -82,7 +99,7 @@ - (void)pickerView:(__unused UIPickerView *)pickerView if (_onChange) { _onChange(@{ @"newIndex": @(row), - @"newValue": [self valueForRow:row] + @"newValue": RCTNullIfNil(_items[row][@"value"]), }); } } diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index 79d9c4758a63..759a05b6d8ad 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -25,6 +25,24 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray) RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(color, UIColor) +RCT_EXPORT_VIEW_PROPERTY(textAlign, NSTextAlignment) +RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTPicker) +{ + view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)]; +} +RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTPicker) +{ + view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal +} +RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTPicker) +{ + view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal +} +RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTPicker) +{ + view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName]; +} - (NSDictionary *)constantsToExport { From c9d796fc6a07199314452e79c1cc97b3ba789bd3 Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Tue, 8 Dec 2015 09:30:07 -0800 Subject: [PATCH 0273/1411] NetInfo: Cleaned up code and examples Reviewed By: andreicoman11 Differential Revision: D2723578 fb-gh-sync-id: b7bb9ce69f24ff5dcf38409de92d57f53da003fa --- Examples/UIExplorer/NetInfoExample.android.js | 158 --------------- Examples/UIExplorer/NetInfoExample.ios.js | 147 -------------- Examples/UIExplorer/NetInfoExample.js | 182 ++++++++++++++++++ Examples/UIExplorer/UIExplorerList.android.js | 2 +- Examples/UIExplorer/UIExplorerList.ios.js | 2 +- Libraries/Network/NetInfo.js | 75 ++++---- 6 files changed, 220 insertions(+), 346 deletions(-) delete mode 100644 Examples/UIExplorer/NetInfoExample.android.js delete mode 100644 Examples/UIExplorer/NetInfoExample.ios.js create mode 100644 Examples/UIExplorer/NetInfoExample.js diff --git a/Examples/UIExplorer/NetInfoExample.android.js b/Examples/UIExplorer/NetInfoExample.android.js deleted file mode 100644 index 41d77232195f..000000000000 --- a/Examples/UIExplorer/NetInfoExample.android.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - */ -'use strict'; - -const React = require('react-native'); -const { - NetInfo, // requires android.permission.ACCESS_NETWORK_STATE - Text, - View -} = React; -const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); - -const ConnectionSubscription = React.createClass({ - getInitialState() { - return { - connectionHistory: [], - }; - }, - componentDidMount: function() { - NetInfo.addEventListener( - 'change', - this._handleConnectionChange - ); - }, - componentWillUnmount: function() { - NetInfo.removeEventListener( - 'change', - this._handleConnectionChange - ); - }, - _handleConnectionChange: function(netInfo) { - var connectionHistory = this.state.connectionHistory.slice(); - connectionHistory.push(netInfo); - this.setState({ - connectionHistory, - }); - }, - render() { - return ( - {JSON.stringify(this.state.connectionHistory)} - ); - } -}); - -const ConnectionCurrent = React.createClass({ - getInitialState() { - return { - netInfo: null, - }; - }, - componentDidMount: function() { - NetInfo.addEventListener( - 'change', - this._handleConnectionChange - ); - NetInfo.fetch().done( - (netInfo) => { this.setState({netInfo}); } - ); - }, - componentWillUnmount: function() { - NetInfo.removeEventListener( - 'change', - this._handleConnectionChange - ); - }, - _handleConnectionChange: function(netInfo) { - this.setState({ - netInfo, - }); - }, - render() { - return ( - {JSON.stringify(this.state.netInfo)} - ); - } -}); - -const IsConnected = React.createClass({ - getInitialState() { - return { - isConnected: null, - }; - }, - componentDidMount: function() { - NetInfo.isConnected.addEventListener( - 'change', - this._handleConnectivityChange - ); - NetInfo.isConnected.fetch().done( - (isConnected) => { this.setState({isConnected}); } - ); - }, - componentWillUnmount: function() { - NetInfo.isConnected.removeEventListener( - 'change', - this._handleConnectivityChange - ); - }, - _handleConnectivityChange: function(isConnected) { - this.setState({ - isConnected, - }); - }, - render() { - return ( - {this.state.isConnected ? 'Online' : 'Offline'} - ); - } -}); - -const NetInfoExample = React.createClass({ - statics: { - title: '', - description: 'Monitor network status.' - }, - - getInitialState() { - return { - isMetered: null, - }; - }, - render() { - return ( - - Is Connected: - Current Connection Type: - Connection History: - - - Click to see if connection is metered: {this.state.isMetered} - - - - ); - }, - isConnectionMetered: function() { - NetInfo.isConnectionMetered((isConnectionMetered) => { - this.setState({ - isMetered: isConnectionMetered ? 'Is Metered' : 'Is Not Metered', - }); - }); - } -}); - -module.exports = NetInfoExample; diff --git a/Examples/UIExplorer/NetInfoExample.ios.js b/Examples/UIExplorer/NetInfoExample.ios.js deleted file mode 100644 index 6ab1805dfee4..000000000000 --- a/Examples/UIExplorer/NetInfoExample.ios.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - */ -'use strict'; - -var React = require('react-native'); -var { - NetInfo, - Text, - View -} = React; - -var ReachabilitySubscription = React.createClass({ - getInitialState() { - return { - reachabilityHistory: [], - }; - }, - componentDidMount: function() { - NetInfo.addEventListener( - 'change', - this._handleReachabilityChange - ); - }, - componentWillUnmount: function() { - NetInfo.removeEventListener( - 'change', - this._handleReachabilityChange - ); - }, - _handleReachabilityChange: function(reachability) { - var reachabilityHistory = this.state.reachabilityHistory.slice(); - reachabilityHistory.push(reachability); - this.setState({ - reachabilityHistory, - }); - }, - render() { - return ( - - {JSON.stringify(this.state.reachabilityHistory)} - - ); - } -}); - -var ReachabilityCurrent = React.createClass({ - getInitialState() { - return { - reachability: null, - }; - }, - componentDidMount: function() { - NetInfo.addEventListener( - 'change', - this._handleReachabilityChange - ); - NetInfo.fetch().done( - (reachability) => { this.setState({reachability}); } - ); - }, - componentWillUnmount: function() { - NetInfo.removeEventListener( - 'change', - this._handleReachabilityChange - ); - }, - _handleReachabilityChange: function(reachability) { - this.setState({ - reachability, - }); - }, - render() { - return ( - - {this.state.reachability} - - ); - } -}); - -var IsConnected = React.createClass({ - getInitialState() { - return { - isConnected: null, - }; - }, - componentDidMount: function() { - NetInfo.isConnected.addEventListener( - 'change', - this._handleConnectivityChange - ); - NetInfo.isConnected.fetch().done( - (isConnected) => { this.setState({isConnected}); } - ); - }, - componentWillUnmount: function() { - NetInfo.isConnected.removeEventListener( - 'change', - this._handleConnectivityChange - ); - }, - _handleConnectivityChange: function(isConnected) { - this.setState({ - isConnected, - }); - }, - render() { - return ( - - {this.state.isConnected ? 'Online' : 'Offline'} - - ); - } -}); - -exports.title = 'NetInfo'; -exports.description = 'Monitor network status'; -exports.examples = [ - { - title: 'NetInfo.isConnected', - description: 'Asynchronously load and observe connectivity', - render(): ReactElement { return ; } - }, - { - title: 'NetInfo.reachabilityIOS', - description: 'Asynchronously load and observe iOS reachability', - render(): ReactElement { return ; } - }, - { - title: 'NetInfo.reachabilityIOS', - description: 'Observed updates to iOS reachability', - render(): ReactElement { return ; } - }, -]; diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js new file mode 100644 index 000000000000..acbbb26733af --- /dev/null +++ b/Examples/UIExplorer/NetInfoExample.js @@ -0,0 +1,182 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const { + NetInfo, + Text, + View, + TouchableWithoutFeedback, +} = React; + +const ConnectionInfoSubscription = React.createClass({ + getInitialState() { + return { + connectionInfoHistory: [], + }; + }, + componentDidMount: function() { + NetInfo.addEventListener( + 'change', + this._handleConnectionInfoChange + ); + }, + componentWillUnmount: function() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionInfoChange + ); + }, + _handleConnectionInfoChange: function(connectionInfo) { + const connectionInfoHistory = this.state.connectionInfoHistory.slice(); + connectionInfoHistory.push(connectionInfo); + this.setState({ + connectionInfoHistory, + }); + }, + render() { + return ( + + {JSON.stringify(this.state.connectionInfoHistory)} + + ); + } +}); + +const ConnectionInfoCurrent = React.createClass({ + getInitialState() { + return { + connectionInfo: null, + }; + }, + componentDidMount: function() { + NetInfo.addEventListener( + 'change', + this._handleConnectionInfoChange + ); + NetInfo.fetch().done( + (connectionInfo) => { this.setState({connectionInfo}); } + ); + }, + componentWillUnmount: function() { + NetInfo.removeEventListener( + 'change', + this._handleConnectionInfoChange + ); + }, + _handleConnectionInfoChange: function(connectionInfo) { + this.setState({ + connectionInfo, + }); + }, + render() { + return ( + + {this.state.connectionInfo} + + ); + } +}); + +const IsConnected = React.createClass({ + getInitialState() { + return { + isConnected: null, + }; + }, + componentDidMount: function() { + NetInfo.isConnected.addEventListener( + 'change', + this._handleConnectivityChange + ); + NetInfo.isConnected.fetch().done( + (isConnected) => { this.setState({isConnected}); } + ); + }, + componentWillUnmount: function() { + NetInfo.isConnected.removeEventListener( + 'change', + this._handleConnectivityChange + ); + }, + _handleConnectivityChange: function(isConnected) { + this.setState({ + isConnected, + }); + }, + render() { + return ( + + {this.state.isConnected ? 'Online' : 'Offline'} + + ); + } +}); + +const IsConnectionExpensive = React.createClass({ + getInitialState() { + return { + isConnectionExpensive: null, + }; + }, + _checkIfExpensive() { + NetInfo.isConnectionExpensive( + (isConnectionExpensive) => { this.setState({isConnectionExpensive}); } + ); + }, + render() { + return ( + + + + Click to see if connection is expensive: + {this.state.isConnectionExpensive === true ? 'Expensive' : + this.state.isConnectionExpensive === false ? 'Not expensive' + : 'Unknown'} + + + + + ); + } +}); + +exports.title = 'NetInfo'; +exports.description = 'Monitor network status'; +exports.examples = [ + { + title: 'NetInfo.isConnected', + description: 'Asynchronously load and observe connectivity', + render(): ReactElement { return ; } + }, + { + title: 'NetInfo.update', + description: 'Asynchronously load and observe connectionInfo', + render(): ReactElement { return ; } + }, + { + title: 'NetInfo.updateHistory', + description: 'Observed updates to connectionInfo', + render(): ReactElement { return ; } + }, + { + platform: 'android', + title: 'NetInfo.isConnectionExpensive (Android)', + description: 'Asycnronously check isConnectionExpensive', + render(): ReactElement { return ; } + }, +]; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 6445eff49905..a2ff43096c55 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -43,7 +43,7 @@ var APIS = [ require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), require('./LayoutExample'), - require('./NetInfoExample.android'), + require('./NetInfoExample'), require('./PanResponderExample'), require('./PointerEventsExample'), require('./TimerExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 2f937cbeb6be..940bd79ba42e 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -68,7 +68,7 @@ var APIS = [ require('./CameraRollExample.ios'), require('./GeolocationExample'), require('./LayoutExample'), - require('./NetInfoExample.ios'), + require('./NetInfoExample'), require('./PanResponderExample'), require('./PointerEventsExample'), require('./PushNotificationIOSExample'), diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index c2099c9e3eac..2b17603adedf 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -11,13 +11,13 @@ */ 'use strict'; -var Map = require('Map'); -var NativeModules = require('NativeModules'); -var Platform = require('Platform'); -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var RCTNetInfo = NativeModules.NetInfo; +const Map = require('Map'); +const NativeModules = require('NativeModules'); +const Platform = require('Platform'); +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +const RCTNetInfo = NativeModules.NetInfo; -var DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange'; +const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange'; type ChangeEventName = $Enum<{ change: string; @@ -54,24 +54,20 @@ type ConnectivityStateAndroid = $Enum<{ }>; -var _subscriptions = new Map(); +const _subscriptions = new Map(); +let _isConnected; if (Platform.OS === 'ios') { - var _isConnected = function( - reachability: ReachabilityStateIOS - ): bool { - return reachability !== 'none' && - reachability !== 'unknown'; + _isConnected = (reachability: ReachabilityStateIOS): bool => { + return reachability !== 'none' && reachability !== 'unknown'; }; } else if (Platform.OS === 'android') { - var _isConnected = function( - connectionType: ConnectivityStateAndroid - ): bool { + _isConnected = (connectionType: ConnectivityStateAndroid) : bool => { return connectionType !== 'NONE' && connectionType !== 'UNKNOWN'; }; } -var _isConnectedSubscriptions = new Map(); +const _isConnectedSubscriptions = new Map(); /** * NetInfo exposes info about online/offline status @@ -128,15 +124,15 @@ var _isConnectedSubscriptions = new Map(); * * The rest ConnectivityStates are hidden by the Android API, but can be used if necessary. * - * ### isConnectionMetered + * ### isConnectionExpensive * * Available on Android. Detect if the current active connection is metered or not. A network is * classified as metered when the user is sensitive to heavy data usage on that connection due to * monetary costs, data limitations or battery/performance issues. * * ``` - * NetInfo.isConnectionMetered((isConnectionMetered) => { - * console.log('Connection is ' + (isConnectionMetered ? 'Metered' : 'Not Metered')); + * NetInfo.isConnectionExpensive((isConnectionExpensive) => { + * console.log('Connection is ' + (isConnectionExpensive ? 'Expensive' : 'Not Expensive')); * }); * ``` * @@ -162,12 +158,12 @@ var _isConnectedSubscriptions = new Map(); * ); * ``` */ -var NetInfo = { - addEventListener: function ( +const NetInfo = { + addEventListener( eventName: ChangeEventName, handler: Function ): void { - var listener = RCTDeviceEventEmitter.addListener( + const listener = RCTDeviceEventEmitter.addListener( DEVICE_CONNECTIVITY_EVENT, (appStateData) => { handler(appStateData.network_info); @@ -176,11 +172,11 @@ var NetInfo = { _subscriptions.set(handler, listener); }, - removeEventListener: function( + removeEventListener( eventName: ChangeEventName, handler: Function ): void { - var listener = _subscriptions.get(handler); + const listener = _subscriptions.get(handler); if (!listener) { return; } @@ -188,7 +184,7 @@ var NetInfo = { _subscriptions.delete(handler); }, - fetch: function(): Promise { + fetch(): Promise { return new Promise((resolve, reject) => { RCTNetInfo.getCurrentConnectivity( function(resp) { @@ -200,11 +196,11 @@ var NetInfo = { }, isConnected: { - addEventListener: function ( + addEventListener( eventName: ChangeEventName, handler: Function ): void { - var listener = (connection) => { + const listener = (connection) => { handler(_isConnected(connection)); }; _isConnectedSubscriptions.set(handler, listener); @@ -214,11 +210,11 @@ var NetInfo = { ); }, - removeEventListener: function( + removeEventListener( eventName: ChangeEventName, handler: Function ): void { - var listener = _isConnectedSubscriptions.get(handler); + const listener = _isConnectedSubscriptions.get(handler); NetInfo.removeEventListener( eventName, listener @@ -226,22 +222,23 @@ var NetInfo = { _isConnectedSubscriptions.delete(handler); }, - fetch: function(): Promise { + fetch(): Promise { return NetInfo.fetch().then( (connection) => _isConnected(connection) ); }, }, - isConnectionMetered: ({}: {} | (callback:Function) => void), + isConnectionExpensive(callback: (metered: ?boolean, error: string) => void): void { + if (Platform.OS === 'android') { + RCTNetInfo.isConnectionMetered((_isMetered) => { + callback(_isMetered); + }); + } else { + // TODO t9296080 consider polyfill and more features later on + callback(null, "Unsupported"); + } + }, }; -if (Platform.OS === 'android') { - NetInfo.isConnectionMetered = function(callback): void { - RCTNetInfo.isConnectionMetered((_isMetered) => { - callback(_isMetered); - }); - }; -} - module.exports = NetInfo; From c489660f264859a10fc89e8f9bf091ead5d59e15 Mon Sep 17 00:00:00 2001 From: Edvin Erikson Date: Tue, 8 Dec 2015 10:09:16 -0800 Subject: [PATCH 0274/1411] Inspecting stateless components. Summary: Fixes #4602 Fixes a bug where the app crashes when you try to inspect a stateless component. Fixed by replacing all occurrences of the getPublicInstance method in Libraries/Inspector/Inspector.js with the _instance property instead. Defaults to an empty object if _instance is falsy. Closes https://github.com/facebook/react-native/pull/4642 Reviewed By: svcscm Differential Revision: D2734491 Pulled By: androidtrunkagent fb-gh-sync-id: 4ea753b7e0ef3fd05af2d80abadc365c5c787f98 --- Libraries/Inspector/Inspector.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index 7dbec7443ace..c72da8e7e765 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -98,7 +98,9 @@ class Inspector extends React.Component { setSelection(i: number) { var instance = this.state.hierarchy[i]; - var publicInstance = instance.getPublicInstance(); + // if we inspect a stateless component we can't use the getPublicInstance method + // therefore we use the internal _instance property directly. + var publicInstance = instance._instance || {}; UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => { this.setState({ inspected: { @@ -115,7 +117,9 @@ class Inspector extends React.Component { this.state.devtoolsAgent.selectFromReactInstance(instance, true); } var hierarchy = InspectorUtils.getOwnerHierarchy(instance); - var publicInstance = instance.getPublicInstance(); + // if we inspect a stateless component we can't use the getPublicInstance method + // therefore we use the internal _instance property directly. + var publicInstance = instance._instance || {}; var props = publicInstance.props || {}; this.setState({ panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom', From d0135540835b7fe9e95336d800227c5f61a52721 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 8 Dec 2015 10:58:35 -0800 Subject: [PATCH 0275/1411] Prepend magic number to unbundle files Summary: In order to be able to reliable identify unbundles when loading files, prepend a magic number (0xFB0BD1E5) public Reviewed By: martinbigio Differential Revision: D2734359 fb-gh-sync-id: b469e26459234e7f6270fffa0b872a93d137381d --- local-cli/bundle/output/unbundle.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/local-cli/bundle/output/unbundle.js b/local-cli/bundle/output/unbundle.js index 65156dcca031..de265c7dfc39 100644 --- a/local-cli/bundle/output/unbundle.js +++ b/local-cli/bundle/output/unbundle.js @@ -12,6 +12,7 @@ const fs = require('fs'); const Promise = require('promise'); const writeFile = require('./writeFile'); +const MAGIC_UNBUNDLE_FILE_HEADER = 0xFB0BD1E5; const MAGIC_STARTUP_MODULE_ID = ''; function buildBundle(packagerClient, requestOptions) { @@ -49,6 +50,8 @@ function saveUnbundle(bundle, options, log) { } /* global Buffer: true */ +const fileHeader = Buffer(4); +fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER); const nullByteBuffer = Buffer(1).fill(0); const moduleToBuffer = ({name, code}, encoding) => ({ @@ -109,7 +112,7 @@ function buildModuleTable(buffers) { function buildTableAndContents(startupCode, modules, encoding) { const buffers = buildModuleBuffers(startupCode, modules, encoding); const table = buildModuleTable(buffers, encoding); - return [table].concat(buffers.map(({buffer}) => buffer)); + return [fileHeader, table].concat(buffers.map(({buffer}) => buffer)); } function writeBuffers(stream, buffers) { From 7b13ce30f2ba070f8a11217779a937f27e083e9e Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 8 Dec 2015 11:53:55 -0800 Subject: [PATCH 0276/1411] Fix flow Reviewed By: gabelevi Differential Revision: D2734936 fb-gh-sync-id: 0c085c4968e52907a60bbd1d8af30dac6f449e46 --- Examples/Movies/MovieCell.js | 3 --- Examples/Movies/MovieScreen.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js index 62062cb2af22..85c616d329cc 100644 --- a/Examples/Movies/MovieCell.js +++ b/Examples/Movies/MovieCell.js @@ -45,9 +45,6 @@ var MovieCell = React.createClass({ onShowUnderlay={this.props.onHighlight} onHideUnderlay={this.props.onUnhighlight}> - {/* $FlowIssue #7363964 - There's a bug in Flow where you cannot - * omit a property or set it to undefined if it's inside a shape, - * even if it isn't required */} - {/* $FlowIssue #7363964 - There's a bug in Flow where you cannot - * omit a property or set it to undefined if it's inside a shape, - * even if it isn't required */} Date: Tue, 8 Dec 2015 12:02:17 -0800 Subject: [PATCH 0277/1411] Fixed Flow error Reviewed By: gabelevi Differential Revision: D2734738 fb-gh-sync-id: 5ba3d4285185cd220a5bf6a26cb3818311822e52 --- Libraries/Network/NetInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 2b17603adedf..d4295754f259 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -229,7 +229,7 @@ const NetInfo = { }, }, - isConnectionExpensive(callback: (metered: ?boolean, error: string) => void): void { + isConnectionExpensive(callback: (metered: ?boolean, error?: string) => void): void { if (Platform.OS === 'android') { RCTNetInfo.isConnectionMetered((_isMetered) => { callback(_isMetered); From 202222504e3c92453c6e539ac2d57fd45157221a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 8 Dec 2015 12:23:41 -0800 Subject: [PATCH 0278/1411] Introduce `loadBundles` Summary: public Introduce a new Polyfill module to load bundles. This polyfill contains the function to which System.import gets transformed into. It works similarly to require in the way it's registered. It keeps track of the bundles that have ever been requested using promises, either fulfilled or not. The use of promises makes the implementation quite easy as we don't need to differenciate whether the request has been started or not. There're a couple of follow up steps that still need to get done: - Included this polyfill on the ones we automatically include on the bundle. - Modify the transform to include the modules the user is actually requesting and pipe that through loadBundles so that the promise, once resolved, has the ordered list of modules the user requested. - Implement the actual native code that loads a bundle. This shouldn't be that taught as native will be able to assume it will never receive the same request twice. Reviewed By: davidaurelio Differential Revision: D2727241 fb-gh-sync-id: 317d80754783caf43f10c71a34a4558a4d298d45 --- .../polyfills/__tests__/loadBundles-test.js | 64 +++++++++++++++++++ .../src/Resolver/polyfills/loadBundles.js | 50 +++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js create mode 100644 packager/react-packager/src/Resolver/polyfills/loadBundles.js diff --git a/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js b/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js new file mode 100644 index 000000000000..ad0dcf279473 --- /dev/null +++ b/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js @@ -0,0 +1,64 @@ +/** + * 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('../loadBundles'); +jest.mock('NativeModules'); + +let loadBundles; +let loadBundlesCalls; + +describe('loadBundles', () => { + beforeEach(() => { + loadBundles = jest.genMockFunction(); + loadBundlesCalls = loadBundles.mock.calls; + require('NativeModules').RCTBundlesLoader = {loadBundles}; + + require('../loadBundles'); + }); + + it('should set `global.__loadBundles` function when polyfill is initialized', () => { + expect(typeof global.__loadBundles).toBe('function'); + }); + + it('should return a promise', () => { + loadBundles.mockImpl((bundles, callback) => callback()); + expect(global.__loadBundles(['bundle.0']) instanceof Promise).toBeTruthy(); + }); + + pit('shouldn\'t request already loaded bundles', () => { + loadBundles.mockImpl((bundles, callback) => callback()); + return global + .__loadBundles(['bundle.0']) + .then(() => global.__loadBundles(['bundle.0'])) + .then(() => expect(loadBundlesCalls.length).toBe(1)); + }); + + pit('shouldn\'n request inflight bundles', () => { + loadBundles.mockImpl((bundles, callback) => { + if (bundles.length === 1 && bundles[0] === 'bundle.0') { + setTimeout(callback, 1000); + } else if (bundles.length === 1 && bundles[0] === 'bundle.1') { + setTimeout(callback, 500); + } + }); + + const promises = Promise.all([ + global.__loadBundles(['bundle.0']), + global.__loadBundles(['bundle.0', 'bundle.1']), + ]).then(() => { + expect(loadBundlesCalls.length).toBe(2); + expect(loadBundlesCalls[0][0][0]).toBe('bundle.0'); + expect(loadBundlesCalls[1][0][0]).toBe('bundle.1'); + }); + + jest.runAllTimers(); + return promises; + }); +}); diff --git a/packager/react-packager/src/Resolver/polyfills/loadBundles.js b/packager/react-packager/src/Resolver/polyfills/loadBundles.js new file mode 100644 index 000000000000..551d4cac22dc --- /dev/null +++ b/packager/react-packager/src/Resolver/polyfills/loadBundles.js @@ -0,0 +1,50 @@ +/* eslint global-strict:0 */ +(global => { + let loadBundlesOnNative = (bundles) => + new Promise((_, resolve) => + require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve)); + + let requestedBundles = Object.create(null); + + /** + * Returns a promise that is fulfilled once all the indicated bundles are + * loaded into memory and injected into the JS engine. + * This invokation might need to go through the bridge + * and run native code to load some, if not all, the requested bundles. + * If all the bundles have already been loaded, the promise is resolved + * immediately. Otherwise, we'll determine which bundles still need to get + * loaded considering both, the ones already loaded, and the ones being + * currently asynchronously loaded by other invocations to `__loadBundles`, + * and return a promise that will get fulfilled once all these are finally + * loaded. + * + * Note this function should only be invoked by generated code. + */ + global.__loadBundles = function(bundles) { + // split bundles by whether they've already been requested or not + const bundlesToRequest = bundles.filter(b => !requestedBundles[b]); + const bundlesAlreadyRequested = bundles.filter(b => !!requestedBundles[b]); + + // keep a reference to the promise loading each bundle + if (bundlesToRequest.length > 0) { + const nativePromise = loadBundlesOnNative(bundlesToRequest); + bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise); + } + + return Promise.all(bundles.map(bundle => requestedBundles[bundle])); + }; +})(global); + + +// turns a callback async based function into a promised based one +function promisify(fn) { + return function() { + var self = this; + var args = Array.prototype.slice.call(arguments); + + return new Promise((resolve, reject) => { + args.push(resolve); + fn.apply(self, args); + }); + }; +} From 066ebc021f2ac89b811305c3e8335e4b7c592bc6 Mon Sep 17 00:00:00 2001 From: "glevi@fb.com" Date: Tue, 8 Dec 2015 13:23:01 -0800 Subject: [PATCH 0279/1411] Fix PickerIOSExample Reviewed By: nicklockwood Differential Revision: D2735834 fb-gh-sync-id: 8f2777ec2afe007523ea85cca27f40497910d2d1 --- Examples/UIExplorer/PickerIOSExample.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js index d38ef54e9f3b..51b8f9f30e76 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/PickerIOSExample.js @@ -113,9 +113,10 @@ var PickerStyleExample = React.createClass({ getInitialState: function() { return { carMake: 'cadillac', + modelIndex: 0, }; }, - + render: function() { var make = CAR_MAKES_AND_MODELS[this.state.carMake]; var selectionString = make.name + ' ' + make.models[this.state.modelIndex]; From 16a29b0b8701697372ed40dfa4874d66edb6f77c Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Tue, 8 Dec 2015 14:30:07 -0800 Subject: [PATCH 0280/1411] Fix EmbeddedAppIOS documentation Fix with correct init call on RCTRootView due to change in method signature from commit 261f9434e54274a68d2cb50644c436197ef48baa --- docs/EmbeddedAppIOS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/EmbeddedAppIOS.md b/docs/EmbeddedAppIOS.md index 655809fa044a..b82c608d1971 100644 --- a/docs/EmbeddedAppIOS.md +++ b/docs/EmbeddedAppIOS.md @@ -136,6 +136,7 @@ NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.b // curl http://localhost:8081/index.ios.bundle -o main.jsbundle RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName: @"SimpleApp" + initialProperties:nil launchOptions:nil]; ``` From 99bba8ca4e5731f6442fc0bf626b8ce2dd6127bb Mon Sep 17 00:00:00 2001 From: Gabe Levi Date: Tue, 8 Dec 2015 14:47:09 -0800 Subject: [PATCH 0281/1411] Use .flow files to tell Flow about react-native module Reviewed By: jeffmo Differential Revision: D2735788 fb-gh-sync-id: 7a15caa5effb89b902bba7e0031822f534813c52 --- Examples/Movies/MovieCell.js | 3 + Examples/Movies/MovieScreen.js | 3 + Examples/UIExplorer/NetInfoExample.js | 2 +- Libraries/react-native/react-native.js.flow | 121 ++++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 Libraries/react-native/react-native.js.flow diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js index 85c616d329cc..62062cb2af22 100644 --- a/Examples/Movies/MovieCell.js +++ b/Examples/Movies/MovieCell.js @@ -45,6 +45,9 @@ var MovieCell = React.createClass({ onShowUnderlay={this.props.onHighlight} onHideUnderlay={this.props.onUnhighlight}> + {/* $FlowIssue #7363964 - There's a bug in Flow where you cannot + * omit a property or set it to undefined if it's inside a shape, + * even if it isn't required */} + {/* $FlowIssue #7363964 - There's a bug in Flow where you cannot + * omit a property or set it to undefined if it's inside a shape, + * even if it isn't required */} Date: Tue, 8 Dec 2015 15:57:34 -0800 Subject: [PATCH 0282/1411] Decouple Module System from Native Calls Summary: The JavaScript ecosystem doesn't have the notion of a built-in native module loader. Even Node is decoupled from its module loader. The module loader system is just JS that runs on top of the global `process` object which has all the built-in goodies. Additionally there is no such thing as a global require. That is something unique to our providesModule system. In other module systems such as node, every require is contextual. Even registered npm names are localized by version. The only global namespace that is accessible to the host environment is the global object. Normally module systems attaches itself onto the hooks provided by the host environment on the global object. Currently, we have two forms of dispatch that reaches directly into the module system. executeJSCall which reaches directly into require. Everything now calls through the BatchedBridge module (except one RCTLog edge case that I will fix). I propose that the executors calls directly onto `BatchedBridge` through an instance on the global so that everything is guaranteed to go through it. It becomes the main communication hub. I also propose that we drop the dynamic requires inside of MessageQueue/BatchBridge and instead have the modules register themselves with the bridge. executeJSCall was originally modeled after the XHP equivalent. The XHP equivalent was designed that way because the act of doing the call was the thing that defined a dependency on the module from the page. However, that is not how React Native works. The JS side is driving the dependencies by virtue of requiring new modules and frameworks and the existence of dependencies is driven by the JS side, so this design doesn't make as much sense. The main driver for this is to be able to introduce a new module system like Prepack's module system. However, it also unlocks the possibility to do dead module elimination even in our current module system. It is currently not possible because we don't know which module might be called from native. Since the module system now becomes decoupled we could publish all our providesModule modules as npm/CommonJS modules using a rewrite script. That's what React Core does. That way people could use any CommonJS bundler such as Webpack, Closure Compiler, Rollup or some new innovation to create a JS bundle. This diff expands the executeJSCalls to the BatchedBridge's three individual pieces to make them first class instead of being dynamic. This removes one layer of abstraction. Hopefully we can also remove more of the things that register themselves with the BatchedBridge (various EventEmitters) and instead have everything go through the public protocol. ReactMethod/RCT_EXPORT_METHOD. public Reviewed By: vjeux Differential Revision: D2717535 fb-gh-sync-id: 70114f05483124f5ac5c4570422bb91a60a727f6 --- .../UIExplorerUnitTests/RCTBridgeTests.m | 20 ++- .../RCTContextExecutorTests.m | 18 ++- IntegrationTests/LoggingTestModule.js | 11 +- Libraries/AppRegistry/AppRegistry.js | 17 +++ Libraries/BatchedBridge/BatchedBridge.js | 20 ++- .../BatchedBridgedModules/RCTEventEmitter.js | 6 + .../RCTDebugComponentOwnership.js | 10 +- Libraries/Device/RCTDeviceEventEmitter.js | 6 + .../NativeApp/RCTNativeAppEventEmitter.js | 6 + Libraries/Utilities/MessageQueue.js | 26 +++- Libraries/Utilities/PerformanceLogger.js | 6 + Libraries/Utilities/RCTLog.js | 7 + .../Utilities/__tests__/MessageQueue-test.js | 5 +- Libraries/WebSocket/RCTWebSocketExecutor.m | 26 +++- React/Base/RCTBatchedBridge.m | 135 ++++++++++++------ React/Base/RCTBridge.m | 8 +- React/Base/RCTJavaScriptExecutor.h | 28 +++- React/Base/RCTModuleMethod.m | 24 ++-- React/Base/RCTRootView.m | 2 +- React/Executors/RCTContextExecutor.m | 106 ++++++++------ .../facebook/react/CoreModulesPackage.java | 2 - .../react/ReactInstanceManagerImpl.java | 5 +- .../bridge/JSDebuggerWebSocketClient.java | 5 +- .../facebook/react/bridge/JavaJSExecutor.java | 3 +- .../bridge/WebsocketJavaScriptExecutor.java | 3 +- .../facebook/react/uimanager/AppRegistry.java | 2 + .../facebook/react/uimanager/ReactNative.java | 19 --- ReactAndroid/src/main/jni/react/Bridge.cpp | 43 ++++-- ReactAndroid/src/main/jni/react/Bridge.h | 20 ++- ReactAndroid/src/main/jni/react/Executor.h | 26 +++- .../src/main/jni/react/JSCExecutor.cpp | 59 +++++--- ReactAndroid/src/main/jni/react/JSCExecutor.h | 13 +- .../src/main/jni/react/jni/OnLoad.cpp | 22 ++- .../src/main/jni/react/jni/ProxyExecutor.cpp | 45 ++++-- .../src/main/jni/react/jni/ProxyExecutor.h | 12 +- .../src/main/jni/react/test/jscexecutor.cpp | 20 ++- local-cli/server/util/debugger.html | 10 +- local-cli/server/util/debuggerWorker.js | 23 ++- 38 files changed, 545 insertions(+), 274 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index c201f5799389..54a58787e108 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -54,10 +54,22 @@ - (BOOL)isValid return YES; } -- (void)executeJSCall:(__unused NSString *)name - method:(__unused NSString *)method - arguments:(__unused NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index d00d650467bc..c1b96b13e52b 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -123,8 +123,13 @@ - (void)testJavaScriptCallSpeed } \ } \ }; \ + var Bridge = { \ + callFunctionReturnFlushedQueue: function(module, method, args) { \ + modules[module].apply(modules[module], args); \ + } \ + }; \ function require(module) { \ - return modules[module]; \ + return Bridge; \ } \ "; @@ -138,12 +143,11 @@ function require(module) { \ for (int j = 0; j < runs; j++) { @autoreleasepool { double start = _get_time_nanoseconds(); - [_executor executeJSCall:@"module" - method:@"method" - arguments:params - callback:^(id json, __unused NSError *unused) { - XCTAssert([json isEqual:@YES], @"Invalid return"); - }]; + [_executor callFunctionOnModule:@"module" + method:@"method" + arguments:params + callback:^(__unused id json, __unused NSError *unused) { + }]; double run = _get_time_nanoseconds() - start; if ((j % frequency) == frequency - 1) { // Warmup total += run; diff --git a/IntegrationTests/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js index 96e59298d8be..5d3073a37715 100644 --- a/IntegrationTests/LoggingTestModule.js +++ b/IntegrationTests/LoggingTestModule.js @@ -10,10 +10,12 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); + var warning = require('warning'); var invariant = require('invariant'); -module.exports = { +var LoggingTestModule = { logToConsole: function(str) { console.log(str); }, @@ -30,3 +32,10 @@ module.exports = { throw new Error(str); } }; + +BatchedBridge.registerCallableModule( + 'LoggingTestModule', + LoggingTestModule +); + +module.exports = LoggingTestModule; diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index 3cf7f3ab04c8..57bafd2da85a 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -11,6 +11,9 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); +var ReactNative = require('ReactNative'); + var invariant = require('invariant'); var renderApplication = require('renderApplication'); @@ -37,6 +40,10 @@ type AppConfig = { * for the app and then actually run the app when it's ready by invoking * `AppRegistry.runApplication`. * + * To "stop" an application when a view should be destroyed, call + * `AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was + * pass into `runApplication`. These should always be used as a pair. + * * `AppRegistry` should be `require`d early in the `require` sequence to make * sure the JS execution environment is setup before other modules are * `require`d. @@ -87,6 +94,16 @@ var AppRegistry = { ); runnables[appKey].run(appParameters); }, + + unmountApplicationComponentAtRootTag: function(rootTag : number) { + ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag); + }, + }; +BatchedBridge.registerCallableModule( + 'AppRegistry', + AppRegistry +); + module.exports = AppRegistry; diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js index ea49b202fc01..e4269735afb5 100644 --- a/Libraries/BatchedBridge/BatchedBridge.js +++ b/Libraries/BatchedBridge/BatchedBridge.js @@ -10,11 +10,27 @@ */ 'use strict'; -let MessageQueue = require('MessageQueue'); +const MessageQueue = require('MessageQueue'); -let BatchedBridge = new MessageQueue( +const BatchedBridge = new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, __fbBatchedBridgeConfig.localModulesConfig, ); +// TODO: Move these around to solve the cycle in a cleaner way. + +const BridgeProfiling = require('BridgeProfiling'); +const JSTimersExecution = require('JSTimersExecution'); + +BatchedBridge.registerCallableModule('BridgeProfiling', BridgeProfiling); +BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution); + +// Wire up the batched bridge on the global object so that we can call into it. +// Ideally, this would be the inverse relationship. I.e. the native environment +// provides this global directly with its script embedded. Then this module +// would export it. A possible fix would be to trim the dependencies in +// MessageQueue to its minimal features and embed that in the native runtime. + +Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge }); + module.exports = BatchedBridge; diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js index 8c66aac9aa7b..2b8f578e701a 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js @@ -11,7 +11,13 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); +BatchedBridge.registerCallableModule( + 'RCTEventEmitter', + ReactNativeEventEmitter +); + // Completely locally implemented - no native hooks. module.exports = ReactNativeEventEmitter; diff --git a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js index 4ee5a1f03976..d7462bf23b62 100644 --- a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js +++ b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js @@ -15,6 +15,7 @@ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule; var InspectorUtils = require('InspectorUtils'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); @@ -35,7 +36,7 @@ function getRootTagForTag(tag: number): ?number { return ReactNativeTagHandles.rootNodeIDToTag[rootID]; } -module.exports = { +var RCTDebugComponentOwnership = { /** * Asynchronously returns the owner hierarchy as an array of strings. Request id is @@ -53,3 +54,10 @@ module.exports = { DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy); }, }; + +BatchedBridge.registerCallableModule( + 'RCTDebugComponentOwnership', + RCTDebugComponentOwnership +); + +module.exports = RCTDebugComponentOwnership; diff --git a/Libraries/Device/RCTDeviceEventEmitter.js b/Libraries/Device/RCTDeviceEventEmitter.js index 586663e343a7..41e4d408f4f2 100644 --- a/Libraries/Device/RCTDeviceEventEmitter.js +++ b/Libraries/Device/RCTDeviceEventEmitter.js @@ -12,7 +12,13 @@ 'use strict'; var EventEmitter = require('EventEmitter'); +var BatchedBridge = require('BatchedBridge'); var RCTDeviceEventEmitter = new EventEmitter(); +BatchedBridge.registerCallableModule( + 'RCTDeviceEventEmitter', + RCTDeviceEventEmitter +); + module.exports = RCTDeviceEventEmitter; diff --git a/Libraries/NativeApp/RCTNativeAppEventEmitter.js b/Libraries/NativeApp/RCTNativeAppEventEmitter.js index 38ccb0dcfdce..9d4de85f39e8 100644 --- a/Libraries/NativeApp/RCTNativeAppEventEmitter.js +++ b/Libraries/NativeApp/RCTNativeAppEventEmitter.js @@ -11,8 +11,14 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var EventEmitter = require('EventEmitter'); var RCTNativeAppEventEmitter = new EventEmitter(); +BatchedBridge.registerCallableModule( + 'RCTNativeAppEventEmitter', + RCTNativeAppEventEmitter +); + module.exports = RCTNativeAppEventEmitter; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 940b3b459997..b7dd38a24927 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -43,10 +43,10 @@ var guard = (fn) => { class MessageQueue { - constructor(remoteModules, localModules, customRequire) { + constructor(remoteModules, localModules) { this.RemoteModules = {}; - this._require = customRequire || require; + this._callableModules = {}; this._queue = [[],[],[]]; this._moduleTable = {}; this._methodTable = {}; @@ -154,8 +154,22 @@ class MessageQueue { if (__DEV__ && SPY_MODE) { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } - module = this._require(module); - module[method].apply(module, args); + var moduleMethods = this._callableModules[module]; + if (!moduleMethods) { + // TODO: Register all the remaining test modules and make this an invariant. #9317773 + // Fallback to require temporarily. A follow up diff will clean up the remaining + // modules and make this an invariant. + console.warn('Module is not registered:', module); + moduleMethods = require(module); + /* + invariant( + !!moduleMethods, + 'Module %s is not a registered callable module.', + module + ); + */ + } + moduleMethods[method].apply(moduleMethods, args); BridgeProfiling.profileEnd(); } @@ -337,6 +351,10 @@ class MessageQueue { return fn; } + registerCallableModule(name, methods) { + this._callableModules[name] = methods; + } + } function moduleHasConstants(moduleArray: Array>): boolean { diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js index daafe46a5663..e2d0e5c81ad2 100644 --- a/Libraries/Utilities/PerformanceLogger.js +++ b/Libraries/Utilities/PerformanceLogger.js @@ -10,6 +10,7 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); var performanceNow = require('performanceNow'); @@ -140,4 +141,9 @@ var PerformanceLogger = { } }; +BatchedBridge.registerCallableModule( + 'PerformanceLogger', + PerformanceLogger +); + module.exports = PerformanceLogger; diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js index 5b280dd66631..65abc5883d03 100644 --- a/Libraries/Utilities/RCTLog.js +++ b/Libraries/Utilities/RCTLog.js @@ -11,6 +11,8 @@ */ 'use strict'; +var BatchedBridge = require('BatchedBridge'); + var invariant = require('invariant'); var levelsMap = { @@ -39,4 +41,9 @@ class RCTLog { } } +BatchedBridge.registerCallableModule( + 'RCTLog', + RCTLog +); + module.exports = RCTLog; diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js index dc0bab73b079..8b9b689fd8bc 100644 --- a/Libraries/Utilities/__tests__/MessageQueue-test.js +++ b/Libraries/Utilities/__tests__/MessageQueue-test.js @@ -39,10 +39,11 @@ describe('MessageQueue', () => { beforeEach(() => { queue = new MessageQueue( remoteModulesConfig, - localModulesConfig, - customRequire, + localModulesConfig ); + queue.registerCallableModule('one', TestModule); + TestModule.testHook1 = jasmine.createSpy(); TestModule.testHook2 = jasmine.createSpy(); }); diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 6ef4b4999fcd..c5813bc5c250 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -161,13 +161,31 @@ - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onCompl }]; } -- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete]; +} + +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete]; +} + +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete]; +} + +- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"callback was missing for exec JS call"); NSDictionary *message = @{ - @"method": @"executeJSCall", - @"moduleName": name, - @"moduleMethod": method, + @"method": method, @"arguments": arguments }; [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) { diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index bb6aff8e4fee..696de22ad51b 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -63,7 +63,7 @@ @implementation RCTBatchedBridge BOOL _valid; BOOL _wasBatchActive; __weak id _javaScriptExecutor; - NSMutableArray *_pendingCalls; + NSMutableArray *_pendingCalls; NSMutableDictionary *_moduleDataByName; NSArray *_moduleDataByID; NSDictionary> *_modulesByName_DEPRECATED; @@ -436,10 +436,8 @@ - (void)didFinishLoading { _loading = NO; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - for (NSArray *call in _pendingCalls) { - [self _actuallyInvokeAndProcessModule:call[0] - method:call[1] - arguments:call[2]]; + for (dispatch_block_t call in _pendingCalls) { + call(); } }]; } @@ -579,10 +577,8 @@ - (void)invalidate - (void)logMessage:(NSString *)message level:(NSString *)level { if (RCT_DEBUG) { - [_javaScriptExecutor executeJSCall:@"RCTLog" - method:@"logIfNoNativeHook" - arguments:@[level, message] - callback:^(__unused id json, __unused NSError *error) {}]; + [self enqueueJSCall:@"RCTLog.logIfNoNativeHook" + args:@[level, message]]; } } @@ -593,11 +589,66 @@ - (void)logMessage:(NSString *)message level:(NSString *)level */ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args { + /** + * AnyThread + */ + NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."]; - [self _invokeAndProcessModule:@"BatchedBridge" - method:@"callFunctionReturnFlushedQueue" - arguments:@[ids[0], ids[1], args ?: @[]]]; + NSString *module = ids[0]; + NSString *method = ids[1]; + + RCTProfileBeginFlowEvent(); + + __weak RCTBatchedBridge *weakSelf = self; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid) { + return; + } + + if (strongSelf.loading) { + dispatch_block_t pendingCall = ^{ + [weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + }; + [strongSelf->_pendingCalls addObject:pendingCall]; + } else { + [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]]; + } + }]; +} + +/** + * Called by RCTModuleMethod from any thread. + */ +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args +{ + /** + * AnyThread + */ + + RCTProfileBeginFlowEvent(); + + __weak RCTBatchedBridge *weakSelf = self; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); + + RCTBatchedBridge *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid) { + return; + } + + if (strongSelf.loading) { + dispatch_block_t pendingCall = ^{ + [weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]]; + }; + [strongSelf->_pendingCalls addObject:pendingCall]; + } else { + [strongSelf _actuallyInvokeCallback:cbID arguments:args]; + } + }]; } /** @@ -608,9 +659,9 @@ - (void)_immediatelyCallTimer:(NSNumber *)timer RCTAssertJSThread(); dispatch_block_t block = ^{ - [self _actuallyInvokeAndProcessModule:@"BatchedBridge" - method:@"callFunctionReturnFlushedQueue" - arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]]; + [self _actuallyInvokeAndProcessModule:@"JSTimersExecution" + method:@"callTimers" + arguments:@[@[timer]]]; }; if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) { @@ -637,10 +688,7 @@ - (void)enqueueApplicationScript:(NSData *)script } RCT_PROFILE_BEGIN_EVENT(0, @"FetchApplicationScriptCallbacks", nil); - [_javaScriptExecutor executeJSCall:@"BatchedBridge" - method:@"flushedQueue" - arguments:@[] - callback:^(id json, NSError *error) + [_javaScriptExecutor flushedQueue:^(id json, NSError *error) { RCT_PROFILE_END_EVENT(0, @"js_call,init", @{ @"json": RCTNullIfNil(json), @@ -656,38 +704,34 @@ - (void)enqueueApplicationScript:(NSData *)script #pragma mark - Payload Generation -/** - * Called by enqueueJSCall from any thread, or from _immediatelyCallTimer, - * on the JS thread, but only in non-batched mode. - */ -- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args +- (void)_actuallyInvokeAndProcessModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args { - /** - * AnyThread - */ + RCTAssertJSThread(); - RCTProfileBeginFlowEvent(); + [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; - __weak RCTBatchedBridge *weakSelf = self; - [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - RCTProfileEndFlowEvent(); + RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { + if (error) { + RCTFatal(error); + } - RCTBatchedBridge *strongSelf = weakSelf; - if (!strongSelf || !strongSelf.valid) { + if (!self.isValid) { return; } + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; + [self handleBuffer:json batchEnded:YES]; + }; - if (strongSelf.loading) { - [strongSelf->_pendingCalls addObject:@[module, method, args]]; - } else { - [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args]; - } - }]; + [_javaScriptExecutor callFunctionOnModule:module + method:method + arguments:args + callback:processResponse]; } -- (void)_actuallyInvokeAndProcessModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)args +- (void)_actuallyInvokeCallback:(NSNumber *)cbID + arguments:(NSArray *)args { RCTAssertJSThread(); @@ -705,10 +749,9 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module [self handleBuffer:json batchEnded:YES]; }; - [_javaScriptExecutor executeJSCall:module - method:method - arguments:args - callback:processResponse]; + [_javaScriptExecutor invokeCallbackID:cbID + arguments:args + callback:processResponse]; } #pragma mark - Payload Processing diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 013a541368c5..3e4a1bc5c0d1 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -310,9 +310,11 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args [self.batchedBridge enqueueJSCall:moduleDotMethod args:args]; } -RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module - method:(__unused NSString *)method - arguments:(__unused NSArray *)args); +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args +{ + [self.batchedBridge enqueueCallback:cbID args:args]; +} + @end @implementation RCTBridge(Deprecated) diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 5506ddfa3187..832b78e3cdda 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -35,13 +35,29 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @property (nonatomic, readonly, getter=isValid) BOOL valid; /** - * Executes given method with arguments on JS thread and calls the given callback - * with JSValue and JSContext as a result of the JS module call. + * Executes BatchedBridge.flushedQueue on JS thread and calls the given callback + * with JSValue, containing the next queue, and JSContext. */ -- (void)executeJSCall:(NSString *)name - method:(NSString *)method - arguments:(NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete; +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete; + +/** + * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module name, + * method name and optional additional arguments on the JS thread and calls the + * given callback with JSValue, containing the next queue, and JSContext. + */ +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete; + +/** + * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, + * and optional additional arguments on the JS thread and calls the + * given callback with JSValue, containing the next queue, and JSContext. + */ +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete; /** * Runs an application script, and notifies of the script load being complete via `onComplete`. diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index c35017c982fd..0182fe93d858 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -37,9 +37,11 @@ - (instancetype)initWithType:(NSString *)type @interface RCTBridge (RCTModuleMethod) -- (void)_invokeAndProcessModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)args; +/** + * This method is used to invoke a callback that was registered in the + * JavaScript application context. Safe to call from any thread. + */ +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args; @end @@ -191,9 +193,7 @@ - (void)processMethodSignature } RCT_BLOCK_ARGUMENT(^(NSArray *args) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, args]]; + [bridge enqueueCallback:json args:args]; }); ) }; @@ -290,9 +290,7 @@ - (void)processMethodSignature } RCT_BLOCK_ARGUMENT(^(NSError *error) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, @[RCTJSErrorFromNSError(error)]]]; + [bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]]; }); ) } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) { @@ -306,9 +304,7 @@ - (void)processMethodSignature } RCT_BLOCK_ARGUMENT(^(id result) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, result ? @[result] : @[]]]; + [bridge enqueueCallback:json args:result ? @[result] : @[]]; }); ) } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) { @@ -323,9 +319,7 @@ - (void)processMethodSignature RCT_BLOCK_ARGUMENT(^(NSError *error) { NSDictionary *errorJSON = RCTJSErrorFromNSError(error); - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, @[errorJSON]]]; + [bridge enqueueCallback:json args:@[errorJSON]]; }); ) } else { diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 6be45621c43e..454e2e317768 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -350,7 +350,7 @@ - (void)invalidate if (self.userInteractionEnabled) { self.userInteractionEnabled = NO; [(RCTRootView *)self.superview contentViewInvalidated]; - [_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer" + [_bridge enqueueJSCall:@"AppRegistry.unmountApplicationComponentAtRootTag" args:@[self.reactTag]]; } } diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index b666690f165d..42b0a37e14f8 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -394,6 +394,7 @@ - (void)toggleProfilingFlag:(NSNotification *)notification { [self executeBlockOnJavaScriptQueue:^{ BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; + // TODO: Don't use require, go through the normal execution modes instead. #9317773 NSString *script = [NSString stringWithFormat:@"var p = require('BridgeProfiling') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"]; JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String); JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL); @@ -435,10 +436,32 @@ - (void)dealloc [self invalidate]; } -- (void)executeJSCall:(NSString *)name - method:(NSString *)method - arguments:(NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 + [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete]; +} + +- (void)callFunctionOnModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 + [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete]; +} + +- (void)invokeCallbackID:(NSNumber *)cbID + arguments:(NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773 + [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete]; +} + +- (void)_executeJSCall:(NSString *)method + arguments:(NSArray *)arguments + callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"onComplete block should not be nil"); __weak RCTContextExecutor *weakSelf = self; @@ -460,58 +483,49 @@ - (void)executeJSCall:(NSString *)name JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx); JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx); - // get require - JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require"); - JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef); - JSStringRelease(requireNameJSStringRef); + // get the BatchedBridge object + JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge"); + JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef); + JSStringRelease(moduleNameJSStringRef); - if (requireJSRef != NULL && !JSValueIsUndefined(contextJSRef, requireJSRef) && errorJSRef == NULL) { + if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) { - // get module - JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name); - JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef); - JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef); - JSStringRelease(moduleNameJSStringRef); + // get method + JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); + JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef); + JSStringRelease(methodNameJSStringRef); - if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) { + if (methodJSRef != NULL && errorJSRef == NULL) { - // get method - JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); - JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef); - JSStringRelease(methodNameJSStringRef); + // direct method invoke with no arguments + if (arguments.count == 0) { + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef); + } - if (methodJSRef != NULL && errorJSRef == NULL) { + // direct method invoke with 1 argument + else if(arguments.count == 1) { + JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); + JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef); + JSStringRelease(argsJSStringRef); - // direct method invoke with no arguments - if (arguments.count == 0) { - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef); - } + } else { + // apply invoke with array of arguments + JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply"); + JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef); + JSStringRelease(applyNameJSStringRef); - // direct method invoke with 1 argument - else if(arguments.count == 1) { + if (applyJSRef != NULL && errorJSRef == NULL) { + // invoke apply JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef); - JSStringRelease(argsJSStringRef); - - } else { - // apply invoke with array of arguments - JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply"); - JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef); - JSStringRelease(applyNameJSStringRef); - if (applyJSRef != NULL && errorJSRef == NULL) { - // invoke apply - JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString); - JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef); + JSValueRef args[2]; + args[0] = JSValueMakeNull(contextJSRef); + args[1] = argsJSRef; - JSValueRef args[2]; - args[0] = JSValueMakeNull(contextJSRef); - args[1] = argsJSRef; - - resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef); - JSStringRelease(argsJSStringRef); - } + resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef); + JSStringRelease(argsJSStringRef); } } } @@ -539,7 +553,7 @@ - (void)executeJSCall:(NSString *)name } onComplete(objcValue, nil); - }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; + }), 0, @"js_call", (@{@"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSData *)script diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index b770b3b756d9..28bc76f82406 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -27,7 +27,6 @@ import com.facebook.react.modules.debug.SourceCodeModule; import com.facebook.react.modules.systeminfo.AndroidInfoModule; import com.facebook.react.uimanager.AppRegistry; -import com.facebook.react.uimanager.ReactNative; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; @@ -97,7 +96,6 @@ public List> createJSModules() { RCTNativeAppEventEmitter.class, AppRegistry.class, BridgeProfiling.class, - ReactNative.class, DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 1169034ee116..7953691c0ad2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -52,7 +52,6 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.uimanager.AppRegistry; -import com.facebook.react.uimanager.ReactNative; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; @@ -581,8 +580,8 @@ private void detachViewFromInstance( ReactRootView rootView, CatalystInstance catalystInstance) { UiThreadUtil.assertOnUiThread(); - catalystInstance.getJSModule(ReactNative.class) - .unmountComponentAtNodeAndRemoveContainer(rootView.getId()); + catalystInstance.getJSModule(AppRegistry.class) + .unmountApplicationComponentAtRootTag(rootView.getId()); } private void tearDownReactContext(ReactContext reactContext) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java index 8940ffc0860d..13c99c043d43 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java @@ -126,7 +126,6 @@ public void executeApplicationScript( } public void executeJSCall( - String moduleName, String methodName, String jsonArgsArray, JSDebuggerCallback callback) { @@ -136,9 +135,7 @@ public void executeJSCall( try { JsonGenerator jg = startMessageObject(requestID); - jg.writeStringField("method","executeJSCall"); - jg.writeStringField("moduleName", moduleName); - jg.writeStringField("moduleMethod", methodName); + jg.writeStringField("method", methodName); jg.writeFieldName("arguments"); jg.writeRawValue(jsonArgsArray); sendMessage(requestID, endMessageObject(jg)); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java index 76b8a1cb5275..3c8eb4574b6b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java @@ -40,13 +40,12 @@ public ProxyExecutorException(Throwable cause) { /** * Execute javascript method within js context - * @param modulename name of the common-js like module to execute the method from * @param methodName name of the method to be executed * @param jsonArgsArray json encoded array of arguments provided for the method call * @return json encoded value returned from the method call */ @DoNotStrip - String executeJSCall(String modulename, String methodName, String jsonArgsArray) + String executeJSCall(String methodName, String jsonArgsArray) throws ProxyExecutorException; @DoNotStrip diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java index e044ca8cd65f..0fba2b0a3bbf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java @@ -160,11 +160,10 @@ public void executeApplicationScript(String script, String sourceURL) } @Override - public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray) + public @Nullable String executeJSCall(String methodName, String jsonArgsArray) throws ProxyExecutorException { JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture(); Assertions.assertNotNull(mWebSocketClient).executeJSCall( - moduleName, methodName, jsonArgsArray, callback); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java index f0cd49855e84..d9a387b7f0db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java @@ -18,4 +18,6 @@ public interface AppRegistry extends JavaScriptModule { void runApplication(String appKey, WritableMap appParameters); + void unmountApplicationComponentAtRootTag(int rootNodeTag); + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java deleted file mode 100644 index 2d99e79f5698..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -package com.facebook.react.uimanager; - -import com.facebook.react.bridge.JavaScriptModule; - -/** - * JS module interface - used by UIManager to communicate with main React JS module methods - */ -public interface ReactNative extends JavaScriptModule { - void unmountComponentAtNodeAndRemoveContainer(int rootNodeTag); -} diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index 84fb525b00b5..90a50d098c18 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -27,11 +27,18 @@ class JSThreadState { m_jsExecutor->executeApplicationScript(script, sourceURL); } - void executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments); + void flush() { + auto returnedJSON = m_jsExecutor->flush(); + m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); + } + + void callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + auto returnedJSON = m_jsExecutor->callFunction(moduleId, methodId, arguments); + m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); + } + + void invokeCallback(const double callbackId, const folly::dynamic& arguments) { + auto returnedJSON = m_jsExecutor->invokeCallback(callbackId, arguments); m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); } @@ -88,17 +95,31 @@ void Bridge::executeApplicationScript(const std::string& script, const std::stri m_threadState->executeApplicationScript(script, sourceURL); } -void Bridge::executeJSCall( - const std::string& script, - const std::string& sourceURL, - const std::vector& arguments) { +void Bridge::flush() { + if (*m_destroyed) { + return; + } + m_threadState->flush(); +} + +void Bridge::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + if (*m_destroyed) { + return; + } + #ifdef WITH_FBSYSTRACE + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.callFunction"); + #endif + m_threadState->callFunction(moduleId, methodId, arguments); +} + +void Bridge::invokeCallback(const double callbackId, const folly::dynamic& arguments) { if (*m_destroyed) { return; } #ifdef WITH_FBSYSTRACE - FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.executeJSCall"); + FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.invokeCallback"); #endif - m_threadState->executeJSCall(script, sourceURL, arguments); + m_threadState->invokeCallback(callbackId, arguments); } void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index 23843de42940..1d0f159458be 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -29,10 +29,22 @@ class Bridge : public Countable { Bridge(const RefPtr& jsExecutorFactory, Callback callback); virtual ~Bridge(); - void executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& values); + /** + * Flush get the next queue of changes. + */ + void flush(); + + /** + * Executes a function with the module ID and method ID and any additional + * arguments in JS. + */ + void callFunction(const double moduleId, const double methodId, const folly::dynamic& args); + + /** + * Invokes a callback with the cbID, and optional additional arguments in JS. + */ + void invokeCallback(const double callbackId, const folly::dynamic& args); + void executeApplicationScript(const std::string& script, const std::string& sourceURL); void setGlobalVariable(const std::string& propName, const std::string& jsonValue); bool supportsProfiling(); diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 6a59bff420a0..bef2d0724f39 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -28,13 +28,31 @@ class JSExecutorFactory : public Countable { class JSExecutor { public: + /** + * Execute an application script bundle in the JS context. + */ virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) = 0; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) = 0; + + /** + * Executes BatchedBridge.flushedQueue in JS to get the next queue of changes. + */ + virtual std::string flush() = 0; + + /** + * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID, + * method ID and optional additional arguments in JS, and returns the next + * queue. + */ + virtual std::string callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) = 0; + + /** + * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, + * and optional additional arguments in JS and returns the next queue. + */ + virtual std::string invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0; + virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) = 0; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 5f23c2b880df..de9dffc05eb2 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -80,6 +80,26 @@ static JSValueRef evaluateScriptWithJSC( return result; } +static std::string executeJSCallWithJSC( + JSGlobalContextRef ctx, + const std::string& methodName, + const std::vector& arguments) { + #ifdef WITH_FBSYSTRACE + FbSystraceSection s( + TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", + "method", methodName); + #endif + + // Evaluate script with JSC + folly::dynamic jsonArgs(arguments.begin(), arguments.end()); + auto js = folly::to( + "__fbBatchedBridge.", methodName, ".apply(null, ", + folly::toJson(jsonArgs), ")"); + auto result = evaluateScriptWithJSC(ctx, String(js.c_str()), nullptr); + JSValueProtect(ctx, result); + return Value(ctx, result).toJSONString(); +} + std::unique_ptr JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) { return std::unique_ptr(new JSCExecutor(cb)); } @@ -130,25 +150,28 @@ void JSCExecutor::executeApplicationScript( evaluateScriptWithJSC(m_context, jsScript, jsSourceURL); } -std::string JSCExecutor::executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - #ifdef WITH_FBSYSTRACE - FbSystraceSection s( - TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", - "module", moduleName, - "method", methodName); - #endif +std::string JSCExecutor::flush() { + // TODO: Make this a first class function instead of evaling. #9317773 + return executeJSCallWithJSC(m_context, "flushedQueue", std::vector()); +} - // Evaluate script with JSC - folly::dynamic jsonArgs(arguments.begin(), arguments.end()); - auto js = folly::to( - "require('", moduleName, "').", methodName, ".apply(null, ", - folly::toJson(jsonArgs), ")"); - auto result = evaluateScriptWithJSC(m_context, String(js.c_str()), nullptr); - JSValueProtect(m_context, result); - return Value(m_context, result).toJSONString(); +std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + // TODO: Make this a first class function instead of evaling. #9317773 + std::vector call{ + (double) moduleId, + (double) methodId, + std::move(arguments), + }; + return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call)); +} + +std::string JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + // TODO: Make this a first class function instead of evaling. #9317773 + std::vector call{ + (double) callbackId, + std::move(arguments) + }; + return executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call)); } void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index a319a9036574..5074247356b4 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -18,13 +18,18 @@ class JSCExecutor : public JSExecutor { public: explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback); ~JSCExecutor() override; + virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) override; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) override; + virtual std::string flush() override; + virtual std::string callFunction( + const double moduleId, + const double methodId, + const folly::dynamic& arguments) override; + virtual std::string invokeCallback( + const double callbackId, + const folly::dynamic& arguments) override; virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) override; diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 610cd160d4ab..8867b0c9e0d8 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -628,7 +628,7 @@ static void executeApplicationScript( try { // Execute the application script and collect/dispatch any native calls that might have occured bridge->executeApplicationScript(script, sourceUri); - bridge->executeJSCall("BatchedBridge", "flushedQueue", std::vector()); + bridge->flush(); } catch (...) { translatePendingCppExceptionToJavaException(); } @@ -682,13 +682,12 @@ static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId, NativeArray::jhybridobject args) { auto bridge = extractRefPtr(env, obj); auto arguments = cthis(wrap_alias(args)); - std::vector call{ - (double) moduleId, - (double) methodId, - std::move(arguments->array), - }; try { - bridge->executeJSCall("BatchedBridge", "callFunctionReturnFlushedQueue", std::move(call)); + bridge->callFunction( + (double) moduleId, + (double) methodId, + std::move(arguments->array) + ); } catch (...) { translatePendingCppExceptionToJavaException(); } @@ -698,12 +697,11 @@ static void invokeCallback(JNIEnv* env, jobject obj, jint callbackId, NativeArray::jhybridobject args) { auto bridge = extractRefPtr(env, obj); auto arguments = cthis(wrap_alias(args)); - std::vector call{ - (double) callbackId, - std::move(arguments->array) - }; try { - bridge->executeJSCall("BatchedBridge", "invokeCallbackAndReturnFlushedQueue", std::move(call)); + bridge->invokeCallback( + (double) callbackId, + std::move(arguments->array) + ); } catch (...) { translatePendingCppExceptionToJavaException(); } diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp index ab86a669854f..ad4e245d188b 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp @@ -12,6 +12,20 @@ namespace react { const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/JavaJSExecutor"; +static std::string executeJSCallWithProxy( + jobject executor, + const std::string& methodName, + const std::vector& arguments) { + static auto executeJSCall = + jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod("executeJSCall"); + + auto result = executeJSCall( + executor, + jni::make_jstring(methodName).get(), + jni::make_jstring(folly::toJson(arguments).c_str()).get()); + return result->toString(); +} + std::unique_ptr ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) { FBASSERTMSGF( m_executor.get() != nullptr, @@ -36,19 +50,26 @@ void ProxyExecutor::executeApplicationScript( jni::make_jstring(sourceURL).get()); } -std::string ProxyExecutor::executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) { - static auto executeJSCall = - jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod("executeJSCall"); - auto result = executeJSCall( - m_executor.get(), - jni::make_jstring(moduleName).get(), - jni::make_jstring(methodName).get(), - jni::make_jstring(folly::toJson(arguments).c_str()).get()); - return result->toString(); +std::string ProxyExecutor::flush() { + return executeJSCallWithProxy(m_executor.get(), "flushedQueue", std::vector()); +} + +std::string ProxyExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { + std::vector call{ + (double) moduleId, + (double) methodId, + std::move(arguments), + }; + return executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call)); +} + +std::string ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { + std::vector call{ + (double) callbackId, + std::move(arguments) + }; + return executeJSCallWithProxy(m_executor.get(), "invokeCallbackAndReturnFlushedQueue", std::move(call)); } void ProxyExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h index 805eda532b2f..2f5d21980d31 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h @@ -32,10 +32,14 @@ class ProxyExecutor : public JSExecutor { virtual void executeApplicationScript( const std::string& script, const std::string& sourceURL) override; - virtual std::string executeJSCall( - const std::string& moduleName, - const std::string& methodName, - const std::vector& arguments) override; + virtual std::string flush() override; + virtual std::string callFunction( + const double moduleId, + const double methodId, + const folly::dynamic& arguments) override; + virtual std::string invokeCallback( + const double callbackId, + const folly::dynamic& arguments) override; virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) override; diff --git a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp index 40d21797485b..9091c1125055 100644 --- a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp +++ b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp @@ -20,17 +20,13 @@ static std::vector executeForMethodCalls( int moduleId, int methodId, std::vector args = std::vector()) { - std::vector call; - call.emplace_back((double) moduleId); - call.emplace_back((double) methodId); - call.emplace_back(std::move(args)); - return parseMethodCalls(e.executeJSCall("Bridge", "callFunction", call)); + return parseMethodCalls(e.callFunction(moduleId, methodId, std::move(args))); } TEST(JSCExecutor, CallFunction) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module + 1], [method + 1], [args]];" " }," "};" @@ -58,7 +54,7 @@ TEST(JSCExecutor, CallFunction) { TEST(JSCExecutor, CallFunctionWithMap) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = args[0].foo + args[0].bar + args[0].baz;" " return [[module], [method], [[s]]];" " }," @@ -85,7 +81,7 @@ TEST(JSCExecutor, CallFunctionWithMap) { TEST(JSCExecutor, CallFunctionReturningMap) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = { foo: 4, bar: true };" " return [[module], [method], [[s]]];" " }," @@ -111,7 +107,7 @@ TEST(JSCExecutor, CallFunctionReturningMap) { TEST(JSCExecutor, CallFunctionWithArray) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = args[0][0]+ args[0][1] + args[0][2] + args[0].length;" " return [[module], [method], [[s]]];" " }," @@ -138,7 +134,7 @@ TEST(JSCExecutor, CallFunctionWithArray) { TEST(JSCExecutor, CallFunctionReturningNumberArray) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " var s = [3, 1, 4];" " return [[module], [method], [[s]]];" " }," @@ -162,7 +158,7 @@ TEST(JSCExecutor, CallFunctionReturningNumberArray) { TEST(JSCExecutor, SetSimpleGlobalVariable) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module], [method], [[__foo]]];" " }," "};" @@ -182,7 +178,7 @@ TEST(JSCExecutor, SetSimpleGlobalVariable) { TEST(JSCExecutor, SetObjectGlobalVariable) { auto jsText = "" "var Bridge = {" - " callFunction: function (module, method, args) {" + " callFunctionReturnFlushedQueue: function (module, method, args) {" " return [[module], [method], [[__foo]]];" " }," "};" diff --git a/local-cli/server/util/debugger.html b/local-cli/server/util/debugger.html index a69424ea98dd..289b1f53b304 100644 --- a/local-cli/server/util/debugger.html +++ b/local-cli/server/util/debugger.html @@ -58,12 +58,6 @@ window.onbeforeunload = undefined; window.localStorage.setItem('sessionID', message.id); window.location.reload(); - }, - 'executeApplicationScript': function(message) { - worker.postMessage(message); - }, - 'executeJSCall': function(message) { - worker.postMessage(message); } }; @@ -87,9 +81,11 @@ var handler = messageHandlers[object.method]; if (handler) { + // If we have a local handler, use it. handler(object); } else { - console.warn('Unknown method: ' + object.method); + // Otherwise, pass through to the worker. + worker.postMessage(object); } }; diff --git a/local-cli/server/util/debuggerWorker.js b/local-cli/server/util/debuggerWorker.js index 94f3b7d58623..fc72c5713c16 100644 --- a/local-cli/server/util/debuggerWorker.js +++ b/local-cli/server/util/debuggerWorker.js @@ -6,7 +6,7 @@ * 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. */ -/* global self, importScripts, postMessage, onmessage: true */ +/* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */ /* eslint no-unused-vars: 0 */ 'use strict'; @@ -17,16 +17,6 @@ var messageHandlers = { } importScripts(message.url); sendReply(); - }, - 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; - try { - if (typeof require === 'function') { - returnValue = require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); - } - } finally { - sendReply(JSON.stringify(returnValue)); - } } }; @@ -39,8 +29,17 @@ onmessage = function(message) { var handler = messageHandlers[object.method]; if (handler) { + // Special cased handlers handler(object, sendReply); } else { - console.warn('Unknown method: ' + object.method); + // Other methods get called on the bridge + var returnValue = [[], [], [], [], []]; + try { + if (typeof __fbBatchedBridge === 'object') { + returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments); + } + } finally { + sendReply(JSON.stringify(returnValue)); + } } }; From 14478f6701520390f00810d574892d0825e5256c Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Wed, 9 Dec 2015 04:04:23 -0800 Subject: [PATCH 0283/1411] better error message for `propTypes` check Summary: When you did a typo in declaring a prop type (like using `boolean` insteal of `bool`) you'd got a bit misleading error message: {F27113768} The truth is that the prop type is defined, but not correctly. So I've changed the message in this case to be more accurate: {F27113627} public Reviewed By: sahrens Differential Revision: D2729340 fb-gh-sync-id: dd12c10a4f3a1c9825293f86481a082908127a76 --- Libraries/ReactIOS/verifyPropTypes.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js index a7ccd210b6b1..1974716ba260 100644 --- a/Libraries/ReactIOS/verifyPropTypes.js +++ b/Libraries/ReactIOS/verifyPropTypes.js @@ -41,11 +41,16 @@ function verifyPropTypes( if (!componentInterface.propTypes[prop] && !ReactNativeStyleAttributes[prop] && (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { - throw new Error( - '`' + componentName + '` has no propType for native prop `' + + var message; + if (componentInterface.propTypes.hasOwnProperty(prop)) { + message = '`' + componentName + '` has incorrectly defined propType for native prop `' + + viewConfig.uiViewClassName + '.' + prop + '` of native type `' + nativeProps[prop]; + } else { + message = '`' + componentName + '` has no propType for native prop `' + viewConfig.uiViewClassName + '.' + prop + '` of native type `' + - nativeProps[prop] + '`' - ); + nativeProps[prop] + '`'; + }; + throw new Error(message); } } } From 54ec6e7cabbcb5aa56487d3633252564cd2a5c00 Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Wed, 9 Dec 2015 04:29:06 -0800 Subject: [PATCH 0284/1411] Docs warn navigator ios Summary: ping https://github.com/facebook/react-native/issues/795#issuecomment-139895307 Closes https://github.com/facebook/react-native/pull/3087 Reviewed By: svcscm Differential Revision: D2738947 Pulled By: mkonicek fb-gh-sync-id: e29dfd933a8da42551576bdb1fb5c270039722ee --- Libraries/Components/Navigation/NavigatorIOS.ios.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 87f49232bffe..37cc1899edd0 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -95,6 +95,12 @@ type Event = Object; * NavigatorIOS wraps UIKit navigation and allows you to add back-swipe * functionality across your app. * + * > **NOTE**: This Component is not maintained by Facebook + * > + * > This component is under community responsibility. + * > If a pure JavaScript solution fits your needs you may try the `Navigator` + * > component instead. + * * #### Routes * A route is an object used to describe each page in the navigator. The first * route is provided to NavigatorIOS as `initialRoute`: From 0ce2bce201696daece2e1b57e328b18d307ced88 Mon Sep 17 00:00:00 2001 From: Brian Leonard Date: Wed, 9 Dec 2015 04:39:36 -0800 Subject: [PATCH 0285/1411] Allow other Xcode configurations with DEV=false Summary: Updates build script to fix https://github.com/facebook/react-native/issues/4362 Closes https://github.com/facebook/react-native/pull/4520 Reviewed By: svcscm Differential Revision: D2730607 Pulled By: mkonicek fb-gh-sync-id: 2fc4a9815f19ab867a6a3dc77cf1b6eac8a25616 --- packager/react-native-xcode.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packager/react-native-xcode.sh b/packager/react-native-xcode.sh index dfa413b80ad8..5c88fdad1c0e 100755 --- a/packager/react-native-xcode.sh +++ b/packager/react-native-xcode.sh @@ -14,16 +14,12 @@ case "$CONFIGURATION" in Debug) DEV=true ;; - Release) - DEV=false - ;; "") echo "$0 must be invoked by Xcode" exit 1 ;; *) - echo "Unsupported value of \$CONFIGURATION=$CONFIGURATION" - exit 1 + DEV=false ;; esac From f8be78379858c5d78eaf81e8a00d5e276481b72f Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 9 Dec 2015 04:37:40 -0800 Subject: [PATCH 0286/1411] Document intentional retain cycle on RCTJavaScriptContext Summary: public More people wanted to understand the motivation behind the intentional retain cycle in `RCTJavaScriptContext`, add a small comment with some context. Reviewed By: jspahrsummers Differential Revision: D2738930 fb-gh-sync-id: d8c950778eb6bf3eaca627aabb6c98335d25d1fc --- React/Executors/RCTContextExecutor.m | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 42b0a37e14f8..3c22398d5d3b 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -51,14 +51,22 @@ - (instancetype)initWithJSContext:(JSContext *)context NS_DESIGNATED_INITIALIZER @implementation RCTJavaScriptContext { - RCTJavaScriptContext *_self; + RCTJavaScriptContext *_selfReference; } - (instancetype)initWithJSContext:(JSContext *)context { if ((self = [super init])) { _context = context; - _self = self; + + /** + * Explicitly introduce a retain cycle here - The RCTContextExecutor might + * be deallocated while there's still work enqueued in the JS thread, so + * we wouldn't be able kill the JSContext. Instead we create this retain + * cycle, and enqueue the -invalidate message in this object, it then + * releases the JSContext, breaks the cycle and stops the runloop. + */ + _selfReference = self; } return self; } @@ -79,7 +87,7 @@ - (void)invalidate { if (self.isValid) { _context = nil; - _self = nil; + _selfReference = nil; } } From 86bb656e6fa52707b36b94fa04d7bec170badb72 Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Wed, 9 Dec 2015 05:08:36 -0800 Subject: [PATCH 0287/1411] Add tintColor for buttons. Summary: Closes #3374 Closes https://github.com/facebook/react-native/pull/4590 Reviewed By: javache Differential Revision: D2729616 Pulled By: mkonicek fb-gh-sync-id: 4a3b6de10a09cad73dbd9d5d552adc3f6ba054e0 --- Examples/UIExplorer/ActionSheetIOSExample.js | 40 ++++++++++++++++++- Libraries/ActionSheetIOS/ActionSheetIOS.js | 5 ++- .../ActionSheetIOS/RCTActionSheetManager.m | 5 ++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 1ef41c1edef7..ccc86fba4a50 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -27,7 +27,7 @@ var BUTTONS = [ 'Option 0', 'Option 1', 'Option 2', - 'Destruct', + 'Delete', 'Cancel', ]; var DESTRUCTIVE_INDEX = 3; @@ -65,6 +65,40 @@ var ActionSheetExample = React.createClass({ } }); +var ActionSheetTintExample = React.createClass({ + getInitialState() { + return { + clicked: 'none', + }; + }, + + render() { + return ( + + + Click to show the ActionSheet + + + Clicked button: {this.state.clicked} + + + ); + }, + + showActionSheet() { + ActionSheetIOS.showActionSheetWithOptions({ + options: BUTTONS, + cancelButtonIndex: CANCEL_INDEX, + destructiveButtonIndex: DESTRUCTIVE_INDEX, + tintColor: 'green', + }, + (buttonIndex) => { + this.setState({ clicked: BUTTONS[buttonIndex] }); + }); + } +}); + + var ShareActionSheetExample = React.createClass({ getInitialState() { return { @@ -123,6 +157,10 @@ exports.examples = [ title: 'Show Action Sheet', render(): ReactElement { return ; } }, + { + title: 'Show Action Sheet with tinted buttons', + render(): ReactElement { return ; } + }, { title: 'Show Share Action Sheet', render(): ReactElement { return ; } diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js index 155b7d74e2f4..c63618441bb0 100644 --- a/Libraries/ActionSheetIOS/ActionSheetIOS.js +++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js @@ -14,6 +14,7 @@ var RCTActionSheetManager = require('NativeModules').ActionSheetManager; var invariant = require('invariant'); +var processColor = require('processColor'); var ActionSheetIOS = { showActionSheetWithOptions(options: Object, callback: Function) { @@ -26,7 +27,7 @@ var ActionSheetIOS = { 'Must provide a valid callback' ); RCTActionSheetManager.showActionSheetWithOptions( - options, + {...options, tintColor: processColor(options.tintColor)}, callback ); }, @@ -49,7 +50,7 @@ var ActionSheetIOS = { 'Must provide a valid successCallback' ); RCTActionSheetManager.showShareActionSheetWithOptions( - options, + {...options, tintColor: processColor(options.tintColor)}, failureCallback, successCallback ); diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index 245493b0ceef..6d568a0a4498 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -16,7 +16,6 @@ #import "RCTUIManager.h" @interface RCTActionSheetManager () - @end @implementation RCTActionSheetManager @@ -139,6 +138,8 @@ - (CGRect)sourceRectInView:(UIView *)sourceView alertController.popoverPresentationController.permittedArrowDirections = 0; } [controller presentViewController:alertController animated:YES completion:nil]; + + alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]]; } } @@ -210,6 +211,8 @@ - (CGRect)sourceRectInView:(UIView *)sourceView } [controller presentViewController:shareController animated:YES completion:nil]; + + shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]]; } #pragma mark UIActionSheetDelegate Methods From cd4574498d27e1b71b7f77f554533997f8552481 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 9 Dec 2015 09:53:00 -0800 Subject: [PATCH 0288/1411] Use actual CADisplayLink timestamp for VSYNC Summary: public Use the actual timestamp provided through `CADisplayLink` instead of the time the handler is called. Reviewed By: jspahrsummers Differential Revision: D2739121 fb-gh-sync-id: 1da28190bb25351dc3dd94efaff21d49279a570f --- React/Base/RCTBatchedBridge.m | 2 +- React/Profiler/RCTProfile.h | 1 + React/Profiler/RCTProfile.m | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 696de22ad51b..b3b290d72bde 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -943,7 +943,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink [self updateJSDisplayLinkState]; - RCTProfileImmediateEvent(0, @"JS Thread Tick", 'g'); + RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g'); RCT_PROFILE_END_EVENT(0, @"objc_call", nil); } diff --git a/React/Profiler/RCTProfile.h b/React/Profiler/RCTProfile.h index 2e6e921d3d26..a013a70703a3 100644 --- a/React/Profiler/RCTProfile.h +++ b/React/Profiler/RCTProfile.h @@ -124,6 +124,7 @@ RCT_EXTERN void RCTProfileEndAsyncEvent(uint64_t tag, */ RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag, NSString *name, + NSTimeInterval time, char scope); /** diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index f311a35045d6..a47931e429c2 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -275,9 +275,9 @@ @interface RCTProfile : NSObject @implementation RCTProfile -+ (void)vsync:(__unused CADisplayLink *)displayLink ++ (void)vsync:(CADisplayLink *)displayLink { - RCTProfileImmediateEvent(0, @"VSYNC", 'g'); + RCTProfileImmediateEvent(0, @"VSYNC", displayLink.timestamp, 'g'); } @end @@ -513,6 +513,7 @@ void RCTProfileEndAsyncEvent( void RCTProfileImmediateEvent( uint64_t tag, NSString *name, + NSTimeInterval time, char scope ) { CHECK(); @@ -522,7 +523,6 @@ void RCTProfileImmediateEvent( return; } - NSTimeInterval time = CACurrentMediaTime(); NSString *threadName = RCTCurrentThreadName(); dispatch_async(RCTProfileGetQueue(), ^{ From 90c7ad112f9e53dc6c71c8f47ea2a31b41d06141 Mon Sep 17 00:00:00 2001 From: tantan Date: Wed, 9 Dec 2015 10:00:19 -0800 Subject: [PATCH 0289/1411] add Clipboard component for ios and android Summary: add Clipboard component for ios and android ```javascript import Clipboard from 'react-native' Clipboard.get((content)=>{ console.log('here is content in clipboard:%s',content) }); var content = 'here is a string'; Clipboard.set(content); ``` Closes https://github.com/facebook/react-native/pull/4384 Reviewed By: svcscm Differential Revision: D2738881 Pulled By: mkonicek fb-gh-sync-id: a06df32d1eb2824cc9ca3de9d45e4e67fd2edbc9 --- Examples/UIExplorer/ClipboardExample.js | 60 +++++++++++++ Examples/UIExplorer/UIExplorerList.android.js | 1 + Examples/UIExplorer/UIExplorerList.ios.js | 1 + .../__mocks__/NativeModules.js | 4 +- Libraries/Components/Clipboard/Clipboard.js | 13 +++ Libraries/react-native/react-native.js | 1 + Libraries/react-native/react-native.js.flow | 1 + .../{RCTPasteboard.h => RCTClipboard.h} | 2 +- .../{RCTPasteboard.m => RCTClipboard.m} | 17 +++- React/React.xcodeproj/project.pbxproj | 18 ++-- .../modules/clipboard/ClipboardModule.java | 84 +++++++++++++++++++ .../react/shell/MainReactPackage.java | 2 + 12 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 Examples/UIExplorer/ClipboardExample.js create mode 100644 Libraries/Components/Clipboard/Clipboard.js rename React/Modules/{RCTPasteboard.h => RCTClipboard.h} (86%) rename React/Modules/{RCTPasteboard.m => RCTClipboard.m} (52%) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java diff --git a/Examples/UIExplorer/ClipboardExample.js b/Examples/UIExplorer/ClipboardExample.js new file mode 100644 index 000000000000..bcc53dfbb6e4 --- /dev/null +++ b/Examples/UIExplorer/ClipboardExample.js @@ -0,0 +1,60 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + Clipboard, + View, + Text, +} = React; + +var ClipboardExample = React.createClass({ + getInitialState: function() { + return { + content: 'Content will appear here' + }; + }, + + _setContentToClipboard:function(){ + Clipboard.setString('Hello World'); + Clipboard.getString(content => { + this.setState({content}); + }); + }, + + render() { + return ( + + + Tap to put "Hello World" in the clipboard + + + {this.state.content} + + + ); + } +}); + +exports.title = 'Clipboard'; +exports.description = 'Show Clipboard contents.'; +exports.examples = [ + { + title: 'Clipboard.setString() and getString()', + render(): ReactElement { return ; } + } +]; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index a2ff43096c55..36746c55861d 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -39,6 +39,7 @@ var COMPONENTS = [ var APIS = [ require('./AccessibilityAndroidExample.android'), require('./BorderExample'), + require('./ClipboardExample'), require('./GeolocationExample'), require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 940bd79ba42e..61a213097909 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -66,6 +66,7 @@ var APIS = [ require('./AsyncStorageExample'), require('./BorderExample'), require('./CameraRollExample.ios'), + require('./ClipboardExample'), require('./GeolocationExample'), require('./LayoutExample'), require('./NetInfoExample'), diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js index 113a09262699..164508ddd038 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js @@ -54,8 +54,8 @@ var NativeModules = { AlertManager: { alertWithArgs: jest.genMockFunction(), }, - Pasteboard: { - setPasteboardString: jest.genMockFunction(), + Clipboard: { + setString: jest.genMockFunction(), }, }; diff --git a/Libraries/Components/Clipboard/Clipboard.js b/Libraries/Components/Clipboard/Clipboard.js new file mode 100644 index 000000000000..fd8461ba4e86 --- /dev/null +++ b/Libraries/Components/Clipboard/Clipboard.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. + * + * @providesModule Clipboard + */ +'use strict'; + +module.exports = require('NativeModules').Clipboard; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 9eb675a8425f..be3dfd26a450 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -58,6 +58,7 @@ var ReactNative = { get AsyncStorage() { return require('AsyncStorage'); }, get BackAndroid() { return require('BackAndroid'); }, get CameraRoll() { return require('CameraRoll'); }, + get Clipboard() { return require('Clipboard'); }, get Dimensions() { return require('Dimensions'); }, get Easing() { return require('Easing'); }, get ImagePickerIOS() { return require('ImagePickerIOS'); }, diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow index 0f314456a486..d6c4aac73602 100644 --- a/Libraries/react-native/react-native.js.flow +++ b/Libraries/react-native/react-native.js.flow @@ -70,6 +70,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { AsyncStorage: require('AsyncStorage'), BackAndroid: require('BackAndroid'), CameraRoll: require('CameraRoll'), + Clipboard: require('Clipboard'), Dimensions: require('Dimensions'), Easing: require('Easing'), ImagePickerIOS: require('ImagePickerIOS'), diff --git a/React/Modules/RCTPasteboard.h b/React/Modules/RCTClipboard.h similarity index 86% rename from React/Modules/RCTPasteboard.h rename to React/Modules/RCTClipboard.h index d29a3fb1a434..bc65c62390dd 100644 --- a/React/Modules/RCTPasteboard.h +++ b/React/Modules/RCTClipboard.h @@ -9,6 +9,6 @@ #import "RCTBridgeModule.h" -@interface RCTPasteboard : NSObject +@interface RCTClipboard : NSObject @end diff --git a/React/Modules/RCTPasteboard.m b/React/Modules/RCTClipboard.m similarity index 52% rename from React/Modules/RCTPasteboard.m rename to React/Modules/RCTClipboard.m index 41ad7c044753..0a62f3f888a8 100644 --- a/React/Modules/RCTPasteboard.m +++ b/React/Modules/RCTClipboard.m @@ -7,11 +7,13 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTPasteboard.h" +#import "RCTClipboard.h" + +#import "RCTUtils.h" #import -@implementation RCTPasteboard +@implementation RCTClipboard RCT_EXPORT_MODULE() @@ -20,9 +22,16 @@ - (dispatch_queue_t)methodQueue return dispatch_get_main_queue(); } -RCT_EXPORT_METHOD(setPasteboardString:(NSString *)string) +RCT_EXPORT_METHOD(getString:(RCTResponseSenderBlock)callback) +{ + UIPasteboard *clipboard = [UIPasteboard generalPasteboard]; + callback(@[RCTNullIfNil(clipboard.string)]); +} + +RCT_EXPORT_METHOD(setString:(NSString *)content) { - [[UIPasteboard generalPasteboard] setString:string]; + UIPasteboard *clipboard = [UIPasteboard generalPasteboard]; + clipboard.string = content; } @end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 53a11ed1d6d8..d5ea0c76d5d4 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -38,12 +38,12 @@ 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; }; 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; }; 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; - 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; - 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; }; 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; + 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; }; 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; }; + 13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D033621C1837FE0021DC29 /* RCTClipboard.m */; }; 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; }; 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; }; 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; @@ -174,12 +174,10 @@ 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = ""; }; 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; }; 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; }; - 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; }; - 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; - 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; }; - 13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = ""; }; 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; }; 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; }; + 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; }; + 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; 13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = ""; }; 13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = ""; }; 13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = ""; }; @@ -189,6 +187,8 @@ 13C325281AA63B6A0048765F /* RCTComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponent.h; sourceTree = ""; }; 13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderDrawing.h; sourceTree = ""; }; 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBorderDrawing.m; sourceTree = ""; }; + 13D033611C1837FE0021DC29 /* RCTClipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTClipboard.h; sourceTree = ""; }; + 13D033621C1837FE0021DC29 /* RCTClipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipboard.m; sourceTree = ""; }; 13E067481A70F434002CDEE1 /* RCTUIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManager.h; sourceTree = ""; }; 13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = ""; }; 13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = ""; }; @@ -318,14 +318,14 @@ 1372B7091AB030C200659ED6 /* RCTAppState.m */, 58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */, 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */, + 13D033611C1837FE0021DC29 /* RCTClipboard.h */, + 13D033621C1837FE0021DC29 /* RCTClipboard.m */, 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */, 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */, 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */, 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */, - 13B201FF1BFB945300C07393 /* RCTPasteboard.h */, - 13B202001BFB945300C07393 /* RCTPasteboard.m */, 13F17A831B8493E5007D4C75 /* RCTRedBox.h */, 13F17A841B8493E5007D4C75 /* RCTRedBox.m */, 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */, @@ -628,7 +628,6 @@ 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */, - 13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */, 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */, 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */, 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */, @@ -669,6 +668,7 @@ 14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, + 13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */, 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java new file mode 100644 index 000000000000..b3689f980d9b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.clipboard; + +import android.annotation.SuppressLint; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.os.Build; + +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.common.ReactConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A module that allows JS to get/set clipboard contents. + */ +public class ClipboardModule extends ReactContextBaseJavaModule { + + public ClipboardModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "Clipboard"; + } + + private ClipboardManager getClipboardService() { + ReactApplicationContext reactContext = getReactApplicationContext(); + return (ClipboardManager) reactContext.getSystemService(reactContext.CLIPBOARD_SERVICE); + } + + @ReactMethod + public void getString(Callback cb) { + try { + ClipboardManager clipboard = getClipboardService(); + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData == null) { + cb.invoke(""); + return; + } + if (clipData.getItemCount() >= 1) { + ClipData.Item firstItem = clipboard.getPrimaryClip().getItemAt(0); + String text = "" + firstItem.getText(); + cb.invoke(text); + } else { + cb.invoke(""); + } + } catch(Exception e) { + FLog.w(ReactConstants.TAG, "Cannot get clipboard contents: " + e.getMessage()); + } + } + + @SuppressLint("DeprecatedMethod") + @ReactMethod + public void setString(String text) { + ReactApplicationContext reactContext = getReactApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + ClipData clipdata = ClipData.newPlainText(null, text); + ClipboardManager clipboard = getClipboardService(); + clipboard.setPrimaryClip(clipdata); + } else { + ClipboardManager clipboard = getClipboardService(); + clipboard.setText(text); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index bf91bade6ccd..973cfbd84c7d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -41,6 +41,7 @@ import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; +import com.facebook.react.modules.clipboard.ClipboardModule; /** * Package defining basic modules and view managers. @@ -51,6 +52,7 @@ public class MainReactPackage implements ReactPackage { public List createNativeModules(ReactApplicationContext reactContext) { return Arrays.asList( new AsyncStorageModule(reactContext), + new ClipboardModule(reactContext), new FrescoModule(reactContext), new IntentModule(reactContext), new LocationModule(reactContext), From e8659b36020ad8af1086d656359ce28c9e1fdfa5 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 9 Dec 2015 20:25:19 +0000 Subject: [PATCH 0290/1411] Remove all BUCK files --- .../src/main/java/com/facebook/csslayout/BUCK | 15 ----- .../src/main/java/com/facebook/jni/BUCK | 17 ------ .../src/main/java/com/facebook/perftest/BUCK | 11 ---- .../com/facebook/proguard/annotations/BUCK | 14 ----- .../src/main/java/com/facebook/quicklog/BUCK | 14 ----- .../com/facebook/quicklog/identifiers/BUCK | 11 ---- .../src/main/java/com/facebook/react/BUCK | 27 --------- .../java/com/facebook/react/animation/BUCK | 18 ------ .../main/java/com/facebook/react/bridge/BUCK | 41 ------------- .../main/java/com/facebook/react/common/BUCK | 29 ---------- .../java/com/facebook/react/devsupport/BUCK | 31 ---------- .../com/facebook/react/modules/common/BUCK | 20 ------- .../java/com/facebook/react/modules/core/BUCK | 22 ------- .../com/facebook/react/modules/debug/BUCK | 21 ------- .../com/facebook/react/modules/fresco/BUCK | 28 --------- .../com/facebook/react/modules/network/BUCK | 24 -------- .../com/facebook/react/modules/storage/BUCK | 21 ------- .../facebook/react/modules/systeminfo/BUCK | 19 ------- .../com/facebook/react/modules/toast/BUCK | 19 ------- .../com/facebook/react/modules/websocket/BUCK | 22 ------- .../main/java/com/facebook/react/shell/BUCK | 43 -------------- .../main/java/com/facebook/react/touch/BUCK | 17 ------ .../java/com/facebook/react/uimanager/BUCK | 25 -------- .../java/com/facebook/react/views/drawer/BUCK | 22 ------- .../java/com/facebook/react/views/image/BUCK | 29 ---------- .../com/facebook/react/views/progressbar/BUCK | 21 ------- .../facebook/react/views/recyclerview/BUCK | 25 -------- .../java/com/facebook/react/views/scroll/BUCK | 23 -------- .../com/facebook/react/views/switchview/BUCK | 21 ------- .../java/com/facebook/react/views/text/BUCK | 21 ------- .../com/facebook/react/views/textinput/BUCK | 23 -------- .../com/facebook/react/views/toolbar/BUCK | 33 ----------- .../java/com/facebook/react/views/view/BUCK | 22 ------- .../com/facebook/react/views/viewpager/BUCK | 21 ------- .../src/main/java/com/facebook/soloader/BUCK | 20 ------- .../src/main/java/com/facebook/systrace/BUCK | 11 ---- ReactAndroid/src/main/jni/react/BUCK | 57 ------------------- ReactAndroid/src/main/jni/react/jni/BUCK | 53 ----------------- .../src/main/jni/react/perftests/BUCK | 21 ------- ReactAndroid/src/main/res/BUCK | 19 ------- 40 files changed, 951 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/csslayout/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/jni/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/perftest/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/proguard/annotations/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/quicklog/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/quicklog/identifiers/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/common/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/shell/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/touch/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/soloader/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/systrace/BUCK delete mode 100644 ReactAndroid/src/main/jni/react/BUCK delete mode 100644 ReactAndroid/src/main/jni/react/jni/BUCK delete mode 100644 ReactAndroid/src/main/jni/react/perftests/BUCK delete mode 100644 ReactAndroid/src/main/res/BUCK diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/BUCK b/ReactAndroid/src/main/java/com/facebook/csslayout/BUCK deleted file mode 100644 index da7888de9298..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/BUCK +++ /dev/null @@ -1,15 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'csslayout', - srcs = glob(['**/*.java']), - deps = [ - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = ['PUBLIC'], -) - -project_config( - src_target = ':csslayout', -) diff --git a/ReactAndroid/src/main/java/com/facebook/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/jni/BUCK deleted file mode 100644 index 220371dbaae6..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/BUCK +++ /dev/null @@ -1,17 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'jni', - srcs = glob(['**/*.java']), - deps = [ - react_native_dep('java/com/facebook/proguard/annotations:annotations'), - react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':jni', -) diff --git a/ReactAndroid/src/main/java/com/facebook/perftest/BUCK b/ReactAndroid/src/main/java/com/facebook/perftest/BUCK deleted file mode 100644 index 655817f6e11b..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/perftest/BUCK +++ /dev/null @@ -1,11 +0,0 @@ -android_library( - name = 'perftest', - srcs = glob(['*.java']), - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':perftest', -) diff --git a/ReactAndroid/src/main/java/com/facebook/proguard/annotations/BUCK b/ReactAndroid/src/main/java/com/facebook/proguard/annotations/BUCK deleted file mode 100644 index 27de0b88a9c7..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/proguard/annotations/BUCK +++ /dev/null @@ -1,14 +0,0 @@ -android_library( - name = 'annotations', - srcs = glob(['*.java']), - proguard_config = 'proguard_annotations.pro', - deps = [ - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':annotations', -) diff --git a/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK b/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK deleted file mode 100644 index 911bbd556733..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK +++ /dev/null @@ -1,14 +0,0 @@ -android_library( - name = 'quicklog', - srcs = glob(['*.java']), - exported_deps = [ - '//java/com/facebook/quicklog/identifiers:identifiers', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':quicklog', -) diff --git a/ReactAndroid/src/main/java/com/facebook/quicklog/identifiers/BUCK b/ReactAndroid/src/main/java/com/facebook/quicklog/identifiers/BUCK deleted file mode 100644 index 38d3b1bfa8dc..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/quicklog/identifiers/BUCK +++ /dev/null @@ -1,11 +0,0 @@ -android_library( - name = 'identifiers', - srcs = glob(['*.java']), - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':identifiers', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK deleted file mode 100644 index 8102c187c31b..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ /dev/null @@ -1,27 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'react', - srcs = glob(['*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/devsupport:devsupport'), - react_native_target('java/com/facebook/react/modules/core:core'), - react_native_target('java/com/facebook/react/modules/debug:debug'), - react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'), - react_native_target('java/com/facebook/react/modules/toast:toast'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':react', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK b/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK deleted file mode 100644 index 6a3307d027a7..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK +++ /dev/null @@ -1,18 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'animation', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC' - ], -) - -project_config( - src_target = ':animation', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK b/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK deleted file mode 100644 index 9d8c59acd80d..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK +++ /dev/null @@ -1,41 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -# We package the JS files from the bundler and local directory into what we -# pretend is an ordinary JAR file. By putting them under the assets/ directory -# within the zip file and relying on Buck to merge its contents into the APK, -# our JS bundles arrive in a place accessible by the AssetManager at runtime. - -python_binary( - name = 'package_js', - main = 'package_js.py', - visibility = [ - 'PUBLIC', - ], -) - -android_library( - name = 'bridge', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/common:common'), - react_native_target('jni/react/jni:jni'), - react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), - react_native_dep('java/com/facebook/systrace:systrace'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jackson:core'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/java/okhttp:okhttp-ws'), - ], - exported_deps = [ - react_native_dep('java/com/facebook/jni:jni'), - react_native_dep('java/com/facebook/proguard/annotations:annotations'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':bridge', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK deleted file mode 100644 index d034ce6ec68e..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK +++ /dev/null @@ -1,29 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'common', - srcs = glob(['**/*.java']), - deps = [ - ':build_config', - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -android_build_config( - name = 'build_config', - package = 'com.facebook.react', - visibility = [ - 'PUBLIC', - ], - values = [ - 'boolean IS_INTERNAL_BUILD = true', - ], -) - -project_config( - src_target = ':common', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK deleted file mode 100644 index 06bf27bc42d8..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK +++ /dev/null @@ -1,31 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'devsupport', - manifest = 'AndroidManifest.xml', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('res:devsupport'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/modules/debug:debug'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/java/okhttp:okhttp'), - react_native_dep('third-party/java/okio:okio'), - ], - visibility = [ - react_native_target('java/com/facebook/react/...'), - '//instrumentation_tests/com/facebook/catalyst/...', - '//java/com/facebook/catalyst/...', - '//java/com/facebook/groups/treehouse/react/...', - '//java/com/facebook/fbreact/...', - '//javatests/com/facebook/catalyst/...', - '//javatests/com/facebook/react/...', - ], -) - -project_config( - src_target = ':devsupport', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK deleted file mode 100644 index d099b3c2efd6..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK +++ /dev/null @@ -1,20 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'common', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':common', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK deleted file mode 100644 index 5507ede15a6b..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK +++ /dev/null @@ -1,22 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'core', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/devsupport:devsupport'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':core', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK deleted file mode 100644 index 6f5d6b9b1500..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'debug', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':debug', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK deleted file mode 100644 index 18e1e514c51a..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK +++ /dev/null @@ -1,28 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'fresco', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/modules/common:common'), - react_native_target('java/com/facebook/react/modules/network:network'), - react_native_dep('java/com/facebook/systrace:systrace'), - '//libraries/fbcore/src/main/java/com/facebook/common/internal:internal', - '//libraries/fbcore/src/main/java/com/facebook/common/soloader:soloader', - '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline', - '//libraries/fresco/imagepipeline-backends/imagepipeline-okhttp/src/main/java/com/facebook/imagepipeline/backends/okhttp:okhttp', - '//libraries/imagepipeline/src/main/java/com/facebook/cache/common:common', - '//libraries/imagepipeline/src/main/java/com/facebook/cache/disk:disk', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/core:core', - '//libraries/soloader/java/com/facebook/soloader:soloader', - '//third-party/java/okhttp:okhttp', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':fresco', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK deleted file mode 100644 index 2de89f0d35db..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK +++ /dev/null @@ -1,24 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'network', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/modules/core:core'), - react_native_target('java/com/facebook/react/common:common'), - '//third-party/java/android/support/v4:lib-support-v4', - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - '//third-party/java/okhttp:okhttp', - '//third-party/java/okio:okio', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':network', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK deleted file mode 100644 index 723db50f703d..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'storage', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/modules/common:common'), - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':storage', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK deleted file mode 100644 index deac058df538..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK +++ /dev/null @@ -1,19 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'systeminfo', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':systeminfo', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK deleted file mode 100644 index 497ef5bd03c9..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK +++ /dev/null @@ -1,19 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'toast', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':toast', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK deleted file mode 100644 index 92ab5d859e68..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK +++ /dev/null @@ -1,22 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'websocket', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/modules/core:core'), - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - '//third-party/java/okhttp:okhttp-ws', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':websocket', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK deleted file mode 100644 index 7043f36df502..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ /dev/null @@ -1,43 +0,0 @@ -include_defs('//instrumentation_tests/DEFS') -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'shell', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('res:shell'), - react_native_target('java/com/facebook/react:react'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/devsupport:devsupport'), - react_native_target('java/com/facebook/react/views/drawer:drawer'), - react_native_target('java/com/facebook/react/views/image:image'), - react_native_target('java/com/facebook/react/views/progressbar:progressbar'), - react_native_target('java/com/facebook/react/views/scroll:scroll'), - react_native_target('java/com/facebook/react/views/switchview:switchview'), - react_native_target('java/com/facebook/react/views/text:text'), - react_native_target('java/com/facebook/react/views/textinput:textinput'), - react_native_target('java/com/facebook/react/views/toolbar:toolbar'), - react_native_target('java/com/facebook/react/views/view:view'), - react_native_target('java/com/facebook/react/views/viewpager:viewpager'), - react_native_target('java/com/facebook/react/modules/core:core'), - react_native_target('java/com/facebook/react/modules/debug:debug'), - react_native_target('java/com/facebook/react/modules/fresco:fresco'), - react_native_target('java/com/facebook/react/modules/network:network'), - react_native_target('java/com/facebook/react/modules/storage:storage'), - react_native_target('java/com/facebook/react/modules/toast:toast'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/modules/websocket:websocket'), - react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), - react_native_dep('third-party/java/android/support/v4:lib-support-v4'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':shell', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK b/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK deleted file mode 100644 index 44cea8fdce54..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK +++ /dev/null @@ -1,17 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'touch', - srcs = glob(['**/*.java']), - deps = [ - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC' - ], -) - -project_config( - src_target = ':touch', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK deleted file mode 100644 index 128e87f67c69..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ /dev/null @@ -1,25 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'uimanager', - srcs = glob(['**/*.java']), - deps = [ - '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging', - react_native_target('java/com/facebook/react/animation:animation'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/touch:touch'), - react_native_dep('java/com/facebook/systrace:systrace'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/java/android/support/v4:lib-support-v4'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':uimanager', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK deleted file mode 100644 index 1bef740699d7..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK +++ /dev/null @@ -1,22 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'drawer', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/views/scroll:scroll'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - '//third-party/java/android/support/v4:lib-support-v4', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':drawer', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK deleted file mode 100644 index 52c2e0b1e8d0..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK +++ /dev/null @@ -1,29 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'image', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - '//libraries/drawee/src/main/java/com/facebook/drawee/controller:controller', - '//libraries/drawee/src/main/java/com/facebook/drawee/drawable:drawable', - '//libraries/drawee/src/main/java/com/facebook/drawee/generic:generic', - '//libraries/drawee/src/main/java/com/facebook/drawee/interfaces:interfaces', - '//libraries/drawee/src/main/java/com/facebook/drawee/view:view', - '//libraries/fbcore/src/main/java/com/facebook/common/references:references', - '//libraries/fbcore/src/main/java/com/facebook/common/util:util', - '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/bitmaps:bitmaps', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/common:common', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/request:request', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':image', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK deleted file mode 100644 index ef7fc96092cd..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'progressbar', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':progressbar', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK deleted file mode 100644 index 54414c312e23..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK +++ /dev/null @@ -1,25 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'recyclerview', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/views/scroll:scroll'), - react_native_target('java/com/facebook/react/views/view:view'), - '//third-party/android-support-v7/recyclerview:recyclerview', - '//third-party/java/android/support/v4:lib-support-v4', - '//third-party/java/infer-annotations:infer-annotations', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':recyclerview', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK deleted file mode 100644 index d087195129da..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK +++ /dev/null @@ -1,23 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'scroll', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/views/view:view'), - react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_dep('third-party/java/android/support/v4:lib-support-v4'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':scroll', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK deleted file mode 100644 index 6c353cbe81ba..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'switchview', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - '//third-party/android-support-for-standalone-apps/v7/appcompat:appcompat-23.1', - '//third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':switchview', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK deleted file mode 100644 index cd0f60c1b3c5..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'text', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':text', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK deleted file mode 100644 index 4c7142f68d38..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK +++ /dev/null @@ -1,23 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'textinput', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/views/text:text'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/modules/core:core'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':textinput', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK deleted file mode 100644 index 35a06f331104..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK +++ /dev/null @@ -1,33 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'toolbar', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/common:common'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/bitmaps:bitmaps', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/common:common', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/request:request', - '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/image:image', - '//libraries/drawee/src/main/java/com/facebook/drawee/controller:controller', - '//libraries/drawee/src/main/java/com/facebook/drawee/drawable:drawable', - '//libraries/drawee/src/main/java/com/facebook/drawee/generic:generic', - '//libraries/drawee/src/main/java/com/facebook/drawee/interfaces:interfaces', - '//libraries/drawee/src/main/java/com/facebook/drawee/view:view', - '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline', - '//third-party/android-support-for-standalone-apps/v7/appcompat:appcompat-23.1', - '//third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native', - '//third-party/java/android/support/v4:lib-support-v4', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':toolbar', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK deleted file mode 100644 index 428372baa2e4..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK +++ /dev/null @@ -1,22 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'view', - srcs = glob(['*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/csslayout:csslayout'), - react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':view', -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK deleted file mode 100644 index 8b0b881fa0a4..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'viewpager', - srcs = glob(['**/*.java']), - deps = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/views/scroll:scroll'), - '//third-party/java/android/support/v4:lib-support-v4', - '//third-party/java/jsr-305:jsr-305', - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':viewpager', -) diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/BUCK b/ReactAndroid/src/main/java/com/facebook/soloader/BUCK deleted file mode 100644 index 322ba2204e20..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/soloader/BUCK +++ /dev/null @@ -1,20 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'soloader', - srcs = glob(['*.java']), - proguard_config = 'soloader.pro', - deps = [ - react_native_dep('third-party/java/jsr-305:jsr-305'), - # Be very careful adding new dependencies here, because this code - # has to run very early in the app startup process. - # Definitely do *not* depend on lib-base or guava. - ], - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':soloader', -) diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/BUCK b/ReactAndroid/src/main/java/com/facebook/systrace/BUCK deleted file mode 100644 index 6d222bb2a0f5..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/systrace/BUCK +++ /dev/null @@ -1,11 +0,0 @@ -android_library( - name = 'systrace', - srcs = glob(['*.java']), - visibility = [ - 'PUBLIC', - ], -) - -project_config( - src_target = ':systrace', -) diff --git a/ReactAndroid/src/main/jni/react/BUCK b/ReactAndroid/src/main/jni/react/BUCK deleted file mode 100644 index 14e7e8ffe550..000000000000 --- a/ReactAndroid/src/main/jni/react/BUCK +++ /dev/null @@ -1,57 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -# We depend on JSC, support the same platforms -SUPPORTED_PLATFORMS = '^android-(armv7|x86)$' - -cxx_library( - name = 'react', - soname = 'libreactnative.so', - header_namespace = 'react', - supported_platforms_regex = SUPPORTED_PLATFORMS, - force_static = True, - srcs = [ - 'Bridge.cpp', - 'Value.cpp', - 'MethodCall.cpp', - 'JSCHelpers.cpp', - 'JSCExecutor.cpp', - 'JSCTracing.cpp', - 'JSCPerfLogging.cpp', - 'JSCLegacyProfiler.cpp', - ], - headers = [ - 'JSCTracing.h', - 'JSCPerfLogging.h', - 'JSCLegacyProfiler.h', - ], - exported_headers = [ - 'Bridge.h', - 'Executor.h', - 'JSCExecutor.h', - 'JSCHelpers.h', - 'MethodCall.h', - 'Value.h', - ], - preprocessor_flags = [ - '-DLOG_TAG="ReactNative"', - '-DWITH_JSC_EXTRA_TRACING=1', - '-DWITH_FBSYSTRACE=1', - ], - compiler_flags = [ - '-Wall', - '-std=c++11', - '-fexceptions', - '-fvisibility=hidden', - ], - visibility = [ - react_native_target('jni/react/jni:jni'), - ], - deps = [ - '//native/fb:fb', - '//xplat/fbsystrace:fbsystrace', - '//native/jni:jni', - '//native/third-party/jsc:jsc', - '//native/third-party/jsc:jsc_legacy_profiler', - '//xplat/folly:json', - ], -) diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK deleted file mode 100644 index b70ecf239e74..000000000000 --- a/ReactAndroid/src/main/jni/react/jni/BUCK +++ /dev/null @@ -1,53 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -# We depend on JSC, support the same platforms -SUPPORTED_PLATFORMS = '^android-(armv7|x86)$' - -cxx_library( - name = 'jni', - soname = 'libreactnativejni.so', - header_namespace = 'react/jni', - supported_platforms_regex = SUPPORTED_PLATFORMS, - srcs = [ - 'NativeArray.cpp', - 'OnLoad.cpp', - 'ProxyExecutor.cpp', - 'JSLoader.cpp', - ], - headers = [ - 'ProxyExecutor.h', - 'JSLoader.h', - ], - exported_headers = [ - 'NativeArray.h', - 'ReadableNativeArray.h', - ], - preprocessor_flags = [ - '-DLOG_TAG="ReactNativeJNI"', - '-DWITH_FBSYSTRACE=1', - ], - compiler_flags = [ - '-Wall', - '-Werror', - '-fexceptions', - '-std=c++11', - '-fvisibility=hidden', - '-frtti', - ], - visibility = [ - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('jni/react/...'), - react_native_dep('native/react/...') - ], - deps = [ - react_native_target('jni/react:react'), - '//native/jni:jni', - '//native/third-party/jsc:jsc', - '//native/third-party/jsc:jsc_legacy_profiler', - '//xplat/folly:json', - ], -) - -project_config( - src_target = ':jni', -) diff --git a/ReactAndroid/src/main/jni/react/perftests/BUCK b/ReactAndroid/src/main/jni/react/perftests/BUCK deleted file mode 100644 index 6358592fb5ee..000000000000 --- a/ReactAndroid/src/main/jni/react/perftests/BUCK +++ /dev/null @@ -1,21 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -cxx_library( - name = 'perftests', - srcs = [ 'OnLoad.cpp' ], - soname = 'libreactnativetests.so', - preprocessor_flags = [ - '-DLOG_TAG=\"ReactPerftests\"', - ], - visibility = [ - '//instrumentation_tests/com/facebook/catalyst/...', - ], - deps = [ - '//native:base', - '//native/jni:jni', - ], -) - -project_config( - src_target = ':perftests', -) diff --git a/ReactAndroid/src/main/res/BUCK b/ReactAndroid/src/main/res/BUCK deleted file mode 100644 index 90f796a13ee2..000000000000 --- a/ReactAndroid/src/main/res/BUCK +++ /dev/null @@ -1,19 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_resource( - name = 'devsupport', - res = 'devsupport', - package = 'com.facebook.react', - visibility = [ - react_native_target('java/com/facebook/react/devsupport/...'), - ], -) - -android_resource( - name = 'shell', - res = 'shell', - package = 'com.facebook.react', - visibility = [ - 'PUBLIC', - ], -) From c0c8e7cfdf83b3a215f15257892f892604eff431 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Wed, 9 Dec 2015 12:34:24 -0800 Subject: [PATCH 0291/1411] Add support for `double` type in ReactPropGroup Reviewed By: astreet Differential Revision: D2735362 fb-gh-sync-id: a8eab400248fc4c8ad5d43e6a34cfd350dfb1d26 --- .../com/facebook/react/uimanager/ReactPropGroup.java | 10 +++++++++- .../react/uimanager/ViewManagersPropertyCache.java | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java index 13c843314a04..9e2a17b6e6d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java @@ -26,7 +26,8 @@ * group of the property being updated. Last, third argument represent the value that should be set. * * - * Currently only {@code int}, {@code float} and {@link String} value types are supported. + * Currently only {@code int}, {@code float}, {@code double} and {@link String} value types are + * supported. * * In case when property has been removed from the corresponding react component annotated setter * will be called and default value will be provided as a value parameter. Default value can be @@ -68,6 +69,13 @@ */ float defaultFloat() default 0.0f; + /** + * Default value for property of type {@code double}. This value will be provided to property + * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed + * from the component description in JS + */ + double defaultDouble() default 0.0; + /** * Default value for property of type {@code int}. This value will be provided to property * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index fd0ce6c41e49..0e2a33148d60 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -141,6 +141,11 @@ public DoublePropSetter(ReactProp prop, Method setter, double defaultValue) { mDefaultValue = defaultValue; } + public DoublePropSetter(ReactPropGroup prop, Method setter, int index, double defaultValue) { + super(prop, "number", setter, index); + mDefaultValue = defaultValue; + } + @Override protected Object extractProperty(CatalystStylesDiffMap props) { return props.getDouble(mPropName, mDefaultValue); @@ -366,6 +371,12 @@ private static void createPropSetters( names[i], new FloatPropSetter(annotation, method, i, annotation.defaultFloat())); } + } else if (propTypeClass == double.class) { + for (int i = 0; i < names.length; i++) { + props.put( + names[i], + new DoublePropSetter(annotation, method, i, annotation.defaultDouble())); + } } else if (propTypeClass == Integer.class) { for (int i = 0; i < names.length; i++) { props.put( From 053a2294b872636ab65d8abdd433323158744c39 Mon Sep 17 00:00:00 2001 From: Thomas Parslow Date: Wed, 9 Dec 2015 12:58:52 -0800 Subject: [PATCH 0292/1411] Add taggedTemplateLiteral to babelHelpers Summary: Needed to support tagged template literals (which are already enabled in babel). Not having this helper means we get a runtime crash. Closes https://github.com/facebook/react-native/pull/4680 Reviewed By: svcscm Differential Revision: D2740233 Pulled By: spicyj fb-gh-sync-id: 12729f670b7942ad7a04bd50ae1eca35d2b1e410 --- .../src/Resolver/polyfills/babelHelpers.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js index a7fac6bcd6ab..e028a782000b 100644 --- a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js +++ b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js @@ -11,7 +11,7 @@ /* eslint-disable strict */ // Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' ')) +// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toConsumableArray '.split(' ')) // then replacing the `global` reference in the last line to also use `this`. // // actually, that's a lie, because babel6 omits _extends and createRawReactElement @@ -207,6 +207,14 @@ } }; })(); + + babelHelpers.taggedTemplateLiteral = function (strings, raw) { + return Object.freeze(Object.defineProperties(strings, { + raw: { + value: Object.freeze(raw) + } + })); + }; babelHelpers.toConsumableArray = function (arr) { if (Array.isArray(arr)) { From fe3686e126ee66adc6b2e44b46ba82e5ae6c434c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 9 Dec 2015 14:06:16 -0800 Subject: [PATCH 0293/1411] Fix asset httpServerLocation on Windows. Summary: Functions from the path module return paths with the native file system path separator. generateAssetModule is using path.join and path.dirname to generate httpServerLocation. That means that on Windows systems, the generated URL path has backslashes rather than forward slashes which breaks the app's ability to retrieve image assets using require('./image.png'). This change fixes this by checking path.sep and replacing backslashes with slashes on systems that require it. Closes https://github.com/facebook/react-native/pull/4416 Reviewed By: svcscm Differential Revision: D2740529 Pulled By: mkonicek fb-gh-sync-id: edae0f6762c7dc1db7af078209e38a2feab1e0a1 --- packager/react-packager/src/Bundler/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index ffe17735626d..470c6821c809 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -364,6 +364,12 @@ class Bundler { generateAssetModule(bundle, module, platform = null) { const relPath = getPathRelativeToRoot(this._projectRoots, module.path); + var assetUrlPath = path.join('/assets', path.dirname(relPath)); + + // On Windows, change backslashes to slashes to get proper URL path from file path. + if (path.sep === '\\') { + assetUrlPath = assetUrlPath.replace(/\\/g, '/'); + } return Promise.all([ sizeOf(module.path), @@ -374,7 +380,7 @@ class Bundler { const img = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), - httpServerLocation: path.join('/assets', path.dirname(relPath)), + httpServerLocation: assetUrlPath, width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, scales: assetData.scales, From f9f123ba96fea76bce6b21aabb5da4d79ee26db7 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 9 Dec 2015 22:32:20 +0000 Subject: [PATCH 0294/1411] Sync internal shrinkwrap file to github --- npm-shrinkwrap.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index ae8e80071546..630e9cded87e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4377,9 +4377,9 @@ } }, "react-haste": { - "version": "0.14.0", - "from": "react-haste@0.14.0", - "resolved": "https://registry.npmjs.org/react-haste/-/react-haste-0.14.0.tgz" + "version": "0.14.2", + "from": "react-haste@0.14.2", + "resolved": "https://registry.npmjs.org/react-haste/-/react-haste-0.14.2.tgz" }, "react-timer-mixin": { "version": "0.13.2", From 139f9945dfdc37bd37709cfbdf3f54a72a056bdb Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 9 Dec 2015 14:51:43 -0800 Subject: [PATCH 0295/1411] Don't use arrow functions with Flow types to fix website generation Summary: Our custom jsdocs parser doesn't support the syntax yet. public Reviewed By: bestander Differential Revision: D2739861 fb-gh-sync-id: eefc3c54b98e06597ca7d93396aa4fe3a92d4a58 --- Examples/UIExplorer/NetInfoExample.js | 2 +- Libraries/Network/NetInfo.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js index af120c591153..a79ed67e2043 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/NetInfoExample.js @@ -176,7 +176,7 @@ exports.examples = [ { platform: 'android', title: 'NetInfo.isConnectionExpensive (Android)', - description: 'Asycnronously check isConnectionExpensive', + description: 'Asynchronously check isConnectionExpensive', render(): ReactElement { return ; } }, ]; diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index d4295754f259..fff16c59860f 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -58,11 +58,15 @@ const _subscriptions = new Map(); let _isConnected; if (Platform.OS === 'ios') { - _isConnected = (reachability: ReachabilityStateIOS): bool => { + _isConnected = function( + reachability: ReachabilityStateIOS, + ): bool { return reachability !== 'none' && reachability !== 'unknown'; }; } else if (Platform.OS === 'android') { - _isConnected = (connectionType: ConnectivityStateAndroid) : bool => { + _isConnected = function( + connectionType: ConnectivityStateAndroid, + ): bool { return connectionType !== 'NONE' && connectionType !== 'UNKNOWN'; }; } From 2aa3e0dd1a70c81a7d50361cb1b409dd2c3a1b16 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 9 Dec 2015 15:16:15 -0800 Subject: [PATCH 0296/1411] Use the new Android Clipboard module Summary: public - Open source the unit test for `ClipboardModule`, start using the `ReactTestHelper` in two unit tests - Fixes a few references to "pasteboard" in strings Reviewed By: bestander Differential Revision: D2739614 fb-gh-sync-id: e076940a3ae5c22314e181a37fe2c3f77a18cf85 --- ...stTestHelper.java => ReactTestHelper.java} | 2 +- .../clipboard/ClipboardModuleTest.java | 61 +++++++++++++++++++ .../storage/AsyncStorageModuleTest.java | 12 +--- 3 files changed, 64 insertions(+), 11 deletions(-) rename ReactAndroid/src/test/java/com/facebook/react/bridge/{CatalystTestHelper.java => ReactTestHelper.java} (98%) create mode 100644 ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java similarity index 98% rename from ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java rename to ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java index 27a1b7a9fee7..0aa236ddb9f6 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/bridge/CatalystTestHelper.java +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java @@ -24,7 +24,7 @@ /** * Utility for creating pre-configured instances of core react components for tests. */ -public class CatalystTestHelper { +public class ReactTestHelper { /** * @return a ReactApplicationContext that has a CatalystInstance mock returned by diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java new file mode 100644 index 000000000000..0486ae79f82f --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.modules.clipboard; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.ClipboardManager; + +import com.facebook.react.bridge.ReactTestHelper; +import com.facebook.react.modules.clipboard.ClipboardModule; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +@SuppressLint({"ClipboardManager", "DeprecatedClass"}) +@RunWith(RobolectricTestRunner.class) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +public class ClipboardModuleTest { + + private static final String TEST_CONTENT = "test"; + + private ClipboardModule mClipboardModule; + private ClipboardManager mClipboardManager; + + @Before + public void setUp() { + mClipboardModule = new ClipboardModule(ReactTestHelper.createCatalystContextForTest()); + mClipboardManager = + (ClipboardManager) RuntimeEnvironment.application.getSystemService(Context.CLIPBOARD_SERVICE); + } + + @Test + public void testSetString() { + mClipboardModule.setString(TEST_CONTENT); + assertTrue(mClipboardManager.getText().equals(TEST_CONTENT)); + + mClipboardModule.setString(null); + assertFalse(mClipboardManager.hasText()); + + mClipboardModule.setString(""); + assertFalse(mClipboardManager.hasText()); + + mClipboardModule.setString(" "); + assertTrue(mClipboardManager.hasText()); + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java index c11e9ba932a4..819aececc8f7 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java @@ -20,12 +20,12 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.SimpleArray; import com.facebook.react.bridge.SimpleMap; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.storage.ReactDatabaseSupplier; - import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; @@ -60,13 +60,6 @@ public class AsyncStorageModuleTest { private AsyncStorageModule mStorage; private SimpleArray mEmptyArray; - private static class FakeFragmentContext extends ContextWrapper { - - public FakeFragmentContext(Context base) { - super(base); - } - } - @Rule public PowerMockRule rule = new PowerMockRule(); @@ -90,8 +83,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { }); // don't use Robolectric before initializing mocks - mStorage = new AsyncStorageModule(new ReactApplicationContext( - new FakeFragmentContext(RuntimeEnvironment.application))); + mStorage = new AsyncStorageModule(ReactTestHelper.createCatalystContextForTest()); mEmptyArray = new SimpleArray(); } From 64a78ed74b20a93d1272d9958a1cbfd6060604ad Mon Sep 17 00:00:00 2001 From: Siddu Duddikunta Date: Wed, 9 Dec 2015 15:49:36 -0800 Subject: [PATCH 0297/1411] Fix bug in Android elevation implementation Summary: If border radius is not set or is zero, then elevation will not work properly. This bug seems to have been introduced when the style in facebook/react-native#4180 was modified slightly to produce commit b65f1f223488b52963f80a67bb41956103263d27. Closes https://github.com/facebook/react-native/pull/4555 Reviewed By: svcscm Differential Revision: D2741203 Pulled By: mkonicek fb-gh-sync-id: f4ee9ccdfc64374d58824a6e988409ac2b7532a4 --- .../facebook/react/views/view/ReactViewBackgroundDrawable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java index 4a8805614e5c..6ded1123d4b6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java @@ -138,7 +138,7 @@ public void getOutline(Outline outline) { : 0; outline.setRoundRect(getBounds(), mBorderRadius + extraRadiusFromBorderWidth); } else { - super.getOutline(outline); + outline.setRect(getBounds()); } } From 96553cf553e26e48a87f83030e04aef788f2dfef Mon Sep 17 00:00:00 2001 From: Felipe Martim Date: Wed, 9 Dec 2015 16:06:53 -0800 Subject: [PATCH 0298/1411] Add rotateX and rotateY transforms to Android Views . Summary: Setting rotateX and rotateY for Android Views. Closes https://github.com/facebook/react-native/pull/4413 Reviewed By: svcscm Differential Revision: D2741328 Pulled By: mkonicek fb-gh-sync-id: 931027c006bc571ef374a7b82cc7074b8a34bc8d --- Libraries/Utilities/MatrixMath.js | 6 ++++-- .../com/facebook/react/uimanager/BaseViewManager.java | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index e248ba75dc87..addc8660ef47 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -465,10 +465,10 @@ var MatrixMath = { // Solve the equation by inverting perspectiveMatrix and multiplying // rightHandSide by the inverse. - var inversePerspectiveMatrix = MatrixMath.inverse3x3( + var inversePerspectiveMatrix = MatrixMath.inverse( perspectiveMatrix ); - var transposedInversePerspectiveMatrix = MatrixMath.transpose4x4( + var transposedInversePerspectiveMatrix = MatrixMath.transpose( inversePerspectiveMatrix ); var perspective = MatrixMath.multiplyVectorByMatrix( @@ -583,6 +583,8 @@ var MatrixMath = { translation, rotate: rotationDegrees[2], + rotateX: rotationDegrees[0], + rotateY: rotationDegrees[1], scaleX: scale[0], scaleY: scale[1], translateX: translation[0], diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index c5dcaa700736..201effea750e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -18,6 +18,8 @@ public abstract class BaseViewManager Date: Wed, 9 Dec 2015 17:16:29 -0800 Subject: [PATCH 0299/1411] Fix Text + TouchableWithoutFeedback Summary: Arrow functions don't have `arguments`...whoops :( public Reviewed By: vjeux, ericvicenti Differential Revision: D2741448 fb-gh-sync-id: 15e9fb0446909e4cbbbb5e30024dde58d872e725 --- Libraries/Text/Text.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 69dc800ded6f..8d9f68fba864 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -181,35 +181,35 @@ const Text = React.createClass({ } return setResponder; }, - onResponderGrant: (e: SyntheticEvent, dispatchID: string) => { + onResponderGrant: function(e: SyntheticEvent, dispatchID: string) { this.touchableHandleResponderGrant(e, dispatchID); this.props.onResponderGrant && this.props.onResponderGrant.apply(this, arguments); - }, - onResponderMove: (e: SyntheticEvent) => { + }.bind(this), + onResponderMove: function(e: SyntheticEvent) { this.touchableHandleResponderMove(e); this.props.onResponderMove && this.props.onResponderMove.apply(this, arguments); - }, - onResponderRelease: (e: SyntheticEvent) => { + }.bind(this), + onResponderRelease: function(e: SyntheticEvent) { this.touchableHandleResponderRelease(e); this.props.onResponderRelease && this.props.onResponderRelease.apply(this, arguments); - }, - onResponderTerminate: (e: SyntheticEvent) => { + }.bind(this), + onResponderTerminate: function(e: SyntheticEvent) { this.touchableHandleResponderTerminate(e); this.props.onResponderTerminate && this.props.onResponderTerminate.apply(this, arguments); - }, - onResponderTerminationRequest: (): bool => { + }.bind(this), + onResponderTerminationRequest: function(): bool { // Allow touchable or props.onResponderTerminationRequest to deny // the request var allowTermination = this.touchableHandleResponderTerminationRequest(); if (allowTermination && this.props.onResponderTerminationRequest) { - allowTermination = this.props.onResponderTerminationRequest(); + allowTermination = this.props.onResponderTerminationRequest.apply(this, arguments); } return allowTermination; - }, + }.bind(this), }; } newProps = { From 423202072c31aaa5a952bff092a2b8307995a0cb Mon Sep 17 00:00:00 2001 From: Qiao Liang Date: Wed, 9 Dec 2015 22:38:28 -0800 Subject: [PATCH 0300/1411] fix android movie example by adding missing permission Summary: ```log 12-10 04:15:40.321 1482-1482/com.facebook.react.movies E/AndroidRuntime: FATAL EXCEPTION: main Process: com.facebook.react.movies, PID: 1482 java.lang.RuntimeException: Error receiving broadcast Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x4000010 (has extras) } in com.facebook.react.modules.netinfo.ConnectivityModule$ConnectivityBroadcastReceiver@529210a0 at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:778) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at Closes https://github.com/facebook/react-native/pull/4694 Reviewed By: svcscm Differential Revision: D2743536 Pulled By: androidtrunkagent fb-gh-sync-id: 7d93ca701c3d6b17add370ab13a68b150910e6a6 --- Examples/Movies/android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Examples/Movies/android/app/src/main/AndroidManifest.xml b/Examples/Movies/android/app/src/main/AndroidManifest.xml index 2123d078fdf1..185ffa506021 100644 --- a/Examples/Movies/android/app/src/main/AndroidManifest.xml +++ b/Examples/Movies/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ package="com.facebook.react.movies"> + Date: Thu, 10 Dec 2015 02:57:33 -0800 Subject: [PATCH 0301/1411] Update touch/measure/hotspot to all use same coordinate space Reviewed By: astreet Differential Revision: D2731165 fb-gh-sync-id: 729943233af66f139907cac2002fed4038b3fa6a --- .../uimanager/NativeViewHierarchyManager.java | 11 ++++- .../react/uimanager/events/TouchesHelper.java | 42 ++++++++++++------- .../react/views/view/ReactViewManager.java | 6 +-- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index ad195004333d..dd40b7a16a95 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -466,8 +466,15 @@ public void measure(int tag, int[] outputBuffer) { throw new NoSuchNativeViewException("No native view for " + tag + " currently exists"); } - // Puts x/y in outputBuffer[0]/[1] - v.getLocationOnScreen(outputBuffer); + View rootView = (View) RootViewUtil.getRootView(v); + rootView.getLocationInWindow(outputBuffer); + int rootX = outputBuffer[0]; + int rootY = outputBuffer[1]; + + v.getLocationInWindow(outputBuffer); + + outputBuffer[0] = outputBuffer[0] - rootX; + outputBuffer[1] = outputBuffer[1] - rootY; outputBuffer[2] = v.getWidth(); outputBuffer[3] = v.getHeight(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index e431fa7aeaeb..d312a402026b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -35,25 +35,35 @@ * given {@param event} instance. This method use {@param reactTarget} parameter to set as a * target view id associated with current gesture. */ - private static WritableArray createsPointersArray(int reactTarget, TouchEvent touchEvent) { - MotionEvent event = touchEvent.getMotionEvent(); - + private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) { WritableArray touches = Arguments.createArray(); - - // Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the - // pointer at index 0. We use those value to calculate "raw" coordinates for other pointers - float offsetX = event.getRawX() - event.getX(); - float offsetY = event.getRawY() - event.getY(); - - for (int index = 0; index < event.getPointerCount(); index++) { + MotionEvent motionEvent = event.getMotionEvent(); + + // Calculate the coordinates for the target view. + // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view + // The TouchEvent contains the X,Y of the touch in the coordinate space of the target view + // Subtracting them allows us to get the coordinates of the target view's top left corner + // We then use this when computing the view specific touches below + // Since only one view is actually handling even multiple touches, the values are all relative + // to this one target view. + float targetViewCoordinateX = motionEvent.getX() - event.getViewX(); + float targetViewCoordinateY = motionEvent.getY() - event.getViewY(); + + for (int index = 0; index < motionEvent.getPointerCount(); index++) { WritableMap touch = Arguments.createMap(); - touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX)); - touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY)); - touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index))); - touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index))); + // pageX,Y values are relative to the RootReactView + // the motionEvent already contains coordinates in that view + touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index))); + touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index))); + // locationX,Y values are relative to the target view + // To compute the values for the view, we subtract that views location from the event X,Y + float locationX = motionEvent.getX(index) - targetViewCoordinateX; + float locationY = motionEvent.getY(index) - targetViewCoordinateY; + touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX)); + touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY)); touch.putInt(TARGET_KEY, reactTarget); - touch.putDouble(TIMESTAMP_KEY, event.getEventTime()); - touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index)); + touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime()); + touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index)); touches.pushMap(touch); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 6e002fb9f978..9b74294ad216 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -47,7 +47,6 @@ public class ReactViewManager extends ViewGroupManager { }; private static final int CMD_HOTSPOT_UPDATE = 1; private static final int CMD_SET_PRESSED = 2; - private static final int[] sLocationBuf = new int[2]; @ReactProp(name = "accessible") public void setAccessible(ReactViewGroup view, boolean accessible) { @@ -160,9 +159,8 @@ public void receiveCommand(ReactViewGroup root, int commandId, @Nullable Readabl "Illegal number of arguments for 'updateHotspot' command"); } if (Build.VERSION.SDK_INT >= 21) { - root.getLocationOnScreen(sLocationBuf); - float x = PixelUtil.toPixelFromDIP(args.getDouble(0)) - sLocationBuf[0]; - float y = PixelUtil.toPixelFromDIP(args.getDouble(1)) - sLocationBuf[1]; + float x = PixelUtil.toPixelFromDIP(args.getDouble(0)); + float y = PixelUtil.toPixelFromDIP(args.getDouble(1)); root.drawableHotspotChanged(x, y); } break; From d138403c2e162a1fa76774a784e3364ae80b3273 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 04:27:45 -0800 Subject: [PATCH 0302/1411] Added deferred module loading feature Reviewed By: javache Differential Revision: D2717687 fb-gh-sync-id: 4c03c721c794a2e7ac67a0b20474129fde7f0a0d --- .../BatchedBridgedModules/NativeModules.js | 74 +++++++++++- Libraries/Utilities/MessageQueue.js | 105 ++++++++---------- .../Utilities/nativeModulePrefixNormalizer.js | 32 ------ React/Base/RCTBatchedBridge.m | 51 +++++---- React/Base/RCTModuleData.m | 7 ++ React/Executors/RCTContextExecutor.m | 24 +++- 6 files changed, 175 insertions(+), 118 deletions(-) delete mode 100644 Libraries/Utilities/nativeModulePrefixNormalizer.js diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index e3a179887b6c..837534ac3517 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -11,10 +11,78 @@ */ 'use strict'; -var NativeModules = require('BatchedBridge').RemoteModules; +const BatchedBridge = require('BatchedBridge'); +const RemoteModules = BatchedBridge.RemoteModules; -var nativeModulePrefixNormalizer = require('nativeModulePrefixNormalizer'); +function normalizePrefix(moduleName: string): string { + return moduleName.replace(/^(RCT|RK)/, ''); +} -nativeModulePrefixNormalizer(NativeModules); +/** + * Dirty hack to support old (RK) and new (RCT) native module name conventions. + */ +Object.keys(RemoteModules).forEach((moduleName) => { + const strippedName = normalizePrefix(moduleName); + if (RemoteModules['RCT' + strippedName] && RemoteModules['RK' + strippedName]) { + throw new Error( + 'Module cannot be registered as both RCT and RK: ' + moduleName + ); + } + if (strippedName !== moduleName) { + RemoteModules[strippedName] = RemoteModules[moduleName]; + delete RemoteModules[moduleName]; + } +}); + +/** + * Define lazy getters for each module. + * These will return the module if already loaded, or load it if not. + */ +const NativeModules = {}; +Object.keys(RemoteModules).forEach((moduleName) => { + Object.defineProperty(NativeModules, moduleName, { + enumerable: true, + get: () => { + let module = RemoteModules[moduleName]; + if (module && module.moduleID) { + const json = global.nativeRequireModuleConfig(moduleName); + const config = json && JSON.parse(json); + module = config && BatchedBridge.processModuleConfig(config, module.moduleID); + RemoteModules[moduleName] = module; + } + return module; + }, + }); +}); + +/** + * Copies the ViewManager constants into UIManager. This is only + * needed for iOS, which puts the constants in the ViewManager + * namespace instead of UIManager, unlike Android. + * + * We'll eventually move this logic to UIManager.js, once all + * the call sites accessing NativeModules.UIManager directly have + * been removed #9344445 + */ +const UIManager = NativeModules.UIManager; +UIManager && Object.keys(UIManager).forEach(viewName => { + const viewConfig = UIManager[viewName]; + const constants = {}; + if (viewConfig.Manager) { + Object.defineProperty(viewConfig, 'Constants', { + enumerable: true, + get: () => { + const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; + viewManager && Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value !== 'function') { + constants[key] = value; + } + }); + return constants; + }, + }); + } +}); module.exports = NativeModules; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index b7dd38a24927..adf9376e33e0 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -66,8 +66,6 @@ class MessageQueue { this._genModulesConfig(localModules),this._moduleTable, this._methodTable ); - this._copyNativeComponentConstants(this.RemoteModules); - this._debugInfo = {}; this._remoteModuleTable = {}; this._remoteMethodTable = {}; @@ -105,6 +103,12 @@ class MessageQueue { return queue[0].length ? queue : null; } + processModuleConfig(config, moduleID) { + const module = this._genModule(config, moduleID); + this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable) + return module; + } + /** * "Private" methods */ @@ -200,30 +204,6 @@ class MessageQueue { * Private helper methods */ - /** - * Copies the ViewManager constants into UIManager. This is only - * needed for iOS, which puts the constants in the ViewManager - * namespace instead of UIManager, unlike Android. - */ - _copyNativeComponentConstants(remoteModules) { - let UIManager = remoteModules.RCTUIManager; - UIManager && Object.keys(UIManager).forEach(viewName => { - let viewConfig = UIManager[viewName]; - if (viewConfig.Manager) { - const viewManager = remoteModules[viewConfig.Manager]; - viewManager && Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value !== 'function') { - if (!viewConfig.Constants) { - viewConfig.Constants = {}; - } - viewConfig.Constants[key] = value; - } - }); - } - }); - } - /** * Converts the old, object-based module structure to the new * array-based structure. TODO (t8823865) Removed this @@ -269,55 +249,62 @@ class MessageQueue { } _genLookupTables(modulesConfig, moduleTable, methodTable) { - modulesConfig.forEach((module, moduleID) => { - if (!module) { - return; - } - - let moduleName, methods; - if (moduleHasConstants(module)) { - [moduleName, , methods] = module; - } else { - [moduleName, methods] = module; - } - - moduleTable[moduleID] = moduleName; - methodTable[moduleID] = Object.assign({}, methods); + modulesConfig.forEach((config, moduleID) => { + this._genLookup(config, moduleID, moduleTable, methodTable); }); } + + _genLookup(config, moduleID, moduleTable, methodTable) { + if (!config) { + return; + } - _genModules(remoteModules) { - remoteModules.forEach((module, moduleID) => { - if (!module) { - return; - } + let moduleName, methods; + if (moduleHasConstants(config)) { + [moduleName, , methods] = config; + } else { + [moduleName, methods] = config; + } - let moduleName, constants, methods, asyncMethods; - if (moduleHasConstants(module)) { - [moduleName, constants, methods, asyncMethods] = module; - } else { - [moduleName, methods, asyncMethods] = module; - } + moduleTable[moduleID] = moduleName; + methodTable[moduleID] = Object.assign({}, methods); + } - const moduleConfig = {moduleID, constants, methods, asyncMethods}; - this.RemoteModules[moduleName] = this._genModule({}, moduleConfig); + _genModules(remoteModules) { + remoteModules.forEach((config, moduleID) => { + this._genModule(config, moduleID); }); } - _genModule(module, moduleConfig) { - const {moduleID, constants, methods = [], asyncMethods = []} = moduleConfig; + _genModule(config, moduleID) { + if (!config) { + return; + } + + let moduleName, constants, methods, asyncMethods; + if (moduleHasConstants(config)) { + [moduleName, constants, methods, asyncMethods] = config; + } else { + [moduleName, methods, asyncMethods] = config; + } - methods.forEach((methodName, methodID) => { + let module = {}; + methods && methods.forEach((methodName, methodID) => { const methodType = - arrayContains(asyncMethods, methodID) ? + asyncMethods && arrayContains(asyncMethods, methodID) ? MethodTypes.remoteAsync : MethodTypes.remote; module[methodName] = this._genMethod(moduleID, methodID, methodType); }); Object.assign(module, constants); - + + if (!constants && !methods && !asyncMethods) { + module.moduleID = moduleID; + } + + this.RemoteModules[moduleName] = module; return module; } - + _genMethod(module, method, type) { let fn = null; let self = this; diff --git a/Libraries/Utilities/nativeModulePrefixNormalizer.js b/Libraries/Utilities/nativeModulePrefixNormalizer.js deleted file mode 100644 index 151417bc5acf..000000000000 --- a/Libraries/Utilities/nativeModulePrefixNormalizer.js +++ /dev/null @@ -1,32 +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 nativeModulePrefixNormalizer - * @flow - */ -'use strict'; - -// Dirty hack to support old (RK) and new (RCT) native module name conventions -function nativeModulePrefixNormalizer( - modules: {[key: string]: any} -): void { - Object.keys(modules).forEach((moduleName) => { - var strippedName = moduleName.replace(/^(RCT|RK)/, ''); - if (modules['RCT' + strippedName] && modules['RK' + strippedName]) { - throw new Error( - 'Module cannot be registered as both RCT and RK: ' + moduleName - ); - } - if (strippedName !== moduleName) { - modules[strippedName] = modules[moduleName]; - delete modules[moduleName]; - } - }); -} - -module.exports = nativeModulePrefixNormalizer; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index b3b290d72bde..a0190a0ae521 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -128,7 +128,7 @@ - (void)start dispatch_group_leave(initModulesAndLoadSource); }]; - // Synchronously initialize all native modules that cannot be deferred + // Synchronously initialize all native modules that cannot be loaded lazily [self initModules]; #if RCT_DEBUG @@ -236,16 +236,21 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad - (id)moduleForName:(NSString *)moduleName { RCTModuleData *moduleData = _moduleDataByName[moduleName]; - if (RCT_DEBUG) { - Class moduleClass = moduleData.moduleClass; - if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { - RCTLogError(@"Class %@ was not exported. Did you forget to use " - "RCT_EXPORT_MODULE()?", moduleClass); - } - } return moduleData.instance; } +- (NSArray *)configForModuleName:(NSString *)moduleName +{ + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (!moduleData) { + moduleData = _moduleDataByName[[@"RCT" stringByAppendingString:moduleName]]; + } + if (moduleData) { + return moduleData.config; + } + return (id)kCFNull; +} + - (void)initModules { RCTAssertMainThread(); @@ -344,21 +349,29 @@ - (void)setUpExecutor [_javaScriptExecutor setUp]; } +- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData +{ + if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + [_frameUpdateObservers addObject:moduleData]; + id observer = (id)moduleData.instance; + __weak typeof(self) weakSelf = self; + __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; + observer.pauseCallback = ^{ + [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ + [weakSelf updateJSDisplayLinkState]; + }]; + }; + } +} + - (NSString *)moduleConfig { NSMutableArray *config = [NSMutableArray new]; for (RCTModuleData *moduleData in _moduleDataByID) { - [config addObject:RCTNullIfNil(moduleData.config)]; - if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { - [_frameUpdateObservers addObject:moduleData]; - id observer = (id)moduleData.instance; - __weak typeof(self) weakSelf = self; - __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; - observer.pauseCallback = ^{ - [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ - [weakSelf updateJSDisplayLinkState]; - }]; - }; + if (self.executorClass == [RCTContextExecutor class]) { + [config addObject:@[moduleData.name]]; + } else { + [config addObject:RCTNullIfNil(moduleData.config)]; } } diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index 011159db829e..b8fa17049091 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -14,6 +14,12 @@ #import "RCTLog.h" #import "RCTUtils.h" +@interface RCTBridge (Private) + +- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData; + +@end + @implementation RCTModuleData { NSString *_queueName; @@ -88,6 +94,7 @@ - (void)setBridgeForInstance:(RCTBridge *)bridge "or provide your own setter method.", self.name); } } + [bridge registerModuleForFrameUpdates:self]; } - (NSString *)name diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 3c22398d5d3b..d37f6c7b9313 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -98,10 +98,11 @@ - (void)dealloc @end -// Private bridge interface to allow middle-batch calls +// Private bridge interface @interface RCTBridge (RCTContextExecutor) - (void)handleBuffer:(NSArray *)buffer batchEnded:(BOOL)hasEnded; +- (NSArray *)configForModuleName:(NSString *)moduleName; @end @@ -351,13 +352,24 @@ - (void)setUp [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; [strongSelf _addNativeHook:RCTNoop withName:"noop"]; - __weak RCTBridge *bridge = strongSelf->_bridge; + __weak RCTBridge *weakBridge = strongSelf->_bridge; + + strongSelf->_context.context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { + if (!weakSelf.valid) { + return nil; + } + NSArray *config = [weakBridge configForModuleName:moduleName]; + if (config) { + return RCTJSONStringify(config, NULL); + } + return nil; + }; + strongSelf->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){ if (!weakSelf.valid || !calls) { return; } - - [bridge handleBuffer:calls batchEnded:NO]; + [weakBridge handleBuffer:calls batchEnded:NO]; }; strongSelf->_context.context[@"RCTPerformanceNow"] = ^{ @@ -365,6 +377,7 @@ - (void)setUp }; #if RCT_DEV + if (RCTProfileIsProfiling()) { strongSelf->_context.context[@"__RCTProfileIsProfiling"] = @YES; } @@ -394,7 +407,9 @@ - (void)setUp name:event object:nil]; } + #endif + }]; } @@ -417,7 +432,6 @@ - (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char JSStringRef JSName = JSStringCreateWithUTF8CString(name); JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); - } - (void)invalidate From 7f710f9050636a4c884052ec989037329f63ad65 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Thu, 10 Dec 2015 04:36:23 -0800 Subject: [PATCH 0303/1411] Add fbsystrace markers using the legacyprofiler Reviewed By: astreet Differential Revision: D2728033 fb-gh-sync-id: 264d40930b8fec0262cbea36529bd8b11efcc58e --- Libraries/Utilities/BridgeProfiling.js | 10 ++- .../src/main/jni/react/JSCExecutor.cpp | 4 ++ .../src/main/jni/react/JSCLegacyProfiler.cpp | 4 ++ .../src/main/jni/react/JSCTracing.cpp | 65 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 8e60354f2a50..36cb86252f61 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -20,8 +20,9 @@ type RelayProfiler = { var GLOBAL = GLOBAL || this; var TRACE_TAG_REACT_APPS = 1 << 17; +var TRACE_TAG_JSC_CALLS = 1 << 27; -var _enabled; +var _enabled = false; var _asyncCookie = 0; var _ReactPerf = null; function ReactPerf() { @@ -33,6 +34,13 @@ function ReactPerf() { var BridgeProfiling = { setEnabled(enabled: boolean) { + if (_enabled !== enabled) { + if (enabled) { + global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS); + } else { + global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS); + } + } _enabled = enabled; ReactPerf().enableMeasure = enabled; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index de9dffc05eb2..6e1f493e8cdd 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -195,7 +195,11 @@ bool JSCExecutor::supportsProfiling() { void JSCExecutor::startProfiler(const std::string &titleString) { #ifdef WITH_JSC_EXTRA_TRACING JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str()); + #if WITH_JSC_INTERNAL + JSStartProfiling(m_context, title, false); + #else JSStartProfiling(m_context, title); + #endif JSStringRelease(title); #endif } diff --git a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp index 74222c2efe28..e1e2d8b4bdcd 100644 --- a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp +++ b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp @@ -26,7 +26,11 @@ static JSValueRef nativeProfilerStart( } JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception); + #if WITH_JSC_INTERNAL + JSStartProfiling(ctx, title, false); + #else JSStartProfiling(ctx, title); + #endif JSStringRelease(title); return JSValueMakeUndefined(ctx); } diff --git a/ReactAndroid/src/main/jni/react/JSCTracing.cpp b/ReactAndroid/src/main/jni/react/JSCTracing.cpp index 945d6fe72bc4..c607cc8c6ed9 100644 --- a/ReactAndroid/src/main/jni/react/JSCTracing.cpp +++ b/ReactAndroid/src/main/jni/react/JSCTracing.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -9,6 +10,8 @@ using std::min; +static const char *ENABLED_FBSYSTRACE_PROFILE_NAME = "__fbsystrace__"; + static uint64_t tagFromJSValue( JSContextRef ctx, JSValueRef value, @@ -409,12 +412,74 @@ static JSValueRef nativeTraceCounter( return JSValueMakeUndefined(ctx); } +static JSValueRef nativeTraceBeginLegacy( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) { + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeTraceBeginLegacy: requires TAG Argument"); + } + return JSValueMakeUndefined(ctx); + } + + uint64_t tag = tagFromJSValue(ctx, arguments[0], exception); + if (!fbsystrace_is_tracing(tag)) { + return JSValueMakeUndefined(ctx); + } + + JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME); + #if WITH_JSC_INTERNAL + JSStartProfiling(ctx, title, true); + #else + JSStartProfiling(ctx, title); + #endif + JSStringRelease(title); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef nativeTraceEndLegacy( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { + if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) { + if (exception) { + *exception = facebook::react::makeJSCException( + ctx, + "nativeTraceBeginLegacy: requires TAG Argument"); + } + return JSValueMakeUndefined(ctx); + } + + uint64_t tag = tagFromJSValue(ctx, arguments[0], exception); + if (!fbsystrace_is_tracing(tag)) { + return JSValueMakeUndefined(ctx); + } + + JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME); + JSEndProfiling(ctx, title); + JSStringRelease(title); + + return JSValueMakeUndefined(ctx); +} + namespace facebook { namespace react { void addNativeTracingHooks(JSGlobalContextRef ctx) { installGlobalFunction(ctx, "nativeTraceBeginSection", nativeTraceBeginSection); installGlobalFunction(ctx, "nativeTraceEndSection", nativeTraceEndSection); + installGlobalFunction(ctx, "nativeTraceBeginLegacy", nativeTraceBeginLegacy); + installGlobalFunction(ctx, "nativeTraceEndLegacy", nativeTraceEndLegacy); installGlobalFunction(ctx, "nativeTraceBeginAsyncSection", nativeTraceBeginAsyncSection); installGlobalFunction(ctx, "nativeTraceEndAsyncSection", nativeTraceEndAsyncSection); installGlobalFunction(ctx, "nativeTraceAsyncSectionStage", nativeTraceAsyncSectionStage); From ec5ca670449505a824d5626021b007d687d90ff1 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 05:56:26 -0800 Subject: [PATCH 0304/1411] Fixed JS crash on Android when requiring a native module that doesn't exist Summary: public NativeModule getters call the nativeRequireModuleConfig function to lazily load a module if it's not already available, but this crashes on Android since the nativeRequireModuleConfig hook hasn't been implemented yet. This diff checks that the hook exists before calling it. Reviewed By: gsaraf Differential Revision: D2744080 fb-gh-sync-id: cae9c8c45a4d3c80ceb8c10f3d4d59a8d9d3c7f8 --- Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index 837534ac3517..5ef61e35fd0c 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -44,7 +44,7 @@ Object.keys(RemoteModules).forEach((moduleName) => { enumerable: true, get: () => { let module = RemoteModules[moduleName]; - if (module && module.moduleID) { + if (module && module.moduleID && global.nativeRequireModuleConfig) { const json = global.nativeRequireModuleConfig(moduleName); const config = json && JSON.parse(json); module = config && BatchedBridge.processModuleConfig(config, module.moduleID); From da153d3928ff376eeb7effd02d60c5e9b1dc89b3 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 07:09:08 -0800 Subject: [PATCH 0305/1411] Fixed bug where module with moduleID of zero wouldn't be loaded Reviewed By: jspahrsummers Differential Revision: D2744167 fb-gh-sync-id: b442af60618703ab19290d042b3614984cd9a3ae --- Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index 5ef61e35fd0c..af1540b75b5d 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -44,7 +44,7 @@ Object.keys(RemoteModules).forEach((moduleName) => { enumerable: true, get: () => { let module = RemoteModules[moduleName]; - if (module && module.moduleID && global.nativeRequireModuleConfig) { + if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) { const json = global.nativeRequireModuleConfig(moduleName); const config = json && JSON.parse(json); module = config && BatchedBridge.processModuleConfig(config, module.moduleID); From 651664b4ceda0afd668fbf293d66addb8eb061c6 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 10 Dec 2015 15:25:49 +0000 Subject: [PATCH 0306/1411] Update breaking-changes.md --- breaking-changes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/breaking-changes.md b/breaking-changes.md index 77f27ca393ea..0d8c5df65246 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -1,5 +1,7 @@ # Breaking Changes +## 0.17 + ## 0.16 - Touch events on Android now have coordinates consistent with iOS: [0c2ee5](https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6) From f4e3512076f05b81ddc8047665b4526259ea4a03 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 10 Dec 2015 15:37:44 +0000 Subject: [PATCH 0307/1411] Add a guide for doing releases --- Releases.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Releases.md diff --git a/Releases.md b/Releases.md new file mode 100644 index 000000000000..89ab4f985424 --- /dev/null +++ b/Releases.md @@ -0,0 +1,56 @@ +The list of releases with notes can be found at: +https://github.com/facebook/react-native/releases + +## Cut a release branch + +- Make sure iOS and Android Getting Started flow works on master. +- Publish to Maven Central: + - Edit `ReactAndroid/gradle.properties` + - Edit `ReactAndroid/release.gradle` and uncomment Javadoc generation + - `./gradlew :ReactAndroid:installArchives` (`java -version` should print 1.7 which is currently needed for Javadoc generation) + - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload) + - Select Artifact(s) with a POM (to publish to a local Maven repo for testing run `./gradlew :ReactAndroid:installArchives`) + - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) +- `git checkout -b 0.12-stable` +- In `package.json`, set version to e.g. `0.12.0-rc`. +- In `React.podspec`, set version to e.g. `0.12.0-rc`. +- In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.12.+` +- `git push origin 0.12-stable` +- `npm publish` + +## Do a release + +To release e.g. 0.12 to npm: + +(For admins, here's the command to promote someone) + +``` +npm owner add react-native +``` + +When you are ready to do a new release. + +- Update package.json and React.podspec with the new number + +``` +git tag v0.6.0-rc 0.6-stable # don't forget the `v` at the beginning! +git push --tags +``` + +- Publish to GitHub + +``` +git push git push origin 0.12-stable +``` + +- Publish to npm + +``` +npm publish +npm dist-tag add react-native@0.12.0 latest +``` + +- Publish to CocoaPods (it takes several minutes to validate the podspec) +- Upgrade tags to a release by going to https://github.com/facebook/react-native/tags +- Click "Add Notes to release" +- Click Publish \ No newline at end of file From e56efc7df8e9f4cdeca5e279492a83a21678e3f0 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 10 Dec 2015 16:29:19 +0000 Subject: [PATCH 0308/1411] Update Releases.md --- Releases.md | 86 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/Releases.md b/Releases.md index 89ab4f985424..41d7c93d61b6 100644 --- a/Releases.md +++ b/Releases.md @@ -1,56 +1,88 @@ The list of releases with notes can be found at: https://github.com/facebook/react-native/releases +Future releases: + +- **0.17 branch cut**, 0.17.0-rc - beginning of **week of Dec 7** +- 0.17.0 - Dec 17 +- (Holiday break) +- **0.18 branch cut**, 0.18.0-rc - beginning of **week of Jan 4** +- 0.18.0 - Jan 14 +- **0.19 branch cut**, 0.19.0-rc - beginning of **week of Jan 18** +- 0.19.0 - Jan 28 +- ... + ## Cut a release branch -- Make sure iOS and Android Getting Started flow works on master. -- Publish to Maven Central: - - Edit `ReactAndroid/gradle.properties` - - Edit `ReactAndroid/release.gradle` and uncomment Javadoc generation - - `./gradlew :ReactAndroid:installArchives` (`java -version` should print 1.7 which is currently needed for Javadoc generation) - - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload) - - Select Artifact(s) with a POM (to publish to a local Maven repo for testing run `./gradlew :ReactAndroid:installArchives`) - - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) -- `git checkout -b 0.12-stable` +Note: replace 0.12 in all the commands below with the version you're releasing :) + +First, set up Sinopia (only need to do this once): https://github.com/facebook/react-native/tree/master/react-native-cli + +Make absolutely sure basic iOS and Android workflow works on master: + - `cd react-native` + - `git pull` + - `git checkout -b 0.12-stable` + - Edit `ReactAndroid/gradle.properties`, set `VERSION_NAME=0.12.0` + - Edit `ReactAndroid/release.gradle`, uncomment Javadoc generation (the line `// archives androidJavadocJar`) + - Make sure `java -version` prints 1.7.x, this is currently needed for Javadoc generation and Javadocs are required by Maven Central (we should make it work with Java 8) + - Run `./gradlew :ReactAndroid:installArchives`, it will print a lot of Javadoc warnings, that's OK. + - Check the artifacts were generated: `ls -al ~/.m2/repository/com/facebook/react/react-native/0.12.0/` should contain: + - `react-native-0.17.0-javadoc.jar`, `react-native-0.17.0-sources.jar`, `react-native-0.17.0.aar`, `react-native-0.17.0.pom` + - For each of the above also `.asc` file - In `package.json`, set version to e.g. `0.12.0-rc`. - In `React.podspec`, set version to e.g. `0.12.0-rc`. - In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.12.+` -- `git push origin 0.12-stable` -- `npm publish` +- Publish to sinopia: + - `npm set registry http://localhost:4873/`, check that it worked: `npm config list` will show registry is set to localhost + - In a separate shell, start sinopia. Run `sinopia`. If started successfully it will print: http address - http://localhost:4873/. + - Make sure http://localhost:4873/ shows no old versions + - `npm publish` + - http://localhost:4873/ will show 0.12.0-rc +- Test that everything works: + - `cd /tmp` + - `react-native init Zero12rc` + - `cd Zero12rc` + - Check that `package.json`, `android/app/build.gradle` have correct versions (`^0.12.0-rc`, `com.facebook.react:react-native:0.12.+`) + - `open ios/Zero12rc.xcodeproj` + - Hit the Run button in Xcode. + - Packager should open in a new window, you should see the Welcome to React Native screen, Reload JS, try Chrome debugging - put a breakpoint somewhere in `index.ios.js` and Reload JS, Chrome debugger should stop on the breakpoint (we don't have tests for Chrome debugging) + - Close the packager window, close Xcode + - Start an Android emulator (ideally Genymotion, it's faster and more reliable than Google emulators) + - `react-native run-android` + - Test is the same way as on iOS, including Chrome debugging ## Do a release -To release e.g. 0.12 to npm: - -(For admins, here's the command to promote someone) +- Revert the Javadoc change in `ReactAndroid/release.gradle` +- `git commit -am` "[0.12-rc] Bump version numbers" +- `git push origin 0.12-stable` -``` -npm owner add react-native -``` +Publish to Maven Central (NOTE we could get rid of this whole section publishing binaries to npm instead): + - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload). You'll need to get permissions for this by filing a ticket explaining you're a core contributor to React Native. [Example ticket](https://issues.sonatype.org/browse/OSSRH-11885). + - Select Artifact(s) with a POM (to publish to a local Maven repo for testing run `./gradlew :ReactAndroid:installArchives`) + - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) + - Wait a few hours until you see the version has propagated to [JCenter](https://bintray.com/bintray/jcenter/com.facebook.react%3Areact-native/view) -When you are ready to do a new release. +To release 0.12-rc to npm: -- Update package.json and React.podspec with the new number +(You need to be a maintainer of the repo. For admins, here's the command to promote someone) ``` -git tag v0.6.0-rc 0.6-stable # don't forget the `v` at the beginning! -git push --tags +npm owner add react-native ``` -- Publish to GitHub - ``` -git push git push origin 0.12-stable +git tag v0.12.0-rc 0.12-stable # don't forget the `v` at the beginning! +git push --tags ``` - Publish to npm ``` npm publish +# Only for non-rc releases: npm dist-tag add react-native@0.12.0 latest ``` - -- Publish to CocoaPods (it takes several minutes to validate the podspec) - Upgrade tags to a release by going to https://github.com/facebook/react-native/tags - Click "Add Notes to release" -- Click Publish \ No newline at end of file +- Click Publish From 6355011d17062530193db2911b6f92f9c68ba6ea Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 10 Dec 2015 16:30:51 +0000 Subject: [PATCH 0309/1411] Update Releases.md --- Releases.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Releases.md b/Releases.md index 41d7c93d61b6..b014e5d36e86 100644 --- a/Releases.md +++ b/Releases.md @@ -16,6 +16,8 @@ Future releases: Note: replace 0.12 in all the commands below with the version you're releasing :) +#### Check that everything works + First, set up Sinopia (only need to do this once): https://github.com/facebook/react-native/tree/master/react-native-cli Make absolutely sure basic iOS and Android workflow works on master: @@ -50,12 +52,14 @@ Make absolutely sure basic iOS and Android workflow works on master: - Start an Android emulator (ideally Genymotion, it's faster and more reliable than Google emulators) - `react-native run-android` - Test is the same way as on iOS, including Chrome debugging + +#### Push to github -## Do a release + - Revert the Javadoc change in `ReactAndroid/release.gradle` + - `git commit -am` "[0.12-rc] Bump version numbers" + - `git push origin 0.12-stable` -- Revert the Javadoc change in `ReactAndroid/release.gradle` -- `git commit -am` "[0.12-rc] Bump version numbers" -- `git push origin 0.12-stable` +## Do a release Publish to Maven Central (NOTE we could get rid of this whole section publishing binaries to npm instead): - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload). You'll need to get permissions for this by filing a ticket explaining you're a core contributor to React Native. [Example ticket](https://issues.sonatype.org/browse/OSSRH-11885). From ce7c0b735f476a4618fd0d68fc39157a0bdb4f57 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 09:06:02 -0800 Subject: [PATCH 0310/1411] Fixed image tinting Summary: public A previous refactor introduced a bug where setting the tintColor of an to null no longer cleared the tint. This fixes it again. Reviewed By: javache Differential Revision: D2744279 fb-gh-sync-id: 1b5e0d546bf456d7b93e2ceee73c568c185c305c --- Libraries/Image/RCTImageView.m | 6 ------ Libraries/Image/RCTImageViewManager.m | 9 ++++++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 656dd61e896e..712f7e7a56db 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -108,12 +108,6 @@ - (void)setCapInsets:(UIEdgeInsets)capInsets } } -- (void)setTintColor:(UIColor *)tintColor -{ - super.tintColor = tintColor; - self.renderingMode = tintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal; -} - - (void)setRenderingMode:(UIImageRenderingMode)renderingMode { if (_renderingMode != renderingMode) { diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index c2067889539d..d318d1f048a9 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -33,6 +33,13 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource) -RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) +RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) +{ + // Default tintColor isn't nil - it's inherited from the superView - but we + // want to treat a null json value for `tintColor` as meaning 'disable tint', + // so we toggle `renderingMode` here instead of in `-[RCTImageView setTintColor:]` + view.tintColor = [RCTConvert UIColor:json] ?: defaultView.tintColor; + view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode; +} @end From 88ac40666cf0372fd9bb481c5b8b35c00a929475 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 10:09:04 -0800 Subject: [PATCH 0311/1411] Replaced RegExp method parser with recursive descent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public This diff replaces the RegEx module method parser with a handwritten recursive descent parser that's faster and easier to maintain. The new parser is ~8 times faster when tested on the UIManager.managerChildren() method, and uses ~1/10 as much RAM. The new parser also supports lightweight generics, and is more tolerant of white space. (This means that you now can – and should – use types like `NSArray *` for your exported properties and method arguments, instead of `NSStringArray`). Reviewed By: jspahrsummers Differential Revision: D2736636 fb-gh-sync-id: f6a11431935fa8acc8ac36f3471032ec9a1c8490 --- .../UIExplorerUnitTests/RCTAllocationTests.m | 2 +- .../RCTMethodArgumentTests.m | 102 ++++++++---- .../RCTModuleMethodTests.m | 56 +++---- Libraries/Network/RCTNetworking.m | 4 +- Libraries/Settings/RCTSettingsManager.m | 2 +- React/Base/RCTConvert.h | 36 +++-- React/Base/RCTConvert.m | 7 + React/Base/RCTModuleData.m | 6 +- React/Base/RCTModuleMethod.h | 9 +- React/Base/RCTModuleMethod.m | 145 +++++++++++------- React/Base/RCTNullability.h | 16 ++ React/Base/RCTParserUtils.h | 31 ++++ React/Base/RCTParserUtils.m | 118 ++++++++++++++ React/Modules/RCTAsyncLocalStorage.m | 8 +- React/Modules/RCTExceptionsManager.m | 8 +- React/Modules/RCTUIManager.m | 10 +- React/React.xcodeproj/project.pbxproj | 8 + React/Views/RCTComponentData.m | 2 +- React/Views/RCTPickerManager.m | 3 +- React/Views/RCTSegmentedControlManager.m | 2 +- 20 files changed, 409 insertions(+), 166 deletions(-) create mode 100644 React/Base/RCTNullability.h create mode 100644 React/Base/RCTParserUtils.h create mode 100644 React/Base/RCTParserUtils.m diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 4fe7294fb3e9..239973ca03e8 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -132,7 +132,7 @@ - (void)testModuleMethodsAreDeallocated { __weak RCTModuleMethod *weakMethod; @autoreleasepool { - __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]]; + __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]]; weakMethod = method; XCTAssertNotNil(method, @"RCTModuleMethod should have been created"); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m index 3f53812473e7..53d9d0f74336 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m @@ -23,14 +23,14 @@ @interface RCTMethodArgumentTests : XCTestCase @implementation RCTMethodArgumentTests -extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes); +extern SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **argTypes); - (void)testOneArgument { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:"); + NSString *methodSignature = @"foo:(NSInteger)foo"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); XCTAssertEqual(arguments.count, (NSUInteger)1); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); } @@ -38,9 +38,9 @@ - (void)testOneArgument - (void)testTwoArguments { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(NSInteger)foo bar:(BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -49,9 +49,9 @@ - (void)testTwoArguments - (void)testSpaces { NSArray *arguments; - NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo : (NSInteger)foo bar : (BOOL) bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -60,9 +60,9 @@ - (void)testSpaces - (void)testNewlines { NSArray *arguments; - NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo : (NSInteger)foo\nbar : (BOOL) bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -71,9 +71,9 @@ - (void)testNewlines - (void)testUnnamedArgs { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo::"); + NSString *methodSignature = @"foo:(NSInteger)foo:(BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo::"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -82,9 +82,9 @@ - (void)testUnnamedArgs - (void)testUntypedUnnamedArgs { NSArray *arguments; - NSString *methodName = @"foo:foo:bar:bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:::"); + NSString *methodSignature = @"foo:foo:bar:bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:::"); XCTAssertEqual(arguments.count, (NSUInteger)3); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id"); @@ -94,9 +94,9 @@ - (void)testUntypedUnnamedArgs - (void)testAttributes { NSArray *arguments; - NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -105,9 +105,9 @@ - (void)testAttributes - (void)testNullability { NSArray *arguments; - NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:baz:"); + NSString *methodSignature = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:baz:"); XCTAssertEqual(arguments.count, (NSUInteger)3); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber"); @@ -120,9 +120,9 @@ - (void)testNullability - (void)testSemicolonStripping { NSArray *arguments; - NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(NSString *)foo bar:(BOOL)bar;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -131,9 +131,9 @@ - (void)testSemicolonStripping - (void)testUnused { NSArray *arguments; - NSString *methodName = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber"); @@ -141,4 +141,44 @@ - (void)testUnused XCTAssertFalse(((RCTMethodArgument *)arguments[1]).unused); } +- (void)testGenericArray +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSArray *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArray"); +} + +- (void)testNestedGenericArray +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSArray *> *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArrayArray"); +} + +- (void)testGenericSet +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSSet *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberSet"); +} + +- (void)testGenericDictionary +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSDictionary *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberDictionary"); +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m index 93ac3b5aa047..f247da4ffc75 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m @@ -47,10 +47,10 @@ - (void)doFooWithBar:(__unused NSString *)bar { } - (void)testNonnull { - NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithBar:(nonnull NSString *)bar"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertFalse(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[@"Hello World"]]; })); @@ -72,40 +72,40 @@ - (void)testNumbersNonnull { // Specifying an NSNumber param without nonnull isn't allowed XCTAssertTrue(RCTLogsError(^{ - NSString *methodName = @"doFooWithNumber:(NSNumber *)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithNumber:(NSNumber *)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; // Invoke method to trigger parsing [method invokeWithBridge:nil module:self arguments:@[@1]]; })); } { - NSString *methodName = @"doFooWithNumber:(nonnull NSNumber *)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithNumber:(nonnull NSNumber *)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); } { - NSString *methodName = @"doFooWithDouble:(double)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithDouble:(double)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); } { - NSString *methodName = @"doFooWithInteger:(NSInteger)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithInteger:(NSInteger)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); @@ -114,10 +114,10 @@ - (void)testNumbersNonnull - (void)testStructArgument { - NSString *methodName = @"doFooWithCGRect:(CGRect)s"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithCGRect:(CGRect)s"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; CGRect r = CGRectMake(10, 20, 30, 40); [method invokeWithBridge:nil module:self arguments:@[@[@10, @20, @30, @40]]]; @@ -126,13 +126,13 @@ - (void)testStructArgument - (void)testWhitespaceTolerance { - NSString *methodName = @"doFoo : \t (NSString *)foo"; + NSString *methodSignature = @"doFoo : \t (NSString *)foo"; __block RCTModuleMethod *method; XCTAssertFalse(RCTLogsError(^{ - method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; })); XCTAssertEqualObjects(method.JSMethodName, @"doFoo"); diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 9afb0914ee80..0c5aeaecd7fa 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -56,7 +56,7 @@ @implementation RCTHTTPFormDataHelper return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES]; } -- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData +- (RCTURLRequestCancellationBlock)process:(NSArray *)formData callback:(RCTHTTPQueryResult)callback { RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue"); @@ -289,7 +289,7 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary } }; } - NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]]; + NSArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]]; if (formData) { RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new]; formDataHelper.networker = self; diff --git a/Libraries/Settings/RCTSettingsManager.m b/Libraries/Settings/RCTSettingsManager.m index 2ff4f3b4d770..c0c915df777c 100644 --- a/Libraries/Settings/RCTSettingsManager.m +++ b/Libraries/Settings/RCTSettingsManager.m @@ -90,7 +90,7 @@ - (void)userDefaultsDidChange:(NSNotification *)note /** * Remove some values from the settings. */ -RCT_EXPORT_METHOD(deleteValues:(NSStringArray *)keys) +RCT_EXPORT_METHOD(deleteValues:(NSArray *)keys) { _ignoringUpdates = YES; for (NSString *key in keys) { diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 6347cc9cab70..231f888c4821 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -94,28 +94,13 @@ typedef NSURL RCTFileURL; size:(id)size weight:(id)weight style:(id)style scaleMultiplier:(CGFloat)scaleMultiplier; -typedef NSArray NSArrayArray; + (NSArray *)NSArrayArray:(id)json; - -typedef NSArray NSStringArray; + (NSArray *)NSStringArray:(id)json; - -typedef NSArray NSStringArrayArray; + (NSArray *> *)NSStringArrayArray:(id)json; - -typedef NSArray NSDictionaryArray; + (NSArray *)NSDictionaryArray:(id)json; - -typedef NSArray NSURLArray; + (NSArray *)NSURLArray:(id)json; - -typedef NSArray RCTFileURLArray; -+ (NSArray *)RCTFileURLArray:(id)json; - -typedef NSArray NSNumberArray; ++ (NSArray *)RCTFileURLArray:(id)json; + (NSArray *)NSNumberArray:(id)json; - -typedef NSArray UIColorArray; + (NSArray *)UIColorArray:(id)json; typedef NSArray CGColorArray; @@ -145,6 +130,18 @@ typedef BOOL css_clip_t, css_backface_visibility_t; @interface RCTConvert (Deprecated) +/** + * Use lightweight generics syntax instead, e.g. NSArray + */ +typedef NSArray NSArrayArray __deprecated_msg("Use NSArray"); +typedef NSArray NSStringArray __deprecated_msg("Use NSArray"); +typedef NSArray NSStringArrayArray __deprecated_msg("Use NSArray *>"); +typedef NSArray NSDictionaryArray __deprecated_msg("Use NSArray"); +typedef NSArray NSURLArray __deprecated_msg("Use NSArray"); +typedef NSArray RCTFileURLArray __deprecated_msg("Use NSArray"); +typedef NSArray NSNumberArray __deprecated_msg("Use NSArray"); +typedef NSArray UIColorArray __deprecated_msg("Use NSArray"); + /** * Synchronous image loading is generally a bad idea for performance reasons. * If you need to pass image references, try to use `RCTImageSource` and then @@ -162,6 +159,11 @@ RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber RCT_EXTERN NSNumber *RCTConvertMultiEnumValue(const char *, NSDictionary *, NSNumber *, id); RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id); +/** + * Get the converter function for the specified type + */ +RCT_EXTERN SEL RCTConvertSelectorForType(NSString *type); + /** * This macro is used for logging conversion errors. This is just used to * avoid repeating the same boilerplate for every error message. @@ -238,7 +240,7 @@ RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter * This macro is used for creating converter functions for typed arrays. */ #define RCT_ARRAY_CONVERTER(type) \ -+ (NSArray *)type##Array:(id)json \ ++ (NSArray *)type##Array:(id)json \ { \ return RCTConvertArrayValue(@selector(type:), json); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 244fe6c0af29..5860bbc59740 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -13,6 +13,7 @@ #import "RCTDefines.h" #import "RCTImageSource.h" +#import "RCTParserUtils.h" #import "RCTUtils.h" @implementation RCTConvert @@ -649,6 +650,12 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family return values; } +SEL RCTConvertSelectorForType(NSString *type) +{ + const char *input = type.UTF8String; + return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]); +} + RCT_ARRAY_CONVERTER(NSURL) RCT_ARRAY_CONVERTER(RCTFileURL) RCT_ARRAY_CONVERTER(UIColor) diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index b8fa17049091..d254509a765b 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -122,9 +122,9 @@ - (NSString *)name NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector); id moduleMethod = - [[RCTModuleMethod alloc] initWithObjCMethodName:entries[1] - JSMethodName:entries[0] - moduleClass:_moduleClass]; + [[RCTModuleMethod alloc] initWithMethodSignature:entries[1] + JSMethodName:entries[0] + moduleClass:_moduleClass]; [moduleMethods addObject:moduleMethod]; } diff --git a/React/Base/RCTModuleMethod.h b/React/Base/RCTModuleMethod.h index f450c412cea0..5572dba3a307 100644 --- a/React/Base/RCTModuleMethod.h +++ b/React/Base/RCTModuleMethod.h @@ -10,15 +10,10 @@ #import #import "RCTBridgeMethod.h" +#import "RCTNullability.h" @class RCTBridge; -typedef NS_ENUM(NSUInteger, RCTNullability) { - RCTNullabilityUnspecified, - RCTNullable, - RCTNonnullable, -}; - @interface RCTMethodArgument : NSObject @property (nonatomic, copy, readonly) NSString *type; @@ -32,7 +27,7 @@ typedef NS_ENUM(NSUInteger, RCTNullability) { @property (nonatomic, readonly) Class moduleClass; @property (nonatomic, readonly) SEL selector; -- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName +- (instancetype)initWithMethodSignature:(NSString *)objCMethodName JSMethodName:(NSString *)JSMethodName moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER; diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 0182fe93d858..407fed3dab80 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -15,6 +15,7 @@ #import "RCTBridge.h" #import "RCTConvert.h" #import "RCTLog.h" +#import "RCTParserUtils.h" #import "RCTUtils.h" typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @@ -50,7 +51,7 @@ @implementation RCTModuleMethod Class _moduleClass; NSInvocation *_invocation; NSArray *_argumentBlocks; - NSString *_objCMethodName; + NSString *_methodSignature; SEL _selector; NSDictionary *_profileArgs; } @@ -68,77 +69,106 @@ static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index, RCT_NOT_IMPLEMENTED(- (instancetype)init) -void RCTParseObjCMethodName(NSString **, NSArray **); -void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) +// returns YES if the selector ends in a colon (indicating that there is at +// least one argument, and maybe more selector parts) or NO if it doesn't. +static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector) { - static NSRegularExpression *typeNameRegex; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))"; - NSString *constPattern = @"(?:const)"; - NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))"; - NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))"; - NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:(%@)|%@|(%@)|(%@))\\s*)", - unusedPattern, constPattern, nullablePattern, nonnullPattern]; - NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+", - annotationPattern]; - typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL]; - }); - - // Extract argument types - NSString *methodName = *objCMethodName; - NSRange methodRange = {0, methodName.length}; - NSMutableArray *args = [NSMutableArray array]; - [typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) { - NSRange typeRange = [result rangeAtIndex:5]; - NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id"; - BOOL unused = ([result rangeAtIndex:2].length > 0); - RCTNullability nullability = [result rangeAtIndex:3].length ? RCTNullable : - [result rangeAtIndex:4].length ? RCTNonnullable : RCTNullabilityUnspecified; - [args addObject:[[RCTMethodArgument alloc] initWithType:type - nullability:nullability - unused:unused]]; - }]; - *arguments = [args copy]; + NSString *selectorPart; + if (RCTParseIdentifier(input, &selectorPart)) { + [selector appendString:selectorPart]; + } + RCTSkipWhitespace(input); + if (RCTReadChar(input, ':')) { + [selector appendString:@":"]; + RCTSkipWhitespace(input); + return YES; + } + return NO; +} + +static BOOL RCTParseUnused(const char **input) +{ + return RCTReadString(input, "__unused") || + RCTReadString(input, "__attribute__((unused))"); +} + +static RCTNullability RCTParseNullability(const char **input) +{ + if (RCTReadString(input, "nullable")) { + return RCTNullable; + } else if (RCTReadString(input, "nonnull")) { + return RCTNonnullable; + } + return RCTNullabilityUnspecified; +} + +SEL RCTParseMethodSignature(NSString *, NSArray **); +SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **arguments) +{ + const char *input = methodSignature.UTF8String; + RCTSkipWhitespace(&input); + + NSMutableArray *args; + NSMutableString *selector = [NSMutableString new]; + while (RCTParseSelectorPart(&input, selector)) { + if (!args) { + args = [NSMutableArray new]; + } + + // Parse type + if (RCTReadChar(&input, '(')) { + RCTSkipWhitespace(&input); - // Remove the parameter types and names - methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0 - range:methodRange - withTemplate:@""]; + BOOL unused = RCTParseUnused(&input); + RCTSkipWhitespace(&input); - // Remove whitespace - methodName = [methodName stringByReplacingOccurrencesOfString:@"\n" withString:@""]; - methodName = [methodName stringByReplacingOccurrencesOfString:@" " withString:@""]; + RCTNullability nullability = RCTParseNullability(&input); + RCTSkipWhitespace(&input); - // Strip trailing semicolon - if ([methodName hasSuffix:@";"]) { - methodName = [methodName substringToIndex:methodName.length - 1]; + NSString *type = RCTParseType(&input); + [args addObject:[[RCTMethodArgument alloc] initWithType:type + nullability:nullability + unused:unused]]; + RCTSkipWhitespace(&input); + RCTReadChar(&input, ')'); + RCTSkipWhitespace(&input); + } else { + // Type defaults to id if unspecified + [args addObject:[[RCTMethodArgument alloc] initWithType:@"id" + nullability:RCTNullable + unused:NO]]; + } + + // Argument name + RCTParseIdentifier(&input, NULL); + RCTSkipWhitespace(&input); } - *objCMethodName = methodName; + *arguments = [args copy]; + return NSSelectorFromString(selector); } -- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName - JSMethodName:(NSString *)JSMethodName - moduleClass:(Class)moduleClass +- (instancetype)initWithMethodSignature:(NSString *)methodSignature + JSMethodName:(NSString *)JSMethodName + moduleClass:(Class)moduleClass { if ((self = [super init])) { _moduleClass = moduleClass; - _objCMethodName = [objCMethodName copy]; + _methodSignature = [methodSignature copy]; _JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({ - NSString *methodName = objCMethodName; + NSString *methodName = methodSignature; NSRange colonRange = [methodName rangeOfString:@":"]; if (colonRange.location != NSNotFound) { methodName = [methodName substringToIndex:colonRange.location]; } methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; RCTAssert(methodName.length, @"%@ is not a valid JS function name, please" - " supply an alternative using RCT_REMAP_METHOD()", objCMethodName); + " supply an alternative using RCT_REMAP_METHOD()", methodSignature); methodName; }); - if ([_objCMethodName rangeOfString:@"RCTPromise"].length) { + if ([_methodSignature rangeOfString:@"RCTPromise"].length) { _functionType = RCTFunctionTypePromise; } else { _functionType = RCTFunctionTypeNormal; @@ -151,15 +181,12 @@ - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName - (void)processMethodSignature { NSArray *arguments; - NSString *objCMethodName = _objCMethodName; - RCTParseObjCMethodName(&objCMethodName, &arguments); - - _selector = NSSelectorFromString(objCMethodName); - RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName); + _selector = RCTParseMethodSignature(_methodSignature, &arguments); + RCTAssert(_selector, @"%@ is not a valid selector", _methodSignature); // Create method invocation NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector]; - RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName); + RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", _methodSignature); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; invocation.selector = _selector; _invocation = invocation; @@ -203,7 +230,7 @@ - (void)processMethodSignature BOOL isNullableType = NO; RCTMethodArgument *argument = arguments[i - 2]; NSString *typeName = argument.type; - SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]); + SEL selector = RCTConvertSelectorForType(typeName); if ([RCTConvert respondsToSelector:selector]) { switch (objcType[0]) { @@ -296,7 +323,7 @@ - (void)processMethodSignature } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) { RCTAssert(i == numberOfArguments - 2, @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", - _moduleClass, objCMethodName); + _moduleClass, _methodSignature); RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function"); @@ -310,7 +337,7 @@ - (void)processMethodSignature } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) { RCTAssert(i == numberOfArguments - 1, @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", - _moduleClass, objCMethodName); + _moduleClass, _methodSignature); RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function"); diff --git a/React/Base/RCTNullability.h b/React/Base/RCTNullability.h new file mode 100644 index 000000000000..6d250ee4702e --- /dev/null +++ b/React/Base/RCTNullability.h @@ -0,0 +1,16 @@ +/** + * 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 + +typedef NS_ENUM(NSUInteger, RCTNullability) { + RCTNullabilityUnspecified, + RCTNullable, + RCTNonnullable, +}; diff --git a/React/Base/RCTParserUtils.h b/React/Base/RCTParserUtils.h new file mode 100644 index 000000000000..dcbe413ef713 --- /dev/null +++ b/React/Base/RCTParserUtils.h @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "RCTDefines.h" + +@interface RCTParserUtils : NSObject + +/** + * Generic utility functions for parsing Objective-C source code. + */ +RCT_EXTERN BOOL RCTReadChar(const char **input, char c); +RCT_EXTERN BOOL RCTReadString(const char **input, const char *string); +RCT_EXTERN void RCTSkipWhitespace(const char **input); +RCT_EXTERN BOOL RCTParseIdentifier(const char **input, NSString **string); + +/** + * Parse an Objective-C type into a form that can be used by RCTConvert. + * This doesn't really belong here, but it's used by both RCTConvert and + * RCTModuleMethod, which makes it difficult to find a better home for it. + */ +RCT_EXTERN NSString *RCTParseType(const char **input); + +@end diff --git a/React/Base/RCTParserUtils.m b/React/Base/RCTParserUtils.m new file mode 100644 index 000000000000..5e9182a5fa45 --- /dev/null +++ b/React/Base/RCTParserUtils.m @@ -0,0 +1,118 @@ +/** + * 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 "RCTParserUtils.h" + +#import "RCTLog.h" + +@implementation RCTParserUtils + +BOOL RCTReadChar(const char **input, char c) +{ + if (**input == c) { + (*input)++; + return YES; + } + return NO; +} + +BOOL RCTReadString(const char **input, const char *string) +{ + int i; + for (i = 0; string[i] != 0; i++) { + if (string[i] != (*input)[i]) { + return NO; + } + } + *input += i; + return YES; +} + +void RCTSkipWhitespace(const char **input) +{ + while (isspace(**input)) { + (*input)++; + } +} + +static BOOL RCTIsIdentifierHead(const char c) +{ + return isalpha(c) || c == '_'; +} + +static BOOL RCTIsIdentifierTail(const char c) +{ + return isalnum(c) || c == '_'; +} + +BOOL RCTParseIdentifier(const char **input, NSString **string) +{ + const char *start = *input; + if (!RCTIsIdentifierHead(**input)) { + return NO; + } + (*input)++; + while (RCTIsIdentifierTail(**input)) { + (*input)++; + } + if (string) { + *string = [[NSString alloc] initWithBytes:start + length:(NSInteger)(*input - start) + encoding:NSASCIIStringEncoding]; + } + return YES; +} + +static BOOL RCTIsCollectionType(NSString *type) +{ + static NSSet *collectionTypes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + collectionTypes = [[NSSet alloc] initWithObjects: + @"NSArray", @"NSSet", @"NSDictionary", nil]; + }); + return [collectionTypes containsObject:type]; +} + +NSString *RCTParseType(const char **input) +{ + NSString *type; + RCTParseIdentifier(input, &type); + RCTSkipWhitespace(input); + if (RCTReadChar(input, '<')) { + RCTSkipWhitespace(input); + NSString *subtype = RCTParseType(input); + if (RCTIsCollectionType(type)) { + if ([type isEqualToString:@"NSDictionary"]) { + // Dictionaries have both a key *and* value type, but the key type has + // to be a string for JSON, so we only care about the value type + if (RCT_DEBUG && ![subtype isEqualToString:@"NSString"]) { + RCTLogError(@"%@ is not a valid key type for a JSON dictionary", subtype); + } + RCTSkipWhitespace(input); + RCTReadChar(input, ','); + RCTSkipWhitespace(input); + subtype = RCTParseType(input); + } + if (![subtype isEqualToString:@"id"]) { + type = [type stringByReplacingCharactersInRange:(NSRange){0, 2 /* "NS" */} + withString:subtype]; + } + } else { + // It's a protocol rather than a generic collection - ignore it + } + RCTSkipWhitespace(input); + RCTReadChar(input, '>'); + } + RCTSkipWhitespace(input); + RCTReadChar(input, '*'); + return type; +} + +@end diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 273ef40c6bf8..344832b9aa04 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -319,7 +319,7 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL #pragma mark - Exported JS Functions -RCT_EXPORT_METHOD(multiGet:(NSStringArray *)keys +RCT_EXPORT_METHOD(multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; @@ -338,7 +338,7 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL callback(@[RCTNullIfNil(errors), result]); } -RCT_EXPORT_METHOD(multiSet:(NSStringArrayArray *)kvPairs +RCT_EXPORT_METHOD(multiSet:(NSArray *> *)kvPairs callback:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; @@ -358,7 +358,7 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL callback(@[RCTNullIfNil(errors)]); } -RCT_EXPORT_METHOD(multiMerge:(NSStringArrayArray *)kvPairs +RCT_EXPORT_METHOD(multiMerge:(NSArray *> *)kvPairs callback:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; @@ -394,7 +394,7 @@ - (NSDictionary *)_writeEntry:(NSArray *)entry changedManifest:(BOOL callback(@[RCTNullIfNil(errors)]); } -RCT_EXPORT_METHOD(multiRemove:(NSStringArray *)keys +RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { NSDictionary *errorOut = [self _ensureSetup]; diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index c536e20a10da..791c163f16dd 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -34,7 +34,7 @@ - (instancetype)initWithDelegate:(id)delegate } RCT_EXPORT_METHOD(reportSoftException:(NSString *)message - stack:(NSDictionaryArray *)stack + stack:(NSArray *)stack exceptionId:(nonnull NSNumber *)exceptionId) { [_bridge.redBox showErrorMessage:message withStack:stack]; @@ -45,7 +45,7 @@ - (instancetype)initWithDelegate:(id)delegate } RCT_EXPORT_METHOD(reportFatalException:(NSString *)message - stack:(NSDictionaryArray *)stack + stack:(NSArray *)stack exceptionId:(nonnull NSNumber *)exceptionId) { [_bridge.redBox showErrorMessage:message withStack:stack]; @@ -66,7 +66,7 @@ - (instancetype)initWithDelegate:(id)delegate } RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message - stack:(NSDictionaryArray *)stack + stack:(NSArray *)stack exceptionId:(nonnull NSNumber *)exceptionId) { [_bridge.redBox updateErrorMessage:message withStack:stack]; @@ -78,7 +78,7 @@ - (instancetype)initWithDelegate:(id)delegate // Deprecated. Use reportFatalException directly instead. RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message - stack:(NSDictionaryArray *)stack) + stack:(NSArray *)stack) { [self reportFatalException:message stack:stack exceptionId:@-1]; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index c3c1ea58917b..4812a548ef8e 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -721,11 +721,11 @@ - (void)_removeChildren:(NSArray> *)children } RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag - moveFromIndices:(NSNumberArray *)moveFromIndices - moveToIndices:(NSNumberArray *)moveToIndices - addChildReactTags:(NSNumberArray *)addChildReactTags - addAtIndices:(NSNumberArray *)addAtIndices - removeAtIndices:(NSNumberArray *)removeAtIndices) + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices + addChildReactTags:(NSArray *)addChildReactTags + addAtIndices:(NSArray *)addAtIndices + removeAtIndices:(NSArray *)removeAtIndices) { [self _manageChildren:containerReactTag moveFromIndices:moveFromIndices diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index d5ea0c76d5d4..3bc1cb5cf899 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; }; 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; + 13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */; }; 13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; }; 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; }; 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */; }; @@ -141,6 +142,9 @@ 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; 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 = ""; }; + 13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTParserUtils.h; sourceTree = ""; }; + 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTParserUtils.m; sourceTree = ""; }; + 13A6E20F1C19ABC700845B82 /* RCTNullability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNullability.h; sourceTree = ""; }; 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponentData.h; sourceTree = ""; }; 13AB90C01B6FA36700713B4F /* RCTComponentData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTComponentData.m; sourceTree = ""; }; 13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; @@ -518,6 +522,9 @@ 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */, 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */, 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */, + 13A6E20F1C19ABC700845B82 /* RCTNullability.h */, + 13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */, + 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */, 142014181B32094000CC17BA /* RCTPerformanceLogger.h */, 142014171B32094000CC17BA /* RCTPerformanceLogger.m */, 830A229C1A66C68A008503DA /* RCTRootView.h */, @@ -674,6 +681,7 @@ 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, + 13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */, 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */, 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */, 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */, diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 4a0b45ad1e1b..0f8287b72883 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -114,7 +114,7 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView if ([managerClass respondsToSelector:selector]) { NSArray *typeAndKeyPath = ((NSArray *(*)(id, SEL))objc_msgSend)(managerClass, selector); - type = NSSelectorFromString([typeAndKeyPath[0] stringByAppendingString:@":"]); + type = RCTConvertSelectorForType(typeAndKeyPath[0]); keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil; } else { propBlock = ^(__unused id view, __unused id json) {}; diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index 759a05b6d8ad..54d75f210c9c 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -10,7 +10,6 @@ #import "RCTPickerManager.h" #import "RCTBridge.h" -#import "RCTConvert.h" #import "RCTPicker.h" @implementation RCTPickerManager @@ -22,7 +21,7 @@ - (UIView *)view return [RCTPicker new]; } -RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray) +RCT_EXPORT_VIEW_PROPERTY(items, NSArray) RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(color, UIColor) diff --git a/React/Views/RCTSegmentedControlManager.m b/React/Views/RCTSegmentedControlManager.m index e56be4cf0317..c44f822cf323 100644 --- a/React/Views/RCTSegmentedControlManager.m +++ b/React/Views/RCTSegmentedControlManager.m @@ -22,7 +22,7 @@ - (UIView *)view return [RCTSegmentedControl new]; } -RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray) +RCT_EXPORT_VIEW_PROPERTY(values, NSArray) RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL) From ac783a8a1b92b59683af85dd87518acd9c582db4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 12:04:53 -0800 Subject: [PATCH 0312/1411] Fix retain cycle Summary: public This fixes the following circular reference: RCTComponentData -> _bridge -> RCTBatchedBridge -> _moduleDataByName -> __NSDictionaryI -> RCTModuleData -> _instance -> RCTUIManager -> _componentDataByName -> __NSDictionaryI -> RCTComponentData Reviewed By: javache Differential Revision: D2744742 fb-gh-sync-id: c282786f4dfb550185bc03d3e5e3d03048664c21 --- React/Views/RCTComponentData.h | 2 +- React/Views/RCTComponentData.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h index f7151c22b543..c52af36472af 100644 --- a/React/Views/RCTComponentData.h +++ b/React/Views/RCTComponentData.h @@ -21,7 +21,7 @@ @property (nonatomic, readonly) Class managerClass; @property (nonatomic, copy, readonly) NSString *name; -@property (nonatomic, strong, readonly) RCTViewManager *manager; +@property (nonatomic, weak, readonly) RCTViewManager *manager; - (instancetype)initWithManagerClass:(Class)managerClass bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 0f8287b72883..42d7506eef45 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -44,7 +44,7 @@ @implementation RCTComponentData RCTShadowView *_defaultShadowView; NSMutableDictionary *_viewPropBlocks; NSMutableDictionary *_shadowPropBlocks; - RCTBridge *_bridge; + __weak RCTBridge *_bridge; } @synthesize manager = _manager; From 3cfcd401c1f3e3297ffa9a3abf8bc3a8f286fa33 Mon Sep 17 00:00:00 2001 From: Thomas Parslow Date: Thu, 10 Dec 2015 12:42:22 -0800 Subject: [PATCH 0313/1411] Support onLayout and onContentSizeChange attributes on ListView Summary: Docs say they're supported and presumably they should work exactly as for ScrollView but currently they are intercepted by the ListView Closes https://github.com/facebook/react-native/pull/4712 Reviewed By: svcscm Differential Revision: D2745080 Pulled By: vjeux fb-gh-sync-id: 531907f03ae46d5200003cdb335c10b40c7d3bed --- Libraries/CustomComponents/ListView/ListView.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index fa29da5d87ee..5b1ef4db0c19 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -436,6 +436,9 @@ var ListView = React.createClass({ height : width; this._updateVisibleRows(); this._renderMoreRowsIfNeeded(); + if (this.props.onContentSizeChange) { + this.props.onContentSizeChange(width, height); + } }, _onLayout: function(event) { @@ -444,6 +447,9 @@ var ListView = React.createClass({ height : width; this._updateVisibleRows(); this._renderMoreRowsIfNeeded(); + if (this.props.onLayout) { + this.props.onLayout(event); + } }, _setScrollVisibleLength: function(left, top, width, height) { From 3eb32cbb0e7baf23d62605b98a4635808374252e Mon Sep 17 00:00:00 2001 From: Leland Richardson Date: Thu, 10 Dec 2015 13:27:52 -0800 Subject: [PATCH 0314/1411] Animated.multiply and Animated.add to combine animated values Summary: This PR was created in response to feedback from an older PR: https://github.com/facebook/react-native/pull/2045 The `.interpolate()` API of Animated values is quite effective to accomplish transformations based on a single animated value, but currently there is a class of animations that is impossible: animations being driven by more than one value. Usage would be like the following: ```js getInitialState: function() { return { panY: new Animated.Value(0), offset: new Animated.Value(0), }; } ``` ```js var scale = Animated.add(panY, offset).interpolate({ inputRange: [...], outputRange: [...], }); ``` I have a real use case for this, and I cannot think of any way to accomplish what I need without an API like this. The animation I am trying to accomplish is I have a PanResponder being used to detect both x and y panning. The y-axis panning drives a 3d sroll-like animation (which we can call `panY`), and the x-axis panning is driving a "swipe-to-remove" animation ( Closes https://github.com/facebook/react-native/pull/4395 Reviewed By: svcscm Differential Revision: D2731305 Pulled By: vjeux fb-gh-sync-id: 3b9422f10c7e7f3b3ecd270aeed8ea92315a89e9 --- .../Animated/src/AnimatedImplementation.js | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 3039f887b47c..e7edc16a9645 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -840,6 +840,64 @@ class AnimatedInterpolation extends AnimatedWithChildren { } } +class AnimatedAddition extends AnimatedWithChildren { + _a: Animated; + _b: Animated; + + constructor(a: Animated, b: Animated) { + super(); + this._a = a; + this._b = b; + } + + __getValue(): number { + return this._a.__getValue() + this._b.__getValue(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, Interpolation.create(config)); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + } +} + +class AnimatedMultiplication extends AnimatedWithChildren { + _a: Animated; + _b: Animated; + + constructor(a: Animated, b: Animated) { + super(); + this._a = a; + this._b = b; + } + + __getValue(): number { + return this._a.__getValue() * this._b.__getValue(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, Interpolation.create(config)); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + } +} + class AnimatedTransform extends AnimatedWithChildren { _transforms: Array; @@ -1161,6 +1219,20 @@ type CompositeAnimation = { stop: () => void; }; +var add = function( + a: Animated, + b: Animated +): AnimatedAddition { + return new AnimatedAddition(a, b); +}; + +var multiply = function( + a: Animated, + b: Animated +): AnimatedMultiplication { + return new AnimatedMultiplication(a, b); +}; + var maybeVectorAnim = function( value: AnimatedValue | AnimatedValueXY, config: Object, @@ -1521,6 +1593,17 @@ module.exports = { */ spring, + /** + * Creates a new Animated value composed from two Animated values added + * together. + */ + add, + /** + * Creates a new Animated value composed from two Animated values multiplied + * together. + */ + multiply, + /** * Starts an animation after the given delay. */ From c5c41d9d4c228d145b1a439c83e3802fbd903dab Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Thu, 10 Dec 2015 13:55:32 -0800 Subject: [PATCH 0315/1411] Update Animations.md - LayoutAnimation is no longer iOS only --- docs/Animations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Animations.md b/docs/Animations.md index 22a5f1c0f514..af9ccf0e9981 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -248,7 +248,7 @@ concise, robust, and performant way. Check out more example code in doesn't support what you need, and the following sections cover other animation systems. -### LayoutAnimation (iOS only) +### LayoutAnimation `LayoutAnimation` allows you to globally configure `create` and `update` animations that will be used for all views in the next render/layout cycle. From 53825f520623b4abebb379fb4756b868a3c925ec Mon Sep 17 00:00:00 2001 From: "glevi@fb.com" Date: Thu, 10 Dec 2015 13:47:32 -0800 Subject: [PATCH 0316/1411] Fix errors in fbobjc Reviewed By: nicklockwood Differential Revision: D2745907 fb-gh-sync-id: f4687313ad817bb1d5a56bb766d8efa4a2926da7 --- .../BatchedBridgedModules/NativeModules.js | 5 +++-- .../BatchedBridgedModules/NativeModules.js.flow | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index af1540b75b5d..c6f54bcb9385 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -41,7 +41,7 @@ Object.keys(RemoteModules).forEach((moduleName) => { const NativeModules = {}; Object.keys(RemoteModules).forEach((moduleName) => { Object.defineProperty(NativeModules, moduleName, { - enumerable: true, + enumerable: true, get: () => { let module = RemoteModules[moduleName]; if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) { @@ -69,8 +69,9 @@ UIManager && Object.keys(UIManager).forEach(viewName => { const viewConfig = UIManager[viewName]; const constants = {}; if (viewConfig.Manager) { + /* $FlowFixMe - nice try. Flow doesn't like getters */ Object.defineProperty(viewConfig, 'Constants', { - enumerable: true, + enumerable: true, get: () => { const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; viewManager && Object.keys(viewManager).forEach(key => { diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow new file mode 100644 index 000000000000..f2181b965b08 --- /dev/null +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow @@ -0,0 +1,17 @@ +/** + * 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 NativeModules + * @flow + */ +'use strict'; + +const BatchedBridge = require('BatchedBridge'); +const RemoteModules = BatchedBridge.RemoteModules; + +module.exports = RemoteModules; From 201a3d010a821b22e3f17b935de30e23fbad4918 Mon Sep 17 00:00:00 2001 From: Nader Dabit Date: Thu, 10 Dec 2015 17:27:46 -0600 Subject: [PATCH 0317/1411] fixed various spelling errors --- docs/AndroidUIPerformance.md | 2 +- docs/Animations.md | 2 +- docs/DevelopmentSetupAndroid.md | 2 +- docs/LinkingLibraries.md | 2 +- docs/NativeComponentsAndroid.md | 2 +- docs/NavigatorComparison.md | 2 +- docs/RunningOnDeviceAndroid.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/AndroidUIPerformance.md b/docs/AndroidUIPerformance.md index 399bd09eefe0..321bb0c46bfe 100644 --- a/docs/AndroidUIPerformance.md +++ b/docs/AndroidUIPerformance.md @@ -41,7 +41,7 @@ Once the trace starts collecting, perform the animation or interaction you care ## Reading the trace -After opening the trace in your browser (preferrably Chrome), you should see something like this: +After opening the trace in your browser (preferably Chrome), you should see something like this: ![Example](/react-native/img/SystraceExample.png) diff --git a/docs/Animations.md b/docs/Animations.md index af9ccf0e9981..84c3f48510fc 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -309,7 +309,7 @@ for more information. familiar with. It accepts a function as its only argument and calls that function before the next repaint. It is an essential building block for animations that underlies all of the JavaScript-based animation APIs. In -general, you shouldn't need to call this yourself - the animation API's will +general, you shouldn't need to call this yourself - the animation APIs will manage frame updates for you. ### react-tween-state (Not recommended - use [Animated](#animated) instead) diff --git a/docs/DevelopmentSetupAndroid.md b/docs/DevelopmentSetupAndroid.md index e7d1f8b36ac2..23bf6a6366e1 100644 --- a/docs/DevelopmentSetupAndroid.md +++ b/docs/DevelopmentSetupAndroid.md @@ -45,7 +45,7 @@ __NOTE__: You need to restart the Command Prompt (Windows) / Terminal Emulator ( ### Use gradle daemon -React Native Android use [gradle](https://docs.gradle.org) as a build system. We recommend to enable gradle daemon functionailty which may result in up to 50% improvement in incremental build times for changes in java code. Learn [here](https://docs.gradle.org/2.9/userguide/gradle_daemon.html) how to enable it for your platform. +React Native Android use [gradle](https://docs.gradle.org) as a build system. We recommend to enable gradle daemon functionality which may result in up to 50% improvement in incremental build times for changes in java code. Learn [here](https://docs.gradle.org/2.9/userguide/gradle_daemon.html) how to enable it for your platform. ### Configure your SDK diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md index db29a4bc2f4e..c5cf659cf883 100644 --- a/docs/LinkingLibraries.md +++ b/docs/LinkingLibraries.md @@ -54,7 +54,7 @@ This step is not necessary for libraries that we ship with React Native with the exception of `PushNotificationIOS` and `LinkingIOS`. In the case of the `PushNotificationIOS` for example, you have to call a method -on the library from your `AppDelegate` every time a new push notifiation is +on the library from your `AppDelegate` every time a new push notification is received. For that we need to know the library's headers. To achieve that you have to go diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md index f70c32ee6188..3e729e18b4e8 100644 --- a/docs/NativeComponentsAndroid.md +++ b/docs/NativeComponentsAndroid.md @@ -57,7 +57,7 @@ Views are created in the `createViewInstance` method, the view should initialize ## 3. Expose view property setters using `@ReactProp` (or `@ReactPropGroup`) annotation -Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with `@ReactProp` (or `@ReactPropGroup`). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be declared as a `void` method and should be `public`. Property type sent to JS is determined automatically based on the type of value argumen of the setter. The following type of values are currently supported: `boolean`, `int`, `float`, `double`, `String`, `Boolean`, `Integer`, `ReadableArray`, `ReadableMap`. +Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with `@ReactProp` (or `@ReactPropGroup`). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be declared as a `void` method and should be `public`. Property type sent to JS is determined automatically based on the type of value argument of the setter. The following type of values are currently supported: `boolean`, `int`, `float`, `double`, `String`, `Boolean`, `Integer`, `ReadableArray`, `ReadableMap`. Annotation `@ReactProp` has one obligatory argument `name` of type `String`. Name assigned to the `@ReactProp` annotation linked to the setter method is used to reference the property on JS side. diff --git a/docs/NavigatorComparison.md b/docs/NavigatorComparison.md index 326ef98d1265..cc6c5c6c8bbd 100644 --- a/docs/NavigatorComparison.md +++ b/docs/NavigatorComparison.md @@ -41,7 +41,7 @@ between the two. - Development belongs to open-source community - not used by the React Native team on their apps. - A result of this is that there is currently a backlog of unresolved bugs, nobody who uses this has stepped up to take ownership for it yet. - Wraps UIKit, so it works exactly the same as it would on another native app. Lives in Objective-C and JavaScript. - - Consequently, you get the animations and behaviour that Apple has developed. + - Consequently, you get the animations and behavior that Apple has developed. - iOS only. - Includes a navigation bar by default; this navigation bar is not a React Native view component and the style can only be slightly modified. diff --git a/docs/RunningOnDeviceAndroid.md b/docs/RunningOnDeviceAndroid.md index 08bc5f8da75b..1b64faf22dd8 100644 --- a/docs/RunningOnDeviceAndroid.md +++ b/docs/RunningOnDeviceAndroid.md @@ -20,7 +20,7 @@ Check that your device has been **successfully connected** by running `adb devic Seeing **device** in the right column means the device is connected. Android - go figure :) You must have **only one device connected**. -Now you can use `react-native run-android` to install and lauch your app on the device. +Now you can use `react-native run-android` to install and launch your app on the device. ## Accessing development server from device From 6e6a92eeeedfbad5fe5a0033bbfbbcadc2d16492 Mon Sep 17 00:00:00 2001 From: Cory Binnersley Date: Thu, 10 Dec 2015 15:36:52 -0800 Subject: [PATCH 0318/1411] Added Accio Delivery App to website showcase. --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 4fbcb58ef3a0..953d9406205d 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -116,6 +116,12 @@ var featured = [ ]; var apps = [ + { + name: 'Accio', + icon: 'http://a3.mzstatic.com/us/r30/Purple3/v4/03/a1/5b/03a15b9f-04d7-a70a-620a-9c9850a859aa/icon175x175.png', + link: 'https://itunes.apple.com/us/app/accio-on-demand-delivery/id1047060673?mt=8', + author: 'Accio Delivery Inc.', + }, { name: 'Beetroot', icon: 'http://is1.mzstatic.com/image/pf/us/r30/Purple5/v4/66/fd/dd/66fddd70-f848-4fc5-43ee-4d52197ccab8/pr_source.png', From a04e2bed5439220c39bbace3f1ad441787be7337 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 10 Dec 2015 17:58:30 -0800 Subject: [PATCH 0319/1411] Update node-haste from upstream Reviewed By: zpao Differential Revision: D2747183 fb-gh-sync-id: f1b963b19cb6ea16945b16370d1bee26110bb329 --- .../__tests__/DependencyGraph-test.js | 74 +++++++++---------- .../DependencyGraph/docblock.js | 2 +- .../src/DependencyResolver/Package.js | 2 +- .../src/DependencyResolver/replacePatterns.js | 15 ---- 4 files changed, 39 insertions(+), 54 deletions(-) delete mode 100644 packager/react-packager/src/DependencyResolver/replacePatterns.js diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index d830cc825dda..8b50c8b49c52 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -1362,19 +1362,19 @@ describe('DependencyGraph', function() { }); }); - testBrowserField('browser') - testBrowserField('react-native') + testBrowserField('browser'); + testBrowserField('react-native'); - function replaceBrowserField (json, fieldName) { + function replaceBrowserField(json, fieldName) { if (fieldName !== 'browser') { - json[fieldName] = json.browser - delete json.browser + json[fieldName] = json.browser; + delete json.browser; } - return json + return json; } - function testBrowserField (fieldName) { + function testBrowserField(fieldName) { pit('should support simple browser field in packages ("' + fieldName + '")', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -1393,8 +1393,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1448,8 +1448,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1503,8 +1503,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1559,8 +1559,8 @@ describe('DependencyGraph', function() { }, fieldName)), 'main.js': 'some other code', 'client.js': 'some code', - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1630,15 +1630,15 @@ describe('DependencyGraph', function() { }, 'hello.js': 'hello', 'bye.js': 'bye', - } - } + }, + }, }); - var dgraph = new DependencyGraph({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + const dgraph = new DependencyGraph({ + ...defaults, + roots: [root], + }); + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1716,7 +1716,7 @@ describe('DependencyGraph', function() { name: 'aPackage', browser: { 'node-package': 'browser-package', - } + }, }, fieldName)), 'index.js': 'require("node-package")', 'node-package': { @@ -1731,8 +1731,8 @@ describe('DependencyGraph', function() { }), 'index.js': 'some browser code', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1788,7 +1788,7 @@ describe('DependencyGraph', function() { name: 'aPackage', browser: { 'node-package': 'browser-package', - } + }, }, fieldName)), 'index.js': 'require("node-package")', 'node-package': { @@ -1803,8 +1803,8 @@ describe('DependencyGraph', function() { }), 'index.js': 'some browser code', }, - } - } + }, + }, }); var dgraph = new DependencyGraph({ @@ -1861,13 +1861,13 @@ describe('DependencyGraph', function() { name: 'aPackage', 'react-native': { 'node-package': 'rn-package', - } + }, }), 'index.js': 'require("node-package")', 'node_modules': { 'node-package': { 'package.json': JSON.stringify({ - 'name': 'node-package' + 'name': 'node-package', }), 'index.js': 'some node code', }, @@ -1875,8 +1875,8 @@ describe('DependencyGraph', function() { 'package.json': JSON.stringify({ 'name': 'rn-package', browser: { - 'nested-package': 'nested-browser-package' - } + 'nested-package': 'nested-browser-package', + }, }), 'index.js': 'require("nested-package")', }, @@ -1884,11 +1884,11 @@ describe('DependencyGraph', function() { 'package.json': JSON.stringify({ 'name': 'nested-browser-package', }), - 'index.js': 'some code' - } - } - } - } + 'index.js': 'some code', + }, + }, + }, + }, }); var dgraph = new DependencyGraph({ diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js index 131f09d1f6f2..d710112a9587 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js @@ -25,7 +25,7 @@ function extract(contents) { } -var commentStartRe = /^\/\*\*?/; +var commentStartRe = /^\/\*\*/; var commentEndRe = /\*\/$/; var wsRe = /[\t ]+/g; var stringStartRe = /(\r?\n|^) *\*/g; diff --git a/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js index 2117e3f1976a..ac7ee88c1a06 100644 --- a/packager/react-packager/src/DependencyResolver/Package.js +++ b/packager/react-packager/src/DependencyResolver/Package.js @@ -15,7 +15,7 @@ class Package { getMain() { return this._read().then(json => { - var replacements = getReplacements(json) + var replacements = getReplacements(json); if (typeof replacements === 'string') { return path.join(this.root, replacements); } diff --git a/packager/react-packager/src/DependencyResolver/replacePatterns.js b/packager/react-packager/src/DependencyResolver/replacePatterns.js deleted file mode 100644 index a4e563d2c67c..000000000000 --- a/packager/react-packager/src/DependencyResolver/replacePatterns.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g; -exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; -exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; From b78b8f6cab4439ad32b8c322f59a11ee2abe6c62 Mon Sep 17 00:00:00 2001 From: Dave Sibiski Date: Thu, 10 Dec 2015 22:50:03 -0800 Subject: [PATCH 0320/1411] Fixes grammar in comment Summary: Closes https://github.com/facebook/react-native/pull/4726 Reviewed By: svcscm Differential Revision: D2748499 Pulled By: androidtrunkagent fb-gh-sync-id: cbc6bc7b6692191bc68b8e1782897d17bc01bfa3 --- React/Profiler/RCTProfile.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Profiler/RCTProfile.h b/React/Profiler/RCTProfile.h index a013a70703a3..1cd721ab7cd2 100644 --- a/React/Profiler/RCTProfile.h +++ b/React/Profiler/RCTProfile.h @@ -16,8 +16,8 @@ * * 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 carefully - * before before using it. + * NOTE: This API is a work in progress, please consider carefully before + * using it. */ RCT_EXTERN NSString *const RCTProfileDidStartProfiling; From ae912a83d7e7ddf40beae3b79aabdaa1f54d4088 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 10 Dec 2015 23:15:38 -0800 Subject: [PATCH 0321/1411] Codemod Internal Unit Tests and Remove Require Fallback Summary: MessageQueue no longer falls back to require. To do this we need to register all the modules in our internal unit tests. I did this codemod manually. This is a bit unfortunate boilerplate but there are very few of these modules outside of unit tests. This boilerplate is only a hassle for these test files. public Reviewed By: spicyj Differential Revision: D2736397 fb-gh-sync-id: 59fa4c4e75c538f3577bc9693b93e1b7c4d4d233 --- Libraries/Utilities/MessageQueue.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index adf9376e33e0..888afea2a943 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -159,20 +159,11 @@ class MessageQueue { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } var moduleMethods = this._callableModules[module]; - if (!moduleMethods) { - // TODO: Register all the remaining test modules and make this an invariant. #9317773 - // Fallback to require temporarily. A follow up diff will clean up the remaining - // modules and make this an invariant. - console.warn('Module is not registered:', module); - moduleMethods = require(module); - /* - invariant( - !!moduleMethods, - 'Module %s is not a registered callable module.', - module - ); - */ - } + invariant( + !!moduleMethods, + 'Module %s is not a registered callable module.', + module + ); moduleMethods[method].apply(moduleMethods, args); BridgeProfiling.profileEnd(); } From 44cbec28bdf93ecb90d8a2f4e2c1e5f4888b1ae9 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Fri, 11 Dec 2015 01:42:17 -0800 Subject: [PATCH 0322/1411] Work around race condition in bridge deallocation Reviewed By: tadeuzagallo, nicklockwood Differential Revision: D2748820 fb-gh-sync-id: 40084ab4bcc606ccbde8b71c2389e602c4dd8e22 --- React/Base/RCTBridge.m | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 3e4a1bc5c0d1..70bd92688ef3 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -36,7 +36,11 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZ @interface RCTBridge () -@property (nonatomic, strong) RCTBatchedBridge *batchedBridge; +// This property is mostly used on the main thread, but may be touched from +// a background thread if the RCTBridge happens to deallocate on a background +// thread. Therefore, we want all writes to it to be seen atomically. +@property (atomic, strong) RCTBatchedBridge *batchedBridge; + @property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; @end @@ -232,12 +236,12 @@ - (void)bindKeys - (NSArray *)moduleClasses { - return _batchedBridge.moduleClasses; + return self.batchedBridge.moduleClasses; } - (id)moduleForName:(NSString *)moduleName { - return [_batchedBridge moduleForName:moduleName]; + return [self.batchedBridge moduleForName:moduleName]; } - (id)moduleForClass:(Class)moduleClass @@ -270,30 +274,34 @@ - (void)setUp // Sanitize the bundle URL _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString]; - _batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self]; + self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self]; } - (BOOL)isLoading { - return _batchedBridge.loading; + return self.batchedBridge.loading; } - (BOOL)isValid { - return _batchedBridge.valid; + return self.batchedBridge.valid; } - (void)invalidate { - RCTAssertMainThread(); + RCTBatchedBridge *batchedBridge = self.batchedBridge; + self.batchedBridge = nil; - [_batchedBridge invalidate]; - _batchedBridge = nil; + if (batchedBridge) { + RCTExecuteOnMainThread(^{ + [batchedBridge invalidate]; + }, NO); + } } - (void)logMessage:(NSString *)message level:(NSString *)level { - [_batchedBridge logMessage:message level:level]; + [self.batchedBridge logMessage:message level:level]; } @@ -321,7 +329,7 @@ @implementation RCTBridge(Deprecated) - (NSDictionary *)modules { - return _batchedBridge.modules; + return self.batchedBridge.modules; } @end From 3549ff049c55feb151f454d9181c7d635f46a6ca Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 11 Dec 2015 03:49:15 -0800 Subject: [PATCH 0323/1411] Rename BridgeProfiling to Systrace for consistency Summary: public Rename the `BridgeProfiling` JS module to `Systrace`, since it's actually just an API to Systrace markers. This should make it clearer as we add more perf tooling. Reviewed By: jspahrsummers Differential Revision: D2734001 fb-gh-sync-id: 642848fa7340c545067f2a7cf5cef8af1c8a69a2 --- Libraries/BatchedBridge/BatchedBridge.js | 4 +- .../InitializeJavaScriptAppEngine.js | 4 +- .../System/JSTimers/JSTimersExecution.js | 6 +-- Libraries/Utilities/MessageQueue.js | 22 +++++----- .../{BridgeProfiling.js => Systrace.js} | 44 +++++++++---------- React/Executors/RCTContextExecutor.m | 2 +- .../facebook/react/CoreModulesPackage.java | 3 +- .../react/bridge/CatalystInstanceImpl.java | 4 +- .../{BridgeProfiling.java => Systrace.java} | 4 +- .../src/Resolver/polyfills/require.js | 14 +++--- 10 files changed, 53 insertions(+), 54 deletions(-) rename Libraries/Utilities/{BridgeProfiling.js => Systrace.js} (77%) rename ReactAndroid/src/main/java/com/facebook/react/bridge/{BridgeProfiling.java => Systrace.java} (66%) diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js index e4269735afb5..6d25db30e217 100644 --- a/Libraries/BatchedBridge/BatchedBridge.js +++ b/Libraries/BatchedBridge/BatchedBridge.js @@ -19,10 +19,10 @@ const BatchedBridge = new MessageQueue( // TODO: Move these around to solve the cycle in a cleaner way. -const BridgeProfiling = require('BridgeProfiling'); +const Systrace = require('Systrace'); const JSTimersExecution = require('JSTimersExecution'); -BatchedBridge.registerCallableModule('BridgeProfiling', BridgeProfiling); +BatchedBridge.registerCallableModule('Systrace', Systrace); BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution); // Wire up the batched bridge on the global object so that we can call into it. diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 141ea7045cd7..cb6ba3275542 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -165,8 +165,8 @@ function setUpWebSockets() { function setUpProfile() { if (__DEV__) { - var BridgeProfiling = require('BridgeProfiling'); - BridgeProfiling.swizzleReactPerf(); + var Systrace = require('Systrace'); + Systrace.swizzleReactPerf(); } } diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js index 07a324d41f4c..c85c155a7726 100644 --- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js +++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js @@ -14,7 +14,7 @@ var invariant = require('invariant'); var keyMirror = require('keyMirror'); var performanceNow = require('performanceNow'); var warning = require('warning'); -var BridgeProfiling = require('BridgeProfiling'); +var Systrace = require('Systrace'); /** * JS implementation of timer functions. Must be completely driven by an @@ -113,7 +113,7 @@ var JSTimersExecution = { * more immediates are queued up (can be used as a condition a while loop). */ callImmediatesPass: function() { - BridgeProfiling.profile('JSTimersExecution.callImmediatesPass()'); + Systrace.beginEvent('JSTimersExecution.callImmediatesPass()'); // The main reason to extract a single pass is so that we can track // in the system trace @@ -128,7 +128,7 @@ var JSTimersExecution = { } } - BridgeProfiling.profileEnd(); + Systrace.endEvent(); return JSTimersExecution.immediates.length > 0; }, diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 888afea2a943..10a41919a728 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -13,7 +13,7 @@ 'use strict'; -let BridgeProfiling = require('BridgeProfiling'); +let Systrace = require('Systrace'); let ErrorUtils = require('ErrorUtils'); let JSTimersExecution = require('JSTimersExecution'); @@ -114,9 +114,9 @@ class MessageQueue { */ __callImmediates() { - BridgeProfiling.profile('JSTimersExecution.callImmediates()'); + Systrace.beginEvent('JSTimersExecution.callImmediates()'); guard(() => JSTimersExecution.callImmediates()); - BridgeProfiling.profileEnd(); + Systrace.endEvent(); } __nativeCall(module, method, params, onFail, onSucc) { @@ -154,7 +154,7 @@ class MessageQueue { method = this._methodTable[module][method]; module = this._moduleTable[module]; } - BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`); + Systrace.beginEvent(() => `${module}.${method}(${stringifySafe(args)})`); if (__DEV__ && SPY_MODE) { console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); } @@ -165,11 +165,11 @@ class MessageQueue { module ); moduleMethods[method].apply(moduleMethods, args); - BridgeProfiling.profileEnd(); + Systrace.endEvent(); } __invokeCallback(cbID, args) { - BridgeProfiling.profile( + Systrace.beginEvent( () => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`); this._lastFlush = new Date().getTime(); let callback = this._callbacks[cbID]; @@ -188,7 +188,7 @@ class MessageQueue { this._callbacks[cbID & ~1] = null; this._callbacks[cbID | 1] = null; callback.apply(null, args); - BridgeProfiling.profileEnd(); + Systrace.endEvent(); } /** @@ -244,7 +244,7 @@ class MessageQueue { this._genLookup(config, moduleID, moduleTable, methodTable); }); } - + _genLookup(config, moduleID, moduleTable, methodTable) { if (!config) { return; @@ -287,15 +287,15 @@ class MessageQueue { module[methodName] = this._genMethod(moduleID, methodID, methodType); }); Object.assign(module, constants); - + if (!constants && !methods && !asyncMethods) { module.moduleID = moduleID; } - + this.RemoteModules[moduleName] = module; return module; } - + _genMethod(module, method, type) { let fn = null; let self = this; diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/Systrace.js similarity index 77% rename from Libraries/Utilities/BridgeProfiling.js rename to Libraries/Utilities/Systrace.js index 36cb86252f61..826be67a1748 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/Systrace.js @@ -6,7 +6,7 @@ * 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 BridgeProfiling + * @providesModule Systrace * @flow */ 'use strict'; @@ -32,7 +32,7 @@ function ReactPerf() { return _ReactPerf; } -var BridgeProfiling = { +var Systrace = { setEnabled(enabled: boolean) { if (_enabled !== enabled) { if (enabled) { @@ -47,9 +47,9 @@ var BridgeProfiling = { }, /** - * profile/profileEnd for starting and then ending a profile within the same call stack frame + * beginEvent/endEvent for starting and then ending a profile within the same call stack frame **/ - profile(profileName?: any) { + beginEvent(profileName?: any) { if (_enabled) { profileName = typeof profileName === 'function' ? profileName() : profileName; @@ -57,18 +57,18 @@ var BridgeProfiling = { } }, - profileEnd() { + endEvent() { if (_enabled) { global.nativeTraceEndSection(TRACE_TAG_REACT_APPS); } }, /** - * profileAsync/profileAsyncEnd for starting and then ending a profile where the end can either + * beginAsyncEvent/endAsyncEvent for starting and then ending a profile where the end can either * occur on another thread or out of the current stack frame, eg await - * the returned cookie variable should be used as input into the asyncEnd call to end the profile + * the returned cookie variable should be used as input into the endAsyncEvent call to end the profile **/ - profileAsync(profileName?: any): any { + beginAsyncEvent(profileName?: any): any { var cookie = _asyncCookie; if (_enabled) { _asyncCookie++; @@ -79,7 +79,7 @@ var BridgeProfiling = { return cookie; }, - profileAsyncEnd(profileName?: any, cookie?: any) { + endAsyncEvent(profileName?: any, cookie?: any) { if (_enabled) { profileName = typeof profileName === 'function' ? profileName() : profileName; @@ -94,15 +94,15 @@ var BridgeProfiling = { } var name = objName === 'ReactCompositeComponent' && this.getName() || ''; - BridgeProfiling.profile(`${objName}.${fnName}(${name})`); + Systrace.beginEvent(`${objName}.${fnName}(${name})`); var ret = func.apply(this, arguments); - BridgeProfiling.profileEnd(); + Systrace.endEvent(); return ret; }; }, swizzleReactPerf() { - ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure); + ReactPerf().injection.injectMeasure(Systrace.reactPerfMeasure); }, /** @@ -111,9 +111,9 @@ var BridgeProfiling = { **/ attachToRelayProfiler(relayProfiler: RelayProfiler) { relayProfiler.attachProfileHandler('*', (name) => { - var cookie = BridgeProfiling.profileAsync(name); + var cookie = Systrace.beginAsyncEvent(name); return () => { - BridgeProfiling.profileAsyncEnd(name, cookie); + Systrace.endAsyncEvent(name, cookie); }; }); }, @@ -121,7 +121,7 @@ var BridgeProfiling = { /* This is not called by default due to perf overhead but it's useful if you want to find traces which spend too much time in JSON. */ swizzleJSON() { - BridgeProfiling.measureMethods(JSON, 'JSON', [ + Systrace.measureMethods(JSON, 'JSON', [ 'parse', 'stringify' ]); @@ -129,7 +129,7 @@ var BridgeProfiling = { /** * Measures multiple methods of a class. For example, you can do: - * BridgeProfiling.measureMethods(JSON, 'JSON', ['parse', 'stringify']); + * Systrace.measureMethods(JSON, 'JSON', ['parse', 'stringify']); * * @param object * @param objectName @@ -141,7 +141,7 @@ var BridgeProfiling = { } methodNames.forEach(methodName => { - object[methodName] = BridgeProfiling.measure( + object[methodName] = Systrace.measure( objectName, methodName, object[methodName] @@ -151,7 +151,7 @@ var BridgeProfiling = { /** * Returns an profiled version of the input function. For example, you can: - * JSON.parse = BridgeProfiling.measure('JSON', 'parse', JSON.parse); + * JSON.parse = Systrace.measure('JSON', 'parse', JSON.parse); * * @param objName * @param fnName @@ -169,14 +169,14 @@ var BridgeProfiling = { return func.apply(this, arguments); } - BridgeProfiling.profile(profileName); + Systrace.beginEvent(profileName); var ret = func.apply(this, arguments); - BridgeProfiling.profileEnd(); + Systrace.endEvent(); return ret; }; }, }; -BridgeProfiling.setEnabled(global.__RCTProfileIsProfiling || false); +Systrace.setEnabled(global.__RCTProfileIsProfiling || false); -module.exports = BridgeProfiling; +module.exports = Systrace; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index d37f6c7b9313..55b0d506c35c 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -418,7 +418,7 @@ - (void)toggleProfilingFlag:(NSNotification *)notification [self executeBlockOnJavaScriptQueue:^{ BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; // TODO: Don't use require, go through the normal execution modes instead. #9317773 - NSString *script = [NSString stringWithFormat:@"var p = require('BridgeProfiling') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"]; + NSString *script = [NSString stringWithFormat:@"var p = require('Systrace') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"]; JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String); JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL); JSStringRelease(scriptJSRef); diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 28bc76f82406..07b18b066b30 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -13,7 +13,6 @@ import java.util.Arrays; import java.util.List; -import com.facebook.react.bridge.BridgeProfiling; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; @@ -95,7 +94,7 @@ public List> createJSModules() { RCTEventEmitter.class, RCTNativeAppEventEmitter.class, AppRegistry.class, - BridgeProfiling.class, + com.facebook.react.bridge.Systrace.class, DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 06d53a85efca..87ae11a2bb27 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -469,12 +469,12 @@ public void run() { private class JSProfilerTraceListener implements TraceListener { @Override public void onTraceStarted() { - getJSModule(BridgeProfiling.class).setEnabled(true); + getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true); } @Override public void onTraceStopped() { - getJSModule(BridgeProfiling.class).setEnabled(false); + getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java similarity index 66% rename from ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java rename to ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java index 50a3a72b984f..9f571e8651fa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java @@ -5,10 +5,10 @@ import com.facebook.proguard.annotations.DoNotStrip; /** - * Interface to the JavaScript BridgeProfiling Module + * Interface to the JavaScript Systrace Module */ @DoNotStrip -public interface BridgeProfiling extends JavaScriptModule{ +public interface Systrace extends JavaScriptModule{ @DoNotStrip void setEnabled(boolean enabled); } diff --git a/packager/react-packager/src/Resolver/polyfills/require.js b/packager/react-packager/src/Resolver/polyfills/require.js index ebaa8c406d75..506d38e02484 100644 --- a/packager/react-packager/src/Resolver/polyfills/require.js +++ b/packager/react-packager/src/Resolver/polyfills/require.js @@ -54,13 +54,13 @@ // require cycles inside the factory from causing an infinite require loop. mod.isInitialized = true; - __DEV__ && BridgeProfiling().profile('JS_require_' + id); + __DEV__ && Systrace().beginEvent('JS_require_' + id); // keep args in sync with with defineModuleCode in // packager/react-packager/src/Resolver/index.js mod.factory.call(global, global, require, mod.module, mod.module.exports); - __DEV__ && BridgeProfiling().profileEnd(); + __DEV__ && Systrace().endEvent(); } catch (e) { mod.hasError = true; mod.isInitialized = false; @@ -70,14 +70,14 @@ return mod.module.exports; } - const BridgeProfiling = __DEV__ && (() => { - var _BridgeProfiling; + const Systrace = __DEV__ && (() => { + var _Systrace; try { - _BridgeProfiling = require('BridgeProfiling'); + _Systrace = require('Systrace'); } catch(e) {} - return _BridgeProfiling && _BridgeProfiling.profile ? - _BridgeProfiling : { profile: () => {}, profileEnd: () => {} }; + return _Systrace && _Systrace.beginEvent ? + _Systrace : { beginEvent: () => {}, endEvent: () => {} }; }); global.__d = define; From 73daa9595bdcf182493bb9fb72eb9e9e7b3109e1 Mon Sep 17 00:00:00 2001 From: Timm Preetz Date: Fri, 11 Dec 2015 15:22:57 +0100 Subject: [PATCH 0324/1411] Fix typo in Testing.md --- docs/Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Testing.md b/docs/Testing.md index 436de2c95dca..4c881b2d654a 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -64,4 +64,4 @@ You can run integration tests locally with cmd+U in the IntegrationTest and UIEx A common type of integration test is the snapshot test. These tests render a component, and verify snapshots of the screen against reference images using `TestModule.verifySnapshot()`, using the [`FBSnapshotTestCase`](https://github.com/facebook/ios-snapshot-test-case) library behind the scenes. Reference images are recorded by setting `recordMode = YES` on the `RCTTestRunner`, then running the tests. Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration. It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies. See [`SimpleSnapshotTest`](https://github.com/facebook/react-native/blob/master/IntegrationTests/SimpleSnapshotTest.js) for a basic example. -If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshotshot reference image. To do this, simply change to `_runner.recordMode = YES;` in [UIExplorer/UIExplorerSnapshotTests.m](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m#L42), re-run the failing tests, then flip record back to `NO` and submit/update your PR and wait to see if the Travis build passes. +If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshot reference image. To do this, simply change to `_runner.recordMode = YES;` in [UIExplorer/UIExplorerSnapshotTests.m](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m#L42), re-run the failing tests, then flip record back to `NO` and submit/update your PR and wait to see if the Travis build passes. From a86171a48284c440226a79a26c6d6a4704d401e9 Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Fri, 11 Dec 2015 06:54:56 -0800 Subject: [PATCH 0325/1411] exposed a way to trigger layout on the uimanager Summary: A component can be backed by native "node" that can change its internal state, which would result in a new UI after the next layout. Since js has no way of knowing that this has happened it wouldn't trigger a layout if nothing in js world has changed. Therefore we need a way how to trigger layout from native code. This diff does it by adding methods `layoutIfNeeded` on the uimanager and `isBatchActive` on the bridge. When `layoutIfNeeded` is called it checks whether a batch is in progress. If it is we do nothing, since at it's end layout happens. If a batch is not in progress we immidiately do layout. I went with the easiest way how to implement this - `isBatchActive` is a public method on the bridge. It's not ideal, but consistent with other methods for modules. public Reviewed By: jspahrsummers, nicklockwood Differential Revision: D2748896 fb-gh-sync-id: f3664c4af980d40a463b538e069b26c9ebad6300 --- React/Base/RCTBatchedBridge.m | 5 +++++ React/Base/RCTBridge.h | 5 +++++ React/Base/RCTBridge.m | 5 +++++ React/Modules/RCTUIManager.h | 6 ++++++ React/Modules/RCTUIManager.m | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index a0190a0ae521..1999f32b026b 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -982,6 +982,11 @@ - (void)stopProfiling:(void (^)(NSData *))callback }]; } +- (BOOL)isBatchActive +{ + return _wasBatchActive; +} + @end @implementation RCTBatchedBridge(Deprecated) diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 487773f5217a..f341b75aab7a 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -163,6 +163,11 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class); */ - (void)reload; +/** + * Says whether bridge has started recieving calls from javascript. + */ +- (BOOL)isBatchActive; + @end /** diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 70bd92688ef3..c0b89be0a22c 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -287,6 +287,11 @@ - (BOOL)isValid return self.batchedBridge.valid; } +- (BOOL)isBatchActive +{ + return [_batchedBridge isBatchActive]; +} + - (void)invalidate { RCTBatchedBridge *batchedBridge = self.batchedBridge; diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index dc0bcd9d985b..bdcf2e2c5350 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -88,6 +88,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ @property (atomic, assign) BOOL unsafeFlushUIChangesBeforeBatchEnds; +/** + * In some cases we might want to trigger layout from native side. + * React won't be aware of this, so we need to make sure it happens. + */ +- (void)setNeedsLayout; + @end /** diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 4812a548ef8e..46c991e3c3c4 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -888,6 +888,15 @@ - (void)partialBatchDidFlush } - (void)batchDidComplete +{ + [self _layoutAndMount]; +} + +/** + * Sets up animations, computes layout, creates UI mounting blocks for computed layout, + * runs these blocks and all other already existing blocks. + */ +- (void)_layoutAndMount { // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) @@ -962,6 +971,15 @@ - (void)flushUIBlocks } } +- (void)setNeedsLayout +{ + // If there is an active batch layout will happen when batch finished, so we will wait for that. + // Otherwise we immidiately trigger layout. + if (![_bridge isBatchActive]) { + [self _layoutAndMount]; + } +} + RCT_EXPORT_METHOD(measure:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) { From 7003f8dbeb621242b217713451244a2eac43b74d Mon Sep 17 00:00:00 2001 From: Sharath Prabhal Date: Fri, 11 Dec 2015 10:09:31 -0500 Subject: [PATCH 0326/1411] Add 'Eat or Not' to showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 953d9406205d..ae6dc2e0959e 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -162,6 +162,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/dropbot-phone-replacement/id1000855694?mt=8', author: 'Peach Labs', }, + { + name: 'Eat or Not', + icon: 'http://a3.mzstatic.com/us/r30/Purple5/v4/51/be/34/51be3462-b015-ebf2-11c5-69165b37fadc/icon175x175.jpeg', + link: 'https://itunes.apple.com/us/app/eat-or-not/id1054565697?mt=8', + author: 'Sharath Prabhal', + }, { name: 'Fan of it', icon: 'http://a4.mzstatic.com/us/r30/Purple3/v4/c9/3f/e8/c93fe8fb-9332-e744-f04a-0f4f78e42aa8/icon350x350.png', From 1a9469865832121deeabb3dd0d84a6af58ce775f Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Fri, 11 Dec 2015 07:28:50 -0800 Subject: [PATCH 0327/1411] expose systemclock time to JS Reviewed By: tadeuzagallo Differential Revision: D2748749 fb-gh-sync-id: 4d1dbae61f69a07b7106cb57caff03cadfb85776 --- .../src/main/jni/react/JSCExecutor.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 6e1f493e8cdd..8b954f156835 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "Value.h" #include "jni/OnLoad.h" @@ -34,6 +35,9 @@ using fbsystrace::FbSystraceSection; #include #endif +static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL; +static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL; + using namespace facebook::jni; namespace facebook { @@ -54,6 +58,13 @@ static JSValueRef nativeLoggingHook( size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); +static JSValueRef nativePerformanceNow( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception); static JSValueRef evaluateScriptWithJSC( JSGlobalContextRef ctx, @@ -110,6 +121,7 @@ JSCExecutor::JSCExecutor(FlushImmediateCallback cb) : s_globalContextRefToJSCExecutor[m_context] = this; installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook); + installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow); #ifdef WITH_FB_JSC_TUNING configureJSCForAndroid(); @@ -283,4 +295,17 @@ static JSValueRef nativeLoggingHook( return JSValueMakeUndefined(ctx); } +static JSValueRef nativePerformanceNow( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], JSValueRef *exception) { + // This is equivalent to android.os.SystemClock.elapsedRealtime() in native + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + int64_t nano = now.tv_sec * NANOSECONDS_IN_SECOND + now.tv_nsec; + return JSValueMakeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND)); +} + } } From eb3152a397f75cea7340b5ad60dd26433c4b4398 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 11 Dec 2015 15:44:13 +0000 Subject: [PATCH 0328/1411] Update Releases.md --- Releases.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Releases.md b/Releases.md index b014e5d36e86..8a63520b32d1 100644 --- a/Releases.md +++ b/Releases.md @@ -14,7 +14,7 @@ Future releases: ## Cut a release branch -Note: replace 0.12 in all the commands below with the version you're releasing :) +Note: replace 0.18 in all the commands below with the version you're releasing :) #### Check that everything works @@ -23,28 +23,28 @@ First, set up Sinopia (only need to do this once): https://github.com/facebook/r Make absolutely sure basic iOS and Android workflow works on master: - `cd react-native` - `git pull` - - `git checkout -b 0.12-stable` - - Edit `ReactAndroid/gradle.properties`, set `VERSION_NAME=0.12.0` + - `git checkout -b 0.18-stable` + - Edit `ReactAndroid/gradle.properties`, set `VERSION_NAME=0.18.0` - Edit `ReactAndroid/release.gradle`, uncomment Javadoc generation (the line `// archives androidJavadocJar`) - Make sure `java -version` prints 1.7.x, this is currently needed for Javadoc generation and Javadocs are required by Maven Central (we should make it work with Java 8) - Run `./gradlew :ReactAndroid:installArchives`, it will print a lot of Javadoc warnings, that's OK. - - Check the artifacts were generated: `ls -al ~/.m2/repository/com/facebook/react/react-native/0.12.0/` should contain: - - `react-native-0.17.0-javadoc.jar`, `react-native-0.17.0-sources.jar`, `react-native-0.17.0.aar`, `react-native-0.17.0.pom` + - Check the artifacts were generated: `ls -al ~/.m2/repository/com/facebook/react/react-native/0.18.0/` should contain: + - `react-native-0.18.0-javadoc.jar`, `react-native-0.18.0-sources.jar`, `react-native-0.18.0.aar`, `react-native-0.18.0.pom` - For each of the above also `.asc` file -- In `package.json`, set version to e.g. `0.12.0-rc`. -- In `React.podspec`, set version to e.g. `0.12.0-rc`. -- In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.12.+` +- In `package.json`, set version to e.g. `0.18.0-rc`. +- In `React.podspec`, set version to e.g. `0.18.0-rc`. +- In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.18.+` - Publish to sinopia: - `npm set registry http://localhost:4873/`, check that it worked: `npm config list` will show registry is set to localhost - In a separate shell, start sinopia. Run `sinopia`. If started successfully it will print: http address - http://localhost:4873/. - Make sure http://localhost:4873/ shows no old versions - `npm publish` - - http://localhost:4873/ will show 0.12.0-rc + - http://localhost:4873/ will show 0.18.0-rc - Test that everything works: - `cd /tmp` - `react-native init Zero12rc` - `cd Zero12rc` - - Check that `package.json`, `android/app/build.gradle` have correct versions (`^0.12.0-rc`, `com.facebook.react:react-native:0.12.+`) + - Check that `package.json`, `android/app/build.gradle` have correct versions (`^0.18.0-rc`, `com.facebook.react:react-native:0.18.+`) - `open ios/Zero12rc.xcodeproj` - Hit the Run button in Xcode. - Packager should open in a new window, you should see the Welcome to React Native screen, Reload JS, try Chrome debugging - put a breakpoint somewhere in `index.ios.js` and Reload JS, Chrome debugger should stop on the breakpoint (we don't have tests for Chrome debugging) @@ -56,8 +56,8 @@ Make absolutely sure basic iOS and Android workflow works on master: #### Push to github - Revert the Javadoc change in `ReactAndroid/release.gradle` - - `git commit -am` "[0.12-rc] Bump version numbers" - - `git push origin 0.12-stable` + - `git commit -am` "[0.18-rc] Bump version numbers" + - `git push origin 0.18-stable` ## Do a release @@ -67,7 +67,7 @@ Publish to Maven Central (NOTE we could get rid of this whole section publishing - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) - Wait a few hours until you see the version has propagated to [JCenter](https://bintray.com/bintray/jcenter/com.facebook.react%3Areact-native/view) -To release 0.12-rc to npm: +To release 0.18-rc to npm: (You need to be a maintainer of the repo. For admins, here's the command to promote someone) @@ -76,7 +76,7 @@ npm owner add react-native ``` ``` -git tag v0.12.0-rc 0.12-stable # don't forget the `v` at the beginning! +git tag v0.18.0-rc 0.18-stable # don't forget the `v` at the beginning! git push --tags ``` @@ -84,8 +84,8 @@ git push --tags ``` npm publish -# Only for non-rc releases: -npm dist-tag add react-native@0.12.0 latest +# Only when doing a non-rc release: +npm dist-tag add react-native@0.18.0 latest ``` - Upgrade tags to a release by going to https://github.com/facebook/react-native/tags - Click "Add Notes to release" From 9443a4e65f59b28620940aac43f236641a0a0df2 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 11 Dec 2015 16:14:31 +0000 Subject: [PATCH 0329/1411] Update Releases.md --- Releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Releases.md b/Releases.md index 8a63520b32d1..d2c8c8812407 100644 --- a/Releases.md +++ b/Releases.md @@ -14,7 +14,7 @@ Future releases: ## Cut a release branch -Note: replace 0.18 in all the commands below with the version you're releasing :) +Note: Make sure you replace 0.18 in all the commands below with the version you're releasing :) For example, copy-paste all of this into an editor and replace 0.18. #### Check that everything works From 339c114b96b6d04f790cf202dab77edb7694edf6 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 11 Dec 2015 16:15:14 +0000 Subject: [PATCH 0330/1411] Update Releases.md --- Releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Releases.md b/Releases.md index d2c8c8812407..1ed509786e2e 100644 --- a/Releases.md +++ b/Releases.md @@ -61,7 +61,7 @@ Make absolutely sure basic iOS and Android workflow works on master: ## Do a release -Publish to Maven Central (NOTE we could get rid of this whole section publishing binaries to npm instead): +Publish to Maven Central (Note: We could get rid of this whole section publishing binaries to npm instead): - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload). You'll need to get permissions for this by filing a ticket explaining you're a core contributor to React Native. [Example ticket](https://issues.sonatype.org/browse/OSSRH-11885). - Select Artifact(s) with a POM (to publish to a local Maven repo for testing run `./gradlew :ReactAndroid:installArchives`) - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) From 402fd067250d7628db256814bbfc5cabbe470a2d Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 9 Dec 2015 15:29:00 -0800 Subject: [PATCH 0331/1411] Introduce code-analysis bot --- .travis.yml | 1 + bots/code-analysis-bot.js | 242 ++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 bots/code-analysis-bot.js diff --git a/.travis.yml b/.travis.yml index ea84c8bc8ab5..d1baaddd5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ script: elif [ "$TEST_TYPE" = js ] then + cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js flow check && npm test -- '\/Libraries\/' elif [ "$TEST_TYPE" = packager ] diff --git a/bots/code-analysis-bot.js b/bots/code-analysis-bot.js new file mode 100644 index 000000000000..8fabcfb1e429 --- /dev/null +++ b/bots/code-analysis-bot.js @@ -0,0 +1,242 @@ +/** + * 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'; + +if (!process.env.TRAVIS_REPO_SLUG) { + console.error('Missing TRAVIS_REPO_SLUG. Example: facebook/react-native'); + process.exit(1); +} +if (!process.env.GITHUB_TOKEN) { + console.error('Missing GITHUB_TOKEN. Example: 5fd88b964fa214c4be2b144dc5af5d486a2f8c1e'); + process.exit(1); +} +if (!process.env.TRAVIS_PULL_REQUEST) { + console.error('Missing TRAVIS_PULL_REQUEST. Example: 4687'); + process.exit(1); +} + +var GitHubApi = require('github'); +var path = require('path'); + +var github = new GitHubApi({ + version: '3.0.0', +}); + +github.authenticate({ + type: 'oauth', + token: process.env.GITHUB_TOKEN, +}); + +function push(arr, key, value) { + if (!arr[key]) { + arr[key] = []; + } + arr[key].push(value); +} + +/** + * There is unfortunately no standard format to report an error, so we have + * to write a specific converter for each tool we want to support. + * + * Those functions take a json object as input and fill the output with the + * following format: + * + * { [ path: string ]: Array< { message: string, line: number }> } + * + * This is an object where the keys are the path of the files and values + * is an array of objects of the shape message and line. + */ +var converters = { + raw: function(output, input) { + for (var key in input) { + input[key].forEach(function(message) { + push(output, key, message); + }); + } + }, + + flow: function(output, input) { + if (!input || !input.errors) { + return; + } + + input.errors.forEach(function(error) { + push(output, error.message[0].path, { + message: error.message.map(message => message.descr).join(' '), + line: error.message[0].line, + }); + }); + }, + + eslint: function(output, input) { + if (!input) { + return; + } + + input.forEach(function(file) { + file.messages.forEach(function(message) { + push(output, file.filePath, { + message: message.ruleId + ': ' + message.message, + line: message.line, + }); + }); + }); + } +}; + +function getShaFromPullRequest(user, repo, number, callback) { + github.pullRequests.get({user, repo, number}, (error, res) => { + if (error) { + console.log(error); + return; + } + callback(res.head.sha); + }); +} + +function getFilesFromCommit(user, repo, sha, callback) { + github.repos.getCommit({user, repo, sha}, (error, res) => { + if (error) { + console.log(error); + return; + } + callback(res.files); + }); +} + + +/** + * Sadly we can't just give the line number to github, we have to give the + * line number relative to the patch file which is super annoying. This + * little function builds a map of line number in the file to line number + * in the patch file + */ +function getLineMapFromPatch(patchString) { + var diffLineIndex = 0; + var fileLineIndex = 0; + var lineMap = {}; + + patchString.split('\n').forEach((line) => { + if (line.match(/^@@/)) { + fileLineIndex = line.match(/\+([0-9]+)/)[1] - 1; + return; + } + + diffLineIndex++; + if (line[0] !== '-') { + fileLineIndex++; + if (line[0] === '+') { + lineMap[fileLineIndex] = diffLineIndex; + } + } + }); + + return lineMap; +} + +function sendComment(user, repo, number, sha, filename, lineMap, message) { + if (!lineMap[message.line]) { + // Do not send messages on lines that did not change + return; + } + + var opts = { + user, + repo, + number, + sha, + path: filename, + commit_id: sha, + body: message.message, + position: lineMap[message.line], + }; + github.pullRequests.createComment(opts, function(error, res) { + if (error) { + console.log(error); + return; + } + }); + console.log('Sending comment', opts); +} + +function main(messages, user, repo, number) { + // No message, we don't need to do anything :) + if (Object.keys(messages).length === 0) { + return; + } + + getShaFromPullRequest(user, repo, number, (sha) => { + getFilesFromCommit(user, repo, sha, (files) => { + files + .filter((file) => messages[file.filename]) + .forEach((file) => { + var lineMap = getLineMapFromPatch(file.patch); + messages[file.filename].forEach((message) => { + sendComment(user, repo, number, sha, file.filename, lineMap, message); + }); + }); + }); + }); +} + +var content = ''; +process.stdin.resume(); +process.stdin.on('data', function(buf) { content += buf.toString(); }); +process.stdin.on('end', function() { + var messages = {}; + + // Since we send a few http requests to setup the process, we don't want + // to run this file one time per code analysis tool. Instead, we write all + // the results in the same stdin stream. + // The format of this stream is + // + // name-of-the-converter + // {"json":"payload"} + // name-of-the-other-converter + // {"other": ["json", "payload"]} + // + // In order to generate such stream, here is a sample bash command: + // + // cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | node code-analysis-bot.js + + var lines = content.trim().split('\n'); + for (var i = 0; i < Math.ceil(lines.length / 2); ++i) { + var converter = converters[lines[i * 2]]; + if (!converter) { + throw new Error('Unknown converter ' + lines[i * 2]); + } + var json; + try { + json = JSON.parse(lines[i * 2 + 1]); + } catch (e) {} + + converter(messages, json); + } + + // The paths are returned in absolute from code analysis tools but github works + // on paths relative from the root of the project. Doing the normalization here. + var pwd = path.resolve('.'); + for (var absolutePath in messages) { + var relativePath = path.relative(pwd, absolutePath); + if (relativePath === absolutePath) { + continue; + } + messages[relativePath] = messages[absolutePath]; + delete messages[absolutePath]; + } + + // TRAVIS_REPO_SLUG // 'facebook/react-native' + var user_repo = process.env.TRAVIS_REPO_SLUG.split('/'); + var user = user_repo[0]; + var repo = user_repo[1]; + var number = process.env.TRAVIS_PULL_REQUEST; + + // intentional lint warning to make sure that the bot is working :) + main(messages, user, repo, number) +}); diff --git a/package.json b/package.json index d2249d02e799..54af188aa0b0 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ ], "scripts": { "test": "NODE_ENV=test jest", - "lint": "eslint Examples/ Libraries/", + "lint": "eslint Examples/ Libraries/ bots/code-analysis-bot.js", "start": "/usr/bin/env bash -c './packager/packager.sh \"$@\" || true' --" }, "bin": { @@ -122,6 +122,7 @@ "yeoman-generator": "^0.20.3" }, "devDependencies": { + "github": "^0.2.4", "jest-cli": "0.7.1", "babel-eslint": "4.1.4", "eslint": "1.3.1", From f168fc335e8a120c8bb4c8e31398c6241e0c4f74 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Fri, 11 Dec 2015 10:22:37 -0800 Subject: [PATCH 0332/1411] Don't insta-crash when network permission is missing Summary: public Rename the `ConnectivityModule` to `NetInfoModule` (there's no need to name things differently in two places). Add exception handling to the module in case the network permission is missing. When the permission is missing, throw an actionable error from all calls to `NetInfo`: http://imgur.com/zVIMxOV Without this change, the app immediately crashes on startup: `getCurrentConnectionType` is called from `Activity.onResume`. Reviewed By: andreicoman11, bestander Differential Revision: D2749230 fb-gh-sync-id: 1b752d21a8f28ffeaf60a3322cb76f869dc70a14 --- ...ectivityModule.java => NetInfoModule.java} | 49 +++++++++++++++---- .../react/shell/MainReactPackage.java | 4 +- 2 files changed, 42 insertions(+), 11 deletions(-) rename ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/{ConnectivityModule.java => NetInfoModule.java} (71%) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java similarity index 71% rename from ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java rename to ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java index acf6060b1af5..bff80bf26b22 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/ConnectivityModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java @@ -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. + */ package com.facebook.react.modules.netinfo; @@ -10,6 +17,7 @@ import android.net.NetworkInfo; import android.support.v4.net.ConnectivityManagerCompat; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; @@ -17,25 +25,30 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.common.ReactConstants; import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; /** * Module that monitors and provides information about the connectivity state of the device. */ -public class ConnectivityModule extends ReactContextBaseJavaModule +public class NetInfoModule extends ReactContextBaseJavaModule implements LifecycleEventListener { private static final String CONNECTION_TYPE_NONE = "NONE"; private static final String CONNECTION_TYPE_UNKNOWN = "UNKNOWN"; + private static final String MISSING_PERMISSION_MESSAGE = + "To use NetInfo on Android, add the following to your AndroidManifest.xml:\n" + + ""; private final ConnectivityManager mConnectivityManager; private final ConnectivityManagerCompat mConnectivityManagerCompat; private final ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver; + private boolean mNoNetworkPermission = false; private String mConnectivity = ""; - public ConnectivityModule(ReactApplicationContext reactContext) { + public NetInfoModule(ReactApplicationContext reactContext) { super(reactContext); mConnectivityManager = (ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -69,11 +82,23 @@ public String getName() { @ReactMethod public void getCurrentConnectivity(Callback successCallback, Callback errorCallback) { + if (mNoNetworkPermission) { + if (errorCallback == null) { + FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE); + return; + } + errorCallback.invoke(MISSING_PERMISSION_MESSAGE); + return; + } successCallback.invoke(createConnectivityEventMap()); } @ReactMethod public void isConnectionMetered(Callback successCallback) { + if (mNoNetworkPermission) { + FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE); + return; + } successCallback.invoke(mConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager)); } @@ -98,15 +123,21 @@ private void updateAndSendConnectionType() { } private String getCurrentConnectionType() { - NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); - if (networkInfo == null || !networkInfo.isConnected()) { - return CONNECTION_TYPE_NONE; - } else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) { - return networkInfo.getTypeName().toUpperCase(); - } else { + try { + NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected()) { + return CONNECTION_TYPE_NONE; + } else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) { + return networkInfo.getTypeName().toUpperCase(); + } else { + return CONNECTION_TYPE_UNKNOWN; + } + } catch (SecurityException e) { + mNoNetworkPermission = true; return CONNECTION_TYPE_UNKNOWN; } } + private void sendConnectivityChangedEvent() { getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) .emit("networkStatusDidChange", createConnectivityEventMap()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 973cfbd84c7d..d6d8e37fbb16 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -20,7 +20,7 @@ import com.facebook.react.modules.fresco.FrescoModule; import com.facebook.react.modules.intent.IntentModule; import com.facebook.react.modules.location.LocationModule; -import com.facebook.react.modules.netinfo.ConnectivityModule; +import com.facebook.react.modules.netinfo.NetInfoModule; import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.toast.ToastModule; @@ -57,7 +57,7 @@ public List createNativeModules(ReactApplicationContext reactConte new IntentModule(reactContext), new LocationModule(reactContext), new NetworkingModule(reactContext), - new ConnectivityModule(reactContext), + new NetInfoModule(reactContext), new WebSocketModule(reactContext), new ToastModule(reactContext)); } From 836ba8fb38e8da0583ff531818222ddc46503364 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Fri, 11 Dec 2015 11:38:19 -0800 Subject: [PATCH 0333/1411] BridgeProfiler: add sync Relay methods Reviewed By: sahrens Differential Revision: D2721549 fb-gh-sync-id: af6a83f8505a83e323af3992d9552654ea644324 --- Libraries/Utilities/Systrace.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Libraries/Utilities/Systrace.js b/Libraries/Utilities/Systrace.js index 826be67a1748..ffd58715d4be 100644 --- a/Libraries/Utilities/Systrace.js +++ b/Libraries/Utilities/Systrace.js @@ -15,7 +15,12 @@ type RelayProfiler = { attachProfileHandler( name: string, handler: (name: string, state?: any) => () => void - ): void + ): void, + + attachAggregateHandler( + name: string, + handler: (name: string, callback: () => void) => void + ): void, }; var GLOBAL = GLOBAL || this; @@ -116,6 +121,12 @@ var Systrace = { Systrace.endAsyncEvent(name, cookie); }; }); + + relayProfiler.attachAggregateHandler('*', (name, callback) => { + Systrace.beginEvent(name); + callback(); + Systrace.endEvent(); + }); }, /* This is not called by default due to perf overhead but it's useful From 2696f9440e456232cfedae65113d21991521c7d8 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 11 Dec 2015 16:35:36 -0800 Subject: [PATCH 0334/1411] enqueueJSCall instead of global dynamic require Summary: BridgeProfiling.setEnabled used a one off eval. Let's use the bridge to do this like everything else. This is already what the Android equivalent is doing. public Reviewed By: tadeuzagallo Differential Revision: D2745059 fb-gh-sync-id: 5b633365b8cfc8abc6b80255e82ef3053ead9b50 --- React/Executors/RCTContextExecutor.m | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 55b0d506c35c..2205a8d4846d 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -417,11 +417,7 @@ - (void)toggleProfilingFlag:(NSNotification *)notification { [self executeBlockOnJavaScriptQueue:^{ BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; - // TODO: Don't use require, go through the normal execution modes instead. #9317773 - NSString *script = [NSString stringWithFormat:@"var p = require('Systrace') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"]; - JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String); - JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL); - JSStringRelease(scriptJSRef); + [_bridge enqueueJSCall:@"Systrace.setEnabled" args:@[enabled ? @YES : @NO]]; }]; } From bd3cb3ae1d1955dc45a5b98df03b885197dec6ae Mon Sep 17 00:00:00 2001 From: Mark Oswald Date: Fri, 11 Dec 2015 20:30:57 -0800 Subject: [PATCH 0335/1411] add quotes to handle pathnames with spaces in it Summary: fixes #3135 Closes https://github.com/facebook/react-native/pull/4369 Reviewed By: svcscm Differential Revision: D2752708 Pulled By: androidtrunkagent fb-gh-sync-id: ada11eecb6bd5b8519e30636d18aa6b8962198aa --- React/React.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 3bc1cb5cf899..c7f7294cc16c 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -608,7 +608,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if nc -w 5 -z localhost 8081 ; then\n if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n exit 2\n fi\nelse\n open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi"; + shellScript = "if nc -w 5 -z localhost 8081 ; then\n if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n exit 2\n fi\nelse\n open \"$SRCROOT/../packager/launchPackager.command\" || echo \"Can't start packager automatically\"\nfi"; }; 142C4F7F1B582EA6001F0B58 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; From e4fe55708972c0b09674e99b4841830339620196 Mon Sep 17 00:00:00 2001 From: Christopher Dro Date: Fri, 11 Dec 2015 21:21:02 -0800 Subject: [PATCH 0336/1411] Fix all warnings for missing keys. Summary: I had to go through each component while debugging for another issue and decided to clean these warnings up along the way. Closes https://github.com/facebook/react-native/pull/4653 Reviewed By: svcscm Differential Revision: D2752740 Pulled By: androidtrunkagent fb-gh-sync-id: 94da8ad693cae04e353f33f540c15214f6f3e7d8 --- Examples/UIExplorer/AnimatedExample.js | 1 + Examples/UIExplorer/AsyncStorageExample.js | 2 +- Examples/UIExplorer/CameraRollExample.ios.js | 4 ++-- .../PullToRefreshViewAndroidExample.android.js | 4 ++-- Examples/UIExplorer/StatusBarIOSExample.js | 6 +++--- Examples/UIExplorer/TextInputExample.ios.js | 2 +- Examples/UIExplorer/TimerExample.js | 15 +++++++++++++-- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/AnimatedExample.js index e417cacbefcc..5f6096338115 100644 --- a/Examples/UIExplorer/AnimatedExample.js +++ b/Examples/UIExplorer/AnimatedExample.js @@ -195,6 +195,7 @@ exports.examples = [ {['Composite', 'Easing', 'Animations!'].map( (text, ii) => ( diff --git a/Examples/UIExplorer/AsyncStorageExample.js b/Examples/UIExplorer/AsyncStorageExample.js index a091c0caaede..a3ca76437474 100644 --- a/Examples/UIExplorer/AsyncStorageExample.js +++ b/Examples/UIExplorer/AsyncStorageExample.js @@ -80,7 +80,7 @@ var BasicStorageExample = React.createClass({ {' '} Messages: - {this.state.messages.map((m) => {m})} + {this.state.messages.map((m) => {m})} ); }, diff --git a/Examples/UIExplorer/CameraRollExample.ios.js b/Examples/UIExplorer/CameraRollExample.ios.js index d783d9d8e243..d1f3e96c909a 100644 --- a/Examples/UIExplorer/CameraRollExample.ios.js +++ b/Examples/UIExplorer/CameraRollExample.ios.js @@ -79,8 +79,8 @@ var CameraRollExample = React.createClass({ var location = asset.node.location.longitude ? JSON.stringify(asset.node.location) : 'Unknown location'; return ( - - + + { - return ; + const rows = this.state.rowData.map((row, ii) => { + return ; }); return ( {['default', 'light-content'].map((style) => - StatusBarIOS.setStyle(style)}> setStyle('{style}') @@ -49,7 +49,7 @@ exports.examples = [{ return ( {['default', 'light-content'].map((style) => - StatusBarIOS.setStyle(style, true)}> setStyle('{style}', true) @@ -65,7 +65,7 @@ exports.examples = [{ return ( {['none', 'fade', 'slide'].map((animation) => - + StatusBarIOS.setHidden(true, animation)}> diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 9efde2ea0aeb..2479e1d61aa3 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -158,7 +158,7 @@ class TokenizedTextExample extends React.Component { //highlight hashtags parts = parts.map((text) => { if (/^#/.test(text)) { - return {text}; + return {text}; } else { return text; } diff --git a/Examples/UIExplorer/TimerExample.js b/Examples/UIExplorer/TimerExample.js index 55272a8220af..51a8f1af557c 100644 --- a/Examples/UIExplorer/TimerExample.js +++ b/Examples/UIExplorer/TimerExample.js @@ -171,9 +171,20 @@ exports.examples = [ } return ( - {timer} + {this.state.showTimer && this._renderTimer()} - {toggleText} + {this.state.showTimer ? 'Unmount timer' : 'Mount new timer'} + + + ); + }, + + _renderTimer: function() { + return ( + + + this.refs.interval.clear() }> + Clear interval ); From e0c724a4c36de8120986dd9491dfdcc34f6b92ce Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Fri, 11 Dec 2015 23:00:48 -0800 Subject: [PATCH 0337/1411] fix lint warnings from DrawerLayoutAndroid Summary: fix 3 lint warnings from DrawerLayoutAndroid.android.js Closes https://github.com/facebook/react-native/pull/4451 Reviewed By: svcscm Differential Revision: D2715067 Pulled By: mkonicek fb-gh-sync-id: 9cd0158087c8533950cdab85fdbfb137fcca20e4 --- .../Components/DrawerAndroid/DrawerLayoutAndroid.android.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 4b209bd41792..5a48ee9aba79 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -13,7 +13,6 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var React = require('React'); var ReactPropTypes = require('ReactPropTypes'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var StyleSheet = require('StyleSheet'); var UIManager = require('UIManager'); var View = require('View'); @@ -21,7 +20,6 @@ var View = require('View'); var DrawerConsts = UIManager.AndroidDrawerLayout.Constants; var dismissKeyboard = require('dismissKeyboard'); -var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); var RK_DRAWER_REF = 'drawerlayout'; From 0ad6f5ebf14c92d99ebdf896fcf22c29a49879c2 Mon Sep 17 00:00:00 2001 From: Alex Ledak Date: Sat, 12 Dec 2015 17:10:43 +0700 Subject: [PATCH 0338/1411] Add 'Thai Tone' to showcase --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index ae6dc2e0959e..1e595b17f205 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -374,6 +374,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/tabtor-math/id1018651199?utm_source=ParentAppLP', author: 'PrazAs Learning Inc.', }, + { + name: 'Thai Tone', + icon: 'http://a5.mzstatic.com/us/r30/Purple2/v4/b1/e6/2b/b1e62b3d-6747-0d0b-2a21-b6ba316a7890/icon175x175.png', + link: 'https://itunes.apple.com/us/app/thai-tone/id1064086189?mt=8', + author: 'Alexey Ledak', + }, { name: 'Tong Xing Wang', icon: 'http://a3.mzstatic.com/us/r30/Purple1/v4/7d/52/a7/7d52a71f-9532-82a5-b92f-87076624fdb2/icon175x175.jpeg', From e6c8848c07c907dd908fefcd0e7b58cc83047d93 Mon Sep 17 00:00:00 2001 From: zjlovezj Date: Sat, 12 Dec 2015 19:21:31 +0800 Subject: [PATCH 0339/1411] fix sample code error by removing the extra chars fix sample code error by removing the extra ',' and ')' --- docs/NativeModulesIOS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md index b50b82a27ae1..ce1bf2e8660e 100644 --- a/docs/NativeModulesIOS.md +++ b/docs/NativeModulesIOS.md @@ -181,8 +181,8 @@ Refactoring the above code to use a promise instead of callbacks looks like this ```objective-c RCT_REMAP_METHOD(findEvents, - resolver:(RCTPromiseResolveBlock)resolve, - rejecter:(RCTPromiseRejectBlock)reject)) + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { NSArray *events = ... if (events) { From 70b695922e784ce7d11930f8645300fcc664324d Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 12 Dec 2015 13:10:36 +0100 Subject: [PATCH 0340/1411] Fix typo --- react-native-cli/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-native-cli/index.js b/react-native-cli/index.js index ccddab423631..91e87000d1b2 100755 --- a/react-native-cli/index.js +++ b/react-native-cli/index.js @@ -138,7 +138,7 @@ function createAfterConfirmation(name, verbose) { var property = { name: 'yesno', - message: 'Directory ' + name + ' already exist. Continue?', + message: 'Directory ' + name + ' already exists. Continue?', validator: /y[es]*|n[o]?/, warning: 'Must respond yes or no', default: 'no' From c60b581327e5f3c8aebb1ebd5ecb5550ba2ffa76 Mon Sep 17 00:00:00 2001 From: Qiao Liang Date: Sat, 12 Dec 2015 09:09:09 -0800 Subject: [PATCH 0341/1411] fix POST/PUT/PATCH has no body redbox, when xhr is used without body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: … by passing a empty body fix #3371 referring to https://github.com/square/okhttp/pull/1559/files Closes https://github.com/facebook/react-native/pull/4518 Reviewed By: svcscm Differential Revision: D2753086 Pulled By: lexs fb-gh-sync-id: 5c486b127b194b29cd0f8a2cb9a1ef19449109e3 --- .../react/modules/network/NetworkingModule.java | 2 +- .../react/modules/network/RequestBodyUtil.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index 1156aa2fd195..728acf7fe99f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -136,7 +136,7 @@ public void sendRequest( requestBuilder.headers(requestHeaders); if (data == null) { - requestBuilder.method(method, null); + requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method)); } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) { if (contentType == null) { onRequestError(requestId, "Payload is set but no content-type header specified"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java index 7ce69c37e2d9..f834120acaf1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java @@ -27,6 +27,7 @@ import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.internal.Util; import okio.BufferedSink; +import okio.ByteString; import okio.Okio; import okio.Source; @@ -112,4 +113,15 @@ public void writeTo(BufferedSink sink) throws IOException { } }; } + + /** + * Creates a empty RequestBody if required by the http method spec, otherwise use null + */ + public static RequestBody getEmptyBody(String method) { + if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH")) { + return RequestBody.create(null, ByteString.EMPTY); + } else { + return null; + } + } } From d2907238456efeb3874c3b48e58d2cff4818c87e Mon Sep 17 00:00:00 2001 From: Billy Lamberta Date: Sat, 12 Dec 2015 13:06:14 -0800 Subject: [PATCH 0342/1411] Add the npm global prefix to PATH. Summary: Related to issue https://github.com/facebook/react-native/issues/4034#issuecomment-164134543. The npm global install path may be set to a non-standard location. `react-native` is assumed to be in `PATH` but doesn't take into account the user's environment so building in Xcode fails: ~~~ react-native bundle --entry-file index.ios.js --platform ios --dev true --bundle-output [...] ../node_modules/react-native/packager/react-native-xcode.sh: line 48: react-native: command not found Command /bin/sh failed with exit code 127 ~~~ This fix uses `npm prefix -g` to get the bin location and adds it to `PATH`. Closes https://github.com/facebook/react-native/pull/4749 Reviewed By: svcscm Differential Revision: D2753215 Pulled By: androidtrunkagent fb-gh-sync-id: 964d1a71ac1bf204545a594a9fa433a7bc367f35 --- packager/react-native-xcode.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packager/react-native-xcode.sh b/packager/react-native-xcode.sh index 5c88fdad1c0e..b0206be779f3 100755 --- a/packager/react-native-xcode.sh +++ b/packager/react-native-xcode.sh @@ -29,6 +29,9 @@ cd .. set -x DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +# npm global install path may be a non-standard location +PATH="$(npm prefix -g)/bin:$PATH" + # Define NVM_DIR and source the nvm.sh setup script [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" From 8de172e92d393d5fcba43b806a69cdb0288b8862 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Sat, 12 Dec 2015 14:19:16 -0800 Subject: [PATCH 0343/1411] Refactor so that mBridge can not be null and can be final Reviewed By: astreet Differential Revision: D2717983 fb-gh-sync-id: 969499eb062d54e0271f910c06c4539e2cb20030 --- .../react/bridge/CatalystInstanceImpl.java | 66 +++++++----------- .../bridge/queue/MessageQueueThread.java | 10 +++ .../bridge/queue/MessageQueueThreadImpl.java | 35 +++++++++- .../common/futures/SimpleSettableFuture.java | 67 ++++++++++++++----- 4 files changed, 122 insertions(+), 56 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 87ae11a2bb27..dd7f89dc7e3c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Collection; +import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -63,7 +64,7 @@ public class CatalystInstanceImpl implements CatalystInstance { private boolean mInitialized = false; // Access from JS thread - private @Nullable ReactBridge mBridge; + private final ReactBridge mBridge; private boolean mJSBundleHasLoaded; private CatalystInstanceImpl( @@ -83,39 +84,34 @@ private CatalystInstanceImpl( mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; mTraceListener = new JSProfilerTraceListener(); - final CountDownLatch initLatch = new CountDownLatch(1); - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge"); - try { - initializeBridge(jsExecutor, jsModulesConfig); - initLatch.countDown(); - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }); - try { - Assertions.assertCondition( - initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS), - "Timed out waiting for bridge to initialize!"); - } catch (InterruptedException e) { - throw new RuntimeException(e); + mBridge = mCatalystQueueConfiguration.getJSQueueThread().callOnQueue( + new Callable() { + @Override + public ReactBridge call() throws Exception { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge"); + try { + return initializeBridge(jsExecutor, jsModulesConfig); + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + }).get(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (Exception t) { + throw new RuntimeException("Failed to initialize bridge", t); } } - private void initializeBridge( + private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor"); + ReactBridge bridge; try { - mBridge = new ReactBridge( + bridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mCatalystQueueConfiguration.getNativeModulesQueueThread()); @@ -125,15 +121,17 @@ private void initializeBridge( Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); try { - mBridge.setGlobalVariable( + bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); - mBridge.setGlobalVariable( + bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false"); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } + + return bridge; } @Override @@ -260,13 +258,11 @@ public void destroy() { } } - if (mBridge != null) { - Systrace.unregisterListener(mTraceListener); - } + Systrace.unregisterListener(mTraceListener); // We can access the Bridge from any thread now because we know either we are on the JS thread // or the JS thread has finished via CatalystQueueConfiguration#destroy() - Assertions.assertNotNull(mBridge).dispose(); + mBridge.dispose(); } @Override @@ -294,8 +290,7 @@ public CatalystQueueConfiguration getCatalystQueueConfiguration() { } @VisibleForTesting - public @Nullable - ReactBridge getBridge() { + public ReactBridge getBridge() { return mBridge; } @@ -341,25 +336,16 @@ public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener l @Override public boolean supportsProfiling() { - if (mBridge == null) { - return false; - } return mBridge.supportsProfiling(); } @Override public void startProfiler(String title) { - if (mBridge == null) { - return; - } mBridge.startProfiler(title); } @Override public void stopProfiler(String title, String filename) { - if (mBridge == null) { - return; - } mBridge.stopProfiler(title, filename); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java index b04285a6f601..36b190a002f7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java @@ -9,6 +9,9 @@ package com.facebook.react.bridge.queue; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + import com.facebook.proguard.annotations.DoNotStrip; /** @@ -23,6 +26,13 @@ public interface MessageQueueThread { @DoNotStrip void runOnQueue(Runnable runnable); + /** + * Runs the given Callable on this Thread. It will be submitted to the end of the event queue even + * if it is being submitted from the same queue Thread. + */ + @DoNotStrip + Future callOnQueue(final Callable callable); + /** * @return whether the current Thread is also the Thread associated with this MessageQueueThread. */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java index 5453e5355638..453351632bc1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java @@ -9,6 +9,10 @@ package com.facebook.react.bridge.queue; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + import android.os.Looper; import com.facebook.common.logging.FLog; @@ -45,6 +49,7 @@ private MessageQueueThreadImpl( * if it is being submitted from the same queue Thread. */ @DoNotStrip + @Override public void runOnQueue(Runnable runnable) { if (mIsFinished) { FLog.w( @@ -55,9 +60,29 @@ public void runOnQueue(Runnable runnable) { mHandler.post(runnable); } + + @DoNotStrip + @Override + public Future callOnQueue(final Callable callable) { + final SimpleSettableFuture future = new SimpleSettableFuture<>(); + runOnQueue( + new Runnable() { + @Override + public void run() { + try { + future.set(callable.call()); + } catch (Exception e) { + future.setException(e); + } + } + }); + return future; + } + /** * @return whether the current Thread is also the Thread associated with this MessageQueueThread. */ + @Override public boolean isOnThread() { return mLooper.getThread() == Thread.currentThread(); } @@ -66,6 +91,7 @@ public boolean isOnThread() { * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an * {@link AssertionError}) if the assertion fails. */ + @Override public void assertIsOnThread() { SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage); } @@ -139,6 +165,13 @@ public void run() { }, "mqt_" + name); bgThread.start(); - return new MessageQueueThreadImpl(name, simpleSettableFuture.get(5000), exceptionHandler); + try { + return new MessageQueueThreadImpl( + name, + simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS), + exceptionHandler); + } catch (Throwable t) { + throw new RuntimeException(t); + } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java index a94a65cbc6e4..23619eccf947 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java @@ -12,29 +12,61 @@ import javax.annotation.Nullable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A super simple Future-like class that can safely notify another Thread when a value is ready. - * Does not support setting errors or canceling. + * Does not support canceling. */ -public class SimpleSettableFuture { - +public class SimpleSettableFuture implements Future { private final CountDownLatch mReadyLatch = new CountDownLatch(1); - private volatile @Nullable T mResult; + private @Nullable T mResult; + private @Nullable Exception mException; /** * Sets the result. If another thread has called {@link #get}, they will immediately receive the - * value. Must only be called once. + * value. set or setException must only be called once. */ public void set(T result) { - if (mReadyLatch.getCount() == 0) { - throw new RuntimeException("Result has already been set!"); - } + checkNotSet(); mResult = result; mReadyLatch.countDown(); } + /** + * Sets the eception. If another thread has called {@link #get}, they will immediately receive the + * exception. set or setException must only be called once. + */ + public void setException(Exception exception) { + checkNotSet(); + mException = exception; + mReadyLatch.countDown(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return mReadyLatch.getCount() == 0; + } + + @Deprecated + @Override + public T get() throws InterruptedException, ExecutionException { + throw new UnsupportedOperationException("Must use a timeout"); + } + /** * Wait up to the timeout time for another Thread to set a value on this future. If a value has * already been set, this method will return immediately. @@ -42,21 +74,26 @@ public void set(T result) { * NB: For simplicity, we catch and wrap InterruptedException. Do NOT use this class if you * are in the 1% of cases where you actually want to handle that. */ - public @Nullable T get(long timeoutMS) { + @Override + public @Nullable T get(long timeout, TimeUnit unit) throws + InterruptedException, ExecutionException, TimeoutException { try { - if (!mReadyLatch.await(timeoutMS, TimeUnit.MILLISECONDS)) { - throw new TimeoutException(); + if (!mReadyLatch.await(timeout, unit)) { + throw new TimeoutException("Timed out waiting for result"); } } catch (InterruptedException e) { throw new RuntimeException(e); } + if (mException != null) { + throw new ExecutionException(mException); + } + return mResult; } - public static class TimeoutException extends RuntimeException { - - public TimeoutException() { - super("Timed out waiting for future"); + private void checkNotSet() { + if (mReadyLatch.getCount() == 0) { + throw new RuntimeException("Result has already been set!"); } } } From 01e291751a35299a8059f6a14173c57000f7f6ac Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Sat, 12 Dec 2015 14:19:31 -0800 Subject: [PATCH 0344/1411] Use callOnQueue() instead of latch Reviewed By: astreet Differential Revision: D2717989 fb-gh-sync-id: 9770e773015838301f6e9520a1ca7a283f647de7 --- .../react/bridge/CatalystInstanceImpl.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index dd7f89dc7e3c..77264028578f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -16,7 +16,6 @@ import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -137,13 +136,11 @@ private ReactBridge initializeBridge( @Override public void runJSBundle() { try { - final CountDownLatch initLatch = new CountDownLatch(1); - mCatalystQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { + mJSBundleHasLoaded = mCatalystQueueConfiguration.getJSQueueThread().callOnQueue( + new Callable() { @Override - public void run() { + public Boolean call() throws Exception { Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); - mJSBundleHasLoaded = true; incrementPendingJSCalls(); @@ -159,14 +156,11 @@ public void run() { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } - initLatch.countDown(); + return true; } - }); - Assertions.assertCondition( - initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS), - "Timed out loading JS!"); - } catch (InterruptedException e) { - throw new RuntimeException(e); + }).get(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (Exception t) { + throw new RuntimeException(t); } } From 2c7409b9ee10d429880e4c684a5b512a004800d9 Mon Sep 17 00:00:00 2001 From: WanderWang Date: Sat, 12 Dec 2015 17:26:22 -0800 Subject: [PATCH 0345/1411] improve exception message when we can't find a file Summary: in ```fastfs.js ``` when ```getFile()``` got a exception it will print ``` Unable to find file with path: null ``` in terminal . It's confused Closes https://github.com/facebook/react-native/pull/4737 Reviewed By: svcscm Differential Revision: D2752888 Pulled By: androidtrunkagent fb-gh-sync-id: a366da1eea27c691248dcb17019f4462a639ea70 --- packager/react-packager/src/DependencyResolver/fastfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js index 17738744b7ab..3a6c3ee1386a 100644 --- a/packager/react-packager/src/DependencyResolver/fastfs.js +++ b/packager/react-packager/src/DependencyResolver/fastfs.js @@ -109,7 +109,7 @@ class Fastfs extends EventEmitter { readFile(filePath) { const file = this._getFile(filePath); if (!file) { - throw new Error(`Unable to find file with path: ${file}`); + throw new Error(`Unable to find file with path: ${filePath}`); } return file.read(); } From de677205ecd922ec787075eacb9c1caec81aa1d3 Mon Sep 17 00:00:00 2001 From: James Ide Date: Sat, 12 Dec 2015 20:30:39 -0800 Subject: [PATCH 0346/1411] [Website] Add PullToRefreshViewAndroid to the web docs --- website/server/extractDocs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 8279a6f8d0e7..cdd69e9a0e37 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -198,6 +198,7 @@ var components = [ '../Libraries/Picker/PickerIOS.ios.js', '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js', '../Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js', + '../Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js', '../Libraries/Components/ScrollView/ScrollView.js', '../Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js', '../Libraries/Components/SliderIOS/SliderIOS.ios.js', From 331fb61376b8e4775ca3d247fc2435901fa2cf76 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Sun, 13 Dec 2015 17:29:18 +0800 Subject: [PATCH 0347/1411] update the way of using module Dimensions --- docs/Animations.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Animations.md b/docs/Animations.md index 84c3f48510fc..0050b3d58576 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -529,7 +529,9 @@ make them customizable, React Native exposes a [NavigatorSceneConfigs](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js) API. ```javascript -var SCREEN_WIDTH = require('Dimensions').get('window').width; +var React = require('react-native'); +var { Dimensions } = React; +var SCREEN_WIDTH = Dimensions.get('window').width; var BaseConfig = Navigator.SceneConfigs.FloatFromRight; var CustomLeftToRightGesture = Object.assign({}, BaseConfig.gestures.pop, { From 9cf2edb45876a6094a54d2412c85341d9949a467 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Sun, 13 Dec 2015 11:57:28 +0000 Subject: [PATCH 0348/1411] Update Releases.md --- Releases.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Releases.md b/Releases.md index 1ed509786e2e..01003c6e2f96 100644 --- a/Releases.md +++ b/Releases.md @@ -31,32 +31,32 @@ Make absolutely sure basic iOS and Android workflow works on master: - Check the artifacts were generated: `ls -al ~/.m2/repository/com/facebook/react/react-native/0.18.0/` should contain: - `react-native-0.18.0-javadoc.jar`, `react-native-0.18.0-sources.jar`, `react-native-0.18.0.aar`, `react-native-0.18.0.pom` - For each of the above also `.asc` file -- In `package.json`, set version to e.g. `0.18.0-rc`. -- In `React.podspec`, set version to e.g. `0.18.0-rc`. -- In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.18.+` -- Publish to sinopia: - - `npm set registry http://localhost:4873/`, check that it worked: `npm config list` will show registry is set to localhost - - In a separate shell, start sinopia. Run `sinopia`. If started successfully it will print: http address - http://localhost:4873/. - - Make sure http://localhost:4873/ shows no old versions - - `npm publish` - - http://localhost:4873/ will show 0.18.0-rc -- Test that everything works: - - `cd /tmp` - - `react-native init Zero12rc` - - `cd Zero12rc` - - Check that `package.json`, `android/app/build.gradle` have correct versions (`^0.18.0-rc`, `com.facebook.react:react-native:0.18.+`) - - `open ios/Zero12rc.xcodeproj` - - Hit the Run button in Xcode. - - Packager should open in a new window, you should see the Welcome to React Native screen, Reload JS, try Chrome debugging - put a breakpoint somewhere in `index.ios.js` and Reload JS, Chrome debugger should stop on the breakpoint (we don't have tests for Chrome debugging) - - Close the packager window, close Xcode - - Start an Android emulator (ideally Genymotion, it's faster and more reliable than Google emulators) - - `react-native run-android` - - Test is the same way as on iOS, including Chrome debugging + - In `package.json`, set version to e.g. `0.18.0-rc`. + - In `React.podspec`, set version to e.g. `0.18.0-rc`. + - In `local-cli/generator-android/templates/src/app/build.gradle` update the dependency to e.g. `com.facebook.react:react-native:0.18.+` + - Publish to sinopia: + - `npm set registry http://localhost:4873/`, check that it worked: `npm config list` will show registry is set to localhost + - In a separate shell, start sinopia. Run `sinopia`. If started successfully it will print: http address - http://localhost:4873/. + - Make sure http://localhost:4873/ shows no old versions + - `npm publish` + - http://localhost:4873/ will show 0.18.0-rc + - Test that everything works: + - `cd /tmp` + - `react-native init Zero12rc` + - `cd Zero12rc` + - Check that `package.json`, `android/app/build.gradle` have correct versions (`^0.18.0-rc`, `com.facebook.react:react-native:0.18.+`) + - `open ios/Zero12rc.xcodeproj` + - Hit the Run button in Xcode. + - Packager should open in a new window, you should see the Welcome to React Native screen, Reload JS, try Chrome debugging - put a breakpoint somewhere in `index.ios.js` and Reload JS, Chrome debugger should stop on the breakpoint (we don't have tests for Chrome debugging) + - Close the packager window, close Xcode + - Start an Android emulator (ideally Genymotion, it's faster and more reliable than Google emulators) + - `react-native run-android` + - Test is the same way as on iOS, including Chrome debugging #### Push to github - Revert the Javadoc change in `ReactAndroid/release.gradle` - - `git commit -am` "[0.18-rc] Bump version numbers" + - `git commit -am "[0.18-rc] Bump version numbers"` - `git push origin 0.18-stable` ## Do a release From a5c0fd3272f0b6526262ceeeccaf270bda4c61ec Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Sun, 13 Dec 2015 12:06:43 +0000 Subject: [PATCH 0349/1411] Update Releases.md --- Releases.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Releases.md b/Releases.md index 01003c6e2f96..162f36ee52bf 100644 --- a/Releases.md +++ b/Releases.md @@ -12,6 +12,11 @@ Future releases: - 0.19.0 - Jan 28 - ... +## Ideas for improvements + +- A lot of these steps could be done by a script +- We could simplify the process quite a bit by publishing the Android binaries to npm. This will increase the size of the npm package by about 3.3MB. To do that: after `installArchives`, move the binaries to somewhere where `npm publish` will pick them up. Then, change the `build.gradle` file(s) of your generated app so that Gradle will pick up the binaries from `node_modules`. + ## Cut a release branch Note: Make sure you replace 0.18 in all the commands below with the version you're releasing :) For example, copy-paste all of this into an editor and replace 0.18. @@ -61,7 +66,7 @@ Make absolutely sure basic iOS and Android workflow works on master: ## Do a release -Publish to Maven Central (Note: We could get rid of this whole section publishing binaries to npm instead): +Publish to Maven Central (Note: **We could get rid of this whole section by publishing binaries to npm instead**): - Log into Sonatype and go to [Staging upload](https://oss.sonatype.org/#staging-upload). You'll need to get permissions for this by filing a ticket explaining you're a core contributor to React Native. [Example ticket](https://issues.sonatype.org/browse/OSSRH-11885). - Select Artifact(s) with a POM (to publish to a local Maven repo for testing run `./gradlew :ReactAndroid:installArchives`) - Add all files: .aar, sources jar, javadoc jar, .asc for everything (including the POM file) From 476b0af510c3d478a5e2f4c74af74fec7c7b17e2 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Sun, 13 Dec 2015 14:14:09 +0000 Subject: [PATCH 0350/1411] Update NewIssueGreeting.md --- bots/NewIssueGreeting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md index 87e421b499eb..8d22aec68d61 100644 --- a/bots/NewIssueGreeting.md +++ b/bots/NewIssueGreeting.md @@ -2,8 +2,8 @@ Hey {author}, thanks for reporting this issue! React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly. -- If this is a feature request or a bug that you would like to be fixed by the team, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing. +- If you **don't know how to do something** or not sure whether some behavior is expected or a bug, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. -- If you don't know how to do something or not sure whether some behavior is expected or a bug, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel. +- If this is a **feature request or a bug** that you would like to be fixed, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing. -- We welcome clear issues and PRs that are ready for in-depth discussion; thank you for your contributions! +- We welcome clear issues and PRs that are ready for in-depth discussion. Please provide **screenshots** where appropriate and always mention the **version** of React Native you're using. Thank you for your contributions! From 2f30acb5bcadc549eb9bb7affe8fa5a87a21d50e Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Sun, 13 Dec 2015 14:33:52 +0000 Subject: [PATCH 0351/1411] Update Releases.md --- Releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Releases.md b/Releases.md index 162f36ee52bf..d3fe4184d6a9 100644 --- a/Releases.md +++ b/Releases.md @@ -15,7 +15,7 @@ Future releases: ## Ideas for improvements - A lot of these steps could be done by a script -- We could simplify the process quite a bit by publishing the Android binaries to npm. This will increase the size of the npm package by about 3.3MB. To do that: after `installArchives`, move the binaries to somewhere where `npm publish` will pick them up. Then, change the `build.gradle` file(s) of your generated app so that Gradle will pick up the binaries from `node_modules`. +- We could simplify the process quite a bit by publishing the Android binaries to npm. This will increase the size of the npm package by about 3.3MB. To do that: after `installArchives`, move the binaries to somewhere where `npm publish` will pick them up. Then, change the `build.gradle` file(s) of your generated app so that Gradle will pick up the binaries from `node_modules`. This might also **fix issues with incompatible versions of JS and Android binaries** (e.g. [#4488](https://github.com/facebook/react-native/issues/4488)). ## Cut a release branch From 1280df0464714b5003c9bff6e907c59f535ac6c4 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Sun, 13 Dec 2015 14:34:57 +0000 Subject: [PATCH 0352/1411] Update Releases.md --- Releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Releases.md b/Releases.md index d3fe4184d6a9..4356b2ecb4d8 100644 --- a/Releases.md +++ b/Releases.md @@ -15,7 +15,7 @@ Future releases: ## Ideas for improvements - A lot of these steps could be done by a script -- We could simplify the process quite a bit by publishing the Android binaries to npm. This will increase the size of the npm package by about 3.3MB. To do that: after `installArchives`, move the binaries to somewhere where `npm publish` will pick them up. Then, change the `build.gradle` file(s) of your generated app so that Gradle will pick up the binaries from `node_modules`. This might also **fix issues with incompatible versions of JS and Android binaries** (e.g. [#4488](https://github.com/facebook/react-native/issues/4488)). +- We could simplify the process quite a bit by publishing the Android binaries to npm. This will increase the size of the npm package by about 3.3MB. To do that: after `installArchives`, move the binaries to somewhere where `npm publish` will pick them up. Then, change the `build.gradle` file(s) of your generated app so that Gradle will pick up the binaries from `node_modules`. This will likely also **fix issues with incompatible versions of JS and Android binaries** (e.g. [#4488](https://github.com/facebook/react-native/issues/4488)). ## Cut a release branch From 4373aa68228054916d55c0956ede6eeacd40241a Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Sun, 13 Dec 2015 11:42:53 -0800 Subject: [PATCH 0353/1411] fix animated lint warnings Summary: fix lint warnings under 'Libraries/Animated' directory Closes https://github.com/facebook/react-native/pull/4448 Reviewed By: svcscm Differential Revision: D2753880 Pulled By: androidtrunkagent fb-gh-sync-id: c6619c636ff67a74e6f063f70526327d756271db --- Libraries/Animated/src/Easing.js | 6 +++--- Libraries/Animated/src/bezier.js | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Libraries/Animated/src/Easing.js b/Libraries/Animated/src/Easing.js index 46c30aed0646..a8685a7182d6 100644 --- a/Libraries/Animated/src/Easing.js +++ b/Libraries/Animated/src/Easing.js @@ -72,14 +72,14 @@ class Easing { static elastic(bounciness: number = 1): (t: number) => number { var p = bounciness * Math.PI; return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p); - }; + } static back(s: number): (t: number) => number { if (s === undefined) { s = 1.70158; } return (t) => t * t * ((s + 1) * t - s); - }; + } static bounce(t: number): number { if (t < 1 / 2.75) { @@ -98,7 +98,7 @@ class Easing { t -= 2.625 / 2.75; return 7.5625 * t * t + 0.984375; - }; + } static bezier( x1: number, diff --git a/Libraries/Animated/src/bezier.js b/Libraries/Animated/src/bezier.js index 11b02f501c89..19cc855a4d95 100644 --- a/Libraries/Animated/src/bezier.js +++ b/Libraries/Animated/src/bezier.js @@ -42,7 +42,7 @@ module.exports = function(x1, y1, x2, y2, epsilon){ var derivativeCurveX = function(t){ var v = 1 - t; - return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2; + return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2; }; return function(t){ @@ -52,24 +52,26 @@ module.exports = function(x1, y1, x2, y2, epsilon){ // First try a few iterations of Newton's method -- normally very fast. for (t2 = x, i = 0; i < 8; i++){ x2 = curveX(t2) - x; - if (Math.abs(x2) < epsilon) return curveY(t2); + if (Math.abs(x2) < epsilon) { return curveY(t2); } d2 = derivativeCurveX(t2); - if (Math.abs(d2) < 1e-6) break; + if (Math.abs(d2) < 1e-6) { break; } t2 = t2 - x2 / d2; } - t0 = 0, t1 = 1, t2 = x; + t0 = 0; + t1 = 1; + t2 = x; - if (t2 < t0) return curveY(t0); - if (t2 > t1) return curveY(t1); + if (t2 < t0) { return curveY(t0); } + if (t2 > t1) { return curveY(t1); } // Fallback to the bisection method for reliability. while (t0 < t1){ x2 = curveX(t2); - if (Math.abs(x2 - x) < epsilon) return curveY(t2); - if (x > x2) t0 = t2; - else t1 = t2; - t2 = (t1 - t0) * .5 + t0; + if (Math.abs(x2 - x) < epsilon) { return curveY(t2); } + if (x > x2) { t0 = t2; } + else { t1 = t2; } + t2 = (t1 - t0) * 0.5 + t0; } // Failure From a1077ba6e9eae571285db9ac561ecaf6550ec49e Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 14 Dec 2015 15:10:00 +0800 Subject: [PATCH 0354/1411] remove the undocumented @provideModules Using relative file path --- docs/NativeModulesAndroid.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index 8ce2ef3d2986..56a8c2f83a6d 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -125,12 +125,7 @@ mReactInstanceManager = ReactInstanceManager.builder() To make it simpler to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of `NativeModules` each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality. ```js -/** - * @providesModule ToastAndroid - */ - 'use strict'; - /** * This exposes the native ToastAndroid module as a JS module. This has a function 'show' * which takes the following parameters: @@ -142,16 +137,12 @@ var { NativeModules } = require('react-native'); module.exports = NativeModules.ToastAndroid; ``` -Save the above code into a file named "ToastAndroid.js". Now, from your other JavaScript file you can call the method like this: +Now, from your other JavaScript file you can call the method like this: ```js -// Suppose this js file is under the same directory as ToastAndroid.js. -// Otherwise change the require path. var ToastAndroid = require('./ToastAndroid'); -ToastAndroid.show('Awesome', ToastAndroid.SHORT); -// Note: We require ToastAndroid without any relative filepath because -// of the @providesModule directive. Using @providesModule is optional. +ToastAndroid.show('Awesome', ToastAndroid.SHORT); ``` ## Beyond Toasts From b8f3730d8da89ab97b58e751018f3a37580a368e Mon Sep 17 00:00:00 2001 From: Dotan Nahum Date: Mon, 14 Dec 2015 13:22:21 +0200 Subject: [PATCH 0355/1411] Adding Due to showcase (Apps) --- website/src/react-native/showcase.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 1e595b17f205..38fbf50b8699 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -422,6 +422,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, + { + name: 'Due', + icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a2/41/5d/a2415d5f-407a-c565-ffb4-4f27f23d8ac2/icon175x175.png', + link: 'https://itunes.apple.com/us/app/due-countdown-reminders-for/id1050909468?mt=8', + author: 'Dotan Nahum', + }, ]; var AppList = React.createClass({ From ff58629640ece8daf872b0ea43cd845b12045d41 Mon Sep 17 00:00:00 2001 From: Dotan Nahum Date: Mon, 14 Dec 2015 14:13:58 +0200 Subject: [PATCH 0356/1411] Alphabetical ordering --- website/src/react-native/showcase.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index 38fbf50b8699..fdd7a73a34a5 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -162,6 +162,12 @@ var apps = [ link: 'https://itunes.apple.com/us/app/dropbot-phone-replacement/id1000855694?mt=8', author: 'Peach Labs', }, + { + name: 'Due', + icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a2/41/5d/a2415d5f-407a-c565-ffb4-4f27f23d8ac2/icon175x175.png', + link: 'https://itunes.apple.com/us/app/due-countdown-reminders-for/id1050909468?mt=8', + author: 'Dotan Nahum', + }, { name: 'Eat or Not', icon: 'http://a3.mzstatic.com/us/r30/Purple5/v4/51/be/34/51be3462-b015-ebf2-11c5-69165b37fadc/icon175x175.jpeg', @@ -422,12 +428,6 @@ var apps = [ link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8', author: 'Melih Mucuk', }, - { - name: 'Due', - icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a2/41/5d/a2415d5f-407a-c565-ffb4-4f27f23d8ac2/icon175x175.png', - link: 'https://itunes.apple.com/us/app/due-countdown-reminders-for/id1050909468?mt=8', - author: 'Dotan Nahum', - }, ]; var AppList = React.createClass({ From b33e091765ccaba164a57b03a6b9e7ce7b836ee7 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 14 Dec 2015 12:55:09 +0000 Subject: [PATCH 0357/1411] Update Releases.md --- Releases.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Releases.md b/Releases.md index 4356b2ecb4d8..6e19390f672a 100644 --- a/Releases.md +++ b/Releases.md @@ -88,6 +88,7 @@ git push --tags - Publish to npm ``` +npm set registry https://registry.npmjs.org/ npm publish # Only when doing a non-rc release: npm dist-tag add react-native@0.18.0 latest From 1d71457934f7086f9438e06908c1c6d06e09b226 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 14 Dec 2015 14:32:25 +0000 Subject: [PATCH 0358/1411] Update AndroidBuildingFromSource.md --- docs/AndroidBuildingFromSource.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index b09c53811208..6a7cd37f1b0a 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -16,7 +16,7 @@ Assuming you have the Android SDK installed, run `android` to open the Android S Make sure you have the following installed: 1. Android SDK version 23 (compileSdkVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle)) -2. SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle)]) +2. SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle)) 3. Android Support Repository >= 17 (for Android Support Library) 4. Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html)) @@ -47,8 +47,7 @@ npm install --save github:facebook/react-native#master Alternatively, you can clone the repo to your `node_modules` directory and run `npm install` inside the cloned repo. - -#### 2. Adding missing dependencies +#### 2. Adding gradle dependencies Add `gradle-download-task` as dependency in `android/build.gradle`: @@ -64,7 +63,6 @@ Add `gradle-download-task` as dependency in `android/build.gradle`: ... ``` - #### 3. Adding the `:ReactAndroid` project Add the `:ReactAndroid` project in `android/settings.gradle`: @@ -73,7 +71,8 @@ Add the `:ReactAndroid` project in `android/settings.gradle`: ... include ':ReactAndroid' -project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid') +project(':ReactAndroid').projectDir = new File( + rootProject.projectDir, '../node_modules/react-native/ReactAndroid') ... ``` @@ -109,7 +108,7 @@ compile(project(':react-native-custom-module')) { ## Additional notes -Building from source can take a long time, especially for the first build, as it needs to download ~200 MB of files and compile JSC etc. Every time you update the `react-native` version from your repo, the build directory may get deleted, and all the files are re-downloaded. To avoid this, you might want to change your build directory path by editing the `~/.gradle/init.gradle ` file: +Building from source can take a long time, especially for the first build, as it needs to download ~200 MB of artifacts and compile the native code. Every time you update the `react-native` version from your repo, the build directory may get deleted, and all the files are re-downloaded. To avoid this, you might want to change your build directory path by editing the `~/.gradle/init.gradle ` file: ```gradle gradle.projectsLoaded { From 2f56c0c90a43288e64bb54d96e55e9067b8e2014 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Mon, 14 Dec 2015 06:23:06 -0800 Subject: [PATCH 0359/1411] Upgrade gradle to 2.9 Summary: public New version of gradle has a better support for zipTree copy task. Since we have a few of those including one for boost library which used to take very long, after upgrading we no longer need 6a656a1. Also seems like many improvements made to gradle since 2.2 made it perform better on incremental builds (around 10% improvement on my laptop). Command used to upgrade gradle version: gradle wrapper --gradle-version 2.9 Some of the plugins require updating as well since the previous versions were incompatible with gradle 2.9. Closes https://github.com/facebook/react-native/pull/4462 Reviewed By: mkonicek Differential Revision: D2754786 Pulled By: mkonicek fb-gh-sync-id: 92c07d29aec6d5b4b2c55205b42b135c4d9479a9 --- ReactAndroid/build.gradle | 16 +++++----------- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 10 +++------- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index ef343d4a7fdf..464a2ad18a34 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -29,17 +29,11 @@ task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { dest new File(downloadsDir, 'boost_1_57_0.zip') } -task prepareBoost(dependsOn: downloadBoost) { - inputs.files downloadBoost.dest, 'src/main/jni/third-party/boost/Android.mk' - outputs.dir "$thirdPartyNdkDir/boost" - doLast { - copy { - from { zipTree(downloadBoost.dest) } - from 'src/main/jni/third-party/boost/Android.mk' - include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk' - into "$thirdPartyNdkDir/boost" - } - } +task prepareBoost(dependsOn: downloadBoost, type: Copy) { + from zipTree(downloadBoost.dest) + from 'src/main/jni/third-party/boost/Android.mk' + include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk' + into "$thirdPartyNdkDir/boost" } task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { diff --git a/build.gradle b/build.gradle index d87035a390a4..eed2379a8759 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ buildscript { mavenLocal() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' - classpath 'de.undercouch:gradle-download-task:1.2' + classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'de.undercouch:gradle-download-task:2.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..941144813d241db74e1bf25b6804c679fbe7f0a3 100644 GIT binary patch delta 46902 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 L1!dUzSL6Q&8czo! delta 43293 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�{>`y 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 +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -114,6 +109,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` From c8108bdbe13590660d7f575d0747e100ea7bb866 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 14 Dec 2015 06:41:45 -0800 Subject: [PATCH 0360/1411] Fixed controlled component on iOS and remove unnecessary code Summary: Closes #4290 `mostRecentEventCount` was always being set after `text` on iOS, so let's be really explicit about the order here as we were doing on Android: always call `setNativeProps` providing the `mostRecentEventCount` before we call `onChange` or `onChangeText`. I also ripped out storing `mostRecentEventCount` in the state, which isn't necessary since we're always doing it through `setNativeProps`. Closes https://github.com/facebook/react-native/pull/4588 Reviewed By: svcscm Differential Revision: D2754565 Pulled By: nicklockwood fb-gh-sync-id: a1401f39b4e19248095517c2a3503cd2af59fa47 --- Examples/UIExplorer/TextInputExample.ios.js | 27 +++++++++++++ Libraries/Components/TextInput/TextInput.js | 45 ++++++++------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 2479e1d61aa3..f8324c51af1d 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -125,6 +125,27 @@ class RewriteExample extends React.Component { } } +class RewriteExampleInvalidCharacters extends React.Component { + constructor(props) { + super(props); + this.state = {text: ''}; + } + render() { + return ( + + { + this.setState({text: text.replace(/\s/g, '')}); + }} + style={styles.default} + value={this.state.text} + /> + + ); + } +} + class TokenizedTextExample extends React.Component { constructor(props) { super(props); @@ -313,6 +334,12 @@ exports.examples = [ return ; } }, + { + title: "Live Re-Write (no spaces allowed)", + render: function() { + return ; + } + }, { title: 'Auto-capitalize', render: function() { diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 9f8f44ed75e9..fe62ec7fb2a7 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -329,12 +329,6 @@ var TextInput = React.createClass({ React.findNodeHandle(this.refs.input); }, - getInitialState: function() { - return { - mostRecentEventCount: 0, - }; - }, - contextTypes: { onFocusRequested: React.PropTypes.func, focusEmitter: React.PropTypes.instanceOf(EventEmitter), @@ -431,7 +425,6 @@ var TextInput = React.createClass({ onSelectionChange={onSelectionChange} onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue} text={this._getText()} - mostRecentEventCount={this.state.mostRecentEventCount} />; } else { for (var propKey in notMultiline) { @@ -460,7 +453,6 @@ var TextInput = React.createClass({ ref="input" {...props} children={children} - mostRecentEventCount={this.state.mostRecentEventCount} onFocus={this._onFocus} onBlur={this._onBlur} onChange={this._onChange} @@ -506,7 +498,7 @@ var TextInput = React.createClass({ textAlign={textAlign} textAlignVertical={textAlignVertical} keyboardType={this.props.keyboardType} - mostRecentEventCount={this.state.mostRecentEventCount} + mostRecentEventCount={0} multiline={this.props.multiline} numberOfLines={this.props.numberOfLines} maxLength={this.props.maxLength} @@ -548,29 +540,24 @@ var TextInput = React.createClass({ }, _onChange: function(event: Event) { - if (Platform.OS === 'android') { - // Android expects the event count to be updated as soon as possible. - this.refs.input.setNativeProps({ - mostRecentEventCount: event.nativeEvent.eventCount, - }); - } + // Make sure to fire the mostRecentEventCount first so it is already set on + // native when the text value is set. + this.refs.input.setNativeProps({ + mostRecentEventCount: event.nativeEvent.eventCount, + }); + var text = event.nativeEvent.text; - var eventCount = event.nativeEvent.eventCount; this.props.onChange && this.props.onChange(event); this.props.onChangeText && this.props.onChangeText(text); - this.setState({mostRecentEventCount: eventCount}, () => { - // NOTE: this doesn't seem to be needed on iOS - keeping for now in case it's required on Android - if (Platform.OS === 'android') { - // This is a controlled component, so make sure to force the native value - // to match. Most usage shouldn't need this, but if it does this will be - // more correct but might flicker a bit and/or cause the cursor to jump. - if (text !== this.props.value && typeof this.props.value === 'string') { - this.refs.input.setNativeProps({ - text: this.props.value, - }); - } - } - }); + + // This is necessary in case native updates the text and JS decides + // that the update should be ignored and we should stick with the value + // that we have in JS. + if (text !== this.props.value && typeof this.props.value === 'string') { + this.refs.input.setNativeProps({ + text: this.props.value, + }); + } }, _onBlur: function(event: Event) { From b6fce4ad52520fc911a6bebaa3424a16d739848d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 14 Dec 2015 06:59:39 -0800 Subject: [PATCH 0361/1411] Prevent the profiler from hooking into stret methods Summary: public In order to handle methods that returns struct in i386 and x86_64 we'd need to implement special methods (like objc_msgSend_stret), but we'll just bail out for now, since there's very few usages. Reviewed By: jspahrsummers Differential Revision: D2754732 fb-gh-sync-id: d3585d244633d918770ef79a52dee9cdf87a53da --- React/Profiler/RCTProfile.m | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index a47931e429c2..cac086c055d1 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -232,7 +232,20 @@ void RCTProfileHookModules(RCTBridge *bridge) for (NSUInteger i = 0; i < methodCount; i++) { Method method = methods[i]; SEL selector = method_getName(method); - if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) { + + /** + * Bail out on struct returns (except arm64) - we don't use it enough + * to justify writing a stret version + */ +#ifdef __arm64__ + BOOL returnsStruct = NO; +#else + const char *typeEncoding = method_getTypeEncoding(method); + // bail out on structs and unions (since they might contain structs) + BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; +#endif + + if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || returnsStruct) { continue; } const char *types = method_getTypeEncoding(method); From 4626de9ba393d975413aae1bcea5ff8afbdbe556 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 14 Dec 2015 09:02:40 -0800 Subject: [PATCH 0362/1411] Update fbjs-haste Reviewed By: bestander Differential Revision: D2754889 fb-gh-sync-id: 5484fbd1079b1434b2853179e663eff30d03b82d --- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 630e9cded87e..6ea6abf56eb9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1756,9 +1756,9 @@ } }, "fbjs-haste": { - "version": "0.3.2", - "from": "fbjs-haste@0.3.2", - "resolved": "https://registry.npmjs.org/fbjs-haste/-/fbjs-haste-0.3.2.tgz" + "version": "0.3.3", + "from": "fbjs-haste@0.3.3", + "resolved": "https://registry.npmjs.org/fbjs-haste/-/fbjs-haste-0.3.3.tgz" }, "fbjs-scripts": { "version": "0.4.0", diff --git a/package.json b/package.json index 54af188aa0b0..3efa72e81bac 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "connect": "^2.8.3", "debug": "^2.2.0", "event-target-shim": "^1.0.5", - "fbjs-haste": "^0.3.2", + "fbjs-haste": "^0.3.3", "fbjs-scripts": "^0.4.0", "graceful-fs": "^4.1.2", "image-size": "^0.3.5", From e35469e1db7f00cc296745b76d9a0024eb80c0de Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Mon, 14 Dec 2015 11:07:36 -0800 Subject: [PATCH 0363/1411] Upgrade fbjs-haste to 0.3.4 Summary: public Attempt to fix https://travis-ci.org/facebook/react-native/builds Steps: cd .../react-native-github npm install fbjs-haste@0.3.4 # manually update npm-shrinkwrap.json (normally done using npm shrinkwrap --dev but the shrinkwrap file is out of date) Reviewed By: zpao Differential Revision: D2755259 fb-gh-sync-id: c5237adcc14e9e21cc09dfad765eff16ddf28484 --- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6ea6abf56eb9..6b0c320f615c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1756,9 +1756,9 @@ } }, "fbjs-haste": { - "version": "0.3.3", - "from": "fbjs-haste@0.3.3", - "resolved": "https://registry.npmjs.org/fbjs-haste/-/fbjs-haste-0.3.3.tgz" + "version": "0.3.4", + "from": "fbjs-haste@0.3.4", + "resolved": "https://registry.npmjs.org/fbjs-haste/-/fbjs-haste-0.3.4.tgz" }, "fbjs-scripts": { "version": "0.4.0", diff --git a/package.json b/package.json index 3efa72e81bac..45913996d365 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "connect": "^2.8.3", "debug": "^2.2.0", "event-target-shim": "^1.0.5", - "fbjs-haste": "^0.3.3", + "fbjs-haste": "^0.3.4", "fbjs-scripts": "^0.4.0", "graceful-fs": "^4.1.2", "image-size": "^0.3.5", From 537598d705b2391d0dfe0b46fe9072d4a62f6ac2 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Mon, 14 Dec 2015 11:12:54 -0800 Subject: [PATCH 0364/1411] Move FileWatcher into node-haste Reviewed By: davidaurelio Differential Revision: D2752711 fb-gh-sync-id: e656a3019b95c7677d5b27e74dc921ef62ba5c83 --- .../FileWatcher/__mocks__/sane.js | 2 +- .../FileWatcher/__tests__/FileWatcher-test.js | 23 +++++++++++++------ .../FileWatcher/index.js | 8 ++----- .../src/Server/__tests__/Server-test.js | 2 +- packager/react-packager/src/Server/index.js | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) rename packager/react-packager/src/{ => DependencyResolver}/FileWatcher/__mocks__/sane.js (98%) rename packager/react-packager/src/{ => DependencyResolver}/FileWatcher/__tests__/FileWatcher-test.js (80%) rename packager/react-packager/src/{ => DependencyResolver}/FileWatcher/index.js (94%) diff --git a/packager/react-packager/src/FileWatcher/__mocks__/sane.js b/packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js similarity index 98% rename from packager/react-packager/src/FileWatcher/__mocks__/sane.js rename to packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js index 9823a930d128..2a36bb39dc3b 100644 --- a/packager/react-packager/src/FileWatcher/__mocks__/sane.js +++ b/packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js @@ -9,5 +9,5 @@ 'use strict'; module.exports = { - WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher') + WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'), }; diff --git a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/packager/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js similarity index 80% rename from packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js rename to packager/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js index 973c4f7804bc..b0d2fd090bb7 100644 --- a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/packager/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js @@ -12,28 +12,37 @@ jest .dontMock('util') .dontMock('events') .dontMock('../') - .dontMock('q') .setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman'); - } + }, }); -var FileWatcher = require('../'); var sane = require('sane'); describe('FileWatcher', function() { var Watcher; + var FileWatcher; + var config; beforeEach(function() { Watcher = sane.WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { callback(); }); + FileWatcher = require('../'); + + config = [{ + dir: 'rootDir', + globs: [ + '**/*.js', + '**/*.json', + ], + }]; }); pit('it should get the watcher instance when ready', function() { - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); return fileWatcher.getWatchers().then(function(watchers) { watchers.forEach(function(watcher) { expect(watcher instanceof Watcher).toBe(true); @@ -46,10 +55,10 @@ describe('FileWatcher', function() { Watcher.prototype.on.mockImplementation(function(type, callback) { cb = callback; }); - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); var handler = jest.genMockFn(); fileWatcher.on('all', handler); - return fileWatcher.getWatchers().then(function(){ + return fileWatcher.getWatchers().then(function() { cb(1, 2, 3, 4); jest.runAllTimers(); expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]); @@ -57,7 +66,7 @@ describe('FileWatcher', function() { }); pit('it should end the watcher', function() { - var fileWatcher = new FileWatcher(['rootDir']); + var fileWatcher = new FileWatcher(config); Watcher.prototype.close.mockImplementation(function(callback) { callback(); }); diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/DependencyResolver/FileWatcher/index.js similarity index 94% rename from packager/react-packager/src/FileWatcher/index.js rename to packager/react-packager/src/DependencyResolver/FileWatcher/index.js index e32c36963ae5..227687e1c820 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/DependencyResolver/FileWatcher/index.js @@ -12,11 +12,10 @@ const EventEmitter = require('events').EventEmitter; const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; -const _ = require('underscore'); const MAX_WAIT_TIME = 25000; -// TODO(amasad): can we use watchman version command instead?r +// TODO(amasad): can we use watchman version command instead? const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -81,13 +80,10 @@ class FileWatcher extends EventEmitter { } static createDummyWatcher() { - const ev = new EventEmitter(); - _.extend(ev, { + return Object.assign(new EventEmitter(), { isWatchman: () => Promise.resolve(false), end: () => Promise.resolve(), }); - - return ev; } } diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index b8fee5fad049..2fdb5e73845d 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -19,7 +19,7 @@ jest.setMock('worker-farm', function() { return () => {}; }) const Promise = require('promise'); var Bundler = require('../../Bundler'); -var FileWatcher = require('../../FileWatcher'); +var FileWatcher = require('../../DependencyResolver/FileWatcher'); var Server = require('../'); var Server = require('../../Server'); var AssetServer = require('../../AssetServer'); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index b2ca6d73b66d..121dcddb87ed 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -10,7 +10,7 @@ const Activity = require('../Activity'); const AssetServer = require('../AssetServer'); -const FileWatcher = require('../FileWatcher'); +const FileWatcher = require('../DependencyResolver/FileWatcher'); const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension'); const Bundler = require('../Bundler'); const Promise = require('promise'); From dbc35b69fa0256920b3b3608c84b6ff844bca4b4 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Mon, 14 Dec 2015 12:04:38 -0800 Subject: [PATCH 0365/1411] Make it possible to set DB size Reviewed By: oli Differential Revision: D2749219 fb-gh-sync-id: 2165ed8a89c48687ad82cd1facf2b875d31ca997 --- .../modules/storage/ReactDatabaseSupplier.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java index 620d231461d9..87f9daf5c147 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java @@ -30,7 +30,6 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final int SLEEP_TIME_MS = 30; - private static final long DEFAULT_MAX_DB_SIZE = 6L * 1024L * 1024L; // 6 MB in bytes static final String TABLE_CATALYST = "catalystLocalStorage"; static final String KEY_COLUMN = "key"; @@ -46,6 +45,7 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { private Context mContext; private @Nullable SQLiteDatabase mDb; + private long mMaximumDatabaseSize = 6L * 1024L * 1024L; // 6 MB in bytes private ReactDatabaseSupplier(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -105,7 +105,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This is a sane limit to protect the user from the app storing too much data in the database. // This also protects the database from filling up the disk cache and becoming malformed // (endTransaction() calls will throw an exception, not rollback, and leave the db malformed). - mDb.setMaximumSize(DEFAULT_MAX_DB_SIZE); + mDb.setMaximumSize(mMaximumDatabaseSize); return true; } @@ -137,6 +137,17 @@ public synchronized void clearAndCloseDatabase() throws RuntimeException { get().delete(TABLE_CATALYST, null, null); } + /** + * Sets the maximum size the database will grow to. The maximum size cannot + * be set below the current size. + */ + public synchronized void setMaximumSize(long size) { + mMaximumDatabaseSize = size; + if (mDb != null) { + mDb.setMaximumSize(mMaximumDatabaseSize); + } + } + private synchronized boolean deleteDatabase() { closeDatabase(); return mContext.deleteDatabase(DATABASE_NAME); From 7028206b973fc4b5019d6819c74e458b42749b3f Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 14 Dec 2015 12:28:34 -0800 Subject: [PATCH 0366/1411] Keep the original console as `global.originalConsole` Summary: If a console exists, keep the original as `global.originalConsole` before overwriting `global.console` with a polyfill. This matches what we do for XHR, fetch, and some other libraries. Closes https://github.com/facebook/react-native/pull/3322 Reviewed By: svcscm Differential Revision: D2755873 Pulled By: androidtrunkagent fb-gh-sync-id: 4c23f807b73b79cfa9fbbd4e2814d76eecabd596 --- .../src/Resolver/polyfills/console.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packager/react-packager/src/Resolver/polyfills/console.js b/packager/react-packager/src/Resolver/polyfills/console.js index 5461602b0ed4..6aa485508065 100644 --- a/packager/react-packager/src/Resolver/polyfills/console.js +++ b/packager/react-packager/src/Resolver/polyfills/console.js @@ -366,8 +366,6 @@ }; function setupConsole(global) { - var originalConsole = global.console; - if (!global.nativeLoggingHook) { return; } @@ -461,7 +459,14 @@ global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); } - global.console = { + // Preserve the original `console` as `originalConsole` + var originalConsole = global.console; + var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); + if (descriptor) { + Object.defineProperty(global, 'originalConsole', descriptor); + } + + var console = { error: getNativeLogFunction(LOG_LEVELS.error), info: getNativeLogFunction(LOG_LEVELS.info), log: getNativeLogFunction(LOG_LEVELS.info), @@ -469,17 +474,19 @@ trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; + descriptor.value = console; + Object.defineProperty(global, 'console', descriptor); // If available, also call the original `console` method since that is // sometimes useful. Ex: on OS X, this will let you see rich output in // the Safari Web Inspector console. if (__DEV__ && originalConsole) { - Object.keys(global.console).forEach(methodName => { - var reactNativeMethod = global.console[methodName]; + Object.keys(console).forEach(methodName => { + var reactNativeMethod = console[methodName]; if (originalConsole[methodName]) { - global.console[methodName] = function() { + console[methodName] = function() { originalConsole[methodName](...arguments); - reactNativeMethod.apply(global.console, arguments); + reactNativeMethod.apply(console, arguments); }; } }); From acf977a4815a65236e91005b4470a4a24b706c1a Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 14 Dec 2015 12:53:22 -0800 Subject: [PATCH 0367/1411] Exposes `setNativeProps` method for PullToRefreshViewAndroid Summary: Keep `PullToRefreshViewAndroid` consistent with other components that allow optimization through `setNativeProps`. Also updates the example to make sure it is working. Closes https://github.com/facebook/react-native/pull/4771 Reviewed By: svcscm Differential Revision: D2756033 Pulled By: androidtrunkagent fb-gh-sync-id: a1f483a3809f01bca06a6a09498f9a89fd65f572 --- Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js index 40842d0a2715..41acc8017db8 100644 --- a/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js +++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js @@ -57,6 +57,10 @@ var PullToRefreshViewAndroid = React.createClass({ return this.refs[NATIVE_REF]; }, + setNativeProps: function(props) { + return this.refs[NATIVE_REF].setNativeProps(props); + }, + render: function() { return ( Date: Mon, 14 Dec 2015 13:07:24 -0800 Subject: [PATCH 0368/1411] Add profiler hooks to views Summary: public The profiler currently only hooks into bridge modules, extend it so we also log method calls on views. Reviewed By: jspahrsummers Differential Revision: D2755213 fb-gh-sync-id: e8ff224eec08898340d05e104772ff1626538bd5 --- React/Profiler/RCTProfile.m | 163 +++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 50 deletions(-) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index cac086c055d1..ab050a286231 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -20,10 +20,12 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTComponentData.h" #import "RCTDefines.h" #import "RCTLog.h" #import "RCTModuleData.h" #import "RCTUtils.h" +#import "RCTUIManager.h" NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; @@ -196,7 +198,12 @@ IMP RCTProfileGetImplementation(id obj, SEL cmd) RCT_EXTERN void RCTProfileTrampolineStart(id, SEL); void RCTProfileTrampolineStart(id self, SEL cmd) { - RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName([self class]), sel_getName(cmd)], nil); + /** + * This call might be during dealloc, so we shouldn't retain the object in the + * block. + */ + Class klass = [self class]; + RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)], nil); } RCT_EXTERN void RCTProfileTrampolineEnd(void); @@ -205,6 +212,85 @@ void RCTProfileTrampolineEnd(void) RCT_PROFILE_END_EVENT(0, @"objc_call,modules,auto", nil); } +static void RCTProfileHookInstance(id instance) +{ + Class moduleClass = object_getClass(instance); + + /** + * We swizzle the instance -class method to return the original class, but + * object_getClass will return the actual class. + * + * If they are different, it means that the object is returning the original + * class, but it's actual class is the proxy subclass we created. + */ + if ([instance class] != moduleClass) { + return; + } + + Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); + + if (!proxyClass) { + proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); + if (proxyClass) { + object_setClass(instance, proxyClass); + } + return; + } + + unsigned int methodCount; + Method *methods = class_copyMethodList(moduleClass, &methodCount); + for (NSUInteger i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + + /** + * Bail out on struct returns (except arm64) - we don't use it enough + * to justify writing a stret version + */ +#ifdef __arm64__ + BOOL returnsStruct = NO; +#else + const char *typeEncoding = method_getTypeEncoding(method); + // bail out on structs and unions (since they might contain structs) + BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; +#endif + + /** + * Avoid hooking into NSObject methods, methods generated by React Native + * and special methods that start `.` (e.g. .cxx_destruct) + */ + if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || sel_getName(selector)[0] == '.' || returnsStruct) { + continue; + } + + const char *types = method_getTypeEncoding(method); + class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); + } + free(methods); + + class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); + + for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { + Method oldImp = class_getInstanceMethod(cls, @selector(class)); + class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); + } + + objc_registerClassPair(proxyClass); + object_setClass(instance, proxyClass); +} + +static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *, NSDictionary *); + +RCT_EXTERN UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSDictionary *props); +UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSDictionary *props) +{ + UIView *view = originalCreateView(self, _cmd, tag, props); + + RCTProfileHookInstance(view); + + return view; +} + void RCTProfileHookModules(RCTBridge *bridge) { #pragma clang diagnostic push @@ -216,54 +302,28 @@ void RCTProfileHookModules(RCTBridge *bridge) for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { [bridge dispatchBlock:^{ - Class moduleClass = moduleData.moduleClass; - Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); - - if (!proxyClass) { - proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); - if (proxyClass) { - object_setClass(moduleData.instance, proxyClass); - } - return; - } - - unsigned int methodCount; - Method *methods = class_copyMethodList(moduleClass, &methodCount); - for (NSUInteger i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - - /** - * Bail out on struct returns (except arm64) - we don't use it enough - * to justify writing a stret version - */ -#ifdef __arm64__ - BOOL returnsStruct = NO; -#else - const char *typeEncoding = method_getTypeEncoding(method); - // bail out on structs and unions (since they might contain structs) - BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; -#endif - - if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || returnsStruct) { - continue; - } - const char *types = method_getTypeEncoding(method); + RCTProfileHookInstance(moduleData.instance); + } queue:moduleData.methodQueue]; + } - class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); - } - free(methods); + dispatch_async(dispatch_get_main_queue(), ^{ + for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { + RCTProfileHookInstance([bridge.uiManager viewForReactTag:view]); + } - class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); + Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:props:)); - for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { - Method oldImp = class_getInstanceMethod(cls, @selector(class)); - class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); - } + if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) { + originalCreateView = (typeof(originalCreateView))method_getImplementation(createView); + method_setImplementation(createView, (IMP)RCTProfileCreateView); + } + }); +} - objc_registerClassPair(proxyClass); - object_setClass(moduleData.instance, proxyClass); - } queue:moduleData.methodQueue]; +static void RCTProfileUnhookInstance(id instance) +{ + if ([instance class] != object_getClass(instance)) { + object_setClass(instance, [instance class]); } } @@ -272,13 +332,16 @@ void RCTProfileUnhookModules(RCTBridge *bridge) dispatch_group_enter(RCTProfileGetUnhookGroup()); for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { - Class proxyClass = object_getClass(moduleData.instance); - if (moduleData.moduleClass != proxyClass) { - object_setClass(moduleData.instance, moduleData.moduleClass); - } + RCTProfileUnhookInstance(moduleData.instance); } - dispatch_group_leave(RCTProfileGetUnhookGroup()); + dispatch_async(dispatch_get_main_queue(), ^{ + for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { + RCTProfileUnhookInstance(view); + } + + dispatch_group_leave(RCTProfileGetUnhookGroup()); + }); } #pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target From e2b035acf2dc55d8410ebcfd47901d4b309f073a Mon Sep 17 00:00:00 2001 From: Nader Dabit Date: Mon, 14 Dec 2015 15:10:46 -0600 Subject: [PATCH 0369/1411] fixed documentation error for native modules docs page --- docs/NativeModulesIOS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md index ce1bf2e8660e..41266aad7b34 100644 --- a/docs/NativeModulesIOS.md +++ b/docs/NativeModulesIOS.md @@ -374,7 +374,7 @@ Then create a private implementation file that will register the required inform @interface RCT_EXTERN_MODULE(CalendarManager, NSObject) -RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSNumber *)date) +RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date) @end ``` From 328bb5907681df5544fa6f69b9a4145ed7e42e76 Mon Sep 17 00:00:00 2001 From: Baris Sencan Date: Mon, 14 Dec 2015 14:11:35 -0800 Subject: [PATCH 0370/1411] [Showcase] Add `Movie Trailers by MovieLaLa` --- website/src/react-native/showcase.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index fdd7a73a34a5..a7019bb7323c 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -284,6 +284,13 @@ var apps = [ link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8', author: 'YuanYi Zhang (@mockingbot)', }, + { + name: 'Movie Trailers by MovieLaLa', + icon: 'https://lh3.googleusercontent.com/16aug4m_6tvJB7QZden9w1SOMqpZgNp7rHqDhltZNvofw1a4V_ojGGXUMPGiK0dDCqzL=w300', + linkAppStore: 'https://itunes.apple.com/us/app/movie-trailers-by-movielala/id1001416601?mt=8', + linkPlayStore: 'https://play.google.com/store/apps/details?id=com.movielala.trailers', + author: 'MovieLaLa' + }, { name: 'Mr. Dapper', icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple4/v4/e8/3f/7c/e83f7cb3-2602-f8e8-de9a-ce0a775a4a14/mzl.hmdjhfai.png', From 7af12a1c5c6dd50bfe9e5149a734687c16d7f347 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 14 Dec 2015 17:19:33 -0800 Subject: [PATCH 0371/1411] [Showcase] Promote MovieLaLa to featured --- website/src/react-native/showcase.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js index a7019bb7323c..7b57b35f4e7c 100644 --- a/website/src/react-native/showcase.js +++ b/website/src/react-native/showcase.js @@ -63,6 +63,13 @@ var featured = [ link: 'https://itunes.apple.com/us/app/lrn-learn-to-code-at-your/id1019622677', author: 'Lrn Labs, Inc', }, + { + name: 'Movie Trailers by MovieLaLa', + icon: 'https://lh3.googleusercontent.com/16aug4m_6tvJB7QZden9w1SOMqpZgNp7rHqDhltZNvofw1a4V_ojGGXUMPGiK0dDCqzL=w300', + linkAppStore: 'https://itunes.apple.com/us/app/movie-trailers-by-movielala/id1001416601?mt=8', + linkPlayStore: 'https://play.google.com/store/apps/details?id=com.movielala.trailers', + author: 'MovieLaLa' + }, { name: 'Myntra', icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/9c/78/df/9c78dfa6-0061-1af2-5026-3e1d5a073c94/icon350x350.png', @@ -284,13 +291,6 @@ var apps = [ link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8', author: 'YuanYi Zhang (@mockingbot)', }, - { - name: 'Movie Trailers by MovieLaLa', - icon: 'https://lh3.googleusercontent.com/16aug4m_6tvJB7QZden9w1SOMqpZgNp7rHqDhltZNvofw1a4V_ojGGXUMPGiK0dDCqzL=w300', - linkAppStore: 'https://itunes.apple.com/us/app/movie-trailers-by-movielala/id1001416601?mt=8', - linkPlayStore: 'https://play.google.com/store/apps/details?id=com.movielala.trailers', - author: 'MovieLaLa' - }, { name: 'Mr. Dapper', icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple4/v4/e8/3f/7c/e83f7cb3-2602-f8e8-de9a-ce0a775a4a14/mzl.hmdjhfai.png', From 934f4de16ecd06dca0c3fc7784142686f37d0bdc Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Tue, 15 Dec 2015 10:10:20 +0800 Subject: [PATCH 0372/1411] geolocation is now opensourced on android --- Libraries/Geolocation/Geolocation.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js index 681a5f77b895..82bc1c9a059c 100644 --- a/Libraries/Geolocation/Geolocation.js +++ b/Libraries/Geolocation/Geolocation.js @@ -43,9 +43,6 @@ type GeoOptions = { * * `` * - * Geolocation support for Android is planned but not yet open sourced. See - * [Known Issues](http://facebook.github.io/react-native/docs/known-issues.html#missing-modules-and-native-views). - * */ var Geolocation = { From 484fe9155b9653959e0bccd9df76b34155b6679b Mon Sep 17 00:00:00 2001 From: zjlovezj Date: Mon, 14 Dec 2015 18:56:32 -0800 Subject: [PATCH 0373/1411] add backfaceVisibility to ImageStylePropTypes Summary: to eliminate to yellow box. ViewStylePropTypes also has this prop. https://github.com/facebook/react-native/blob/0a3694ce48be8991839c42aab2586202a12d43aa/Libraries/Components/View/ViewStylePropTypes.js Closes https://github.com/facebook/react-native/pull/4765 Reviewed By: svcscm Differential Revision: D2754433 Pulled By: androidtrunkagent fb-gh-sync-id: 4bb53213aea6f7d629b31e0e4d4a46ae980ff219 --- Libraries/Image/ImageStylePropTypes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Image/ImageStylePropTypes.js b/Libraries/Image/ImageStylePropTypes.js index c4ccfb57860a..6974bdc55c2a 100644 --- a/Libraries/Image/ImageStylePropTypes.js +++ b/Libraries/Image/ImageStylePropTypes.js @@ -20,6 +20,7 @@ var ImageStylePropTypes = { ...LayoutPropTypes, ...TransformPropTypes, resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)), + backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']), backgroundColor: ReactPropTypes.string, borderColor: ReactPropTypes.string, borderWidth: ReactPropTypes.number, From f946d14fcce8f574d47c8db630b46e1cae9e245a Mon Sep 17 00:00:00 2001 From: Huang Yu Date: Mon, 14 Dec 2015 22:46:15 -0800 Subject: [PATCH 0374/1411] clean four MapView lint warnings Summary: clean four MapView lint warnings. Closes https://github.com/facebook/react-native/pull/4778 Reviewed By: svcscm Differential Revision: D2759161 Pulled By: androidtrunkagent fb-gh-sync-id: c5b028d0400c5084436d636d3bf9a0072fa09571 --- Libraries/Components/MapView/MapView.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 012f5790f75a..b62b122df40f 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -18,12 +18,8 @@ var Platform = require('Platform'); var RCTMap = require('UIManager').RCTMap; var RCTMapConstants = RCTMap && RCTMap.Constants; var React = require('React'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var View = require('View'); -var deepDiffer = require('deepDiffer'); -var insetsDiffer = require('insetsDiffer'); -var merge = require('merge'); var processColor = require('processColor'); var resolveAssetSource = require('resolveAssetSource'); var requireNativeComponent = require('requireNativeComponent'); From 0fe50055c45c9dd0afb22214fb88f2f341fff665 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Tue, 15 Dec 2015 03:11:30 -0800 Subject: [PATCH 0375/1411] Introduce JSC profiler API Summary: Extract JSC profiler API which can now be used even if profiler is unavailable. public Reviewed By: tadeuzagallo Differential Revision: D2749217 fb-gh-sync-id: 1ffa6f37323ea0ddbda3fdacfdf8a9b360185b2e --- React/Executors/RCTContextExecutor.m | 65 ++----------- React/Profiler/RCTJSCProfiler.h | 22 +++++ React/Profiler/RCTJSCProfiler.m | 137 +++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 56 deletions(-) create mode 100644 React/Profiler/RCTJSCProfiler.h create mode 100644 React/Profiler/RCTJSCProfiler.m diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 2205a8d4846d..b0fa841346aa 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -21,25 +21,10 @@ #import "RCTProfile.h" #import "RCTPerformanceLogger.h" #import "RCTUtils.h" - -#ifndef RCT_JSC_PROFILER -#if RCT_DEV -#define RCT_JSC_PROFILER 1 -#else -#define RCT_JSC_PROFILER 0 -#endif -#endif - -#if RCT_JSC_PROFILER -#include +#import "RCTJSCProfiler.h" static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled"; -#ifndef RCT_JSC_PROFILER_DYLIB -#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String] -#endif -#endif - @interface RCTJavaScriptContext : NSObject @property (nonatomic, strong, readonly) JSContext *context; @@ -219,51 +204,19 @@ static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObje static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) { -#if RCT_JSC_PROFILER - void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW); - if (JSCProfiler != NULL) { - void (*nativeProfilerStart)(JSContextRef, const char *) = - (__typeof__(nativeProfilerStart))dlsym(JSCProfiler, "nativeProfilerStart"); - void (*nativeProfilerEnd)(JSContextRef, const char *, const char *) = - (__typeof__(nativeProfilerEnd))dlsym(JSCProfiler, "nativeProfilerEnd"); - - if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) { - void (*nativeProfilerEnableBytecode)(void) = - (__typeof__(nativeProfilerEnableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode"); - - if (nativeProfilerEnableBytecode != NULL) { - nativeProfilerEnableBytecode(); - } - - static BOOL isProfiling = NO; - - if (isProfiling) { - nativeProfilerStart(context, "profile"); - } - - [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { - - if (shouldStart == isProfiling) { - return; - } - - isProfiling = shouldStart; - + if (RCTJSCProfilerIsSupported()) { + [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { + if (shouldStart != RCTJSCProfilerIsProfiling(context)) { if (shouldStart) { - nativeProfilerStart(context, "profile"); + RCTJSCProfilerStart(context); } else { - NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"]; - nativeProfilerEnd(context, "profile", outputFile.UTF8String); - NSData *profileData = [NSData dataWithContentsOfFile:outputFile - options:NSDataReadingMappedIfSafe - error:NULL]; - + NSString *outputFile = RCTJSCProfilerStop(context); + NSData *profileData = [NSData dataWithContentsOfFile:outputFile options:NSDataReadingMappedIfSafe error:NULL]; RCTProfileSendResult(bridge, @"cpu-profile", profileData); } - }]]; - } + } + }]]; } -#endif } #endif diff --git a/React/Profiler/RCTJSCProfiler.h b/React/Profiler/RCTJSCProfiler.h new file mode 100644 index 000000000000..5f8e23b178c2 --- /dev/null +++ b/React/Profiler/RCTJSCProfiler.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "RCTDefines.h" + +/** The API is not thread-safe. */ + +/** The context is not retained. */ +RCT_EXTERN void RCTJSCProfilerStart(JSContextRef ctx); +/** Returns a file path containing the profiler data. */ +RCT_EXTERN NSString *RCTJSCProfilerStop(JSContextRef ctx); + +RCT_EXTERN BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx); +RCT_EXTERN BOOL RCTJSCProfilerIsSupported(void); diff --git a/React/Profiler/RCTJSCProfiler.m b/React/Profiler/RCTJSCProfiler.m new file mode 100644 index 000000000000..f808c1c5f432 --- /dev/null +++ b/React/Profiler/RCTJSCProfiler.m @@ -0,0 +1,137 @@ +/** + * 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 "RCTJSCProfiler.h" +#import "RCTLog.h" +#import + +#ifndef RCT_JSC_PROFILER + #if RCT_DEV + #define RCT_JSC_PROFILER 1 + #else + #define RCT_JSC_PROFILER 0 + #endif +#endif + +#if RCT_JSC_PROFILER + +#include + +#ifndef RCT_JSC_PROFILER_DYLIB + #define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String] +#endif + +static const char *const JSCProfileName = "profile"; + +typedef void (*JSCProfilerStartFunctionType)(JSContextRef, const char *); +typedef void (*JSCProfilerEndFunctionType)(JSContextRef, const char *, const char *); +typedef void (*JSCProfilerEnableFunctionType)(void); + +static NSMutableDictionary *RCTJSCProfilerStateMap; + +static JSCProfilerStartFunctionType RCTNativeProfilerStart = NULL; +static JSCProfilerEndFunctionType RCTNativeProfilerEnd = NULL; + +NS_INLINE NSValue *RCTJSContextRefKey(JSContextRef ref) { + return [NSValue valueWithPointer:ref]; +} + +static void RCTJSCProfilerStateInit() +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + RCTJSCProfilerStateMap = [NSMutableDictionary new]; + + void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW); + + RCTNativeProfilerStart = (JSCProfilerStartFunctionType)dlsym(JSCProfiler, "nativeProfilerStart"); + RCTNativeProfilerEnd = (JSCProfilerEndFunctionType)dlsym(JSCProfiler, "nativeProfilerEnd"); + JSCProfilerEnableFunctionType enableBytecode = (__typeof__(enableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode"); + + if (RCTNativeProfilerStart && RCTNativeProfilerEnd && enableBytecode) { + enableBytecode(); + RCTLogInfo(@"JSC profiler is available."); + } else { + RCTNativeProfilerStart = NULL; + RCTNativeProfilerEnd = NULL; + RCTLogInfo(@"JSC profiler is not supported."); + } + }); +} + +#endif + +void RCTJSCProfilerStart(JSContextRef ctx) +{ +#if RCT_JSC_PROFILER + if (ctx != NULL) { + if (RCTJSCProfilerIsSupported()) { + NSValue *key = RCTJSContextRefKey(ctx); + BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue]; + if (!isProfiling) { + RCTLogInfo(@"Starting JSC profiler for context: %p", ctx); + RCTJSCProfilerStateMap[key] = @YES; + RCTNativeProfilerStart(ctx, JSCProfileName); + } else { + RCTLogWarn(@"Trying to start JSC profiler on a context which is already profiled."); + } + } else { + RCTLogWarn(@"Cannot start JSC profiler as it's not supported."); + } + } else { + RCTLogWarn(@"Trying to start JSC profiler for NULL context."); + } +#endif +} + +NSString *RCTJSCProfilerStop(JSContextRef ctx) +{ + NSString *outputFile = nil; +#if RCT_JSC_PROFILER + if (ctx != NULL) { + RCTJSCProfilerStateInit(); + NSValue *key = RCTJSContextRefKey(ctx); + BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue]; + if (isProfiling) { + NSString *filename = [NSString stringWithFormat:@"cpu_profile_%ld.json", (long)CFAbsoluteTimeGetCurrent()]; + outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + RCTNativeProfilerEnd(ctx, JSCProfileName, outputFile.UTF8String); + RCTLogInfo(@"Stopped JSC profiler for context: %p", ctx); + } else { + RCTLogWarn(@"Trying to stop JSC profiler on a context which is not being profiled."); + } + [RCTJSCProfilerStateMap removeObjectForKey:key]; + } else { + RCTLogWarn(@"Trying to stop JSC profiler for NULL context."); + } +#endif + return outputFile; +} + +BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx) +{ + BOOL isProfiling = NO; +#if RCT_JSC_PROFILER + if (ctx != NULL) { + RCTJSCProfilerStateInit(); + isProfiling = [RCTJSCProfilerStateMap[RCTJSContextRefKey(ctx)] boolValue]; + } +#endif + return isProfiling; +} + +BOOL RCTJSCProfilerIsSupported(void) +{ + BOOL isSupported = NO; +#if RCT_JSC_PROFILER + RCTJSCProfilerStateInit(); + isSupported = (RCTNativeProfilerStart != NULL); +#endif + return isSupported; +} From 2ac0157a24e2a5d77ac9d432ef1f7e51bf9d4167 Mon Sep 17 00:00:00 2001 From: Milen Dzhumerov Date: Tue, 15 Dec 2015 03:57:26 -0800 Subject: [PATCH 0376/1411] Improve error message when profiler data cannot be uploaded Summary: Improve error message when profiling data cannot be sent to the packager public Reviewed By: tadeuzagallo Differential Revision: D2749489 fb-gh-sync-id: 26bd56d05be5f3579e45c2407974dd2b885460fc --- React/Profiler/RCTProfile.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index ab050a286231..a51b099c2592 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -669,7 +669,7 @@ void _RCTProfileEndFlowEvent(NSNumber *flowID) void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data) { if (![bridge.bundleURL.scheme hasPrefix:@"http"]) { - RCTLogError(@"Cannot upload profile information"); + RCTLogWarn(@"Cannot upload profile information because you're not connected to the packager. The profiling data is still saved in the app container."); return; } From c46936a00df96800fb19ec0823a339a1c13c4a09 Mon Sep 17 00:00:00 2001 From: Martin Kralik Date: Tue, 15 Dec 2015 04:48:45 -0800 Subject: [PATCH 0377/1411] updated css-layout and fixed callsites Reviewed By: foghina Differential Revision: D2757965 fb-gh-sync-id: 061ff38ae59783edb36ff66516866e4a22fd6e25 --- .../java/com/facebook/csslayout/CSSNode.java | 10 ++-- .../facebook/csslayout/CachedCSSLayout.java | 3 +- .../com/facebook/csslayout/LayoutEngine.java | 60 +++++++++++++++---- .../main/java/com/facebook/csslayout/README | 2 +- .../com/facebook/csslayout/README.facebook | 2 +- .../progressbar/ProgressBarShadowNode.java | 2 +- .../views/switchview/ReactSwitchManager.java | 2 +- .../react/views/text/ReactTextShadowNode.java | 2 +- .../textinput/ReactTextInputShadowNode.java | 2 +- 9 files changed, 62 insertions(+), 23 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java index a5519e129af8..46827fb92fdd 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<4b95f0548441afa1e91e957a93fa6f0b>> +// @generated SignedSource<<1f520d46cbfddbbea0661a8fb6a00748>> package com.facebook.csslayout; @@ -56,7 +56,7 @@ public static interface MeasureFunction { * * NB: measure is NOT guaranteed to be threadsafe/re-entrant safe! */ - public void measure(CSSNode node, float width, MeasureOutput measureOutput); + public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput); } // VisibleForTesting @@ -128,13 +128,13 @@ public boolean isMeasureDefined() { return mMeasureFunction != null; } - /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width) { + /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width, float height) { if (!isMeasureDefined()) { throw new RuntimeException("Measure function isn't defined!"); } measureOutput.height = CSSConstants.UNDEFINED; measureOutput.width = CSSConstants.UNDEFINED; - Assertions.assertNotNull(mMeasureFunction).measure(this, width, measureOutput); + Assertions.assertNotNull(mMeasureFunction).measure(this, width, height, measureOutput); return measureOutput; } @@ -143,7 +143,7 @@ public boolean isMeasureDefined() { */ public void calculateLayout(CSSLayoutContext layoutContext) { layout.resetResult(); - LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, null); + LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, CSSConstants.UNDEFINED, null); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java index 97ef4886f360..6d631d37c945 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<8276834951a75286a0b6d4a980bc43ce>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -21,4 +21,5 @@ public class CachedCSSLayout extends CSSLayout { public float requestedWidth = CSSConstants.UNDEFINED; public float requestedHeight = CSSConstants.UNDEFINED; public float parentMaxWidth = CSSConstants.UNDEFINED; + public float parentMaxHeight = CSSConstants.UNDEFINED; } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java index 30e14f052f61..711aed2ab2d5 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<> +// @generated SignedSource<> package com.facebook.csslayout; @@ -19,7 +19,7 @@ import static com.facebook.csslayout.CSSLayout.POSITION_TOP; /** - * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float)}. + * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float, float)}. */ public class LayoutEngine { @@ -109,7 +109,7 @@ private static void setDimensionFromStyle(CSSNode node, int axis) { return; } // We only run if there's a width or height defined - if (Float.isNaN(node.style.dimensions[dim[axis]]) || + if (Float.isNaN(node.style.dimensions[dim[axis]]) || node.style.dimensions[dim[axis]] <= 0.0) { return; } @@ -183,7 +183,7 @@ private static boolean isMeasureDefined(CSSNode node) { return node.isMeasureDefined(); } - static boolean needsRelayout(CSSNode node, float parentMaxWidth) { + static boolean needsRelayout(CSSNode node, float parentMaxWidth, float parentMaxHeight) { return node.isDirty() || !FloatUtil.floatsEqual( node.lastLayout.requestedHeight, @@ -191,20 +191,23 @@ static boolean needsRelayout(CSSNode node, float parentMaxWidth) { !FloatUtil.floatsEqual( node.lastLayout.requestedWidth, node.layout.dimensions[DIMENSION_WIDTH]) || - !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth); + !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth) || + !FloatUtil.floatsEqual(node.lastLayout.parentMaxHeight, parentMaxHeight); } /*package*/ static void layoutNode( CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, + float parentMaxHeight, CSSDirection parentDirection) { - if (needsRelayout(node, parentMaxWidth)) { + if (needsRelayout(node, parentMaxWidth, parentMaxHeight)) { node.lastLayout.requestedWidth = node.layout.dimensions[DIMENSION_WIDTH]; node.lastLayout.requestedHeight = node.layout.dimensions[DIMENSION_HEIGHT]; node.lastLayout.parentMaxWidth = parentMaxWidth; + node.lastLayout.parentMaxHeight = parentMaxHeight; - layoutNodeImpl(layoutContext, node, parentMaxWidth, parentDirection); + layoutNodeImpl(layoutContext, node, parentMaxWidth, parentMaxHeight, parentDirection); node.lastLayout.copy(node.layout); } else { node.layout.copy(node.lastLayout); @@ -217,6 +220,7 @@ private static void layoutNodeImpl( CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, + float parentMaxHeight, CSSDirection parentDirection) { for (int i = 0, childCount = node.getChildCount(); i < childCount; i++) { node.getChildAt(i).layout.resetResult(); @@ -251,6 +255,7 @@ private static void layoutNodeImpl( // invocations during the layout calculation. int childCount = node.getChildCount(); float paddingAndBorderAxisResolvedRow = ((node.style.padding.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.border.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis])) + (node.style.padding.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]) + node.style.border.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]))); + float paddingAndBorderAxisColumn = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); if (isMeasureDefined(node)) { boolean isResolvedRowDimDefined = !Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]); @@ -266,6 +271,17 @@ private static void layoutNodeImpl( } width -= paddingAndBorderAxisResolvedRow; + float height = CSSConstants.UNDEFINED; + if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + height = node.style.dimensions[DIMENSION_HEIGHT]; + } else if (!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]])) { + height = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]; + } else { + height = parentMaxHeight - + (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])); + } + height -= ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); + // We only need to give a dimension for the text if we haven't got any // for it computed yet. It can either be from the style attribute or because // the element is flexible. @@ -278,7 +294,8 @@ private static void layoutNodeImpl( MeasureOutput measureDim = node.measure( layoutContext.measureOutput, - width + width, + height ); if (isRowUndefined) { node.layout.dimensions[DIMENSION_WIDTH] = measureDim.width + @@ -286,7 +303,7 @@ private static void layoutNodeImpl( } if (isColumnUndefined) { node.layout.dimensions[DIMENSION_HEIGHT] = measureDim.height + - ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); + paddingAndBorderAxisColumn; } } if (childCount == 0) { @@ -367,6 +384,7 @@ private static void layoutNodeImpl( float crossDim = 0; float maxWidth; + float maxHeight; for (i = startLine; i < childCount; ++i) { child = node.getChildAt(i); child.lineIndex = linesCount; @@ -447,6 +465,8 @@ private static void layoutNodeImpl( } else { maxWidth = CSSConstants.UNDEFINED; + maxHeight = CSSConstants.UNDEFINED; + if (!isMainRowDirection) { if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) { maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] - @@ -456,11 +476,20 @@ private static void layoutNodeImpl( (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) - paddingAndBorderAxisResolvedRow; } + } else { + if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + paddingAndBorderAxisColumn; + } else { + maxHeight = parentMaxHeight - + (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) - + paddingAndBorderAxisColumn; + } } // This is the main recursive call. We layout non flexible children. if (alreadyComputedNextLayout == 0) { - layoutNode(layoutContext, child, maxWidth, direction); + layoutNode(layoutContext, child, maxWidth, maxHeight, direction); } // Absolute positioned elements do not take part of the layout, so we @@ -590,9 +619,18 @@ private static void layoutNodeImpl( (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) - paddingAndBorderAxisResolvedRow; } + maxHeight = CSSConstants.UNDEFINED; + if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + paddingAndBorderAxisColumn; + } else if (isMainRowDirection) { + maxHeight = parentMaxHeight - + (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) - + paddingAndBorderAxisColumn; + } // And we recursively call the layout algorithm for this child - layoutNode(layoutContext, currentFlexChild, maxWidth, direction); + layoutNode(layoutContext, currentFlexChild, maxWidth, maxHeight, direction); child = currentFlexChild; currentFlexChild = currentFlexChild.nextFlexChild; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README index 4916c22183a1..955473ba0b55 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/d3b702e1ad0925f8683ce3039be8e493abbf179b +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/219bdaed15c16bbf7c1f2bab17ad629d04cc4199 There is generated code in: - README (this file) diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook index b7b2a9e90972..8d78b2695779 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/d3b702e1ad0925f8683ce3039be8e493abbf179b +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/219bdaed15c16bbf7c1f2bab17ad629d04cc4199 There is generated code in: - README.facebook (this file) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java index dc49c4aa1327..28309d79b9b9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java @@ -51,7 +51,7 @@ public void setStyle(@Nullable String style) { } @Override - public void measure(CSSNode node, float width, MeasureOutput measureOutput) { + public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { final int style = ReactProgressBarViewManager.getStyleFromString(getStyle()); if (!mMeasured.contains(style)) { ProgressBar progressBar = new ProgressBar(getThemedContext(), null, style); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index 115cc9621cc6..60b71f0eb7ba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -45,7 +45,7 @@ private ReactSwitchShadowNode() { } @Override - public void measure(CSSNode node, float width, MeasureOutput measureOutput) { + public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { if (!mMeasured) { // Create a switch with the default config and measure it; since we don't (currently) // support setting custom switch text, this is fine, as all switches will measure the same diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 1e4286f78751..e6e19ad8cbf7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -194,7 +194,7 @@ protected static final Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode private static final CSSNode.MeasureFunction TEXT_MEASURE_FUNCTION = new CSSNode.MeasureFunction() { @Override - public void measure(CSSNode node, float width, MeasureOutput measureOutput) { + public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic) ReactTextShadowNode reactCSSNode = (ReactTextShadowNode) node; TextPaint textPaint = sTextPaintInstance; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index 47b3f48fdba3..2a37f82f1e4d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -68,7 +68,7 @@ protected void setThemedContext(ThemedReactContext themedContext) { } @Override - public void measure(CSSNode node, float width, MeasureOutput measureOutput) { + public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { // measure() should never be called before setThemedContext() EditText editText = Assertions.assertNotNull(mEditText); From 03a0e4ee2ba144f121f6185d953df642ef103777 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 15 Dec 2015 05:32:24 -0800 Subject: [PATCH 0378/1411] Use JSC block API Summary: public Clean up the `RCTContextExecutor` a little bit by converting the exposed hooks to use the ObjC API. Reviewed By: nicklockwood Differential Revision: D2757363 fb-gh-sync-id: c6f5f53c5c1adb78af1cdb449268b6b3cc9740e8 --- React/Executors/RCTContextExecutor.m | 121 +++++++-------------------- 1 file changed, 31 insertions(+), 90 deletions(-) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index b0fa841346aa..0d2d181def9d 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -102,38 +102,6 @@ @implementation RCTContextExecutor RCT_EXPORT_MODULE() -/** - * The one tiny pure native hook that we implement is a native logging hook. - * You could even argue that this is not necessary - we could plumb logging - * calls through a batched bridge, but having the pure native hook allows - * logging to successfully come through even in the event that a batched bridge - * crashes. - */ - -static JSValueRef RCTNativeLoggingHook(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) -{ - if (argumentCount > 0) { - NSString *message = RCTJSValueToNSString(context, arguments[0], exception); - - RCTLogLevel level = RCTLogLevelInfo; - if (argumentCount > 1) { - level = MAX(level, JSValueToNumber(context, arguments[1], exception)); - } - - _RCTLogJavaScriptInternal(level, message); - } - - return JSValueMakeUndefined(context); -} - -// Do-very-little native hook for testing. -static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception) -{ - static int counter = 0; - counter++; - return JSValueMakeUndefined(context); -} - static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value, JSValueRef *exception) { JSStringRef JSString = JSValueToStringCopy(context, value, exception); @@ -165,43 +133,6 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u #if RCT_DEV -static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) -{ - static int profileCounter = 1; - NSString *profileName; - double tag = 0; - - if (argumentCount > 0) { - if (JSValueIsNumber(context, arguments[0])) { - tag = JSValueToNumber(context, arguments[0], NULL); - } else { - profileName = RCTJSValueToNSString(context, arguments[0], exception); - } - } else { - profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++]; - } - - if (argumentCount > 1 && JSValueIsString(context, arguments[1])) { - profileName = RCTJSValueToNSString(context, arguments[1], exception); - } - - if (profileName) { - RCT_PROFILE_BEGIN_EVENT(tag, profileName, nil); - } - - return JSValueMakeUndefined(context); -} - -static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], JSValueRef *exception) -{ - if (argumentCount > 0) { - double tag = JSValueToNumber(context, arguments[0], exception); - RCT_PROFILE_END_EVENT((uint64_t)tag, @"console", nil); - } - - return JSValueMakeUndefined(context); -} - static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) { if (RCTJSCProfilerIsSupported()) { @@ -298,16 +229,27 @@ - (void)setUp if (!strongSelf.isValid) { return; } + if (!strongSelf->_context) { JSContext *context = [JSContext new]; strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context]; } - [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; - [strongSelf _addNativeHook:RCTNoop withName:"noop"]; __weak RCTBridge *weakBridge = strongSelf->_bridge; + JSContext *context = strongSelf->_context.context; + + context[@"noop"] = ^{}; + + context[@"nativeLoggingHook"] = ^(NSString *message, NSNumber *logLevel) { + RCTLogLevel level = RCTLogLevelInfo; + if (logLevel) { + level = MAX(level, logLevel.integerValue); + } + + _RCTLogJavaScriptInternal(level, message); + }; - strongSelf->_context.context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { + context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { if (!weakSelf.valid) { return nil; } @@ -318,39 +260,47 @@ - (void)setUp return nil; }; - strongSelf->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){ + context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){ if (!weakSelf.valid || !calls) { return; } [weakBridge handleBuffer:calls batchEnded:NO]; }; - strongSelf->_context.context[@"RCTPerformanceNow"] = ^{ + context[@"RCTPerformanceNow"] = ^{ return CACurrentMediaTime() * 1000 * 1000; }; #if RCT_DEV if (RCTProfileIsProfiling()) { - strongSelf->_context.context[@"__RCTProfileIsProfiling"] = @YES; + context[@"__RCTProfileIsProfiling"] = @YES; } CFMutableDictionaryRef cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); - strongSelf->_context.context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + context[@"nativeTraceBeginAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil); CFDictionarySetValue(cookieMap, (const void *)cookie, (const void *)newCookie); - return; }; - strongSelf->_context.context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { + context[@"nativeTraceEndAsyncSection"] = ^(uint64_t tag, NSString *name, NSUInteger cookie) { NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(cookieMap, (const void *)cookie); RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, nil); CFDictionaryRemoveValue(cookieMap, (const void *)cookie); - return; }; - [strongSelf _addNativeHook:RCTNativeTraceBeginSection withName:"nativeTraceBeginSection"]; - [strongSelf _addNativeHook:RCTNativeTraceEndSection withName:"nativeTraceEndSection"]; + context[@"nativeTraceBeginSection"] = ^(NSNumber *tag, NSString *profileName){ + static int profileCounter = 1; + if (!profileName) { + profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++]; + } + + RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, nil); + }; + + context[@"nativeTraceEndSection"] = ^(NSNumber *tag) { + RCT_PROFILE_END_EVENT(tag.longLongValue, @"console", nil); + }; RCTInstallJSCProfiler(_bridge, strongSelf->_context.ctx); @@ -374,15 +324,6 @@ - (void)toggleProfilingFlag:(NSNotification *)notification }]; } -- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name -{ - JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); - - JSStringRef JSName = JSStringCreateWithUTF8CString(name); - JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL); - JSStringRelease(JSName); -} - - (void)invalidate { if (!self.isValid) { From 2d61dfd9c1d40998be3375842c5fd9b3d186fe70 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 15 Dec 2015 05:39:30 -0800 Subject: [PATCH 0379/1411] Replace private bridge categories with private header Summary: public A lot of the core modules have to use private methods in the bridge, specially since the `RCTBatchedBridge` interface is never exposed. That was leading to a lot of different private bridge categories spread across different modules, which makes harder to identify which modules are affected by private API changes. Replace all the categories with a single private header. Reviewed By: nicklockwood Differential Revision: D2757564 fb-gh-sync-id: 793158b9082d542b74a6094ed0db4d5dc3a88f78 --- .../UIExplorerUnitTests/RCTAllocationTests.m | 7 +- .../UIExplorerUnitTests/RCTBridgeTests.m | 10 +-- React/Base/RCTBatchedBridge.m | 10 +-- React/Base/RCTBridge+Private.h | 88 +++++++++++++++++++ React/Base/RCTBridge.m | 31 +------ React/Base/RCTLog.m | 8 +- React/Base/RCTModuleData.m | 7 +- React/Base/RCTModuleMethod.m | 11 +-- React/Base/RCTRootView.m | 8 +- React/Executors/RCTContextExecutor.m | 9 +- React/Modules/RCTDevMenu.m | 9 +- React/Modules/RCTTiming.m | 10 +-- React/Profiler/RCTProfile.m | 8 +- React/React.xcodeproj/project.pbxproj | 2 + 14 files changed, 103 insertions(+), 115 deletions(-) create mode 100644 React/Base/RCTBridge+Private.h diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 239973ca03e8..b90a384e4a76 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -16,6 +16,7 @@ #import #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTContextExecutor.h" #import "RCTModuleMethod.h" #import "RCTRootView.h" @@ -29,12 +30,6 @@ } \ _Pragma("clang diagnostic pop") -@interface RCTBridge (RCTAllocationTests) - -@property (nonatomic, weak) RCTBridge *batchedBridge; - -@end - @interface RCTJavaScriptContext : NSObject @property (nonatomic, assign, readonly) JSGlobalContextRef ctx; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 54a58787e108..1a0afe0bfa49 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -16,19 +16,11 @@ #import #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTBridgeModule.h" #import "RCTJavaScriptExecutor.h" #import "RCTUtils.h" -@interface RCTBridge (Testing) - -@property (nonatomic, strong, readonly) RCTBridge *batchedBridge; - -- (void)handleBuffer:(id)buffer; -- (void)setUp; - -@end - @interface TestExecutor : NSObject @property (nonatomic, readonly, copy) NSMutableDictionary *injectedStuff; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 1999f32b026b..21d35a6e1606 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -11,6 +11,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTBridgeMethod.h" #import "RCTConvert.h" #import "RCTContextExecutor.h" @@ -42,15 +43,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCT_EXTERN NSArray *RCTGetModuleClasses(void); -@interface RCTBridge () - -+ (instancetype)currentBridge; -+ (void)setCurrentBridge:(RCTBridge *)bridge; - -@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; - -@end - @interface RCTBatchedBridge : RCTBridge @property (nonatomic, weak) RCTBridge *parentBridge; diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h new file mode 100644 index 000000000000..bb87cc173f58 --- /dev/null +++ b/React/Base/RCTBridge+Private.h @@ -0,0 +1,88 @@ +/** + * 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 "RCTBridge.h" + +@class RCTModuleData; + +@interface RCTBridge () + ++ (instancetype)currentBridge; ++ (void)setCurrentBridge:(RCTBridge *)bridge; + +/** + * Bridge setup code - creates an instance of RCTBachedBridge. Exposed for + * test only + */ +- (void)setUp; + +/** + * This method is used to invoke a callback that was registered in the + * JavaScript application context. Safe to call from any thread. + */ +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args; + +/** + * This property is mostly used on the main thread, but may be touched from + * a background thread if the RCTBridge happens to deallocate on a background + * thread. Therefore, we want all writes to it to be seen atomically. + */ +@property (atomic, strong) RCTBridge *batchedBridge; + +/** + * The block that creates the modules' instances to be added to the bridge. + * Exposed for the RCTBatchedBridge + */ +@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; + +@end + +@interface RCTBridge (RCTBatchedBridge) + +- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData; + +/** + * Dispatch work to a module's queue - this is also suports the fake RCTJSThread + * queue. Exposed for the RCTProfiler + */ +- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue; + +/** + * Systrace profiler toggling methods exposed for the RCTDevMenu + */ +- (void)startProfiling; +- (void)stopProfiling:(void (^)(NSData *))callback; + +/** + * Executes native calls sent by JavaScript. Exposed for testing purposes only + */ +- (void)handleBuffer:(NSArray *)buffer; + +/** + * Exposed for the RCTContextExecutor for sending native methods called from + * JavaScript in the middle of a batch. + */ +- (void)handleBuffer:(NSArray *)buffer batchEnded:(BOOL)hasEnded; + +/** + * Exposed for the RCTContextExecutor for lazily loading native modules + */ +- (NSArray *)configForModuleName:(NSString *)moduleName; + +/** + * Hook exposed for RCTLog to send logs to JavaScript when not running in JSC + */ +- (void)logMessage:(NSString *)message level:(NSString *)level; + +/** + * Allow super fast, one time, timers to skip the queue and be directly executed + */ +- (void)_immediatelyCallTimer:(NSNumber *)timer; + +@end diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index c0b89be0a22c..1dfac45b339b 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTBridge.h" +#import "RCTBridge+Private.h" #import @@ -24,8 +24,6 @@ NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; -@class RCTBatchedBridge; - @interface RCTBatchedBridge : RCTBridge @property (nonatomic, weak) RCTBridge *parentBridge; @@ -34,17 +32,6 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZ @end -@interface RCTBridge () - -// This property is mostly used on the main thread, but may be touched from -// a background thread if the RCTBridge happens to deallocate on a background -// thread. Therefore, we want all writes to it to be seen atomically. -@property (atomic, strong) RCTBatchedBridge *batchedBridge; - -@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; - -@end - static NSMutableArray *RCTModuleClasses; NSArray *RCTGetModuleClasses(void); NSArray *RCTGetModuleClasses(void) @@ -294,7 +281,7 @@ - (BOOL)isBatchActive - (void)invalidate { - RCTBatchedBridge *batchedBridge = self.batchedBridge; + RCTBatchedBridge *batchedBridge = (RCTBatchedBridge *)self.batchedBridge; self.batchedBridge = nil; if (batchedBridge) { @@ -304,20 +291,6 @@ - (void)invalidate } } -- (void)logMessage:(NSString *)message level:(NSString *)level -{ - [self.batchedBridge logMessage:message level:level]; -} - - -#define RCT_INNER_BRIDGE_ONLY(...) \ -- (void)__VA_ARGS__ \ -{ \ - NSString *errorMessage = [NSString stringWithFormat:@"Called method \"%@\" on top level bridge. \ - This method should oly be called from bridge instance in a bridge module", @(__func__)]; \ - RCTFatal(RCTErrorWithMessage(errorMessage)); \ -} - - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args { [self.batchedBridge enqueueJSCall:moduleDotMethod args:args]; diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index f09aff78b93f..28562bde23d6 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -13,16 +13,10 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTDefines.h" #import "RCTRedBox.h" -@interface RCTBridge () - -+ (RCTBridge *)currentBridge; -- (void)logMessage:(NSString *)message level:(NSString *)level; - -@end - static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack"; const char *RCTLogLevels[] = { diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index d254509a765b..d247f532f3e3 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -10,16 +10,11 @@ #import "RCTModuleData.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTModuleMethod.h" #import "RCTLog.h" #import "RCTUtils.h" -@interface RCTBridge (Private) - -- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData; - -@end - @implementation RCTModuleData { NSString *_queueName; diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 407fed3dab80..8f54f2d9e44a 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -13,6 +13,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTConvert.h" #import "RCTLog.h" #import "RCTParserUtils.h" @@ -36,16 +37,6 @@ - (instancetype)initWithType:(NSString *)type @end -@interface RCTBridge (RCTModuleMethod) - -/** - * This method is used to invoke a callback that was registered in the - * JavaScript application context. Safe to call from any thread. - */ -- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args; - -@end - @implementation RCTModuleMethod { Class _moduleClass; diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 454e2e317768..f53c278b30b3 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -14,7 +14,7 @@ #import #import "RCTAssert.h" -#import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -28,12 +28,6 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification"; -@interface RCTBridge (RCTRootView) - -@property (nonatomic, weak, readonly) RCTBridge *batchedBridge; - -@end - @interface RCTUIManager (RCTRootView) - (NSNumber *)allocateRootTag; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 0d2d181def9d..5d8504fb24bc 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -15,6 +15,7 @@ #import #import "RCTAssert.h" +#import "RCTBridge+Private.h" #import "RCTDefines.h" #import "RCTDevMenu.h" #import "RCTLog.h" @@ -83,14 +84,6 @@ - (void)dealloc @end -// Private bridge interface -@interface RCTBridge (RCTContextExecutor) - -- (void)handleBuffer:(NSArray *)buffer batchEnded:(BOOL)hasEnded; -- (NSArray *)configForModuleName:(NSString *)moduleName; - -@end - @implementation RCTContextExecutor { RCTJavaScriptContext *_context; diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index dd66c900b2cd..5d98f8ad4b01 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -10,7 +10,7 @@ #import "RCTDevMenu.h" #import "RCTAssert.h" -#import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTDefines.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" @@ -22,13 +22,6 @@ #if RCT_DEV -@interface RCTBridge (Profiling) - -- (void)startProfiling; -- (void)stopProfiling:(void (^)(NSData *))callback; - -@end - static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification"; static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu"; diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index fc2d9e716d4e..3aa084d3073f 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -11,18 +11,10 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTLog.h" #import "RCTUtils.h" -@interface RCTBridge (Private) - -/** - * Allow super fast, one time, timers to skip the queue and be directly executed - */ -- (void)_immediatelyCallTimer:(NSNumber *)timer; - -@end - @interface RCTTimer : NSObject @property (nonatomic, strong, readonly) NSDate *target; diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index a51b099c2592..9cc0ae1d9b29 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -20,6 +20,7 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTComponentData.h" #import "RCTDefines.h" #import "RCTLog.h" @@ -32,13 +33,6 @@ #if RCT_DEV -@interface RCTBridge () - -- (void)dispatchBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue; - -@end - #pragma mark - Constants NSString const *RCTProfileTraceEvents = @"traceEvents"; diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index c7f7294cc16c..fa4397f54725 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -221,6 +221,7 @@ 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm64.S"; sourceTree = ""; }; 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86_64.S"; sourceTree = ""; }; 1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeDelegate.h; sourceTree = ""; }; + 14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RCTBridge+Private.h"; sourceTree = ""; }; 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-i386.S"; sourceTree = ""; }; 14BF71811C04795500C97D0C /* RCTMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMacros.h; sourceTree = ""; }; 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = ""; }; @@ -538,6 +539,7 @@ 83CBBA501A601E3B00E9B192 /* RCTUtils.m */, 13BB3D001BECD54500932C10 /* RCTImageSource.h */, 13BB3D011BECD54500932C10 /* RCTImageSource.m */, + 14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */, ); path = Base; sourceTree = ""; From f7edcda5d7670ed71c7c7ddc989cbfc539080a52 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 15 Dec 2015 05:42:45 -0800 Subject: [PATCH 0380/1411] Deprecated RCTDidCreateNativeModules notification Summary: public Thanks to the new lazy initialization system for modules, `RCTDidCreateNativeModules` no longer does what the name implies. Previously, `RCTDidCreateNativeModules` was fired after all native modules had been initialized. Now, it simply fires each time the bridge is reloaded. Modules are created on demand when they are needed, so most of the assumptions about when `RCTDidCreateNativeModules` will fire are now incorrect. This diff deprecates `RCTDidCreateNativeModules`, and adds a new notification, `RCTDidInitializeModuleNotification`, which fires each time a module a new module is instantiated. If you need to access a module at any time you can just call `-[bridge moduleForClass:]` and the module will be instantiated on demand. If you want to access a module *only* after it has already been instantiated, you can use the `RCTDidInitializeModuleNotification` notification. Reviewed By: tadeuzagallo Differential Revision: D2755036 fb-gh-sync-id: 25bab6d5eb6fcd35d43125ac45908035eea01487 --- React/Base/RCTBatchedBridge.m | 48 +++++++++++++++++++++-------------- React/Base/RCTBridge.h | 34 ++++++++++++++++++++----- React/Base/RCTBridge.m | 12 +++++---- React/Base/RCTModuleData.m | 7 +++++ React/Modules/RCTUIManager.m | 4 +-- React/Profiler/RCTProfile.m | 4 +-- 6 files changed, 75 insertions(+), 34 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 21d35a6e1606..9aba7f9ff967 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -29,6 +29,9 @@ [[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \ @"This method must be called on JS thread") +/** + * Used by RKAnalyticsCPULogger + */ NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification"; NSString *const RCTDequeueNotification = @"RCTDequeueNotification"; @@ -90,9 +93,9 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge [RCTBridge setCurrentBridge:self]; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification - object:self - userInfo:@{ @"bridge": self }]; + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTJavaScriptWillStartLoadingNotification + object:_parentBridge userInfo:@{@"bridge": self}]; [self start]; } @@ -208,9 +211,9 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad // Allow testing without a script dispatch_async(dispatch_get_main_queue(), ^{ [self didFinishLoading]; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:_parentBridge - userInfo:@{ @"bridge": self }]; + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTJavaScriptDidLoadNotification + object:_parentBridge userInfo:@{@"bridge": self}]; }); onSourceLoad(nil, nil); } @@ -331,8 +334,15 @@ - (void)initModules } } - [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules - object:self]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTDidCreateNativeModules + object:self userInfo:@{@"bridge": self}]; + +#pragma clang diagnostic pop + RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } @@ -429,10 +439,9 @@ - (void)executeSourceCode:(NSData *)sourceCode // timing issues with RCTRootView dispatch_async(dispatch_get_main_queue(), ^{ [self didFinishLoading]; - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:_parentBridge - userInfo:@{ @"bridge": self }]; + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTJavaScriptDidLoadNotification + object:_parentBridge userInfo:@{@"bridge": self}]; }); }]; } @@ -457,9 +466,10 @@ - (void)stopLoadingWithError:(NSError *)error _loading = NO; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification - object:_parentBridge - userInfo:@{@"bridge": self, @"error": error}]; + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTJavaScriptDidFailToLoadNotification + object:_parentBridge userInfo:@{@"bridge": self, @"error": error}]; + RCTFatal(error); } @@ -715,7 +725,7 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module { RCTAssertJSThread(); - [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:self userInfo:nil]; RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { if (error) { @@ -725,7 +735,7 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module if (!self.isValid) { return; } - [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:self userInfo:nil]; [self handleBuffer:json batchEnded:YES]; }; @@ -740,7 +750,7 @@ - (void)_actuallyInvokeCallback:(NSNumber *)cbID { RCTAssertJSThread(); - [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:self userInfo:nil]; RCTJavaScriptCallback processResponse = ^(id json, NSError *error) { if (error) { @@ -750,7 +760,7 @@ - (void)_actuallyInvokeCallback:(NSNumber *)cbID if (!self.isValid) { return; } - [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:self userInfo:nil]; [self handleBuffer:json batchEnded:YES]; }; diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index f341b75aab7a..a67176e9c72a 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -25,24 +25,28 @@ RCT_EXTERN NSString *const RCTReloadNotification; /** - * This notification fires when the bridge starts loading. + * This notification fires when the bridge starts loading the JS bundle. */ RCT_EXTERN NSString *const RCTJavaScriptWillStartLoadingNotification; /** - * This notification fires when the bridge has finished loading. + * This notification fires when the bridge has finished loading the JS bundle. */ RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification; /** - * This notification fires when the bridge failed to load. + * This notification fires when the bridge failed to load the JS bundle. The + * `error` key can be used to determine the error that occured. */ RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification; /** - * This notification fires when the bridge created all registered native modules + * This notification fires each time a native module is instantiated. The + * `module` key will contain a reference to the newly-created module instance. + * Note that this notification may be fired before the module is available via + * the `[bridge moduleForClass:]` method. */ -RCT_EXTERN NSString *const RCTDidCreateNativeModules; +RCT_EXTERN NSString *const RCTDidInitializeModuleNotification; /** * This block can be used to instantiate modules that require additional @@ -171,10 +175,28 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class); @end /** - * These properties and methods are deprecated and should not be used + * These features are deprecated and should not be used. */ @interface RCTBridge (Deprecated) +/** + * This notification used to fire after all native modules has been initialized, + * but now that native modules are instantiated lazily on demand, its original + * purpose is meaningless. + * + * If you need to access a module, you can do so as soon as the bridge has been + * initialized, by calling `[bridge moduleForClass:]`. If you need to know when + * an individual module has been instantiated, use the `RCTDidInitializeModule` + * notification instead. + */ +RCT_EXTERN NSString *const RCTDidCreateNativeModules +__deprecated_msg("Use RCTDidInitializeModule to observe init of individual modules"); + +/** + * Accessing the modules property causes all modules to be eagerly initialized, + * which stalls the main thread. Use moduleClasses to enumerate through modules + * without causing them to be instantiated. + */ @property (nonatomic, copy, readonly) NSDictionary *modules __deprecated_msg("Use moduleClasses and/or moduleForName: instead"); diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 1dfac45b339b..6ba6b5c36f1a 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -22,7 +22,7 @@ NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; -NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; +NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification"; @interface RCTBatchedBridge : RCTBridge @@ -213,10 +213,10 @@ - (void)bindKeys [commands registerKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification - object:nil - userInfo:nil]; - }]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification + object:nil + userInfo:nil]; + }]; #endif } @@ -305,6 +305,8 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args @implementation RCTBridge(Deprecated) +NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; + - (NSDictionary *)modules { return self.batchedBridge.modules; diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index d247f532f3e3..9b28cd6570c4 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -79,6 +79,7 @@ - (void)cacheImplementedSelectors - (void)setBridgeForInstance:(RCTBridge *)bridge { + _bridge = bridge; if ([_instance respondsToSelector:@selector(bridge)]) { @try { [(id)_instance setValue:bridge forKey:@"bridge"]; @@ -198,6 +199,12 @@ - (dispatch_queue_t)methodQueue } } } + + // Needs to be sent after bridge has been set for all module instances. + // Makes sense to put it here, since the same rules apply for methodQueue. + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTDidInitializeModuleNotification + object:_bridge userInfo:@{@"module": _instance}]; } return _methodQueue; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 46c991e3c3c4..bf1c2ee59fac 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -347,7 +347,7 @@ - (void)registerRootView:(UIView *)rootView [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification object:self - userInfo:@{ RCTUIManagerRootViewKey: rootView }]; + userInfo:@{RCTUIManagerRootViewKey: rootView}]; } - (UIView *)viewForReactTag:(NSNumber *)reactTag @@ -695,7 +695,7 @@ - (void)_removeChildren:(NSArray> *)children [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRemoveRootViewNotification object:uiManager - userInfo:@{ RCTUIManagerRootViewKey: rootView }]; + userInfo:@{RCTUIManagerRootViewKey: rootView}]; }]; } diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index 9cc0ae1d9b29..122dcbe8fad4 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -403,7 +403,7 @@ void RCTProfileInit(RCTBridge *bridge) forMode:NSRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling - object:nil]; + object:bridge]; } void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *)) @@ -417,7 +417,7 @@ void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *)) OSAtomicAnd32Barrier(0, &RCTProfileProfiling); [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling - object:nil]; + object:bridge]; [RCTProfileDisplayLink invalidate]; RCTProfileDisplayLink = nil; From cf94a9ea9599ff1325fb31ba34c3ab913f2bf964 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 15 Dec 2015 05:46:24 -0800 Subject: [PATCH 0381/1411] Avoid copying the JS bundle unnecessarily MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: public When forwarding the unbundle code to the bridge, we’re passing the code by value. This changes the affected method to take it by reference. Reviewed By: astreet Differential Revision: D2759569 fb-gh-sync-id: 508d4f4d56bcbdd5a7df5610cf9040121f8878ef --- ReactAndroid/src/main/jni/react/jni/OnLoad.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 8867b0c9e0d8..b5ad6a11f225 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -623,8 +623,8 @@ static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback, static void executeApplicationScript( const RefPtr& bridge, - const std::string script, - const std::string sourceUri) { + const std::string& script, + const std::string& sourceUri) { try { // Execute the application script and collect/dispatch any native calls that might have occured bridge->executeApplicationScript(script, sourceUri); From eb188c8d98fa08686c6b9335e3dcbefe3f524b7e Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 15 Dec 2015 06:26:51 -0800 Subject: [PATCH 0382/1411] Add deep linking support to IntentAndroid Summary: Add a method to handle URLs registered to the app, ```js IntentAndroid.getInitialURL(url => { if (url) { // do stuff } }); ``` Refer - http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters The API cannot be same as the iOS API (i.e. as a constant), as the activity is not availble at the time of module initialization. Moreover, multiple activties can share the same bridge instance, and the activity itself is not a constant. Hence the initialURL can change. Closes https://github.com/facebook/react-native/pull/4320 Reviewed By: svcscm Differential Revision: D2759667 Pulled By: foghina fb-gh-sync-id: b725231ae1401fa5565d444eee5a30d303e263ae --- .../Intent/IntentAndroid.android.js | 34 ++++++++++++ .../react/modules/intent/IntentModule.java | 54 +++++++++++++++---- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/Libraries/Components/Intent/IntentAndroid.android.js b/Libraries/Components/Intent/IntentAndroid.android.js index 75c620ee99fa..9dad6327bbab 100644 --- a/Libraries/Components/Intent/IntentAndroid.android.js +++ b/Libraries/Components/Intent/IntentAndroid.android.js @@ -16,6 +16,26 @@ var invariant = require('invariant'); /** * `IntentAndroid` gives you a general interface to handle external links. * + * ### Basic Usage + * + * #### Handling deep links + * + * If your app was launched from an external url registered to your app you can + * access and handle it from any component you want with + * + * ``` + * componentDidMount() { + * var url = IntentAndroid.getInitialURL(url => { + * if (url) { + * console.log('Initial url is: ' + url); + * } + * }); + * } + * ``` + * + * NOTE: For instructions on how to add support for deep linking, + * refer [Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters). + * * #### Opening external links * * To start the corresponding activity for a link (web URL, email, contact etc.), call @@ -75,6 +95,20 @@ class IntentAndroid { IntentAndroidModule.canOpenURL(url, callback); } + /** + * If the app launch was triggered by an app link with {@code Intent.ACTION_VIEW}, + * it will give the link url, otherwise it will give `null` + * + * Refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents + */ + static getInitialURL(callback: Function) { + invariant( + typeof callback === 'function', + 'A valid callback function is required' + ); + IntentAndroidModule.getInitialURL(callback); + } + static _validateURL(url: string) { invariant( typeof url === 'string', diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java index e8e6322241b2..ce4681264193 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java @@ -9,15 +9,15 @@ package com.facebook.react.modules.intent; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.JSApplicationIllegalArgumentException; /** * Intent module. Launch other activities or open URLs. @@ -33,25 +33,58 @@ public String getName() { return "IntentAndroid"; } + /** + * Return the URL the activity was started with + * + * @param callback a callback which is called with the initial URL + */ + @ReactMethod + public void getInitialURL(Callback callback) { + try { + Activity currentActivity = getCurrentActivity(); + String initialURL = null; + + if (currentActivity != null) { + Intent intent = currentActivity.getIntent(); + String action = intent.getAction(); + Uri uri = intent.getData(); + + if (Intent.ACTION_VIEW.equals(action) && uri != null) { + initialURL = uri.toString(); + } + } + + callback.invoke(initialURL); + } catch (Exception e) { + throw new JSApplicationIllegalArgumentException( + "Could not get the initial URL : " + e.getMessage()); + } + } + /** * Starts a corresponding external activity for the given URL. * * For example, if the URL is "https://www.facebook.com", the system browser will be opened, * or the "choose application" dialog will be shown. * - * @param URL the URL to open + * @param url the URL to open */ @ReactMethod public void openURL(String url) { if (url == null || url.isEmpty()) { throw new JSApplicationIllegalArgumentException("Invalid URL: " + url); } + try { + Activity currentActivity = getCurrentActivity(); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns - // the ApplicationContext instead of the Activity context. - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getReactApplicationContext().startActivity(intent); + + if (currentActivity != null) { + currentActivity.startActivity(intent); + } else { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getReactApplicationContext().startActivity(intent); + } } catch (Exception e) { throw new JSApplicationIllegalArgumentException( "Could not open URL '" + url + "': " + e.getMessage()); @@ -61,21 +94,22 @@ public void openURL(String url) { /** * Determine whether or not an installed app can handle a given URL. * - * @param URL the URL to open - * @param promise a promise that is always resolved with a boolean argument + * @param url the URL to open + * @param callback a callback that is always called with a boolean argument */ @ReactMethod public void canOpenURL(String url, Callback callback) { if (url == null || url.isEmpty()) { throw new JSApplicationIllegalArgumentException("Invalid URL: " + url); } + try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns // the ApplicationContext instead of the Activity context. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); boolean canOpen = - intent.resolveActivity(this.getReactApplicationContext().getPackageManager()) != null; + intent.resolveActivity(getReactApplicationContext().getPackageManager()) != null; callback.invoke(canOpen); } catch (Exception e) { throw new JSApplicationIllegalArgumentException( From 9f48c004ba866aa24d17242a817929462a091179 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 15 Dec 2015 06:56:07 -0800 Subject: [PATCH 0383/1411] Added setChildren() function Summary: public Most of the time - especially during app startup - when we call UIManager.manageChildren(), we are actually just adding the first set of children to a newly created view. This case is already optimized for in the JS code, by memoizing index arrays at various sizes, but this is not especially efficient since it is still sending an array of indices with each call that could be easily inferred on the native side instead. I've added a hybrid native/JS optimization that improves the performance for this case. It's not a huge win in terms of time saved, but benchmarks show improvements in the ~1% range for several of the app startup metrics. Reviewed By: tadeuzagallo Differential Revision: D2757388 fb-gh-sync-id: 74f0cdbba93af2c04d69b192a8c2cc5cf429fa09 --- .../ReactNative/ReactNativeBaseComponent.js | 32 ++----------------- Libraries/ReactNative/ReactNativeMount.js | 10 ++---- Libraries/Utilities/UIManager.js | 29 +++++++++++++++++ React/Modules/RCTUIManager.m | 27 ++++++++++++++++ 4 files changed, 61 insertions(+), 37 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 266c7b30fbf6..8b60d8682497 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -45,31 +45,6 @@ var ReactNativeBaseComponent = function( this.viewConfig = viewConfig; }; -/** - * Generates and caches arrays of the form: - * - * [0, 1, 2, 3] - * [0, 1, 2, 3, 4] - * [0, 1] - * - * @param {number} size Size of array to generate. - * @return {Array} Array with values that mirror the index. - */ -var cachedIndexArray = function(size) { - var cachedResult = cachedIndexArray._cache[size]; - if (!cachedResult) { - var arr = []; - for (var i = 0; i < size; i++) { - arr[i] = i; - } - cachedIndexArray._cache[size] = arr; - return arr; - } else { - return cachedResult; - } -}; -cachedIndexArray._cache = {}; - /** * Mixin for containers that contain UIViews. NOTE: markup is rendered markup * which is a `viewID` ... see the return value for `mountComponent` ! @@ -104,11 +79,11 @@ ReactNativeBaseComponent.Mixin = { // no children - let's avoid calling out to the native bridge for a large // portion of the children. if (mountImages.length) { - var indexes = cachedIndexArray(mountImages.length); + // TODO: Pool these per platform view class. Reusing the `mountImages` // array would likely be a jit deopt. var createdTags = []; - for (var i = 0; i < mountImages.length; i++) { + for (var i = 0, l = mountImages.length; i < l; i++) { var mountImage = mountImages[i]; var childTag = mountImage.tag; var childID = mountImage.rootNodeID; @@ -122,8 +97,7 @@ ReactNativeBaseComponent.Mixin = { ); createdTags[i] = mountImage.tag; } - UIManager - .manageChildren(containerTag, null, null, createdTags, indexes, null); + UIManager.setChildren(containerTag, createdTags); } }, diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js index 1f6dab51b8a4..2a38c3dbba8c 100644 --- a/Libraries/ReactNative/ReactNativeMount.js +++ b/Libraries/ReactNative/ReactNativeMount.js @@ -188,15 +188,9 @@ var ReactNativeMount = { mountImage.rootNodeID, mountImage.tag ); - var addChildTags = [mountImage.tag]; - var addAtIndices = [0]; - UIManager.manageChildren( + UIManager.setChildren( ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), - null, // moveFromIndices - null, // moveToIndices - addChildTags, - addAtIndices, - null // removeAtIndices + [mountImage.tag] ); } ), diff --git a/Libraries/Utilities/UIManager.js b/Libraries/Utilities/UIManager.js index 3cdfb2e91db1..ff2fc23541de 100644 --- a/Libraries/Utilities/UIManager.js +++ b/Libraries/Utilities/UIManager.js @@ -13,4 +13,33 @@ var UIManager = require('NativeModules').UIManager; +if (!UIManager.setChildren) { + + /** + * Index cache (used by setChildren()) + */ + UIManager._cachedIndexArray = function(size) { + var cachedResult = this._cachedIndexArray._cache[size]; + if (!cachedResult) { + var arr = []; + for (var i = 0; i < size; i++) { + arr[i] = i; + } + this._cachedIndexArray._cache[size] = arr; + return arr; + } else { + return cachedResult; + } + }; + UIManager._cachedIndexArray._cache = {}; + + /** + * Fallback setChildren() implementation for Android + */ + UIManager.setChildren = function(containerTag, createdTags) { + var indexes = this._cachedIndexArray(createdTags.length); + UIManager.manageChildren(containerTag, null, null, createdTags, indexes, null); + } +} + module.exports = UIManager; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index bf1c2ee59fac..3f60b3f53540 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -720,6 +720,33 @@ - (void)_removeChildren:(NSArray> *)children removeAtIndices:removeAtIndices]; } +RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag + reactTags:(NSArray *)reactTags) +{ + RCTSetChildren(containerTag, reactTags, + (NSDictionary> *)_shadowViewRegistry); + + [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + + RCTSetChildren(containerTag, reactTags, + (NSDictionary> *)viewRegistry); + }]; +} + +static void RCTSetChildren(NSNumber *containerTag, + NSArray *reactTags, + NSDictionary> *registry) +{ + id container = registry[containerTag]; + NSInteger index = 0; + for (NSNumber *reactTag in reactTags) { + id view = registry[reactTag]; + if (view) { + [container insertReactSubview:view atIndex:index++]; + } + } +} + RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices From 4cb04315e7de835b52cbdc2ae3072ddaadb9283b Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 15 Dec 2015 07:45:41 -0800 Subject: [PATCH 0384/1411] Unbreak console reassignment on iOS7 Summary: public Unbreaks console assignment on iOS7 introduced in #3322 Reviewed By: alexeylang Differential Revision: D2759689 fb-gh-sync-id: 28cccfdf1123245732fa5ba0337ee8d7bb43c822 --- .../react-packager/src/Resolver/polyfills/console.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/Resolver/polyfills/console.js b/packager/react-packager/src/Resolver/polyfills/console.js index 6aa485508065..a8e5a72f41cf 100644 --- a/packager/react-packager/src/Resolver/polyfills/console.js +++ b/packager/react-packager/src/Resolver/polyfills/console.js @@ -474,8 +474,14 @@ trace: getNativeLogFunction(LOG_LEVELS.trace), table: consoleTablePolyfill }; - descriptor.value = console; - Object.defineProperty(global, 'console', descriptor); + + // don't reassign to the original descriptor. breaks on ios7 + Object.defineProperty(global, 'console', { + value: console, + configurable: descriptor ? descriptor.configurable : true, + enumerable: descriptor ? descriptor.enumerable : true, + writable: descriptor ? descriptor.writable : true, + }); // If available, also call the original `console` method since that is // sometimes useful. Ex: on OS X, this will let you see rich output in From 2f32cccce782c46705826ae4d8990bc680539b0f Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 15 Dec 2015 21:20:40 +0530 Subject: [PATCH 0385/1411] Add docs for 'IntentAndroid' --- docs/KnownIssues.md | 4 ++-- website/server/extractDocs.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index ad112ecce603..8962584f0166 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -51,7 +51,7 @@ There are known cases where the APIs could be made more consistent across iOS an - `` and `` on iOS do a similar thing. We might want to unify them to ``. - `alert()` needs Android support (once the Dialogs module is open sourced) -- It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together. +- It might be possible to bring `LinkingIOS` and `IntentAndroid` closer together. - `ActivityIndicator` could render a native spinning indicator on both platforms (currently this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android). - `ProgressBar` could render a horizontal progress bar on both platforms (on iOS this is `ProgressViewIOS`, on Android it's `ProgressBarAndroid`). @@ -63,7 +63,7 @@ Adding these to your apps should be made simpler. Here's [an example](https://gi ### The `overflow` style property defaults to `hidden` and cannot be changed on Android -This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks. +This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks. Another issue with `overflow: 'hidden'` on Android: a view is not clipped by the parent's `borderRadius` even if the parent has `overflow: 'hidden'` enabled – the corners of the inner view will be visible outside of the rounded corners. This is only on Android; it works as expected on iOS. See a [demo of the bug](https://rnplay.org/apps/BlGjdQ) and the [corresponding issue](https://github.com/facebook/react-native/issues/3198). diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index cdd69e9a0e37..7ae599058a41 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -228,6 +228,7 @@ var apis = [ '../Libraries/Utilities/BackAndroid.android.js', '../Libraries/CameraRoll/CameraRoll.js', '../Libraries/Utilities/Dimensions.js', + '../Libraries/Components/Intent/IntentAndroid.android.js', '../Libraries/Interaction/InteractionManager.js', '../Libraries/LayoutAnimation/LayoutAnimation.js', '../Libraries/LinkingIOS/LinkingIOS.js', From 264ece1f725e4b35d3c7e5a9bfe8230b352c8ea3 Mon Sep 17 00:00:00 2001 From: Justas Brazauskas Date: Tue, 15 Dec 2015 18:13:25 +0200 Subject: [PATCH 0386/1411] Fix typos in ./docs --- docs/Accessibility.md | 2 +- docs/Tutorial.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Accessibility.md b/docs/Accessibility.md index 10ac5884afc6..b968642c8908 100644 --- a/docs/Accessibility.md +++ b/docs/Accessibility.md @@ -81,7 +81,7 @@ Assign this property to a custom function which will be called when someone perf #### accessibilityComponentType (Android) -In some cases, we also want to alert the end user of the type of selected component (ie, that it is a “button”). If we were using native buttons, this would work automatically. Since we are using javascript, we need to provide a bit more context for TalkBack. To do so, you must specify the ‘accessibilityComponentType’ property for any UI component. For instances, we support ‘button’, ‘radiobutton_checked’ and ‘radiobutton_unchecked’ and so on. +In some cases, we also want to alert the end user of the type of selected component (i.e., that it is a “button”). If we were using native buttons, this would work automatically. Since we are using javascript, we need to provide a bit more context for TalkBack. To do so, you must specify the ‘accessibilityComponentType’ property for any UI component. For instances, we support ‘button’, ‘radiobutton_checked’ and ‘radiobutton_unchecked’ and so on. ```javascript Date: Tue, 15 Dec 2015 08:19:53 -0800 Subject: [PATCH 0387/1411] crash gracefully on fatal js errors Reviewed By: AaaChiuuu Differential Revision: D2739392 fb-gh-sync-id: bcc1171c076a4f6425184e7b9ffed204f5ce6f0e --- .../com/facebook/react/ReactInstanceManager.java | 15 ++++++++++++++- .../facebook/react/ReactInstanceManagerImpl.java | 11 +++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index bdffdf261a53..e8793d241d46 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -19,6 +19,7 @@ import android.content.Intent; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; @@ -159,6 +160,7 @@ public static class Builder { protected boolean mUseDeveloperSupport; protected @Nullable LifecycleState mInitialLifecycleState; protected @Nullable UIImplementationProvider mUIImplementationProvider; + protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; protected Builder() { } @@ -242,6 +244,16 @@ public Builder setInitialLifecycleState(LifecycleState initialLifecycleState) { return this; } + /** + * Set the exception handler for all native module calls. If not set, the default + * {@link DevSupportManager} will be used, which shows a redbox in dev mode and rethrows + * (crashes the app) in prod mode. + */ + public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHandler handler) { + mNativeModuleCallExceptionHandler = handler; + return this; + } + /** * Instantiates a new {@link ReactInstanceManagerImpl}. * Before calling {@code build}, the following must be called: @@ -274,7 +286,8 @@ public ReactInstanceManager build() { mUseDeveloperSupport, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), - mUIImplementationProvider); + mUIImplementationProvider, + mNativeModuleCallExceptionHandler); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 7953691c0ad2..e0ce3c97e47b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -35,6 +35,7 @@ import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.JavaScriptModulesConfig; import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NativeModuleRegistry; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.ProxyJavaScriptExecutor; @@ -100,6 +101,7 @@ private volatile boolean mHasStartedCreatingInitialContext = false; private final UIImplementationProvider mUIImplementationProvider; private final MemoryPressureRouter mMemoryPressureRouter; + private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -195,7 +197,8 @@ protected void onPostExecute(ReactApplicationContext reactContext) { boolean useDeveloperSupport, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, - UIImplementationProvider uiImplementationProvider) { + UIImplementationProvider uiImplementationProvider, + NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { initializeSoLoaderIfNecessary(applicationContext); mApplicationContext = applicationContext; @@ -216,6 +219,7 @@ protected void onPostExecute(ReactApplicationContext reactContext) { mLifecycleState = initialLifecycleState; mUIImplementationProvider = uiImplementationProvider; mMemoryPressureRouter = new MemoryPressureRouter(applicationContext); + mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; } @Override @@ -652,13 +656,16 @@ private ReactApplicationContext createReactContext( Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } + NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null + ? mNativeModuleCallExceptionHandler + : mDevSupportManager; CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) - .setNativeModuleCallExceptionHandler(mDevSupportManager); + .setNativeModuleCallExceptionHandler(exceptionHandler); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance"); CatalystInstance catalystInstance; From 7927f1eee5cef05962186a6935ee336dc27206f6 Mon Sep 17 00:00:00 2001 From: Kureev Alexey Date: Tue, 15 Dec 2015 17:55:38 +0100 Subject: [PATCH 0388/1411] Add a section about linking process automation using rnpm --- docs/LinkingLibraries.md | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md index c5cf659cf883..3ffe7b4b8986 100644 --- a/docs/LinkingLibraries.md +++ b/docs/LinkingLibraries.md @@ -24,7 +24,41 @@ error as soon as you try to use the library._ ## Here the few steps to link your libraries that contain native code -### Step 1 +### Automatic linking + +There is a way to link your native dependencies automatically by using [rnpm](http://github.com/rnpm/rnpm): + +#### Step 1 + +Install `rnpm`: +```bash +$ npm install rnpm -g +``` + +**Note:** _`rnpm` requires `node` version 4.1 or higher_ + +#### Step 2 + +Install a library with native dependencies: +```bash +$ npm install library-with-native-dependencies --save +``` + +**Note:** _`--save` or `--save-dev` flag is very important for this step. `rnpm` will link +your libs based on `dependencies` and `devDependencies` in your `package.json` file._ + +#### Step 3 + +Link your native dependencies: +```bash +$ rnpm link +``` + +Done! All libraries with a native dependencies should be successfully linked to your iOS/Android project. + +### Manual linking + +#### Step 1 If the library has native code, there must be a `.xcodeproj` file inside it's folder. @@ -33,7 +67,7 @@ on Xcode); ![](/react-native/img/AddToLibraries.png) -### Step 2 +#### Step 2 Click on your main project file (the one that represents the `.xcodeproj`) select `Build Phases` and drag the static library from the `Products` folder @@ -41,7 +75,7 @@ inside the Library you are importing to `Link Binary With Libraries` ![](/react-native/img/AddToBuildPhases.png) -### Step 3 +#### Step 3 Not every library will need this step, what you need to consider is: From 0e8b207cc34f230eb2cced3981734b522b601817 Mon Sep 17 00:00:00 2001 From: Justas Brazauskas Date: Tue, 15 Dec 2015 09:08:39 -0800 Subject: [PATCH 0389/1411] Bugfix - Typos Summary: Fixed few typos in `./Examples` and `./Libraries` folders. Closes https://github.com/facebook/react-native/pull/4788 Reviewed By: svcscm Differential Revision: D2759918 Pulled By: androidtrunkagent fb-gh-sync-id: d692b5c7f561822353e522f9d4dfde7e60b491cf --- Examples/2048/GameBoard.js | 2 +- .../UIExplorerUnitTests/LayoutSubviewsOrderingTest.m | 4 ++-- .../UIExplorerUnitTests/RCTContextExecutorTests.m | 2 +- Examples/UIExplorer/WebViewExample.js | 2 +- Examples/UIExplorer/XHRExample.android.js | 2 +- Libraries/Animated/examples/demo.html | 2 +- Libraries/Animated/src/AnimatedImplementation.js | 8 ++++---- Libraries/CameraRoll/CameraRoll.js | 2 +- .../DrawerAndroid/DrawerLayoutAndroid.android.js | 2 +- Libraries/Components/MapView/MapView.js | 6 +++--- Libraries/Components/Navigation/NavigatorIOS.ios.js | 2 +- .../ScrollView/RecyclerViewBackedScrollView.android.js | 4 ++-- Libraries/Components/ScrollView/ScrollView.js | 4 ++-- Libraries/Components/SliderIOS/SliderIOS.ios.js | 2 +- Libraries/Components/Switch/Switch.js | 2 +- .../Components/SwitchAndroid/SwitchAndroid.android.js | 2 +- Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js | 4 ++-- .../Components/ToolbarAndroid/ToolbarAndroid.android.js | 2 +- Libraries/Components/Touchable/Position.js | 3 +-- Libraries/Components/Touchable/Touchable.js | 4 ++-- .../Components/ViewPager/ViewPagerAndroid.android.js | 4 ++-- Libraries/CustomComponents/ListView/ListView.js | 2 +- Libraries/CustomComponents/ListView/ListViewDataSource.js | 2 +- Libraries/CustomComponents/Navigator/Navigator.js | 2 +- Libraries/Image/ImageResizeMode.js | 2 +- Libraries/JavaScriptAppEngine/Initialization/SourceMap.js | 6 +++--- Libraries/PushNotificationIOS/PushNotificationIOS.js | 2 +- Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h | 4 ++-- .../RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h | 2 +- Libraries/ReactNative/ReactNativeEventEmitter.js | 8 ++++---- Libraries/ReactNative/ReactNativeReconcileTransaction.js | 4 ++-- Libraries/StyleSheet/processColor.js | 2 +- Libraries/Text/RCTTextField.m | 2 +- Libraries/Text/Text.js | 2 +- Libraries/Utilities/BackAndroid.android.js | 2 +- Libraries/Utilities/CSSVarConfig.js | 2 +- Libraries/Utilities/buildStyleInterpolator.js | 2 +- Libraries/Utilities/mergeIntoFast.js | 2 +- Libraries/Utilities/truncate.js | 2 +- 39 files changed, 57 insertions(+), 58 deletions(-) diff --git a/Examples/2048/GameBoard.js b/Examples/2048/GameBoard.js index 1d7f34085b10..6ed92e6b578d 100644 --- a/Examples/2048/GameBoard.js +++ b/Examples/2048/GameBoard.js @@ -17,7 +17,7 @@ 'use strict'; // NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js -// with no modificiation except to format it for CommonJS and fix lint/flow errors +// with no modification except to format it for CommonJS and fix lint/flow errors var rotateLeft = function (matrix) { var rows = matrix.length; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m index fcd7961143c3..39c0404e0295 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m @@ -14,7 +14,7 @@ @implementation LayoutSubviewsOrderingTest /** * This test exists to insure that didLayoutSubviews is always called immediately after layoutSubviews for a VC:View * pair. In Catalyst we have multiple levels of ViewController containment, and we rely on this ordering - * to insure that layoutGuides are set on RKViewControllers before Views further down in the heirarchy have + * to insure that layoutGuides are set on RKViewControllers before Views further down in the hierarchy have * their layoutSubviews called (and need to use the aforementioned layoutGuides) */ - (void)testLayoutSubviewsOrdering @@ -63,7 +63,7 @@ - (void)testLayoutSubviewsOrdering } }] viewDidLayoutSubviews]; - // setup View heirarchy and force layout + // setup View hierarchy and force layout parentVC.view = parentView; childVC.view = childView; [parentVC addChildViewController:childVC]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index c1b96b13e52b..07d1ebfb00b7 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -98,7 +98,7 @@ - (void)testJavaScriptCallSpeed { /** * Since we almost don't change the RCTContextExecutor logic, and this test is - * very likely to become flaky (specially accross different devices) leave it + * very likely to become flaky (specially across different devices) leave it * to be run manually * * Previous Values: If you change the executor code, you should update this values diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index f8183b12b29b..c8c547f978e9 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -148,7 +148,7 @@ var WebViewExample = React.createClass({ url: url, }); } - // dismiss keyoard + // dismiss keyboard this.refs[TEXT_INPUT_REF].blur(); }, diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js index ad4bbf772a90..0ee51a8e3039 100644 --- a/Examples/UIExplorer/XHRExample.android.js +++ b/Examples/UIExplorer/XHRExample.android.js @@ -30,7 +30,7 @@ var XHRExampleHeaders = require('./XHRExampleHeaders'); var XHRExampleCookies = require('./XHRExampleCookies'); -// TODO t7093728 This is a simlified XHRExample.ios.js. +// TODO t7093728 This is a simplified XHRExample.ios.js. // Once we have Camera roll, Toast, Intent (for opening URLs) // we should make this consistent with iOS. diff --git a/Libraries/Animated/examples/demo.html b/Libraries/Animated/examples/demo.html index bc4c85e1f462..4f95e114fa86 100644 --- a/Libraries/Animated/examples/demo.html +++ b/Libraries/Animated/examples/demo.html @@ -326,7 +326,7 @@

    stopAnimation

    Animated can offload the animation to a different thread (CoreAnimation, CSS transitions, main thread...) and we don't have a good way to know the real value. If you try to query the value then modify it, you are going to be out of sync and the result will look terrible.

    -

    There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in stopAnimation. It will not suffer from beign out of sync since the animation is no longer running.

    +

    There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in stopAnimation. It will not suffer from being out of sync since the animation is no longer running.

    @@ -43,8 +48,8 @@ var Site = React.createClass({
    - - + + React Native @@ -72,7 +77,7 @@ var Site = React.createClass({ fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"); `}} /> -