,
+ RootComponent: ReactClass,
initialProps: P,
rootTag: any
) {
diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js
index ad000f64305183..c3e4f8178044b1 100644
--- a/Libraries/ReactIOS/renderApplication.ios.js
+++ b/Libraries/ReactIOS/renderApplication.ios.js
@@ -18,7 +18,7 @@ var StyleSheet = require('StyleSheet');
var Subscribable = require('Subscribable');
var View = require('View');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var Inspector = __DEV__ ? require('Inspector') : null;
var YellowBox = __DEV__ ? require('YellowBox') : null;
@@ -66,7 +66,7 @@ var AppContainer = React.createClass({
});
function renderApplication(
- RootComponent: ReactClass,
+ RootComponent: ReactClass,
initialProps: P,
rootTag: any
) {
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index 4f8ddaf2e4f435..61993f799fb9be 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -24,7 +24,7 @@ var processColor = require('processColor');
var resolveAssetSource = require('resolveAssetSource');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
/**
* Used to create React components that directly wrap native component
diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js
index 07432eacb58bcd..d284d60ec0aec4 100644
--- a/Libraries/ReactIOS/verifyPropTypes.js
+++ b/Libraries/ReactIOS/verifyPropTypes.js
@@ -13,7 +13,7 @@
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
-export type ComponentInterface = ReactClass | {
+export type ComponentInterface = ReactClass | {
name?: string;
displayName?: string;
propTypes: Object;
diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js
index e2314ba581f999..148a366663efdd 100644
--- a/Libraries/ReactNative/ReactNative.js
+++ b/Libraries/ReactNative/ReactNative.js
@@ -27,9 +27,9 @@ var ReactPropTypes = require('ReactPropTypes');
var ReactUpdates = require('ReactUpdates');
var findNodeHandle = require('findNodeHandle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var onlyChild = require('onlyChild');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
ReactNativeDefaultInjection.inject();
diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js
index 0beef933454f6e..b879b45b6a51a0 100644
--- a/Libraries/ReactNative/ReactNativeBaseComponent.js
+++ b/Libraries/ReactNative/ReactNativeBaseComponent.js
@@ -19,8 +19,8 @@ var ReactMultiChild = require('ReactMultiChild');
var UIManager = require('UIManager');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
var registrationNames = ReactNativeEventEmitter.registrationNames;
var putListener = ReactNativeEventEmitter.putListener;
@@ -115,7 +115,11 @@ ReactNativeBaseComponent.Mixin = {
this._currentElement = nextElement;
if (__DEV__) {
- deepFreezeAndThrowOnMutationInDev(this._currentElement.props);
+ for (var key in this.viewConfig.validAttributes) {
+ if (nextElement.props.hasOwnProperty(key)) {
+ deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
+ }
+ }
}
var updatePayload = ReactNativeAttributePayload.diff(
@@ -181,7 +185,11 @@ ReactNativeBaseComponent.Mixin = {
var tag = ReactNativeTagHandles.allocateTag();
if (__DEV__) {
- deepFreezeAndThrowOnMutationInDev(this._currentElement.props);
+ for (var key in this.viewConfig.validAttributes) {
+ if (this._currentElement.props.hasOwnProperty(key)) {
+ deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
+ }
+ }
}
var updatePayload = ReactNativeAttributePayload.create(
diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js
index 0d55d670c40e14..ee97c2560e61a3 100644
--- a/Libraries/ReactNative/ReactNativeDefaultInjection.js
+++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js
@@ -38,7 +38,7 @@ var ReactUpdates = require('ReactUpdates');
var ResponderEventPlugin = require('ResponderEventPlugin');
var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
// Just to ensure this gets packaged, since its only caller is from Native.
require('RCTEventEmitter');
diff --git a/Libraries/ReactNative/ReactNativeEventEmitter.js b/Libraries/ReactNative/ReactNativeEventEmitter.js
index ac8854b6cb65d2..c7680257aff577 100644
--- a/Libraries/ReactNative/ReactNativeEventEmitter.js
+++ b/Libraries/ReactNative/ReactNativeEventEmitter.js
@@ -18,7 +18,7 @@ var NodeHandle = require('NodeHandle');
var EventConstants = require('EventConstants');
var merge = require('merge');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
var topLevelTypes = EventConstants.topLevelTypes;
diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js
index 2a38c3dbba8c07..cf8c5bb336fdd3 100644
--- a/Libraries/ReactNative/ReactNativeMount.js
+++ b/Libraries/ReactNative/ReactNativeMount.js
@@ -19,7 +19,7 @@ var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
var UIManager = require('UIManager');
-var emptyObject = require('emptyObject');
+var emptyObject = require('fbjs/lib/emptyObject');
var instantiateReactComponent = require('instantiateReactComponent');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
diff --git a/Libraries/ReactNative/ReactNativeTagHandles.js b/Libraries/ReactNative/ReactNativeTagHandles.js
index ab350817c6e90c..5a0de4cda393cc 100644
--- a/Libraries/ReactNative/ReactNativeTagHandles.js
+++ b/Libraries/ReactNative/ReactNativeTagHandles.js
@@ -11,8 +11,8 @@
*/
'use strict';
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
/**
* Keeps track of allocating and associating native "tags" which are numeric,
diff --git a/Libraries/ReactNative/ReactNativeTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js
index e9f67ce59fdb0a..0eb5e0a6b0d0be 100644
--- a/Libraries/ReactNative/ReactNativeTextComponent.js
+++ b/Libraries/ReactNative/ReactNativeTextComponent.js
@@ -15,7 +15,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles');
var UIManager = require('UIManager');
var assign = require('Object.assign');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var ReactNativeTextComponent = function(props) {
// This constructor and its argument is currently used by mocks.
diff --git a/Libraries/ReactNative/createReactNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js
index 61a69a56060524..199e268891a0d2 100644
--- a/Libraries/ReactNative/createReactNativeComponentClass.js
+++ b/Libraries/ReactNative/createReactNativeComponentClass.js
@@ -27,7 +27,7 @@ type ReactNativeBaseComponentViewConfig = {
*/
var createReactNativeComponentClass = function(
viewConfig: ReactNativeBaseComponentViewConfig
-): ReactClass {
+): ReactClass {
var Constructor = function(element) {
this._currentElement = element;
diff --git a/Libraries/ReactNative/findNodeHandle.js b/Libraries/ReactNative/findNodeHandle.js
index 37c772760c503f..dd3191b82d9210 100644
--- a/Libraries/ReactNative/findNodeHandle.js
+++ b/Libraries/ReactNative/findNodeHandle.js
@@ -16,8 +16,8 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
/**
* ReactNative vs ReactWeb
diff --git a/Libraries/Sample/Sample.android.js b/Libraries/Sample/Sample.android.js
index 2024dbdaeafc80..1a66fb9bf57506 100644
--- a/Libraries/Sample/Sample.android.js
+++ b/Libraries/Sample/Sample.android.js
@@ -6,7 +6,7 @@
*/
'use strict';
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
var Sample = {
test: function() {
diff --git a/Libraries/Settings/Settings.ios.js b/Libraries/Settings/Settings.ios.js
index 73d66ca587c3a6..3d1036fedf490a 100644
--- a/Libraries/Settings/Settings.ios.js
+++ b/Libraries/Settings/Settings.ios.js
@@ -14,7 +14,7 @@
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTSettingsManager = require('NativeModules').SettingsManager;
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var subscriptions: Array<{keys: Array; callback: ?Function}> = [];
diff --git a/Libraries/StyleSheet/StyleSheetValidation.js b/Libraries/StyleSheet/StyleSheetValidation.js
index f61decb3cc2ccb..2be4ca2f24961f 100644
--- a/Libraries/StyleSheet/StyleSheetValidation.js
+++ b/Libraries/StyleSheet/StyleSheetValidation.js
@@ -16,7 +16,7 @@ var ReactPropTypeLocations = require('ReactPropTypeLocations');
var TextStylePropTypes = require('TextStylePropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
diff --git a/Libraries/StyleSheet/flattenStyle.js b/Libraries/StyleSheet/flattenStyle.js
index 621c614ffd9f97..a3af4f46e63d8d 100644
--- a/Libraries/StyleSheet/flattenStyle.js
+++ b/Libraries/StyleSheet/flattenStyle.js
@@ -12,7 +12,7 @@
'use strict';
var StyleSheetRegistry = require('StyleSheetRegistry');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
import type { StyleObj } from 'StyleSheetTypes';
diff --git a/Libraries/StyleSheet/processTransform.js b/Libraries/StyleSheet/processTransform.js
index 4a523f90910870..6978efdcfbe67b 100644
--- a/Libraries/StyleSheet/processTransform.js
+++ b/Libraries/StyleSheet/processTransform.js
@@ -14,7 +14,7 @@
var MatrixMath = require('MatrixMath');
var Platform = require('Platform');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var stringifySafe = require('stringifySafe');
/**
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index a4f110ed63e885..e57b93aa191f7a 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -74,8 +74,10 @@ - (void)removeReactSubview:(UIView *)subview
- (void)setTextStorage:(NSTextStorage *)textStorage
{
- _textStorage = textStorage;
- [self setNeedsDisplay];
+ if (_textStorage != textStorage) {
+ _textStorage = textStorage;
+ [self setNeedsDisplay];
+ }
}
- (void)drawRect:(CGRect)rect
diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js
index ec8c926c6edb15..1da237b0c23e5f 100644
--- a/Libraries/Utilities/AlertIOS.js
+++ b/Libraries/Utilities/AlertIOS.js
@@ -45,7 +45,7 @@ type ButtonsArray = Array<{
class AlertIOS {
/**
* Creates a popup to alert the user. See
- * [Alert](/react-native/docs/alert.html).
+ * [Alert](docs/alert.html).
*
* - title: string -- The dialog's title.
* - message: string -- An optional message that appears above the text input.
diff --git a/Libraries/Utilities/CSSVarConfig.js b/Libraries/Utilities/CSSVarConfig.js
deleted file mode 100644
index d3177310ce9bef..00000000000000
--- a/Libraries/Utilities/CSSVarConfig.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule CSSVarConfig
- */
-'use strict';
-
-// this a partial list of the constants in CSSConstants:: from PHP that are applicable to mobile
-
-module.exports = {
- 'fbui-accent-blue': '#5890ff',
- 'fbui-blue-90': '#4e69a2',
- 'fbui-blue-80': '#627aad',
- 'fbui-blue-70': '#758ab7',
- 'fbui-blue-60': '#899bc1',
- 'fbui-blue-50': '#9daccb',
- 'fbui-blue-40': '#b1bdd6',
- 'fbui-blue-30': '#c4cde0',
- 'fbui-blue-20': '#d8deea',
- 'fbui-blue-10': '#ebeef4',
- 'fbui-blue-5': '#f5f7fa',
- 'fbui-blue-2': '#fbfcfd',
- 'fbui-blueblack-90': '#06090f',
- 'fbui-blueblack-80': '#0c121e',
- 'fbui-blueblack-70': '#121b2e',
- 'fbui-blueblack-60': '#18243d',
- 'fbui-blueblack-50': '#1e2d4c',
- 'fbui-blueblack-40': '#23355b',
- 'fbui-blueblack-30': '#293e6b',
- 'fbui-blueblack-20': '#2f477a',
- 'fbui-blueblack-10': '#355089',
- 'fbui-blueblack-5': '#385490',
- 'fbui-blueblack-2': '#3a5795',
- 'fbui-bluegray-90': '#080a10',
- 'fbui-bluegray-80': '#141823',
- 'fbui-bluegray-70': '#232937',
- 'fbui-bluegray-60': '#373e4d',
- 'fbui-bluegray-50': '#4e5665',
- 'fbui-bluegray-40': '#6a7180',
- 'fbui-bluegray-30': '#9197a3',
- 'fbui-bluegray-20': '#bdc1c9',
- 'fbui-bluegray-10': '#dcdee3',
- 'fbui-bluegray-5': '#e9eaed',
- 'fbui-bluegray-2': '#f6f7f8',
- 'fbui-gray-90': '#191919',
- 'fbui-gray-80': '#333333',
- 'fbui-gray-70': '#4c4c4c',
- 'fbui-gray-60': '#666666',
- 'fbui-gray-50': '#7f7f7f',
- 'fbui-gray-40': '#999999',
- 'fbui-gray-30': '#b2b2b2',
- 'fbui-gray-20': '#cccccc',
- 'fbui-gray-10': '#e5e5e5',
- 'fbui-gray-5': '#f2f2f2',
- 'fbui-gray-2': '#fafafa',
- 'fbui-red': '#da2929',
- 'fbui-error': '#ce0d24',
- 'x-mobile-dark-text': '#4e5665',
- 'x-mobile-medium-text': '#6a7180',
- 'x-mobile-light-text': '#9197a3',
- 'x-mobile-base-wash': '#dcdee3',
-};
diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js
index 93e2cc21aa1503..5cf5f21edc21f2 100644
--- a/Libraries/Utilities/Dimensions.js
+++ b/Libraries/Utilities/Dimensions.js
@@ -11,9 +11,10 @@
*/
'use strict';
+var Platform = require('Platform');
var UIManager = require('UIManager');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var dimensions = UIManager.Dimensions;
@@ -31,7 +32,21 @@ if (dimensions && dimensions.windowPhysicalPixels) {
scale: windowPhysicalPixels.scale,
fontScale: windowPhysicalPixels.fontScale,
};
+ if (Platform.OS === 'android') {
+ // Screen and window dimensions are different on android
+ var screenPhysicalPixels = dimensions.screenPhysicalPixels;
+ dimensions.screen = {
+ width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
+ height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
+ scale: screenPhysicalPixels.scale,
+ fontScale: screenPhysicalPixels.fontScale,
+ };
+ // delete so no callers rely on this existing
+ delete dimensions.screenPhysicalPixels;
+ } else {
+ dimensions.screen = dimensions.window;
+ }
// delete so no callers rely on this existing
delete dimensions.windowPhysicalPixels;
}
diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js
index ed55ee6b912ca7..14850f860ba671 100644
--- a/Libraries/Utilities/HMRClient.js
+++ b/Libraries/Utilities/HMRClient.js
@@ -7,34 +7,38 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule HMRClient
+ * @flow
*/
'use strict';
-const invariant = require('invariant');
-const processColor = require('processColor');
+const Platform = require('Platform');
+const invariant = require('fbjs/lib/invariant');
/**
* HMR Client that receives from the server HMR updates and propagates them
* runtime to reflects those changes.
*/
const HMRClient = {
- enable(platform, bundleEntry) {
+ enable(platform: string, bundleEntry: string, host: string, port: number) {
invariant(platform, 'Missing required parameter `platform`');
invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
-
- // TODO(martinb) receive host and port as parameters
- const host = 'localhost';
- const port = '8081';
+ invariant(host, 'Missing required paramenter `host`');
// need to require WebSocket inside of `enable` function because
// this module is defined as a `polyfillGlobal`.
// See `InitializeJavascriptAppEngine.js`
const WebSocket = require('WebSocket');
- const activeWS = new WebSocket(
- `ws://${host}:${port}/hot?platform=${platform}&` +
- `bundleEntry=${bundleEntry.replace('.bundle', '.js')}`
- );
+ const wsHostPort = port !== null && port !== ''
+ ? `${host}:${port}`
+ : host;
+
+ // Build the websocket url
+ const wsUrl = `ws://${wsHostPort}/hot?` +
+ `platform=${platform}&` +
+ `bundleEntry=${bundleEntry.replace('.bundle', '.js')}`;
+
+ const activeWS = new WebSocket(wsUrl);
activeWS.onerror = (e) => {
throw new Error(
`Hot loading isn't working because it cannot connect to the development server.
@@ -50,27 +54,33 @@ Error: ${e.message}`
);
};
activeWS.onmessage = ({data}) => {
- const DevLoadingView = require('NativeModules').DevLoadingView;
+ // Moving to top gives errors due to NativeModules not being initialized
+ const HMRLoadingView = require('HMRLoadingView');
+
data = JSON.parse(data);
- switch(data.type) {
+ switch (data.type) {
case 'update-start': {
- DevLoadingView.showMessage(
- 'Hot Loading...',
- processColor('#000000'),
- processColor('#aaaaaa'),
- );
+ HMRLoadingView.showMessage('Hot Loading...');
break;
}
case 'update': {
- const modules = data.body.modules;
- const sourceMappingURLs = data.body.sourceMappingURLs;
- const sourceURLs = data.body.sourceURLs;
-
- const RCTRedBox = require('NativeModules').RedBox;
- RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
-
- modules.forEach((code, i) => {
+ const {
+ modules,
+ sourceMappingURLs,
+ sourceURLs,
+ inverseDependencies,
+ } = data.body;
+
+ if (Platform.OS === 'ios') {
+ const RCTRedBox = require('NativeModules').RedBox;
+ RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
+ } else {
+ const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
+ RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
+ }
+
+ modules.forEach(({name, code}, i) => {
code = code + '\n\n' + sourceMappingURLs[i];
require('SourceMapsCache').fetch({
@@ -82,22 +92,32 @@ Error: ${e.message}`
// on JSC we need to inject from native for sourcemaps to work
// (Safari doesn't support `sourceMappingURL` nor any variant when
// evaluating code) but on Chrome we can simply use eval
- const injectFunction = typeof __injectHMRUpdate === 'function'
- ? __injectHMRUpdate
+ const injectFunction = typeof global.nativeInjectHMRUpdate === 'function'
+ ? global.nativeInjectHMRUpdate
: eval;
+ // TODO: (martinb) yellow box if cannot accept module
+ code = `
+ __accept(
+ ${name},
+ function(global, require, module, exports) {
+ ${code}
+ },
+ ${JSON.stringify(inverseDependencies)}
+ );`;
+
injectFunction(code, sourceURLs[i]);
});
- DevLoadingView.hide();
+ HMRLoadingView.hide();
break;
}
case 'update-done': {
- DevLoadingView.hide();
+ HMRLoadingView.hide();
break;
}
case 'error': {
- DevLoadingView.hide();
+ HMRLoadingView.hide();
throw new Error(data.body.type + ' ' + data.body.description);
}
default: {
diff --git a/Libraries/Utilities/HMRLoadingView.android.js b/Libraries/Utilities/HMRLoadingView.android.js
new file mode 100644
index 00000000000000..82075ffa1920b6
--- /dev/null
+++ b/Libraries/Utilities/HMRLoadingView.android.js
@@ -0,0 +1,38 @@
+/**
+ * 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 HMRLoadingView
+ * @flow
+ */
+
+'use strict';
+
+var ToastAndroid = require('ToastAndroid');
+
+const TOAST_SHORT_DELAY = 2000;
+
+class HMRLoadingView {
+ static _showing: boolean;
+
+ static showMessage(message: string) {
+ if (HMRLoadingView._showing) {
+ return;
+ }
+ ToastAndroid.show(message, ToastAndroid.SHORT);
+ HMRLoadingView._showing = true;
+ setTimeout(() => {
+ HMRLoadingView._showing = false;
+ }, TOAST_SHORT_DELAY);
+ }
+
+ static hide() {
+ // noop
+ }
+}
+
+module.exports = HMRLoadingView;
diff --git a/Libraries/Utilities/HMRLoadingView.ios.js b/Libraries/Utilities/HMRLoadingView.ios.js
new file mode 100644
index 00000000000000..ea0559fc41d0dd
--- /dev/null
+++ b/Libraries/Utilities/HMRLoadingView.ios.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 HMRLoadingView
+ * @flow
+ */
+
+'use strict';
+
+const processColor = require('processColor');
+const { DevLoadingView } = require('NativeModules');
+
+class HMRLoadingView {
+ static showMessage(message: string) {
+ DevLoadingView.showMessage(
+ message,
+ processColor('#000000'),
+ processColor('#aaaaaa'),
+ );
+ }
+
+ static hide() {
+ DevLoadingView.hide();
+ }
+}
+
+module.exports = HMRLoadingView;
diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js
index 32f3cd32f4deb8..6900106a6e0f72 100755
--- a/Libraries/Utilities/MatrixMath.js
+++ b/Libraries/Utilities/MatrixMath.js
@@ -7,7 +7,7 @@
/* eslint-disable space-infix-ops */
'use strict';
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
/**
* Memory conservative (mutative) matrix math utilities. Uses "command"
diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js
index 8e58ee06e3c71f..2e542b6c40f389 100644
--- a/Libraries/Utilities/MessageQueue.js
+++ b/Libraries/Utilities/MessageQueue.js
@@ -18,8 +18,8 @@ let ErrorUtils = require('ErrorUtils');
let JSTimersExecution = require('JSTimersExecution');
let Platform = require('Platform');
-let invariant = require('invariant');
-let keyMirror = require('keyMirror');
+let invariant = require('fbjs/lib/invariant');
+let keyMirror = require('fbjs/lib/keyMirror');
let stringifySafe = require('stringifySafe');
let MODULE_IDS = 0;
@@ -324,11 +324,7 @@ class MessageQueue {
method,
args,
(data) => {
- // iOS always wraps the data in an Array regardless of what the
- // shape of the data so we strip it out
- // Android sends the data back properly
- // TODO: Remove this once iOS has support for Promises natively (t9774697)
- resolve(Platform.OS == 'ios' ? data[0] : data);
+ resolve(data);
},
(errorData) => {
var error = createErrorFromErrorData(errorData);
diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js
index e2d0e5c81ad2e6..03e5df830bbe58 100644
--- a/Libraries/Utilities/PerformanceLogger.js
+++ b/Libraries/Utilities/PerformanceLogger.js
@@ -12,7 +12,7 @@
var BatchedBridge = require('BatchedBridge');
-var performanceNow = require('performanceNow');
+var performanceNow = require('fbjs/lib/performanceNow');
var timespans = {};
var extras = {};
diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js
index 65abc5883d0355..32394684cb9d72 100644
--- a/Libraries/Utilities/RCTLog.js
+++ b/Libraries/Utilities/RCTLog.js
@@ -13,7 +13,7 @@
var BatchedBridge = require('BatchedBridge');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var levelsMap = {
log: 'log',
diff --git a/Libraries/Utilities/RCTRenderingPerf.js b/Libraries/Utilities/RCTRenderingPerf.js
index 126c8a0328528d..3979780ba36473 100644
--- a/Libraries/Utilities/RCTRenderingPerf.js
+++ b/Libraries/Utilities/RCTRenderingPerf.js
@@ -13,7 +13,7 @@
var ReactDefaultPerf = require('ReactDefaultPerf');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
type perfModule = {
start: () => void;
diff --git a/Libraries/Utilities/UIManager.js b/Libraries/Utilities/UIManager.js
index ff2fc23541de42..efd8ce5b072866 100644
--- a/Libraries/Utilities/UIManager.js
+++ b/Libraries/Utilities/UIManager.js
@@ -12,6 +12,7 @@
'use strict';
var UIManager = require('NativeModules').UIManager;
+var findNodeHandle = require('findNodeHandle');
if (!UIManager.setChildren) {
@@ -39,7 +40,45 @@ if (!UIManager.setChildren) {
UIManager.setChildren = function(containerTag, createdTags) {
var indexes = this._cachedIndexArray(createdTags.length);
UIManager.manageChildren(containerTag, null, null, createdTags, indexes, null);
- }
+ };
}
+const _takeSnapshot = UIManager.takeSnapshot;
+
+/**
+ * Capture an image of the screen, window or an individual view. The image
+ * will be stored in a temporary file that will only exist for as long as the
+ * app is running.
+ *
+ * The `view` argument can be the literal string `window` if you want to
+ * capture the entire window, or it can be a reference to a specific
+ * React Native component.
+ *
+ * The `options` argument may include:
+ * - width/height (number) - the width and height of the image to capture.
+ * - format (string) - either 'png' or 'jpeg'. Defaults to 'png'.
+ * - quality (number) - the quality when using jpeg. 0.0 - 1.0 (default).
+ *
+ * Returns a Promise.
+ * @platform ios
+ */
+UIManager.takeSnapshot = async function(
+ view ?: 'window' | ReactElement | number,
+ options ?: {
+ width ?: number;
+ height ?: number;
+ format ?: 'png' | 'jpeg';
+ quality ?: number;
+ },
+) {
+ if (!_takeSnapshot) {
+ console.warn('UIManager.takeSnapshot is not available on this platform');
+ return;
+ }
+ if (typeof view !== 'number' && view !== 'window') {
+ view = findNodeHandle(view) || 'window';
+ }
+ return _takeSnapshot(view, options);
+};
+
module.exports = UIManager;
diff --git a/Libraries/Utilities/__tests__/MatrixMath-test.js b/Libraries/Utilities/__tests__/MatrixMath-test.js
index 5bfa1e9d7b810b..b8c10ac402e2d7 100644
--- a/Libraries/Utilities/__tests__/MatrixMath-test.js
+++ b/Libraries/Utilities/__tests__/MatrixMath-test.js
@@ -9,7 +9,7 @@
'use strict';
jest.dontMock('MatrixMath');
-jest.dontMock('invariant');
+jest.dontMock('fbjs/lib/invariant');
var MatrixMath = require('MatrixMath');
diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js
index ab04d0fe54cf6c..14c50e746d7d96 100644
--- a/Libraries/Utilities/__tests__/MessageQueue-test.js
+++ b/Libraries/Utilities/__tests__/MessageQueue-test.js
@@ -9,7 +9,7 @@
'use strict';
jest.dontMock('MessageQueue')
- .dontMock('keyMirror');
+ .dontMock('fbjs/lib/keyMirror');
var MessageQueue = require('MessageQueue');
let MODULE_IDS = 0;
diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js
index 0c0fa7fd1221c5..ff51699b033d45 100644
--- a/Libraries/Utilities/buildStyleInterpolator.js
+++ b/Libraries/Utilities/buildStyleInterpolator.js
@@ -9,7 +9,7 @@
*/
/* eslint-disable global-strict */
-var keyOf = require('keyOf');
+var keyOf = require('fbjs/lib/keyOf');
var X_DIM = keyOf({x: null});
var Y_DIM = keyOf({y: null});
diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js
index 883da2459f7c58..b5fe14a1b95051 100644
--- a/Libraries/Utilities/createStrictShapeTypeChecker.js
+++ b/Libraries/Utilities/createStrictShapeTypeChecker.js
@@ -13,7 +13,7 @@
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var merge = require('merge');
function createStrictShapeTypeChecker(
diff --git a/Libraries/Utilities/cssVar.js b/Libraries/Utilities/cssVar.js
deleted file mode 100644
index 0ab64e7f9ca5cb..00000000000000
--- a/Libraries/Utilities/cssVar.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule cssVar
- * @typechecks
- */
-'use strict';
-
-var invariant = require('invariant');
-var CSSVarConfig = require('CSSVarConfig');
-
-var cssVar = function(/*string*/ key) /*string*/ {
- invariant(CSSVarConfig[key], 'invalid css variable ' + key);
-
- return CSSVarConfig[key];
-};
-
-module.exports = cssVar;
diff --git a/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js b/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
index 9263116ed7075f..b1dac7777dea5f 100644
--- a/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
+++ b/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
@@ -42,11 +42,17 @@ function deepFreezeAndThrowOnMutationInDev(object: Object) {
if (object.hasOwnProperty(key)) {
object.__defineGetter__(key, identity.bind(null, object[key]));
object.__defineSetter__(key, throwOnImmutableMutation.bind(null, key));
- deepFreezeAndThrowOnMutationInDev(object[key]);
}
}
+
Object.freeze(object);
Object.seal(object);
+
+ for (var key in object) {
+ if (object.hasOwnProperty(key)) {
+ deepFreezeAndThrowOnMutationInDev(object[key]);
+ }
+ }
}
}
diff --git a/Libraries/Utilities/deprecatedCallback.js b/Libraries/Utilities/deprecatedCallback.js
new file mode 100644
index 00000000000000..3beefdc483a8dd
--- /dev/null
+++ b/Libraries/Utilities/deprecatedCallback.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * Helper for deprecated callback pattern
+ *
+ * @providesModule deprecatedCallback
+ * @flow
+ */
+
+'use strict';
+
+module.exports = function(promise: Promise, callbacks: Array, type: string, warning: string): Promise {
+ if (callbacks.length === 0) {
+ return promise;
+ }
+
+ let success, error, callback;
+
+ console.warn(warning);
+
+ switch (type) {
+ case 'success-first': // handles func(success, error), func(success)
+ [ success, error ] = callbacks;
+ return promise.then(
+ res => success(res),
+ err => error && error(err)
+ );
+ case 'error-first': // handles func(error, success)
+ [ error, success ] = callbacks;
+ return promise.then(
+ res => success(res),
+ err => error(err)
+ );
+ case 'single-callback-value-first': // handles func(callback(value, err))
+ [ callback ] = callbacks;
+ return promise.then(
+ res => callback(res),
+ err => callback(null, err)
+ );
+ case 'node': // handles func(callback(err, value))
+ [ callback ] = callbacks;
+ return promise.then(
+ res => callback(null, res),
+ err => callback(err)
+ );
+ default:
+ throw new Error(`Type of callbacks not specified. Must be one of 'success-first', 'error-first', 'single-callback-value-first', or 'node'`);
+ }
+};
diff --git a/Libraries/Vibration/Vibration.js b/Libraries/Vibration/Vibration.js
new file mode 100644
index 00000000000000..9258eadaa2a7b8
--- /dev/null
+++ b/Libraries/Vibration/Vibration.js
@@ -0,0 +1,39 @@
+/**
+ * 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 Vibration
+ * @flow
+ */
+'use strict';
+
+var RCTVibration = require('NativeModules').Vibration;
+var Platform = require('Platform');
+
+/**
+ * The Vibration API is exposed at `Vibration.vibrate()`.
+ * The vibration is asynchronous so this method will return immediately.
+ *
+ * There will be no effect on devices that do not support Vibration, eg. the simulator.
+ *
+ * Note for android
+ * add `` to `AndroidManifest.xml`
+ *
+ * Vibration patterns are currently unsupported.
+ */
+
+var Vibration = {
+ vibrate: function(duration: number = 400) {
+ if (Platform.OS === 'android') {
+ RCTVibration.vibrate(duration);
+ } else {
+ RCTVibration.vibrate();
+ }
+ }
+};
+
+module.exports = Vibration;
diff --git a/Libraries/Vibration/VibrationIOS.android.js b/Libraries/Vibration/VibrationIOS.android.js
index f52be6b7501b7c..37cdf859c1eb8c 100644
--- a/Libraries/Vibration/VibrationIOS.android.js
+++ b/Libraries/Vibration/VibrationIOS.android.js
@@ -12,7 +12,7 @@
*/
'use strict';
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
var VibrationIOS = {
vibrate: function() {
diff --git a/Libraries/Vibration/VibrationIOS.ios.js b/Libraries/Vibration/VibrationIOS.ios.js
index 2a1dc701c17383..56da5a815b12dc 100644
--- a/Libraries/Vibration/VibrationIOS.ios.js
+++ b/Libraries/Vibration/VibrationIOS.ios.js
@@ -13,9 +13,11 @@
var RCTVibration = require('NativeModules').Vibration;
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
/**
+ * NOTE: `VibrationIOS` is being deprecated. Use `Vibration` instead.
+ *
* The Vibration API is exposed at `VibrationIOS.vibrate()`. On iOS, calling this
* function will trigger a one second vibration. The vibration is asynchronous
* so this method will return immediately.
@@ -27,6 +29,9 @@ var invariant = require('invariant');
*/
var VibrationIOS = {
+ /**
+ * @deprecated
+ */
vibrate: function() {
invariant(
arguments[0] === undefined,
diff --git a/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj b/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
index 62dbee6a7606fd..b1a8757069187e 100644
--- a/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
+++ b/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
@@ -10,6 +10,7 @@
1338BBE01B04ACC80064A9C9 /* RCTSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */; };
1338BBE11B04ACC80064A9C9 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */; };
3C86DF7C1ADF695F0047B81A /* RCTWebSocketModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */; };
+ 3DB9106F1C74B1ED00838BBE /* RCTWebSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -20,6 +21,8 @@
3C86DF461ADF2C930047B81A /* libRCTWebSocket.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocket.a; sourceTree = BUILT_PRODUCTS_DIR; };
3C86DF7A1ADF695F0047B81A /* RCTWebSocketModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketModule.h; sourceTree = ""; };
3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketModule.m; sourceTree = ""; tabWidth = 2; };
+ 3DB9106D1C74B1ED00838BBE /* RCTWebSocketManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketManager.h; sourceTree = ""; };
+ 3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketManager.m; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -40,6 +43,8 @@
1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */,
1338BBDE1B04ACC80064A9C9 /* RCTWebSocketExecutor.h */,
1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */,
+ 3DB9106D1C74B1ED00838BBE /* RCTWebSocketManager.h */,
+ 3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */,
3C86DF7A1ADF695F0047B81A /* RCTWebSocketModule.h */,
3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */,
3C86DF471ADF2C930047B81A /* Products */,
@@ -114,6 +119,7 @@
1338BBE01B04ACC80064A9C9 /* RCTSRWebSocket.m in Sources */,
3C86DF7C1ADF695F0047B81A /* RCTWebSocketModule.m in Sources */,
1338BBE11B04ACC80064A9C9 /* RCTWebSocketExecutor.m in Sources */,
+ 3DB9106F1C74B1ED00838BBE /* RCTWebSocketManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js b/Libraries/WebSocket/RCTWebSocketManager.h
similarity index 64%
rename from packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js
rename to Libraries/WebSocket/RCTWebSocketManager.h
index 6f7632f66c6992..debc3f7676f3e9 100644
--- a/packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js
+++ b/Libraries/WebSocket/RCTWebSocketManager.h
@@ -6,15 +6,14 @@
* 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';
-class Cache {
- get(filepath, field, cb) {
- return cb(filepath);
- }
+#import "RCTDefines.h"
- invalidate(filepath) { }
- end() { }
-}
+#if RCT_DEV // Only supported in dev mode
-module.exports = Cache;
+#import "RCTWebSocketProxy.h"
+
+@interface RCTWebSocketManager : NSObject
+@end
+
+#endif
diff --git a/Libraries/WebSocket/RCTWebSocketManager.m b/Libraries/WebSocket/RCTWebSocketManager.m
new file mode 100644
index 00000000000000..7288dda09765bc
--- /dev/null
+++ b/Libraries/WebSocket/RCTWebSocketManager.m
@@ -0,0 +1,143 @@
+/**
+ * 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 "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+#import "RCTWebSocketManager.h"
+
+#import "RCTConvert.h"
+#import "RCTLog.h"
+#import "RCTUtils.h"
+#import "RCTSRWebSocket.h"
+
+#pragma mark - RCTWebSocketObserver
+
+@interface RCTWebSocketObserver : NSObject
+
+@property (nonatomic, strong) RCTSRWebSocket *socket;
+@property (nonatomic, weak) id delegate;
+@property (nonatomic, strong) dispatch_semaphore_t socketOpenSemaphore;
+
+- (instancetype)initWithURL:(NSURL *)url delegate:(id)delegate;
+
+@end
+
+@implementation RCTWebSocketObserver
+
+- (instancetype)initWithURL:(NSURL *)url delegate:(id)delegate
+{
+ if ((self = [self init])) {
+ _socket = [[RCTSRWebSocket alloc] initWithURL:url];
+ _socket.delegate = self;
+
+ _delegate = delegate;
+}
+ return self;
+}
+
+- (void)start
+{
+ _socketOpenSemaphore = dispatch_semaphore_create(0);
+ [_socket open];
+ dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2));
+}
+
+- (void)stop
+{
+ _socket.delegate = nil;
+ [_socket closeWithCode:1000 reason:@"Invalidated"];
+ _socket = nil;
+}
+
+- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
+{
+ if (_delegate) {
+ NSError *error = nil;
+ NSDictionary *msg = RCTJSONParse(message, &error);
+
+ if (!error) {
+ [_delegate socketProxy:[RCTWebSocketManager sharedInstance] didReceiveMessage:msg];
+ } else {
+ RCTLogError(@"WebSocketManager failed to parse message with error %@\n\n%@\n", error, message);
+ }
+ }
+}
+
+- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
+{
+ dispatch_semaphore_signal(_socketOpenSemaphore);
+}
+
+- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
+{
+ dispatch_semaphore_signal(_socketOpenSemaphore);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // Give the setUp method an opportunity to report an error first
+ RCTLogError(@"WebSocket connection failed with error %@", error);
+ });
+}
+
+@end
+
+#pragma mark - RCTWebSocketManager
+
+@interface RCTWebSocketManager()
+
+@property (nonatomic, strong) NSMutableDictionary *sockets;
+@property (nonatomic, strong) dispatch_queue_t queue;
+
+@end
+
+@implementation RCTWebSocketManager
+
++ (instancetype)sharedInstance
+{
+ static RCTWebSocketManager *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [self new];
+ });
+
+ return sharedInstance;
+}
+
+- (void)setDelegate:(id)delegate forURL:(NSURL *)url
+{
+ NSString *key = [url absoluteString];
+ RCTWebSocketObserver *observer = _sockets[key];
+
+ if (observer) {
+ if (!delegate) {
+ [observer stop];
+ [_sockets removeObjectForKey:key];
+ } else {
+ observer.delegate = delegate;
+ }
+ } else {
+ RCTWebSocketObserver *newObserver = [[RCTWebSocketObserver alloc] initWithURL:url delegate:delegate];
+ [newObserver start];
+ _sockets[key] = newObserver;
+ }
+}
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ _sockets = [NSMutableDictionary new];
+ _queue = dispatch_queue_create("com.facebook.React.WebSocketManager", DISPATCH_QUEUE_SERIAL);
+ }
+ return self;
+}
+
+@end
+
+#endif
diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js
index b9eb255cc76a02..ec6f722ef7a8ab 100644
--- a/Libraries/WebSocket/WebSocket.js
+++ b/Libraries/WebSocket/WebSocket.js
@@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule WebSocket
+ * @flow
*/
'use strict';
@@ -116,6 +117,7 @@ class WebSocket extends WebSocketBase {
var event = new WebSocketEvent('error');
event.message = ev.message;
this.onerror && this.onerror(event);
+ this.onclose && this.onclose(event);
this.dispatchEvent(event);
this._unregisterEvents();
this.close();
diff --git a/Libraries/WebSocket/WebSocketBase.js b/Libraries/WebSocket/WebSocketBase.js
index 931b297494c484..843ae403540c04 100644
--- a/Libraries/WebSocket/WebSocketBase.js
+++ b/Libraries/WebSocket/WebSocketBase.js
@@ -7,11 +7,17 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule WebSocketBase
+ * @flow
*/
'use strict';
var EventTarget = require('event-target-shim');
+const CONNECTING = 0;
+const OPEN = 1;
+const CLOSING = 2;
+const CLOSED = 3;
+
/**
* Shared base for platform-specific WebSocket implementations.
*/
@@ -35,10 +41,10 @@ class WebSocketBase extends EventTarget {
constructor(url: string, protocols: ?string | ?Array, options: ?{origin?: string}) {
super();
- this.CONNECTING = 0;
- this.OPEN = 1;
- this.CLOSING = 2;
- this.CLOSED = 3;
+ this.CONNECTING = CONNECTING;
+ this.OPEN = OPEN;
+ this.CLOSING = CLOSING;
+ this.CLOSED = CLOSED;
if (typeof protocols === 'string') {
protocols = [protocols];
@@ -84,7 +90,7 @@ class WebSocketBase extends EventTarget {
throw new Error('Subclass must define closeConnectionImpl method');
}
- connectToSocketImpl(): void {
+ connectToSocketImpl(url: string, protocols: ?Array, options: ?{origin?: string}): void {
throw new Error('Subclass must define connectToSocketImpl method');
}
@@ -92,7 +98,7 @@ class WebSocketBase extends EventTarget {
throw new Error('Subclass must define cancelConnectionImpl method');
}
- sendStringImpl(): void {
+ sendStringImpl(message: string): void {
throw new Error('Subclass must define sendStringImpl method');
}
@@ -101,9 +107,9 @@ class WebSocketBase extends EventTarget {
}
}
-WebSocketBase.CONNECTING = 0;
-WebSocketBase.OPEN = 1;
-WebSocketBase.CLOSING = 2;
-WebSocketBase.CLOSED = 3;
+WebSocketBase.CONNECTING = CONNECTING;
+WebSocketBase.OPEN = OPEN;
+WebSocketBase.CLOSING = CLOSING;
+WebSocketBase.CLOSED = CLOSED;
module.exports = WebSocketBase;
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index e8eca3c72a5ad9..2b5a0bc964f613 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -86,6 +86,7 @@ var ReactNative = {
get StyleSheet() { return require('StyleSheet'); },
get TimePickerAndroid() { return require('TimePickerAndroid'); },
get UIManager() { return require('UIManager'); },
+ get Vibration() { return require('Vibration'); },
get VibrationIOS() { return require('VibrationIOS'); },
// Plugins
diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow
index 6b4f333c03d2c6..70d05ead71ca06 100644
--- a/Libraries/react-native/react-native.js.flow
+++ b/Libraries/react-native/react-native.js.flow
@@ -23,7 +23,7 @@
//
// var ReactNative = {...require('React'), /* additions */}
//
-var ReactNative = Object.assign(Object.create(require('React')), {
+var ReactNative = Object.assign(Object.create(require('react')), {
// Components
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
ART: require('ReactNativeART'),
@@ -88,6 +88,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
LayoutAnimation: require('LayoutAnimation'),
Linking: require('Linking'),
LinkingIOS: require('LinkingIOS'),
+ NavigationExperimental: require('NavigationExperimental'),
NetInfo: require('NetInfo'),
PanResponder: require('PanResponder'),
PixelRatio: require('PixelRatio'),
@@ -97,6 +98,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
StyleSheet: require('StyleSheet'),
TimePickerAndroid: require('TimePickerAndroid'),
UIManager: require('UIManager'),
+ Vibration: require('Vibration'),
VibrationIOS: require('VibrationIOS'),
// Plugins
diff --git a/Libraries/vendor/core/Map.js b/Libraries/vendor/core/Map.js
index 114add77b308ca..b3153b39df1fef 100644
--- a/Libraries/vendor/core/Map.js
+++ b/Libraries/vendor/core/Map.js
@@ -19,7 +19,7 @@
*/
var guid = require('guid');
-var isNode = require('isNode');
+var isNode = require('fbjs/lib/isNode');
var toIterator = require('toIterator');
var _shouldPolyfillES6Collection = require('_shouldPolyfillES6Collection');
diff --git a/Libraries/vendor/core/mergeHelpers.js b/Libraries/vendor/core/mergeHelpers.js
index 58fa238d4f2bd4..d0c506b3d924ff 100644
--- a/Libraries/vendor/core/mergeHelpers.js
+++ b/Libraries/vendor/core/mergeHelpers.js
@@ -33,8 +33,8 @@
"use strict";
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
+var invariant = require('fbjs/lib/invariant');
+var keyMirror = require('fbjs/lib/keyMirror');
/**
* Maximum number of levels to traverse. Will catch circular structures.
diff --git a/Libraries/vendor/crypto/crc32.js b/Libraries/vendor/crypto/crc32.js
deleted file mode 100644
index 540d60aa832b6e..00000000000000
--- a/Libraries/vendor/crypto/crc32.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * @generated SignedSource<<77bdeb858138636c96c405d64b6be55c>>
- *
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !! 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. !!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- *
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule crc32
- */
-
-/* jslint bitwise: true */
-
-/**
- * Modified from the original for performance improvements.
- *
- * @see http://create.stephan-brumme.com/crc32/
- * @see http://stackoverflow.com/questions/18638900/
- * @copyright 2006 Andrea Ercolino
- * @license MIT
- */
-
-var table = [
- 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
- 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
- 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
- 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
- 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
- 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
- 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
- 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
- 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
- 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
- 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
- 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
- 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
- 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
- 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
- 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
- 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
- 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
- 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
- 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
- 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
- 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
- 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
- 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
- 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
- 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
- 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
- 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
- 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
- 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
- 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
- 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
- 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
- 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
- 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
- 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
- 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
- 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
- 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
- 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
- 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
- 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
- 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
-];
-
-if (global.Int32Array !== undefined) {
- table = new Int32Array(table);
-}
-
-/**
- * @returns Number
- */
-function crc32(str) {
- var crc = -1;
- for (var i = 0, len = str.length; i < len; i++) {
- crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
- }
- return ~crc;
-}
-
-module.exports = crc32;
diff --git a/Libraries/vendor/emitter/EventEmitter.js b/Libraries/vendor/emitter/EventEmitter.js
index b5c14a66a7c7a9..ba6781a57e3f98 100644
--- a/Libraries/vendor/emitter/EventEmitter.js
+++ b/Libraries/vendor/emitter/EventEmitter.js
@@ -14,8 +14,8 @@
var EmitterSubscription = require('EmitterSubscription');
var ErrorUtils = require('ErrorUtils');
var EventSubscriptionVendor = require('EventSubscriptionVendor');
-var emptyFunction = require('emptyFunction');
-var invariant = require('invariant');
+var emptyFunction = require('fbjs/lib/emptyFunction');
+var invariant = require('fbjs/lib/invariant');
/**
* @class EventEmitter
diff --git a/Libraries/vendor/emitter/EventHolder.js b/Libraries/vendor/emitter/EventHolder.js
index 4345c3d387cdf4..76cfd33141e821 100644
--- a/Libraries/vendor/emitter/EventHolder.js
+++ b/Libraries/vendor/emitter/EventHolder.js
@@ -17,7 +17,7 @@
*/
'use strict';
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
class EventHolder {
constructor() {
diff --git a/Libraries/vendor/emitter/EventSubscriptionVendor.js b/Libraries/vendor/emitter/EventSubscriptionVendor.js
index add1f408e4a897..d992a69a553696 100644
--- a/Libraries/vendor/emitter/EventSubscriptionVendor.js
+++ b/Libraries/vendor/emitter/EventSubscriptionVendor.js
@@ -17,7 +17,7 @@
*/
'use strict';
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
/**
* EventSubscriptionVendor stores a set of EventSubscriptions that are
diff --git a/Libraries/vendor/emitter/mixInEventEmitter.js b/Libraries/vendor/emitter/mixInEventEmitter.js
index 4dc938199d401b..fc39f7b649e13f 100644
--- a/Libraries/vendor/emitter/mixInEventEmitter.js
+++ b/Libraries/vendor/emitter/mixInEventEmitter.js
@@ -21,8 +21,8 @@ var EventHolder = require('EventHolder');
var EventValidator = require('EventValidator');
var copyProperties = require('copyProperties');
-var invariant = require('invariant');
-var keyOf = require('keyOf');
+var invariant = require('fbjs/lib/invariant');
+var keyOf = require('fbjs/lib/keyOf');
var TYPES_KEY = keyOf({__types: true});
diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
index 36490a84302696..c633dd724be79f 100644
--- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
+++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
@@ -23,7 +23,7 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY;
* recognize simple multi-touch gestures.
*
* It provides a predictable wrapper of the responder handlers provided by the
- * [gesture responder system](/react-native/docs/gesture-responder-system.html).
+ * [gesture responder system](docs/gesture-responder-system.html).
* For each handler, it provides a new `gestureState` object alongside the
* native event object:
*
diff --git a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
index b91feba9b1e2a2..ca7faa7fecfec0 100644
--- a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
+++ b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
@@ -4,7 +4,7 @@
var ReactNativeTagHandles = require('ReactNativeTagHandles');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
var UniversalWorkerNodeHandle = {
getRootNodeID: function(nodeHandle) {
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000000..4ef624d5845783
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+Thanks for submitting a pull request! Please provide enough information so that others can review your pull request:
+
+(You can skip this if you're fixing a typo or adding an app to the Showcase.)
+
+Explain the **motivation** for making this change. What existing problem does the pull request solve?
+
+Example: When "Adding a function to do X", explain why it is necessary to have a way to do X.
+
+**Test plan (required)**
+
+Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
+
+Make sure tests pass on both Travis and Circle CI.
+
+**Code formatting**
+
+See the simple [style guide](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#style-guide).
diff --git a/React.podspec b/React.podspec
index 4891cb3b247f69..a1e7207e02188c 100644
--- a/React.podspec
+++ b/React.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "React"
- s.version = "0.0.0-master"
+ s.version = "0.0.1-master"
s.summary = "Build high quality mobile apps using React."
s.description = <<-DESC
React Native apps are built using the React JS
diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index debc00d9a02651..9547262cb926cf 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -44,34 +44,29 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
@interface RCTBatchedBridge : RCTBridge
@property (nonatomic, weak) RCTBridge *parentBridge;
+@property (nonatomic, weak) id javaScriptExecutor;
+@property (nonatomic, assign) BOOL moduleSetupComplete;
@end
@implementation RCTBatchedBridge
{
- BOOL _loading;
- BOOL _valid;
BOOL _wasBatchActive;
- __weak id _javaScriptExecutor;
NSMutableArray *_pendingCalls;
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;
}
@synthesize flowID = _flowID;
@synthesize flowIDMap = _flowIDMap;
+@synthesize loading = _loading;
+@synthesize valid = _valid;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
- RCTAssertMainThread();
RCTAssertParam(bridge);
if ((self = [super initWithBundleURL:bridge.bundleURL
@@ -122,11 +117,7 @@ - (void)start
}];
// Synchronously initialize all native modules that cannot be loaded lazily
- [self initModules];
-
-#if RCT_DEBUG
- _syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
-#endif
+ [self initModulesWithDispatchGroup:initModulesAndLoadSource];
if (RCTProfileIsProfiling()) {
// Depends on moduleDataByID being loaded
@@ -145,17 +136,10 @@ - (void)start
// Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
- if (weakSelf.isValid) {
-
+ if (weakSelf.valid) {
RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
config = [weakSelf moduleConfig];
RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
-
-#if RCT_DEBUG
- NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
- _asyncInitializedModules = total - _syncInitializedModules;
-#endif
-
}
});
@@ -217,7 +201,7 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
- (NSArray *)moduleClasses
{
- if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) {
+ if (RCT_DEBUG && _valid && _moduleClassesByID == nil) {
RCTLogError(@"Bridge modules have not yet been initialized. You may be "
"trying to access a module too early in the startup procedure.");
}
@@ -234,8 +218,12 @@ - (RCTModuleData *)moduleDataForName:(NSString *)moduleName
- (id)moduleForName:(NSString *)moduleName
{
- RCTModuleData *moduleData = _moduleDataByName[moduleName];
- return moduleData.instance;
+ return _moduleDataByName[moduleName].instance;
+}
+
+- (BOOL)moduleIsInitialized:(Class)moduleClass
+{
+ return _moduleDataByName[RCTBridgeModuleNameForClass(moduleClass)].hasInstance;
}
- (NSArray *)configForModuleName:(NSString *)moduleName
@@ -250,14 +238,10 @@ - (NSArray *)configForModuleName:(NSString *)moduleName
return (id)kCFNull;
}
-- (void)initModules
+- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
{
- RCTAssertMainThread();
RCTPerformanceLoggerStart(RCTPLNativeModuleInit);
- // Register passed-in module instances
- NSMutableDictionary *preregisteredModules = [NSMutableDictionary new];
-
NSArray> *extraModules = nil;
if (self.delegate) {
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
@@ -267,90 +251,147 @@ - (void)initModules
extraModules = self.moduleProvider();
}
- for (id module in extraModules) {
- preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
- }
+ if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
- SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
- IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
+ // Check for unexported modules
+ static Class *classes;
+ static unsigned int classCount;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ classes = objc_copyClassList(&classCount);
+ });
- // 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:, or has exported constants. 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] ||
- RCTClassOverridesInstanceMethod(moduleClass, @selector(constantsToExport))) {
- module = [moduleClass new];
- if (!module) {
- module = (id)kCFNull;
+ NSMutableSet *moduleClasses = [NSMutableSet new];
+ [moduleClasses addObjectsFromArray:RCTGetModuleClasses()];
+ [moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]];
+
+ for (unsigned int i = 0; i < classCount; i++)
+ {
+ Class cls = classes[i];
+ Class superclass = cls;
+ while (superclass)
+ {
+ if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
+ {
+ if (![moduleClasses containsObject:cls]) {
+ RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
+ "RCT_EXPORT_MODULE()?", cls);
+ }
+ break;
}
+ superclass = class_getSuperclass(superclass);
}
}
+ }
- // Check for module name collisions.
- // It's OK to have a name collision as long as the second instance is null.
- if (module != (id)kCFNull && moduleDataByName[moduleName] && !preregisteredModules[moduleName]) {
- RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name "
- "'%@', but name was already registered by class %@", moduleClass,
- moduleName, moduleDataByName[moduleName].moduleClass);
- }
+ NSMutableArray *moduleClassesByID = [NSMutableArray new];
+ NSMutableArray *moduleDataByID = [NSMutableArray new];
+ NSMutableDictionary *moduleDataByName = [NSMutableDictionary new];
- // Instantiate moduleData (TODO: defer this until config generation)
- RCTModuleData *moduleData;
- if (module) {
- if (module != (id)kCFNull) {
- moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
- bridge:self];
+ // Set up moduleData for pre-initialized module instances
+ for (id module in extraModules) {
+ Class moduleClass = [module class];
+ NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
+
+ if (RCT_DEBUG) {
+ // Check for name collisions between preregistered modules
+ RCTModuleData *moduleData = moduleDataByName[moduleName];
+ if (moduleData) {
+ RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
+ "name '%@', but name was already registered by class %@",
+ moduleClass, moduleName, moduleData.moduleClass);
+ continue;
}
- } else {
- moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
- bridge:self];
}
+
+ // Instantiate moduleData container
+ RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
+ bridge:self];
+ moduleDataByName[moduleName] = moduleData;
+ [moduleClassesByID addObject:moduleClass];
+ [moduleDataByID addObject:moduleData];
+ }
+
+ // Set up moduleData for automatically-exported modules
+ for (Class moduleClass in RCTGetModuleClasses()) {
+ NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
+
+ // Check for module name collisions
+ RCTModuleData *moduleData = moduleDataByName[moduleName];
if (moduleData) {
- moduleDataByName[moduleName] = moduleData;
- [moduleDataByID addObject:moduleData];
+ if (moduleData.hasInstance) {
+ // Existing module was preregistered, so it takes precedence
+ continue;
+ } else if ([moduleClass new] == nil) {
+ // The new module returned nil from init, so use the old module
+ continue;
+ } else if ([moduleData.moduleClass new] != nil) {
+ // Both modules were non-nil, so it's unclear which should take precedence
+ RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
+ "name '%@', but name was already registered by class %@",
+ moduleClass, moduleName, moduleData.moduleClass);
+ }
}
+
+ // Instantiate moduleData (TODO: can we defer this until config generation?)
+ moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
+ bridge:self];
+ moduleDataByName[moduleName] = moduleData;
+ [moduleClassesByID addObject:moduleClass];
+ [moduleDataByID addObject:moduleData];
}
// Store modules
_moduleDataByID = [moduleDataByID copy];
_moduleDataByName = [moduleDataByName copy];
- _moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"];
+ _moduleClassesByID = [moduleClassesByID copy];
- /**
- * The executor is a bridge module, wait for it to be created and set it before
- * any other module has access to the bridge
- */
+ // The executor is a bridge module, wait for it to be created and set it up
+ // before any other module has access to the bridge.
_javaScriptExecutor = [self moduleForClass:self.executorClass];
+ // Dispatch module init onto main thead for those modules that require it
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
- [moduleData setBridgeForInstance];
+ // Modules that were pre-initialized need to be set up before bridge init
+ // has finished, otherwise the caller may try to access the module
+ // directly rather than via `[bridge moduleForClass:]`, which won't
+ // trigger the lazy initialization process.
+ (void)[moduleData instance];
}
}
+ // From this point on, RCTDidInitializeModuleNotification notifications will
+ // be sent the first time a module is accessed.
+ _moduleSetupComplete = YES;
+
+ // Set up modules that require main thread init or constants export
for (RCTModuleData *moduleData in _moduleDataByID) {
- if (moduleData.hasInstance) {
- [moduleData finishSetupForInstance];
+ __weak RCTBatchedBridge *weakSelf = self;
+ if (moduleData.requiresMainThreadSetup) {
+ // Modules that need to be set up on the main thread cannot be initialized
+ // lazily when required without doing a dispatch_sync to the main thread,
+ // which can result in deadlock. To avoid this, we initialize all of these
+ // modules on the main thread in parallel with loading the JS code, so that
+ // they will already be available before they are ever required.
+ dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
+ if (weakSelf.valid) {
+ (void)[moduleData instance];
+ [moduleData gatherConstants];
+ }
+ });
+ } else if (moduleData.hasConstantsToExport) {
+ // Constants must be exported on the main thread, but module setup can
+ // be done on any queue
+ (void)[moduleData instance];
+ dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
+ if (weakSelf.valid) {
+ [moduleData gatherConstants];
+ }
+ });
}
}
-#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);
}
@@ -412,7 +453,7 @@ - (void)updateJSDisplayLinkState
- (void)injectJSONConfiguration:(NSString *)configJSON
onComplete:(void (^)(NSError *))onComplete
{
- if (!self.valid) {
+ if (!_valid) {
return;
}
@@ -423,7 +464,7 @@ - (void)injectJSONConfiguration:(NSString *)configJSON
- (void)executeSourceCode:(NSData *)sourceCode
{
- if (!self.valid || !_javaScriptExecutor) {
+ if (!_valid || !_javaScriptExecutor) {
return;
}
@@ -432,7 +473,7 @@ - (void)executeSourceCode:(NSData *)sourceCode
sourceCodeModule.scriptData = sourceCode;
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
- if (!self.isValid) {
+ if (!_valid) {
return;
}
@@ -461,7 +502,9 @@ - (void)executeSourceCode:(NSData *)sourceCode
if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
- [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path]];
+ NSString *host = self.bundleURL.host;
+ NSNumber *port = self.bundleURL.port;
+ [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path, host, RCTNullIfNil(port)]];
}
#endif
@@ -482,7 +525,7 @@ - (void)stopLoadingWithError:(NSError *)error
{
RCTAssertMainThread();
- if (!self.isValid || !self.loading) {
+ if (!_valid || !_loading) {
return;
}
@@ -562,7 +605,7 @@ - (void)dispatchBlock:(dispatch_block_t)block
- (void)invalidate
{
- if (!self.valid) {
+ if (!_valid) {
return;
}
@@ -607,7 +650,6 @@ - (void)invalidate
_moduleDataByName = nil;
_moduleDataByID = nil;
_moduleClassesByID = nil;
- _modulesByName_DEPRECATED = nil;
_frameUpdateObservers = nil;
_pendingCalls = nil;
@@ -763,7 +805,7 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module
RCTFatal(error);
}
- if (!self.isValid) {
+ if (!_valid) {
return;
}
[self handleBuffer:json batchEnded:YES];
@@ -785,7 +827,7 @@ - (void)_actuallyInvokeCallback:(NSNumber *)cbID
RCTFatal(error);
}
- if (!self.isValid) {
+ if (!_valid) {
return;
}
[self handleBuffer:json batchEnded:YES];
@@ -923,7 +965,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
methodID:(NSUInteger)methodID
params:(NSArray *)params
{
- if (!self.isValid) {
+ if (!_valid) {
return NO;
}
@@ -981,7 +1023,6 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
[self updateJSDisplayLinkState];
-
RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g');
RCT_PROFILE_END_EVENT(0, @"objc_call", nil);
@@ -1014,24 +1055,3 @@ - (BOOL)isBatchActive
}
@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+Private.h b/React/Base/RCTBridge+Private.h
index eedda8cd8b771b..19f33c1a3ffe93 100644
--- a/React/Base/RCTBridge+Private.h
+++ b/React/Base/RCTBridge+Private.h
@@ -54,6 +54,16 @@
@interface RCTBridge (RCTBatchedBridge)
+/**
+ * Used for unit testing, to detect when executor has been invalidated.
+ */
+@property (nonatomic, weak, readonly) id javaScriptExecutor;
+
+/**
+ * Used by RCTModuleData
+ */
+@property (nonatomic, assign, readonly) BOOL moduleSetupComplete;
+
/**
* Used by RCTModuleData to register the module for frame updates after it is
* lazily initialized.
diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
index 0b9fc69605605f..9666b2cbeed4b9 100644
--- a/React/Base/RCTBridge.h
+++ b/React/Base/RCTBridge.h
@@ -110,11 +110,25 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
* 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.
+ * potentially blocking both the calling thread and main thread for a short time.
*/
- (id)moduleForName:(NSString *)moduleName;
- (id)moduleForClass:(Class)moduleClass;
+/**
+ * Convenience method for retrieving all modules conforming to a given protocol.
+ * Modules will be sychronously instantiated if they haven't already been,
+ * potentially blocking both the calling thread and main thread for a short time.
+ */
+- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol;
+
+/**
+ * Test if a module has been initialized. Use this prior to calling
+ * `moduleForClass:` or `moduleForName:` if you do not want to cause the module
+ * to be instantiated if it hasn't been already.
+ */
+- (BOOL)moduleIsInitialized:(Class)moduleClass;
+
/**
* All registered bridge module classes.
*/
@@ -173,31 +187,3 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
- (BOOL)isBatchActive;
@end
-
-/**
- * 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, add an observer for the
- * `RCTDidInitializeModuleNotification` instead.
- */
-RCT_EXTERN NSString *const RCTDidCreateNativeModules
-__deprecated_msg("Use RCTDidInitializeModuleNotification 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");
-
-@end
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 650d3c958d6379..bd3cf74ef41fc2 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -15,6 +15,7 @@
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
+#import "RCTModuleData.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"
@@ -68,7 +69,8 @@ void RCTRegisterModule(Class moduleClass)
NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEV
- RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module classes must conform to RCTBridgeModule");
+ RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
+ @"Bridge module `%@` does not conform to RCTBridgeModule", cls);
#endif
NSString *name = [cls moduleName];
@@ -103,35 +105,6 @@ + (void)initialize
// Set up JS thread
RCTJSThread = (id)kCFNull;
-
-#if RCT_DEBUG
-
- // Set up module classes
- static unsigned int classCount;
- Class *classes = objc_copyClassList(&classCount);
-
- for (unsigned int i = 0; i < classCount; i++)
- {
- Class cls = classes[i];
- Class superclass = cls;
- while (superclass)
- {
- if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
- {
- if (![RCTModuleClasses containsObject:cls]) {
- RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
- "RCT_EXPORT_MODULE()?", cls);
- }
- break;
- }
- superclass = class_getSuperclass(superclass);
- }
- }
-
- free(classes);
-
-#endif
-
});
}
@@ -156,15 +129,13 @@ + (void)setCurrentBridge:(RCTBridge *)currentBridge
- (instancetype)initWithDelegate:(id)delegate
launchOptions:(NSDictionary *)launchOptions
{
- RCTAssertMainThread();
-
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_delegate = delegate;
_launchOptions = [launchOptions copy];
[self setUp];
- [self bindKeys];
+ RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO);
}
return self;
}
@@ -173,8 +144,6 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
- RCTAssertMainThread();
-
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
@@ -182,7 +151,7 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
- [self bindKeys];
+ RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO);
}
return self;
}
@@ -238,6 +207,25 @@ - (id)moduleForClass:(Class)moduleClass
return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
}
+- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
+{
+ NSMutableArray *modules = [NSMutableArray new];
+ for (Class moduleClass in self.moduleClasses) {
+ if ([moduleClass conformsToProtocol:protocol]) {
+ id module = [self moduleForClass:moduleClass];
+ if (module) {
+ [modules addObject:module];
+ }
+ }
+ }
+ return [modules copy];
+}
+
+- (BOOL)moduleIsInitialized:(Class)moduleClass
+{
+ return [self.batchedBridge moduleIsInitialized:moduleClass];
+}
+
- (RCTEventDispatcher *)eventDispatcher
{
return [self moduleForClass:[RCTEventDispatcher class]];
@@ -246,7 +234,7 @@ - (RCTEventDispatcher *)eventDispatcher
- (void)reload
{
/**
- * AnyThread
+ * Any thread
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
@@ -256,8 +244,6 @@ - (void)reload
- (void)setUp
{
- RCTAssertMainThread();
-
// Only update bundleURL from delegate if delegate bundleURL has changed
NSURL *previousDelegateURL = _delegateBundleURL;
_delegateBundleURL = [self.delegate sourceURLForBridge:self];
@@ -268,6 +254,11 @@ - (void)setUp
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
+ [self createBatchedBridge];
+}
+
+- (void)createBatchedBridge
+{
self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}
@@ -288,7 +279,7 @@ - (BOOL)isBatchActive
- (void)invalidate
{
- RCTBatchedBridge *batchedBridge = (RCTBatchedBridge *)self.batchedBridge;
+ RCTBridge *batchedBridge = self.batchedBridge;
self.batchedBridge = nil;
if (batchedBridge) {
@@ -309,14 +300,3 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
}
@end
-
-@implementation RCTBridge(Deprecated)
-
-NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
-
-- (NSDictionary *)modules
-{
- return self.batchedBridge.modules;
-}
-
-@end
diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h
index e29dc64a6e9688..d8606c8d953ace 100644
--- a/React/Base/RCTBridgeDelegate.h
+++ b/React/Base/RCTBridgeDelegate.h
@@ -47,10 +47,4 @@ typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source);
- (void)loadSourceForBridge:(RCTBridge *)bridge
withBlock:(RCTSourceLoadBlock)loadCallback;
-/**
- * Indicates whether Hot Loading is supported or not.
- * Note: this method will be removed soon, once Hot Loading is supported on OSS.
- */
-- (BOOL)bridgeSupportsHotLoading:(RCTBridge *)bridge;
-
@end
diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m
index efcc12659b846d..459d8a6660d543 100644
--- a/React/Base/RCTLog.m
+++ b/React/Base/RCTLog.m
@@ -16,6 +16,7 @@
#import "RCTBridge+Private.h"
#import "RCTDefines.h"
#import "RCTRedBox.h"
+#import "RCTUtils.h"
static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
@@ -226,8 +227,10 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
});
}
- // Log to JS executor
- [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
+ if (!RCTRunningInTestEnvironment()) {
+ // Log to JS executor
+ [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
+ }
#endif
diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h
index ec922c8042c9f2..7bd297739f6886 100644
--- a/React/Base/RCTModuleData.h
+++ b/React/Base/RCTModuleData.h
@@ -21,21 +21,15 @@
bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithModuleInstance:(id)instance
- bridge:(RCTBridge *)bridge;
+ bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
/**
- * Sets the bridge for the module instance. This is only needed when using the
- * `initWithModuleInstance:bridge:` constructor. Otherwise, the bridge will be set
- * automatically when the module is first accessed.
+ * Calls `constantsToExport` on the module and stores the result. Note that
+ * this will init the module if it has not already been created. This method
+ * can be called on any thread, but may block the main thread briefly if the
+ * module implements `constantsToExport`.
*/
-- (void)setBridgeForInstance;
-
-/**
- * Sets the methodQueue and performs the remaining setup for the module. This is
- * only needed when using the `initWithModuleInstance:bridge:` constructor.
- * Otherwise it will be done automatically when the module is first accessed.
- */
-- (void)finishSetupForInstance;
+- (void)gatherConstants;
@property (nonatomic, strong, readonly) Class moduleClass;
@property (nonatomic, copy, readonly) NSString *name;
@@ -51,6 +45,16 @@
*/
@property (nonatomic, assign, readonly) BOOL hasInstance;
+/**
+ * Returns YES if module instance must be created on the main thread.
+ */
+@property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup;
+
+/**
+ * Returns YES if module has constants to export.
+ */
+@property (nonatomic, assign, readonly) BOOL hasConstantsToExport;
+
/**
* 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
@@ -65,9 +69,8 @@
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
/**
- * 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`.
+ * Returns the module config. Calls `gatherConstants` internally, so the same
+ * usage caveats apply.
*/
@property (nonatomic, copy, readonly) NSArray *config;
diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m
index 78ef8471e6f6a4..bf2f7f8461306c 100644
--- a/React/Base/RCTModuleData.m
+++ b/React/Base/RCTModuleData.m
@@ -28,17 +28,40 @@ @implementation RCTModuleData
@synthesize instance = _instance;
@synthesize methodQueue = _methodQueue;
+- (void)setUp
+{
+ _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
+ _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
+
+ _instanceLock = [NSLock new];
+
+ static IMP objectInitMethod;
+ static SEL setBridgeSelector;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
+ setBridgeSelector = NSSelectorFromString(@"setBridge:");
+ });
+
+ // If a module overrides `init`, `setBridge:` then we must assume that it
+ // expects for both of those methods to be called on the main thread, because
+ // they may need to access UIKit.
+ _requiresMainThreadSetup =
+ [_moduleClass instancesRespondToSelector:setBridgeSelector] ||
+ (!_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod);
+
+ // If a module overrides `constantsToExport` then we must assume that it
+ // must be called on the main thread, because it may need to access UIKit.
+ _hasConstantsToExport = RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport));
+}
+
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
- _moduleClass = moduleClass;
_bridge = bridge;
-
- _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
- _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
-
- _instanceLock = [NSLock new];
+ _moduleClass = moduleClass;
+ [self setUp];
}
return self;
}
@@ -46,8 +69,11 @@ - (instancetype)initWithModuleClass:(Class)moduleClass
- (instancetype)initWithModuleInstance:(id)instance
bridge:(RCTBridge *)bridge
{
- if ((self = [self initWithModuleClass:[instance class] bridge:bridge])) {
+ if ((self = [super init])) {
+ _bridge = bridge;
_instance = instance;
+ _moduleClass = [instance class];
+ [self setUp];
}
return self;
}
@@ -56,9 +82,47 @@ - (instancetype)initWithModuleInstance:(id)instance
#pragma mark - private setup methods
+- (void)setUpInstanceAndBridge
+{
+ [_instanceLock lock];
+ if (!_setupComplete && _bridge.valid) {
+ if (!_instance) {
+ if (RCT_DEBUG && _requiresMainThreadSetup) {
+ RCTAssertMainThread();
+ }
+ _instance = [_moduleClass new];
+ if (!_instance) {
+ // Module init returned nil, probably because automatic instantatiation
+ // of the module is not supported, and it is supposed to be passed in to
+ // the bridge constructor. Mark setup complete to avoid doing more work.
+ _setupComplete = YES;
+ RCTLogWarn(@"The module %@ is returning nil from its constructor. You "
+ "may need to instantiate it yourself and pass it into the "
+ "bridge.", _moduleClass);
+ }
+ }
+ // 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];
+ }
+ [_instanceLock unlock];
+
+ // This is called outside of the lock in order to prevent deadlock issues
+ // because the logic in `setUpMethodQueue` can cause `moduleData.instance`
+ // to be accessed re-entrantly.
+ [self setUpMethodQueue];
+
+ // This is called outside of the lock in order to prevent deadlock issues
+ // because the logic in `finishSetupForInstance` can cause
+ // `moduleData.instance` to be accessed re-entrantly.
+ if (_bridge.moduleSetupComplete) {
+ [self finishSetupForInstance];
+ }
+}
+
- (void)setBridgeForInstance
{
- RCTAssert(_instance, @"setBridgeForInstance called before %@ initialized", self.name);
if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
@try {
[(id)_instance setValue:_bridge forKey:@"bridge"];
@@ -73,30 +137,23 @@ - (void)setBridgeForInstance
- (void)finishSetupForInstance
{
- if (!_setupComplete) {
+ if (!_setupComplete && _instance) {
_setupComplete = YES;
- [self setUpMethodQueue];
[_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
object:_bridge
userInfo:@{@"module": _instance}];
-
- if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) {
- RCTAssertMainThread();
- _constantsToExport = [_instance constantsToExport];
- }
}
}
- (void)setUpMethodQueue
{
- if (!_methodQueue) {
- RCTAssert(_instance, @"setUpMethodQueue called before %@ initialized", self.name);
+ if (_instance && !_methodQueue && _bridge.valid) {
BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
- if (implementsMethodQueue) {
+ if (implementsMethodQueue && _bridge.valid) {
_methodQueue = _instance.methodQueue;
}
- if (!_methodQueue) {
+ if (!_methodQueue && _bridge.valid) {
// Create new queue (store queueName, as it isn't retained by dispatch_queue)
_queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name];
@@ -127,20 +184,19 @@ - (BOOL)hasInstance
- (id)instance
{
- [_instanceLock lock];
if (!_setupComplete) {
- if (!_instance) {
- _instance = [_moduleClass new];
+ if (_requiresMainThreadSetup) {
+ // The chances of deadlock here are low, because module init very rarely
+ // calls out to other threads, however we can't control when a module might
+ // get accessed by client code during bridge setup, and a very low risk of
+ // deadlock is better than a fairly high risk of an assertion being thrown.
+ RCTExecuteOnMainThread(^{
+ [self setUpInstanceAndBridge];
+ }, YES);
+ } else {
+ [self setUpInstanceAndBridge];
}
- // 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];
}
- [_instanceLock unlock];
-
- [self finishSetupForInstance];
-
return _instance;
}
@@ -185,10 +241,21 @@ - (NSString *)name
return _methods;
}
+- (void)gatherConstants
+{
+ if (_hasConstantsToExport && !_constantsToExport) {
+ (void)[self instance];
+ RCTExecuteOnMainThread(^{
+ _constantsToExport = [_instance constantsToExport] ?: @{};
+ }, YES);
+ }
+}
+
- (NSArray *)config
{
+ [self gatherConstants];
__block NSDictionary *constants = _constantsToExport;
- _constantsToExport = nil; // Not needed any more
+ _constantsToExport = nil; // Not needed anymore
if (constants.count == 0 && self.methods.count == 0) {
return (id)kCFNull; // Nothing to export
@@ -222,7 +289,7 @@ - (NSArray *)config
- (dispatch_queue_t)methodQueue
{
- [self instance];
+ (void)[self instance];
return _methodQueue;
}
@@ -231,4 +298,9 @@ - (void)invalidate
_methodQueue = nil;
}
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name];
+}
+
@end
diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h
index ba1fe8712c536c..de59089747193e 100644
--- a/React/Base/RCTRootView.h
+++ b/React/Base/RCTRootView.h
@@ -83,13 +83,6 @@ extern NSString *const RCTContentDidAppearNotification;
*/
@property (nonatomic, copy, readwrite) NSDictionary *appProperties;
-/**
- * The class of the RCTJavaScriptExecutor to use with this view.
- * If not specified, it will default to using RCTJSCExecutor.
- * Changes will take effect next time the bundle is reloaded.
- */
-@property (nonatomic, strong) Class executorClass;
-
/**
* The size flexibility mode of the root view.
*/
diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m
index d5726a2bcfa3c5..d571a0690b2d80 100644
--- a/React/Base/RCTRootView.m
+++ b/React/Base/RCTRootView.m
@@ -38,10 +38,11 @@ - (NSNumber *)allocateRootTag;
@interface RCTRootContentView : RCTView
@property (nonatomic, readonly) BOOL contentHasAppeared;
-
@property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler;
-- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithFrame:(CGRect)frame
+ bridge:(RCTBridge *)bridge
+ reactTag:(NSNumber *)reactTag NS_DESIGNATED_INITIALIZER;
@end
@@ -74,6 +75,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_loadingViewFadeDuration = 0.25;
_sizeFlexibility = RCTRootViewSizeFlexibilityNone;
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(bridgeDidReload)
+ name:RCTJavaScriptWillStartLoadingNotification
+ object:_bridge];
+
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
@@ -83,6 +89,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
+
if (!_bridge.loading) {
[self bundleFinishedLoading:_bridge.batchedBridge];
}
@@ -165,6 +172,29 @@ - (void)hideLoadingView
}
}
+- (NSNumber *)reactTag
+{
+ RCTAssertMainThread();
+ if (!super.reactTag) {
+ /**
+ * Every root view that is created must have a unique react tag.
+ * Numbering of these tags goes from 1, 11, 21, 31, etc
+ *
+ * NOTE: Since the bridge persists, the RootViews might be reused, so the
+ * react tag must be re-assigned every time a new UIManager is created.
+ */
+ self.reactTag = [_bridge.uiManager allocateRootTag];
+ }
+ return super.reactTag;
+}
+
+- (void)bridgeDidReload
+{
+ RCTAssertMainThread();
+ // Clear the reactTag so it can be re-assigned
+ self.reactTag = nil;
+}
+
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTAssertMainThread();
@@ -179,7 +209,9 @@ - (void)bundleFinishedLoading:(RCTBridge *)bridge
}
[_contentView removeFromSuperview];
- _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge];
+ _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
+ bridge:bridge
+ reactTag:self.reactTag];
[self runApplication:bridge];
_contentView.backgroundColor = self.backgroundColor;
@@ -247,11 +279,6 @@ - (void)setIntrinsicSize:(CGSize)intrinsicSize
[_delegate rootViewDidChangeIntrinsicSize:self];
}
-- (NSNumber *)reactTag
-{
- return _contentView.reactTag;
-}
-
- (void)contentViewInvalidated
{
[_contentView removeFromSuperview];
@@ -291,10 +318,14 @@ @implementation RCTRootContentView
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
+ reactTag:(NSNumber *)reactTag
{
if ((self = [super initWithFrame:frame])) {
_bridge = bridge;
- [self setUp];
+ self.reactTag = reactTag;
+ _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
+ [self addGestureRecognizer:_touchHandler];
+ [_bridge.uiManager registerRootView:self];
self.layer.backgroundColor = NULL;
}
return self;
@@ -337,21 +368,6 @@ - (UIColor *)backgroundColor
return _backgroundColor;
}
-- (void)setUp
-{
- /**
- * Every root view that is created must have a unique react tag.
- * Numbering of these tags goes from 1, 11, 21, 31, etc
- *
- * NOTE: Since the bridge persists, the RootViews might be reused, so now
- * the react tag is assigned every time we load new content.
- */
- self.reactTag = [_bridge.uiManager allocateRootTag];
- _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
- [self addGestureRecognizer:_touchHandler];
- [_bridge.uiManager registerRootView:self];
-}
-
- (void)invalidate
{
if (self.userInteractionEnabled) {
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 9d63bdbf3379d5..ea319814588cf2 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -109,6 +109,9 @@ RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL);
// Determines if a given image URL actually refers to an XCAsset
RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL);
+// Creates a new, unique temporary file path with the specified extension
+RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *__nullable extension, NSError **error);
+
// 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 68e3a15e520f28..c693ea1a702ef2 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -589,6 +589,51 @@ BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL)
return YES;
}
+RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *extension, NSError **error)
+{
+ static NSError *setupError = nil;
+ static NSString *directory;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
+ // If the temporary directory already exists, we'll delete it to ensure
+ // that temp files from the previous run have all been deleted. This is not
+ // a security measure, it simply prevents the temp directory from using too
+ // much space, as the circumstances under which iOS clears it automatically
+ // are not well-defined.
+ NSFileManager *fileManager = [NSFileManager new];
+ if ([fileManager fileExistsAtPath:directory]) {
+ [fileManager removeItemAtPath:directory error:NULL];
+ }
+ if (![fileManager fileExistsAtPath:directory]) {
+ NSError *localError = nil;
+ if (![fileManager createDirectoryAtPath:directory
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&localError]) {
+ // This is bad
+ RCTLogError(@"Failed to create temporary directory: %@", localError);
+ setupError = localError;
+ directory = nil;
+ }
+ }
+ });
+
+ if (!directory || setupError) {
+ if (error) {
+ *error = setupError;
+ }
+ return nil;
+ }
+
+ // Append a unique filename
+ NSString *filename = [NSUUID new].UUIDString;
+ if (extension) {
+ filename = [filename stringByAppendingPathExtension:extension];
+ }
+ return [directory stringByAppendingPathComponent:filename];
+}
+
static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
{
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
diff --git a/React/Base/RCTWebSocketProxy.h b/React/Base/RCTWebSocketProxy.h
new file mode 100644
index 00000000000000..e823c3c8fe68a4
--- /dev/null
+++ b/React/Base/RCTWebSocketProxy.h
@@ -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.
+ */
+
+#import "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+#import "RCTWebSocketProxyDelegate.h"
+
+@protocol RCTWebSocketProxy
+
++ (instancetype)sharedInstance;
+
+- (void)setDelegate:(id)delegate forURL:(NSURL *)url;
+
+- (instancetype) init __attribute__((unavailable("init not available, call sharedInstance instead")));
+
+@end
+
+#endif
diff --git a/React/Base/RCTWebSocketProxyDelegate.h b/React/Base/RCTWebSocketProxyDelegate.h
new file mode 100644
index 00000000000000..f668bf1610a344
--- /dev/null
+++ b/React/Base/RCTWebSocketProxyDelegate.h
@@ -0,0 +1,20 @@
+/**
+ * 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 "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+@protocol RCTWebSocketProxy;
+
+@protocol RCTWebSocketProxyDelegate
+- (void)socketProxy:(id)sender didReceiveMessage:(NSDictionary *)message;
+@end
+
+#endif
diff --git a/React/Executors/RCTJSCExecutor.h b/React/Executors/RCTJSCExecutor.h
index cbcf224fcd8a24..f8beaa67373f5a 100644
--- a/React/Executors/RCTJSCExecutor.h
+++ b/React/Executors/RCTJSCExecutor.h
@@ -11,6 +11,20 @@
#import "RCTJavaScriptExecutor.h"
+/**
+ * Default name for the JS thread
+ */
+RCT_EXTERN NSString *const RCTJSCThreadName;
+
+/**
+ * This notification fires on the JS thread immediately after a `JSContext`
+ * is fully initialized, but before the JS bundle has been loaded. The object
+ * of this notification is the `JSContext`. Native modules should listen for
+ * notification only if they need to install custom functionality into the
+ * context. Note that this notification won't fire when debugging in Chrome.
+ */
+RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
+
/**
* Uses a JavaScriptCore context as the execution engine.
*/
diff --git a/React/Executors/RCTJSCExecutor.m b/React/Executors/RCTJSCExecutor.m
index f96d2992a9c372..2a0e5f137998d0 100644
--- a/React/Executors/RCTJSCExecutor.m
+++ b/React/Executors/RCTJSCExecutor.m
@@ -26,6 +26,10 @@
#import "RCTRedBox.h"
#import "RCTSourceCode.h"
+NSString *const RCTJSCThreadName = @"com.facebook.React.JavaScript";
+
+NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
+
static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
@interface RCTJavaScriptContext : NSObject
@@ -172,7 +176,7 @@ - (instancetype)init
NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
selector:@selector(runRunLoopThread)
object:nil];
- javaScriptThread.name = @"com.facebook.React.JavaScript";
+ javaScriptThread.name = RCTJSCThreadName;
if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
[javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
@@ -330,7 +334,16 @@ - (void)setUp
}];
[self executeBlockOnJavaScriptQueue:^{
- RCTInstallJSCProfiler(_bridge, self.context.ctx);
+ RCTJSCExecutor *strongSelf = weakSelf;
+ if (!strongSelf.valid) {
+ return;
+ }
+
+ JSContext *context = strongSelf.context.context;
+ RCTInstallJSCProfiler(_bridge, context.JSGlobalContextRef);
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
+ object:context];
}];
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
@@ -339,6 +352,20 @@ - (void)setUp
name:event
object:nil];
}
+
+ // Inject handler used by HMR
+ [self addSynchronousHookWithName:@"nativeInjectHMRUpdate" usingBlock:^(NSString *sourceCode, NSString *sourceCodeURL) {
+ RCTJSCExecutor *strongSelf = weakSelf;
+ if (!strongSelf.valid) {
+ return;
+ }
+
+ JSStringRef execJSString = JSStringCreateWithUTF8CString(sourceCode.UTF8String);
+ JSStringRef jsURL = JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
+ JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, NULL);
+ JSStringRelease(jsURL);
+ JSStringRelease(execJSString);
+ }];
#endif
}
@@ -361,6 +388,11 @@ - (void)invalidate
#if RCT_DEV
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
+}
+
+- (void)dealloc
+{
+ [self invalidate];
[_context performSelector:@selector(invalidate)
onThread:_javaScriptThread
@@ -369,11 +401,6 @@ - (void)invalidate
_context = nil;
}
-- (void)dealloc
-{
- [self invalidate];
-}
-
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
{
// TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
@@ -511,21 +538,6 @@ - (void)executeApplicationScript:(NSData *)script
RCTAssertParam(sourceURL);
__weak RCTJSCExecutor *weakSelf = self;
-#if RCT_DEV
- _context.context[@"__injectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
- RCTJSCExecutor *strongSelf = weakSelf;
-
- if (!strongSelf) {
- return;
- }
-
- JSStringRef execJSString = JSStringCreateWithUTF8CString(sourceCode.UTF8String);
- JSStringRef jsURL = JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
- JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, NULL);
- JSStringRelease(jsURL);
- JSStringRelease(execJSString);
- };
-#endif
[self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
RCTJSCExecutor *strongSelf = weakSelf;
diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m
index b2e7fe0a3ae055..7ab8f49e71b263 100644
--- a/React/Modules/RCTAlertManager.m
+++ b/React/Modules/RCTAlertManager.m
@@ -75,7 +75,6 @@ - (void)invalidate
NSString *message = [RCTConvert NSString:args[@"message"]];
UIAlertViewStyle type = [RCTConvert UIAlertViewStyle:args[@"type"]];
NSArray *buttons = [RCTConvert NSDictionaryArray:args[@"buttons"]];
- NSString *defaultValue = [RCTConvert NSString:args[@"defaultValue"]];
NSString *cancelButtonKey = [RCTConvert NSString:args[@"cancelButtonKey"]];
NSString *destructiveButtonKey = [RCTConvert NSString:args[@"destructiveButtonKey"]];
@@ -113,6 +112,7 @@ - (void)invalidate
alertView.message = message;
if (type != UIAlertViewStyleDefault) {
+ NSString *defaultValue = [RCTConvert NSString:args[@"defaultValue"]];
[alertView textFieldAtIndex:0].text = defaultValue;
}
diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m
index 6a22ae626f0e18..a71e2b3a8c7f6d 100644
--- a/React/Modules/RCTAppState.m
+++ b/React/Modules/RCTAppState.m
@@ -21,8 +21,7 @@
dispatch_once(&onceToken, ^{
states = @{
@(UIApplicationStateActive): @"active",
- @(UIApplicationStateBackground): @"background",
- @(UIApplicationStateInactive): @"inactive"
+ @(UIApplicationStateBackground): @"background"
};
});
@@ -53,9 +52,12 @@ - (void)setBridge:(RCTBridge *)bridge
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification,
- UIApplicationDidFinishLaunchingNotification]) {
+ UIApplicationDidFinishLaunchingNotification,
+ UIApplicationWillResignActiveNotification,
+ UIApplicationWillEnterForegroundNotification]) {
+
[[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(handleAppStateDidChange)
+ selector:@selector(handleAppStateDidChange:)
name:name
object:nil];
}
@@ -79,9 +81,18 @@ - (void)dealloc
#pragma mark - App Notification Methods
-- (void)handleAppStateDidChange
+- (void)handleAppStateDidChange:(NSNotification *)notification
{
- NSString *newState = RCTCurrentAppBackgroundState();
+ NSString *newState;
+
+ if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) {
+ newState = @"inactive";
+ } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) {
+ newState = @"active";
+ } else {
+ newState = RCTCurrentAppBackgroundState();
+ }
+
if (![newState isEqualToString:_lastKnownState]) {
_lastKnownState = newState;
[_bridge.eventDispatcher sendDeviceEventWithName:@"appStateDidChange"
diff --git a/React/Modules/RCTClipboard.m b/React/Modules/RCTClipboard.m
index 0a0bf14816f868..8d059c4a374555 100644
--- a/React/Modules/RCTClipboard.m
+++ b/React/Modules/RCTClipboard.m
@@ -26,14 +26,14 @@ - (dispatch_queue_t)methodQueue
RCT_EXPORT_METHOD(setString:(NSString *)content)
{
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
- clipboard.string = content;
+ clipboard.string = (content ? : @"");
}
RCT_EXPORT_METHOD(getString:(RCTPromiseResolveBlock)resolve
rejecter:(__unused RCTPromiseRejectBlock)reject)
{
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
- resolve(@[RCTNullIfNil(clipboard.string)]);
+ resolve((clipboard.string ? : @""));
}
@end
diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m
index 5c799791cf2abb..708454754fd50f 100644
--- a/React/Modules/RCTDevLoadingView.m
+++ b/React/Modules/RCTDevLoadingView.m
@@ -13,6 +13,7 @@
#import "RCTDevLoadingView.h"
#import "RCTDefines.h"
#import "RCTUtils.h"
+#import "RCTModalHostViewController.h"
#if RCT_DEV
@@ -68,6 +69,9 @@ - (void)setBridge:(RCTBridge *)bridge
_window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)];
_window.windowLevel = UIWindowLevelStatusBar + 1;
+ // set a root VC so rotation is supported
+ _window.rootViewController = [RCTModalHostViewController new];
+
_label = [[UILabel alloc] initWithFrame:_window.bounds];
_label.font = [UIFont systemFontOfSize:12.0];
_label.textAlignment = NSTextAlignmentCenter;
diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m
index c3a47cb274976d..92cfaaacd6fd07 100644
--- a/React/Modules/RCTDevMenu.m
+++ b/React/Modules/RCTDevMenu.m
@@ -19,6 +19,7 @@
#import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
+#import "RCTWebSocketProxy.h"
#if RCT_DEV
@@ -117,7 +118,7 @@ - (void)callHandler
@end
-@interface RCTDevMenu ()
+@interface RCTDevMenu ()
@property (nonatomic, strong) Class executorClass;
@@ -194,6 +195,7 @@ - (instancetype)init
// Delay setup until after Bridge init
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateSettings:_settings];
+ [weakSelf connectPackager];
});
#if TARGET_IPHONE_SIMULATOR
@@ -228,6 +230,54 @@ - (instancetype)init
return self;
}
+- (NSURL *)packagerURL
+{
+ NSString *host = [_bridge.bundleURL host];
+ if (!host) {
+ return nil;
+ }
+
+ NSString *scheme = [_bridge.bundleURL scheme];
+ NSNumber *port = [_bridge.bundleURL port];
+ return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=shell", scheme, host, port]];
+}
+
+// TODO: Move non-UI logic into separate RCTDevSettings module
+- (void)connectPackager
+{
+ Class webSocketManagerClass = NSClassFromString(@"RCTWebSocketManager");
+ id webSocketManager = (id )[webSocketManagerClass sharedInstance];
+ NSURL *url = [self packagerURL];
+ if (url) {
+ [webSocketManager setDelegate:self forURL:url];
+ }
+}
+
+- (BOOL)isSupportedVersion:(NSNumber *)version
+{
+ NSArray *const kSupportedVersions = @[ @1 ];
+ return [kSupportedVersions containsObject:version];
+}
+
+- (void)socketProxy:(__unused id)sender didReceiveMessage:(NSDictionary *)message
+{
+ if ([self isSupportedVersion:message[@"version"]]) {
+ [self processTarget:message[@"target"] action:message[@"action"] options:message[@"options"]];
+ }
+}
+
+- (void)processTarget:(NSString *)target action:(NSString *)action options:(NSDictionary *)options
+{
+ if ([target isEqualToString:@"bridge"]) {
+ if ([action isEqualToString:@"reload"]) {
+ if ([options[@"debug"] boolValue]) {
+ _bridge.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
+ }
+ [_bridge reload];
+ }
+ }
+}
+
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
@@ -319,7 +369,7 @@ - (void)jsLoaded:(NSNotification *)notification
if (!sourceCodeModule.scriptURL) {
if (!sourceCodeModule) {
RCTLogWarn(@"RCTSourceCode module not found");
- } else {
+ } else if (!RCTRunningInTestEnvironment()) {
RCTLogWarn(@"RCTSourceCode module scriptURL has not been set");
}
} else if (!sourceCodeModule.scriptURL.fileURL) {
@@ -422,7 +472,7 @@ - (void)addItem:(RCTDevMenuItem *)item
}
if ([self hotLoadingAvailable]) {
- NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Loading" : @"Enable Hot Loading";
+ NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Reloading" : @"Enable Hot Reloading";
[items addObject:[RCTDevMenuItem buttonItemWithTitle:hotLoadingTitle handler:^{
weakSelf.hotLoadingEnabled = !_hotLoadingEnabled;
}]];
@@ -531,9 +581,7 @@ - (void)setLiveReloadEnabled:(BOOL)enabled
- (BOOL)hotLoadingAvailable
{
- return !_bridge.bundleURL.fileURL // Only works when running from server
- && [_bridge.delegate respondsToSelector:@selector(bridgeSupportsHotLoading:)]
- && [_bridge.delegate bridgeSupportsHotLoading:_bridge];
+ return _bridge.bundleURL && !_bridge.bundleURL.fileURL; // Only works when running from server
}
- (void)setHotLoadingEnabled:(BOOL)enabled
diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m
index e538fa5d0c9608..f8d96fac68e88d 100644
--- a/React/Modules/RCTSourceCode.m
+++ b/React/Modules/RCTSourceCode.m
@@ -32,7 +32,7 @@ - (void)setScriptText:(NSString *)scriptText {}
if (RCT_DEV && self.scriptData && self.scriptURL) {
NSString *scriptText = [[NSString alloc] initWithData:self.scriptData encoding:NSUTF8StringEncoding];
- resolve(@[@{@"text": scriptText, @"url": self.scriptURL.absoluteString}]);
+ resolve(@{@"text": scriptText, @"url": self.scriptURL.absoluteString});
} else {
reject(RCTErrorUnavailable, nil, RCTErrorWithMessage(@"Source code is not available"));
}
diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m
index 3aa084d3073f0f..8e2f865bd726d6 100644
--- a/React/Modules/RCTTiming.m
+++ b/React/Modules/RCTTiming.m
@@ -174,15 +174,7 @@ - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
return;
}
- NSTimeInterval jsSchedulingOverhead = -jsSchedulingTime.timeIntervalSinceNow;
- if (jsSchedulingOverhead < 0) {
- RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
-
- /**
- * Probably debugging on device, set to 0 so we don't ignore the interval
- */
- jsSchedulingOverhead = 0;
- }
+ NSTimeInterval jsSchedulingOverhead = MAX(-jsSchedulingTime.timeIntervalSinceNow, 0);
NSTimeInterval targetTime = jsDuration - jsSchedulingOverhead;
if (jsDuration < 0.018) { // Make sure short intervals run each frame
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index bdcf2e2c53502c..8a14a4a02eee7d 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -60,6 +60,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
*/
- (void)setFrame:(CGRect)frame forView:(UIView *)view;
+/**
+ * Set the natural size of a view, which is used when no explicit size is set.
+ * Use UIViewNoIntrinsicMetric to ignore a dimension.
+ */
+- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view;
+
/**
* Update the background color of a root view. This is usually triggered by
* manually setting the background color of the root view with native code.
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index d98cf73363df8d..d0db1c40aa5bb0 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -375,17 +375,40 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view
NSNumber *reactTag = view.reactTag;
dispatch_async(_shadowQueue, ^{
- RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
- RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
+ RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
+ RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag);
- if (RCTIsReactRootView(reactTag)) {
- rootShadowView.frame = frame;
- rootShadowView.sizeFlexibility = sizeFlexibility;
- } else {
- rootShadowView.frame = frame;
+ BOOL dirtyLayout = NO;
+
+ if (!CGRectEqualToRect(frame, shadowView.frame)) {
+ shadowView.frame = frame;
+ dirtyLayout = YES;
+ }
+
+ // Trigger re-layout when size flexibility changes, as the root view might grow or
+ // shrink in the flexible dimensions.
+ if (RCTIsReactRootView(reactTag) && shadowView.sizeFlexibility != sizeFlexibility) {
+ shadowView.sizeFlexibility = sizeFlexibility;
+ dirtyLayout = YES;
+ }
+
+ if (dirtyLayout) {
+ [shadowView dirtyLayout];
+ [self batchDidComplete];
}
+ });
+}
+
+- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view
+{
+ RCTAssertMainThread();
+
+ NSNumber *reactTag = view.reactTag;
+ dispatch_async(_shadowQueue, ^{
+ RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
+ RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag);
- [rootShadowView dirtyLayout];
+ shadowView.intrinsicContentSize = size;
[self batchDidComplete];
});
@@ -955,7 +978,7 @@ - (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)
for (RCTComponentData *componentData in _componentDataByName.allValues) {
- RCTViewManagerUIBlock uiBlock = [componentData.manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
+ RCTViewManagerUIBlock uiBlock = [componentData uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
[self addUIBlock:uiBlock];
}
@@ -1045,18 +1068,16 @@ - (void)setNeedsLayout
callback(@[]);
return;
}
- CGRect frame = view.frame;
+ // If in a , rootView will be the root of the modal container.
UIView *rootView = view;
- while (rootView && ![rootView isReactRootView]) {
+ while (rootView.superview && ![rootView isReactRootView]) {
rootView = rootView.superview;
}
- // TODO: this doesn't work because sometimes view is inside a modal window
- // RCTAssert([rootView isReactRootView], @"React view is not inside a React root view");
-
// By convention, all coordinates, whether they be touch coordinates, or
// measurement coordinates are with respect to the root view.
+ CGRect frame = view.frame;
CGPoint pagePoint = [view.superview convertPoint:frame.origin toView:rootView];
callback(@[
@@ -1070,6 +1091,29 @@ - (void)setNeedsLayout
}];
}
+RCT_EXPORT_METHOD(measureInWindow:(nonnull NSNumber *)reactTag
+ callback:(RCTResponseSenderBlock)callback)
+{
+ [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ UIView *view = viewRegistry[reactTag];
+ if (!view) {
+ // this view was probably collapsed out
+ RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
+ callback(@[]);
+ return;
+ }
+
+ // Return frame coordinates in window
+ CGRect windowFrame = [view.window convertRect:view.frame fromView:view.superview];
+ callback(@[
+ @(windowFrame.origin.x),
+ @(windowFrame.origin.y),
+ @(windowFrame.size.width),
+ @(windowFrame.size.height),
+ ]);
+ }];
+}
+
static void RCTMeasureLayout(RCTShadowView *view,
RCTShadowView *ancestor,
RCTResponseSenderBlock callback)
@@ -1182,6 +1226,73 @@ static void RCTMeasureLayout(RCTShadowView *view,
callback(@[results]);
}
+RCT_EXPORT_METHOD(takeSnapshot:(id /* NSString or NSNumber */)target
+ withOptions:(NSDictionary *)options
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+
+ // Get view
+ UIView *view;
+ if (target == nil || [target isEqual:@"window"]) {
+ view = RCTKeyWindow();
+ } else if ([target isKindOfClass:[NSNumber class]]) {
+ view = viewRegistry[target];
+ if (!view) {
+ RCTLogError(@"No view found with reactTag: %@", target);
+ return;
+ }
+ }
+
+ // Get options
+ CGSize size = [RCTConvert CGSize:options];
+ NSString *format = [RCTConvert NSString:options[@"format"] ?: @"png"];
+
+ // Capture image
+ if (size.width < 0.1 || size.height < 0.1) {
+ size = view.bounds.size;
+ }
+ UIGraphicsBeginImageContextWithOptions(size, NO, 0);
+ BOOL success = [view drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ if (!success || !image) {
+ reject(RCTErrorUnspecified, @"Failed to capture view snapshot.", nil);
+ return;
+ }
+
+ // Convert image to data (on a background thread)
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+ NSData *data;
+ if ([format isEqualToString:@"png"]) {
+ data = UIImagePNGRepresentation(image);
+ } else if ([format isEqualToString:@"jpeg"]) {
+ CGFloat quality = [RCTConvert CGFloat:options[@"quality"] ?: @1];
+ data = UIImageJPEGRepresentation(image, quality);
+ } else {
+ RCTLogError(@"Unsupported image format: %@", format);
+ return;
+ }
+
+ // Save to a temp file
+ NSError *error = nil;
+ NSString *tempFilePath = RCTTempFilePath(format, &error);
+ if (tempFilePath) {
+ if ([data writeToFile:tempFilePath options:(NSDataWritingOptions)0 error:&error]) {
+ resolve(tempFilePath);
+ return;
+ }
+ }
+
+ // If we reached here, something went wrong
+ reject(RCTErrorUnspecified, error.localizedDescription, error);
+ });
+ }];
+}
+
/**
* JS sets what *it* considers to be the responder. Later, scroll views can use
* this in order to determine if scrolling is appropriate.
@@ -1265,10 +1376,6 @@ static void RCTMeasureLayout(RCTShadowView *view,
@"height": @(RCTScreenSize().height),
@"scale": @(RCTScreenScale()),
},
- @"modalFullscreenView": @{
- @"width": @(RCTScreenSize().width),
- @"height": @(RCTScreenSize().height),
- },
},
}];
diff --git a/React/Profiler/RCTJSCProfiler.m b/React/Profiler/RCTJSCProfiler.m
index 8ad34b62682e8a..fc0a21fff8bb7e 100644
--- a/React/Profiler/RCTJSCProfiler.m
+++ b/React/Profiler/RCTJSCProfiler.m
@@ -99,7 +99,9 @@ void RCTJSCProfilerStart(JSContextRef ctx)
if (isProfiling) {
NSString *filename = [NSString stringWithFormat:@"cpu_profile_%ld.json", (long)CFAbsoluteTimeGetCurrent()];
outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
- RCTNativeProfilerEnd(ctx, JSCProfileName, outputFile.UTF8String);
+ if (RCTNativeProfilerEnd) {
+ 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.");
diff --git a/React/Profiler/RCTPerfMonitor.m b/React/Profiler/RCTPerfMonitor.m
index 93a6fd70a47d4e..caf660385e8ba7 100644
--- a/React/Profiler/RCTPerfMonitor.m
+++ b/React/Profiler/RCTPerfMonitor.m
@@ -501,12 +501,13 @@ - (void)threadUpdate:(CADisplayLink *)displayLink
- (void)loadPerformanceLoggerData
{
- NSMutableArray *data = [NSMutableArray new];
- NSArray *times = RCTPerformanceLoggerOutput();
NSUInteger i = 0;
+ NSMutableArray *data = [NSMutableArray new];
+ NSArray *values = RCTPerformanceLoggerOutput();
for (NSString *label in RCTPerformanceLoggerLabels()) {
- [data addObject:[NSString stringWithFormat:@"%@: %lldus", label,
- [times[i+1] longLongValue] - [times[i] longLongValue]]];
+ long long value = values[i+1].longLongValue - values[i].longLongValue;
+ NSString *unit = [label isEqualToString:@"BundleSize"] ? @"b" : @"ms";
+ [data addObject:[NSString stringWithFormat:@"%@: %lld%@", label, value, unit]];
i += 2;
}
_perfLoggerMarks = [data copy];
diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m
index 32f63e91658c97..14dbefb1f1406e 100644
--- a/React/Profiler/RCTProfile.m
+++ b/React/Profiler/RCTProfile.m
@@ -27,6 +27,7 @@
#import "RCTModuleData.h"
#import "RCTUtils.h"
#import "RCTUIManager.h"
+#import "RCTJSCExecutor.h"
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
@@ -372,7 +373,6 @@ BOOL RCTProfileIsProfiling(void)
void RCTProfileInit(RCTBridge *bridge)
{
// TODO: enable assert JS thread from any file (and assert here)
-
if (RCTProfileIsProfiling()) {
return;
}
@@ -395,6 +395,20 @@ void RCTProfileInit(RCTBridge *bridge)
});
}
+ // Set up thread ordering
+ dispatch_async(RCTProfileGetQueue(), ^{
+ NSString *shadowQueue = @(dispatch_queue_get_label([[bridge uiManager] methodQueue]));
+ NSArray *orderedThreads = @[@"JS async", RCTJSCThreadName, shadowQueue, @"main"];
+ [orderedThreads enumerateObjectsUsingBlock:^(NSString *thread, NSUInteger idx, __unused BOOL *stop) {
+ RCTProfileAddEvent(RCTProfileTraceEvents,
+ @"ph": @"M", // metadata event
+ @"name": @"thread_sort_index",
+ @"tid": thread,
+ @"args": @{ @"sort_index": @(-1000 + (NSInteger)idx) }
+ );
+ }];
+ });
+
RCTProfileHookModules(bridge);
RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class]
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 40147b76de6f0a..30a1710b4a48a4 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -251,6 +251,8 @@
191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControl.m; sourceTree = ""; };
391E86A21C623EC800009732 /* RCTTouchEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchEvent.m; sourceTree = ""; };
391E86A31C623EC800009732 /* RCTTouchEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchEvent.h; sourceTree = ""; };
+ 3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxy.h; sourceTree = ""; };
+ 3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxyDelegate.h; sourceTree = ""; };
58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = ""; };
58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = ""; };
58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = ""; };
@@ -511,15 +513,15 @@
83CBBA491A601E3B00E9B192 /* Base */ = {
isa = PBXGroup;
children = (
- 6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */,
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */,
+ 14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */,
1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */,
- 830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */,
+ 830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
13AF1F851AE6E777005F5298 /* RCTDefines.h */,
@@ -527,6 +529,8 @@
83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */,
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */,
+ 13BB3D001BECD54500932C10 /* RCTImageSource.h */,
+ 13BB3D011BECD54500932C10 /* RCTImageSource.m */,
83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */,
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */,
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */,
@@ -549,6 +553,7 @@
830A229C1A66C68A008503DA /* RCTRootView.h */,
830A229D1A66C68A008503DA /* RCTRootView.m */,
13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */,
+ 6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */,
391E86A31C623EC800009732 /* RCTTouchEvent.h */,
391E86A21C623EC800009732 /* RCTTouchEvent.m */,
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
@@ -557,9 +562,8 @@
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
- 13BB3D001BECD54500932C10 /* RCTImageSource.h */,
- 13BB3D011BECD54500932C10 /* RCTImageSource.m */,
- 14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */,
+ 3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */,
+ 3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */,
);
path = Base;
sourceTree = "";
diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h
index 38b242602d8367..596dcc8399b7df 100644
--- a/React/Views/RCTComponentData.h
+++ b/React/Views/RCTComponentData.h
@@ -11,10 +11,10 @@
#import "RCTComponent.h"
#import "RCTDefines.h"
+#import "RCTViewManager.h"
@class RCTBridge;
@class RCTShadowView;
-@class RCTViewManager;
@class UIView;
@interface RCTComponentData : NSObject
@@ -33,4 +33,6 @@
- (NSDictionary *)viewConfig;
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary *)registry;
+
@end
diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m
index 34f2742a6dd7f6..e182f896bea285 100644
--- a/React/Views/RCTComponentData.m
+++ b/React/Views/RCTComponentData.m
@@ -14,7 +14,6 @@
#import "RCTBridge.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
-#import "RCTViewManager.h"
#import "UIView+React.h"
typedef void (^RCTPropBlock)(id view, id json);
@@ -40,10 +39,10 @@ - (instancetype)initWithType:(NSString *)type
@implementation RCTComponentData
{
- id _defaultView;
- RCTShadowView *_defaultShadowView;
+ id _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
NSMutableDictionary *_viewPropBlocks;
NSMutableDictionary *_shadowPropBlocks;
+ BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
__weak RCTBridge *_bridge;
}
@@ -63,6 +62,14 @@ - (instancetype)initWithManagerClass:(Class)managerClass
if ([_name hasSuffix:@"Manager"]) {
_name = [_name substringToIndex:_name.length - @"Manager".length];
}
+
+ _implementsUIBlockToAmendWithShadowViewRegistry = NO;
+ Class cls = _managerClass;
+ while (cls != [RCTViewManager class]) {
+ _implementsUIBlockToAmendWithShadowViewRegistry = _implementsUIBlockToAmendWithShadowViewRegistry ||
+ RCTClassOverridesInstanceMethod(cls, @selector(uiBlockToAmendWithShadowViewRegistry:));
+ cls = [cls superclass];
+ }
}
return self;
}
@@ -97,10 +104,10 @@ - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
return shadowView;
}
-- (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
+- (RCTPropBlock)propBlockForKey:(NSString *)name
+ inDictionary:(NSMutableDictionary *)propBlocks
{
- BOOL shadowView = [defaultView isKindOfClass:[RCTShadowView class]];
- NSMutableDictionary *propBlocks = shadowView ? _shadowPropBlocks : _viewPropBlocks;
+ BOOL shadowView = (propBlocks == _shadowPropBlocks);
RCTPropBlock propBlock = propBlocks[name];
if (!propBlock) {
@@ -129,8 +136,16 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
SEL customSetter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]);
propBlock = ^(id view, id json) {
+ RCTComponentData *strongSelf = weakSelf;
+ if (!strongSelf) {
+ return;
+ }
+ if (!json && !strongSelf->_defaultView) {
+ // Only create default view if json is null
+ strongSelf->_defaultView = [strongSelf createViewWithTag:nil];
+ }
((void (*)(id, SEL, id, id, id))objc_msgSend)(
- weakSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, defaultView
+ strongSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, strongSelf->_defaultView
);
};
@@ -153,13 +168,13 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
[key substringFromIndex:1]]);
// Build setter block
- void (^setterBlock)(id target, id source, id json) = nil;
+ void (^setterBlock)(id target, id json) = nil;
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
// Special case for event handlers
__weak RCTViewManager *weakManager = _manager;
- setterBlock = ^(id target, __unused id source, id json) {
+ setterBlock = ^(id target, id json) {
__weak id weakTarget = target;
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body];
@@ -180,11 +195,23 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
#define RCT_CASE(_value, _type) \
case _value: { \
+ __block BOOL setDefaultValue = NO; \
+ __block _type defaultValue; \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
- setterBlock = ^(id target, id source, id json) { \
- set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
+ setterBlock = ^(id target, id json) { \
+ if (json) { \
+ if (!setDefaultValue && target) { \
+ if ([target respondsToSelector:getter]) { \
+ defaultValue = get(target, getter); \
+ } \
+ setDefaultValue = YES; \
+ } \
+ set(target, setter, convert([RCTConvert class], type, json)); \
+ } else if (setDefaultValue) { \
+ set(target, setter, defaultValue); \
+ } \
}; \
break; \
}
@@ -214,36 +241,60 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
- __block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
+ __block NSMutableData *defaultValue = nil;
+
+ setterBlock = ^(id target, id json) { \
+
+ if (!target) {
+ return;
+ }
- setterBlock = ^(id target, id source, id json) { \
+ // Get default value
+ if (!defaultValue) {
+ if (!json) {
+ // We only set the defaultValue when we first pass a non-null
+ // value, so if the first value sent for a prop is null, it's
+ // a no-op (we'd be resetting it to its default when its
+ // value is already the default).
+ return;
+ }
+ // Use NSMutableData to store defaultValue instead of malloc, so
+ // it will be freed automatically when setterBlock is released.
+ defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
+ if ([target respondsToSelector:getter]) {
+ NSMethodSignature *signature = [target methodSignatureForSelector:getter];
+ NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
+ sourceInvocation.selector = getter;
+ [sourceInvocation invokeWithTarget:target];
+ [sourceInvocation getReturnValue:defaultValue.mutableBytes];
+ }
+ }
// Get value
- void *value = malloc(typeSignature.methodReturnLength);
+ BOOL freeValueOnCompletion = NO;
+ void *value = defaultValue.mutableBytes;
if (json) {
+ freeValueOnCompletion = YES;
+ value = malloc(typeSignature.methodReturnLength);
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
- } else {
- if (!sourceInvocation && source) {
- NSMethodSignature *signature = [source methodSignatureForSelector:getter];
- sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
- sourceInvocation.selector = getter;
- }
- [sourceInvocation invokeWithTarget:source];
- [sourceInvocation getReturnValue:value];
}
// Set value
- if (!targetInvocation && target) {
+ if (!targetInvocation) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
- free(value);
+ if (freeValueOnCompletion) {
+ // Only free the value if we `malloc`d it locally, otherwise it
+ // points to `defaultValue.mutableBytes`, which is managed by ARC.
+ free(value);
+ }
};
break;
}
@@ -258,20 +309,8 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
target = [target valueForKey:part];
}
- if (json == (id)kCFNull) {
-
- // Copy default property
- id source = defaultView;
- for (NSString *part in parts) {
- source = [source valueForKey:part];
- }
- setterBlock(target, source, nil);
-
- } else {
-
- // Set property with json
- setterBlock(target, nil, json);
- }
+ // Set property with json
+ setterBlock(target, RCTNilIfNull(json));
};
}
@@ -299,12 +338,8 @@ - (void)setProps:(NSDictionary *)props forView:(id
return;
}
- if (!_defaultView) {
- _defaultView = [self createViewWithTag:nil];
- }
-
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
- [self propBlockForKey:key defaultView:_defaultView](view, json);
+ [self propBlockForKey:key inDictionary:_viewPropBlocks](view, json);
}];
if ([view respondsToSelector:@selector(didSetProps:)]) {
@@ -318,12 +353,8 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV
return;
}
- if (!_defaultShadowView) {
- _defaultShadowView = [self createShadowViewWithTag:nil];
- }
-
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
- [self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
+ [self propBlockForKey:key inDictionary:_shadowPropBlocks](shadowView, json);
}];
if ([shadowView respondsToSelector:@selector(didSetProps:)]) {
@@ -412,4 +443,12 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV
};
}
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary *)registry
+{
+ if (_implementsUIBlockToAmendWithShadowViewRegistry) {
+ return [[self manager] uiBlockToAmendWithShadowViewRegistry:registry];
+ }
+ return nil;
+}
+
@end
diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m
index ec4595a5a3a584..ad623c50434fbe 100644
--- a/React/Views/RCTMapManager.m
+++ b/React/Views/RCTMapManager.m
@@ -106,35 +106,6 @@ - (UIView *)view
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}
-- (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": red,
- @"GREEN": green,
- @"PURPLE": purple,
- }
- };
-}
-
#pragma mark MKMapViewDelegate
- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h
index cafe771c8f5e41..c67579da023611 100644
--- a/React/Views/RCTModalHostView.h
+++ b/React/Views/RCTModalHostView.h
@@ -10,6 +10,7 @@
#import
#import "RCTInvalidating.h"
+#import "RCTView.h"
@class RCTBridge;
@@ -18,6 +19,8 @@
@property (nonatomic, assign, getter=isAnimated) BOOL animated;
@property (nonatomic, assign, getter=isTransparent) BOOL transparent;
+@property (nonatomic, copy) RCTDirectEventBlock onShow;
+
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end
diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m
index aecb77dbaec9f3..75e50105c48820 100644
--- a/React/Views/RCTModalHostView.m
+++ b/React/Views/RCTModalHostView.m
@@ -58,13 +58,17 @@ - (void)notifyForBoundsChange:(CGRect)newBounds
- (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
{
+ RCTAssert([_modalViewController.view reactTag] == nil, @"Modal view can only have one subview");
[subview addGestureRecognizer:_touchHandler];
+ subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
+ UIViewAutoresizingFlexibleWidth;
_modalViewController.view = subview;
}
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview == _modalViewController.view, @"Cannot remove view other than modal view");
+ [subview removeGestureRecognizer:_touchHandler];
_modalViewController.view = nil;
}
@@ -82,7 +86,11 @@ - (void)didMoveToWindow
if (!_isPresented && self.window) {
RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller");
- [self.reactViewController presentViewController:_modalViewController animated:self.animated completion:nil];
+ [self.reactViewController presentViewController:_modalViewController animated:self.animated completion:^{
+ if (_onShow) {
+ _onShow(nil);
+ }
+ }];
_isPresented = YES;
}
}
diff --git a/React/Views/RCTModalHostViewController.m b/React/Views/RCTModalHostViewController.m
index ce40551bddc0e2..192e7ffb9d3623 100644
--- a/React/Views/RCTModalHostViewController.m
+++ b/React/Views/RCTModalHostViewController.m
@@ -24,4 +24,14 @@ - (void)viewDidLayoutSubviews
}
}
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations
+{
+ // Picking some defaults here, we should probably make this configurable
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
+ return UIInterfaceOrientationMaskAll;
+ } else {
+ return UIInterfaceOrientationMaskPortrait;
+ }
+}
+
@end
diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m
index ae3b21f2c06317..be4e6b79e05038 100644
--- a/React/Views/RCTModalHostViewManager.m
+++ b/React/Views/RCTModalHostViewManager.m
@@ -40,5 +40,6 @@ - (void)invalidate
RCT_EXPORT_VIEW_PROPERTY(animated, BOOL)
RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
@end
diff --git a/React/Views/RCTRefreshControl.m b/React/Views/RCTRefreshControl.m
index b066f1b632afdc..5d24ffadc7d113 100644
--- a/React/Views/RCTRefreshControl.m
+++ b/React/Views/RCTRefreshControl.m
@@ -30,7 +30,7 @@ - (instancetype)init
- (void)layoutSubviews
{
[super layoutSubviews];
-
+
// If the control is refreshing when mounted we need to call
// beginRefreshing in layoutSubview or it doesn't work.
if (_isInitialRender && _initialRefreshingState) {
@@ -46,9 +46,7 @@ - (void)beginRefreshing
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
// Don't animate when the prop is set initialy.
if (_isInitialRender) {
- // Must use `[scrollView setContentOffset:offset animated:NO]` instead of just setting
- // `scrollview.contentOffset` or it doesn't work, don't ask me why!
- [scrollView setContentOffset:offset animated:NO];
+ scrollView.contentOffset = offset;
[super beginRefreshing];
} else {
// `beginRefreshing` must be called after the animation is done. This is why it is impossible
@@ -64,6 +62,26 @@ - (void)beginRefreshing
}
}
+- (void)endRefreshing
+{
+ // The contentOffset of the scrollview MUST be greater than 0 before calling
+ // endRefreshing otherwise the next pull to refresh will not work properly.
+ UIScrollView *scrollView = (UIScrollView *)self.superview;
+ if (scrollView.contentOffset.y < 0) {
+ CGPoint offset = {scrollView.contentOffset.x, 0};
+ [UIView animateWithDuration:0.25
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^(void) {
+ [scrollView setContentOffset:offset];
+ } completion:^(__unused BOOL finished) {
+ [super endRefreshing];
+ }];
+ } else {
+ [super endRefreshing];
+ }
+}
+
- (NSString *)title
{
return self.attributedTitle.string;
diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m
index 10f2e5ec9c5336..4df421f9169b7b 100644
--- a/React/Views/RCTScrollView.m
+++ b/React/Views/RCTScrollView.m
@@ -145,7 +145,7 @@ @interface RCTCustomScrollView : UIScrollView
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@property (nonatomic, assign) BOOL centerContent;
-@property (nonatomic, strong) UIRefreshControl *refreshControl;
+@property (nonatomic, strong) RCTRefreshControl *refreshControl;
@end
@@ -287,10 +287,12 @@ - (void)dockClosestSectionHeader
__block UIView *nextHeader = nil;
NSUInteger subviewCount = contentView.reactSubviews.count;
[_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:
- ^(NSUInteger idx, __unused BOOL *stop) {
+ ^(NSUInteger idx, BOOL *stop) {
+ // If the subviews are out of sync with the sticky header indices don't
+ // do anything.
if (idx >= subviewCount) {
- RCTLogError(@"Sticky header index %zd was outside the range {0, %zd}", idx, subviewCount);
+ *stop = YES;
return;
}
@@ -349,8 +351,14 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
__block UIView *hitView;
+ NSArray *subviews = [self contentView].reactSubviews;
+ NSUInteger subviewCount = subviews.count;
[_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
- UIView *stickyHeader = [self contentView].reactSubviews[idx];
+ if (idx >= subviewCount) {
+ *stop = YES;
+ return;
+ }
+ UIView *stickyHeader = subviews[idx];
CGPoint convertedPoint = [stickyHeader convertPoint:point fromView:self];
hitView = [stickyHeader hitTest:convertedPoint withEvent:event];
*stop = (hitView != nil);
@@ -359,7 +367,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
return hitView ?: [super hitTest:point withEvent:event];
}
-- (void)setRefreshControl:(UIRefreshControl *)refreshControl
+- (void)setRefreshControl:(RCTRefreshControl *)refreshControl
{
if (_refreshControl) {
[_refreshControl removeFromSuperview];
@@ -487,6 +495,12 @@ - (void)layoutSubviews
_scrollView.frame = self.bounds;
_scrollView.contentOffset = originalOffset;
+ // Adjust the refresh control frame if the scrollview layout changes.
+ RCTRefreshControl *refreshControl = _scrollView.refreshControl;
+ if (refreshControl && refreshControl.refreshing) {
+ refreshControl.frame = (CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}};
+ }
+
[self updateClippedSubviews];
}
@@ -820,6 +834,17 @@ - (void)reactBridgeDidFinishTransaction
_scrollView.contentSize = contentSize;
_scrollView.contentOffset = newOffset;
}
+
+ if (RCT_DEBUG) {
+ // Validate that sticky headers are not out of range.
+ NSUInteger subviewCount = _scrollView.contentView.reactSubviews.count;
+ NSUInteger lastIndex = _scrollView.stickyHeaderIndices.lastIndex;
+ if (lastIndex != NSNotFound && lastIndex >= subviewCount) {
+ RCTLogWarn(@"Sticky header index %zd was outside the range {0, %zd}",
+ lastIndex, subviewCount);
+ }
+ }
+
[_scrollView dockClosestSectionHeader];
}
@@ -828,49 +853,36 @@ - (void)reactBridgeDidFinishTransaction
// setters here that will record the contentOffset beforehand, and
// restore it after the property has been set.
-#define RCT_SET_AND_PRESERVE_OFFSET(setter, type) \
-- (void)setter:(type)value \
-{ \
- CGPoint contentOffset = _scrollView.contentOffset; \
- [_scrollView setter:value]; \
- _scrollView.contentOffset = contentOffset; \
-}
-
-RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setBounces, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, CGFloat)
-RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, UIScrollViewIndicatorStyle)
-RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, UIScrollViewKeyboardDismissMode)
-RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, CGFloat)
-RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, CGFloat)
-RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, CGFloat);
-RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
-
-#pragma mark - Forward methods and properties to underlying UIScrollView
-
-- (BOOL)respondsToSelector:(SEL)aSelector
-{
- return [super respondsToSelector:aSelector] || [_scrollView respondsToSelector:aSelector];
-}
-
-- (void)setValue:(id)value forUndefinedKey:(NSString *)key
-{
- [_scrollView setValue:value forKey:key];
-}
-
-- (id)valueForUndefinedKey:(NSString *)key
-{
- return [_scrollView valueForKey:key];
-}
+#define RCT_SET_AND_PRESERVE_OFFSET(setter, getter, type) \
+- (void)setter:(type)value \
+{ \
+ CGPoint contentOffset = _scrollView.contentOffset; \
+ [_scrollView setter:value]; \
+ _scrollView.contentOffset = contentOffset; \
+} \
+- (type)getter \
+{ \
+ return [_scrollView getter]; \
+}
+
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, alwaysBounceHorizontal, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, alwaysBounceVertical, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBounces, bounces, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, bouncesZoom, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, canCancelContentTouches, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, decelerationRate, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, isDirectionalLockEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, indicatorStyle, UIScrollViewIndicatorStyle)
+RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode)
+RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, maximumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, minimumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, isPagingEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, isScrollEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, scrollsToTop, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
+RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIEdgeInsets);
- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
{
@@ -882,7 +894,7 @@ - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
_onRefreshStart = [onRefreshStart copy];
if (!_scrollView.refreshControl) {
- UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
+ RCTRefreshControl *refreshControl = [[RCTRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
_scrollView.refreshControl = refreshControl;
}
diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m
index 9ab7ca806bc4ba..77f916b4e821ad 100644
--- a/React/Views/RCTScrollViewManager.m
+++ b/React/Views/RCTScrollViewManager.m
@@ -74,16 +74,6 @@ - (UIView *)view
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock)
-- (NSDictionary *)constantsToExport
-{
- return @{
- @"DecelerationRate": @{
- @"normal": @(UIScrollViewDecelerationRateNormal),
- @"fast": @(UIScrollViewDecelerationRateFast),
- },
- };
-}
-
RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index 40917304d48331..e845060f95958a 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -68,6 +68,12 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry
- (void)setTopLeft:(CGPoint)topLeft;
- (void)setSize:(CGSize)size;
+/**
+ * Set the natural size of the view, which is used when no explicit size is set.
+ * Use UIViewNoIntrinsicMetric to ignore a dimension.
+ */
+- (void)setIntrinsicContentSize:(CGSize)size;
+
/**
* Size flexibility type used to find size constraints.
* Default to RCTRootViewSizeFlexibilityNone
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index 42a2f1b8847fb1..c755bbade0425e 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -535,6 +535,29 @@ - (void)setFrame:(CGRect)frame
[self dirtyLayout];
}
+static inline BOOL
+RCTAssignSuggestedDimension(css_node_t *css_node, int dimension, CGFloat amount)
+{
+ if (amount != UIViewNoIntrinsicMetric
+ && isnan(css_node->style.dimensions[dimension])) {
+ css_node->style.dimensions[dimension] = amount;
+ return YES;
+ }
+ return NO;
+}
+
+- (void)setIntrinsicContentSize:(CGSize)size
+{
+ if (_cssNode->style.flex == 0) {
+ BOOL dirty = NO;
+ dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_HEIGHT, size.height);
+ dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_WIDTH, size.width);
+ if (dirty) {
+ [self dirtyLayout];
+ }
+ }
+}
+
- (void)setTopLeft:(CGPoint)topLeft
{
_cssNode->style.position[CSS_LEFT] = topLeft.x;
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 3f312abd1d87f3..7dc5bf3187ad7d 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -90,4 +90,9 @@
*/
@property (nonatomic, assign) RCTBorderStyle borderStyle;
+/**
+ * Insets used when hit testing inside this view.
+ */
+@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
+
@end
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 010c62c492933a..ba0aa82a4337ee 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -109,6 +109,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_borderBottomLeftRadius = -1;
_borderBottomRightRadius = -1;
_borderStyle = RCTBorderStyleSolid;
+ _hitTestEdgeInsets = UIEdgeInsetsZero;
_backgroundColor = super.backgroundColor;
}
@@ -180,6 +181,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
}
}
+- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
+{
+ if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
+ return [super pointInside:point withEvent:event];
+ }
+ CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
+ return CGRectContainsPoint(hitFrame, point);
+}
+
- (BOOL)accessibilityActivate
{
if (_onAccessibilityTap) {
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 5c5cb8f7c2d406..eef814a2bf7958 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -52,6 +52,9 @@ @implementation RCTViewManager
- (dispatch_queue_t)methodQueue
{
+ RCTAssert(_bridge, @"Bridge not set");
+ RCTAssert(_bridge.uiManager || !_bridge.valid, @"UIManager not initialized");
+ RCTAssert(_bridge.uiManager.methodQueue || !_bridge.valid, @"UIManager.methodQueue not initialized");
return _bridge.uiManager.methodQueue;
}
@@ -193,6 +196,17 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
view.borderStyle = json ? [RCTConvert RCTBorderStyle:json] : defaultView.borderStyle;
}
}
+RCT_CUSTOM_VIEW_PROPERTY(hitSlop, UIEdgeInsets, RCTView)
+{
+ if ([view respondsToSelector:@selector(setHitTestEdgeInsets:)]) {
+ if (json) {
+ UIEdgeInsets hitSlopInsets = [RCTConvert UIEdgeInsets:json];
+ view.hitTestEdgeInsets = UIEdgeInsetsMake(-hitSlopInsets.top, -hitSlopInsets.left, -hitSlopInsets.bottom, -hitSlopInsets.right);
+ } else {
+ view.hitTestEdgeInsets = defaultView.hitTestEdgeInsets;
+ }
+ }
+}
RCT_EXPORT_VIEW_PROPERTY(onAccessibilityTap, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMagicTap, RCTDirectEventBlock)
diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h
index 386ce8990cef66..05246a09b78888 100644
--- a/React/Views/RCTWebView.h
+++ b/React/Views/RCTWebView.h
@@ -35,6 +35,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, copy) NSString *injectedJavaScript;
+@property (nonatomic, assign) BOOL scalesPageToFit;
- (void)goForward;
- (void)goBack;
diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m
index cddd9ede1784bf..48307674f0bd55 100644
--- a/React/Views/RCTWebView.m
+++ b/React/Views/RCTWebView.m
@@ -80,6 +80,9 @@ - (void)setSource:(NSDictionary *)source
NSString *html = [RCTConvert NSString:source[@"html"]];
if (html) {
NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
+ if (!baseURL) {
+ baseURL = [NSURL URLWithString:@"about:blank"];
+ }
[_webView loadHTMLString:html baseURL:baseURL];
return;
}
@@ -115,6 +118,19 @@ - (void)setContentInset:(UIEdgeInsets)contentInset
updateOffset:NO];
}
+- (void)setScalesPageToFit:(BOOL)scalesPageToFit
+{
+ if (_webView.scalesPageToFit != scalesPageToFit) {
+ _webView.scalesPageToFit = scalesPageToFit;
+ [_webView reload];
+ }
+}
+
+- (BOOL)scalesPageToFit
+{
+ return _webView.scalesPageToFit;
+}
+
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
@@ -154,12 +170,25 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR
{
BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
+ static NSDictionary *navigationTypes;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ navigationTypes = @{
+ @(UIWebViewNavigationTypeLinkClicked): @"click",
+ @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit",
+ @(UIWebViewNavigationTypeBackForward): @"backforward",
+ @(UIWebViewNavigationTypeReload): @"reload",
+ @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit",
+ @(UIWebViewNavigationTypeOther): @"other",
+ };
+ });
+
// skip this for the JS Navigation handler
if (!isJSNavigation && _onShouldStartLoadWithRequest) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
- @"navigationType": @(navigationType)
+ @"navigationType": navigationTypes[@(navigationType)]
}];
if (![self.delegate webView:self
shouldStartLoadForRequest:event
@@ -175,7 +204,7 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
- @"navigationType": @(navigationType)
+ @"navigationType": navigationTypes[@(navigationType)]
}];
_onLoadingStart(event);
}
diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m
index 33a5d77937fb40..c8d237dda000a8 100644
--- a/React/Views/RCTWebViewManager.m
+++ b/React/Views/RCTWebViewManager.m
@@ -36,8 +36,8 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
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_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
+RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
@@ -47,21 +47,6 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
-- (NSDictionary *)constantsToExport
-{
- return @{
- @"JSNavigationScheme": RCTJSNavigationScheme,
- @"NavigationType": @{
- @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked),
- @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted),
- @"BackForward": @(UIWebViewNavigationTypeBackForward),
- @"Reload": @(UIWebViewNavigationTypeReload),
- @"FormResubmitted": @(UIWebViewNavigationTypeFormResubmitted),
- @"Other": @(UIWebViewNavigationTypeOther)
- },
- };
-}
-
RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS
index 1ba010a939887a..104c161896f566 100644
--- a/ReactAndroid/DEFS
+++ b/ReactAndroid/DEFS
@@ -6,7 +6,6 @@
import os
-
# Example: react_native_target('java/com/facebook/react/common:common')
def react_native_target(path):
return '//ReactAndroid/src/main/' + path
@@ -24,6 +23,18 @@ def react_native_integration_tests_target(path):
def react_native_dep(path):
return '//ReactAndroid/src/main/' + path
+JSC_DEPS = [
+ '//native/third-party/jsc:jsc',
+ '//native/third-party/jsc:jsc_legacy_profiler',
+]
+
+JSC_INTERNAL_DEPS = [
+ '//native/third-party/jsc-internal:jsc',
+ '//native/third-party/jsc-internal:jsc_legacy_profiler',
+]
+
+FBGLOGINIT_TARGET = '//ReactAndroid/src/main/jni/first-party/fbgloginit:fbgloginit'
+
INTERNAL_APP = 'PUBLIC'
# React property preprocessor
@@ -86,4 +97,4 @@ def robolectric3_test(name, deps, vm_args=None, *args, **kwargs):
vm_args=vm_args + extra_vm_args,
*args,
**kwargs
- )
\ No newline at end of file
+ )
diff --git a/ReactAndroid/README.md b/ReactAndroid/README.md
index 891b008ac52274..08c34b99249214 100644
--- a/ReactAndroid/README.md
+++ b/ReactAndroid/README.md
@@ -1,101 +1,3 @@
# Building React Native for Android
-This guide contains instructions for building the Android code and running the sample apps.
-
-## Supported Operating Systems
-
-This setup has only been tested on Mac OS so far.
-
-## Prerequisites
-
-Assuming you have the [Android SDK](https://developer.android.com/sdk/installing/index.html) installed, run `android` to open the Android SDK Manager.
-
-Make sure you have the following installed:
-
-- Android SDK version 23 (compileSdkVersion in [`build.gradle`](build.gradle))
-- SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](build.gradle))
-- Android Support Repository >= 17 (for Android Support Library)
-- Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html))
-
-Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK` defined, or create a `local.properties` file in the root of your `react-native` checkout with the following contents:
-
- sdk.dir=absolute_path_to_android_sdk
- ndk.dir=absolute_path_to_android_ndk
-
-Example:
-
- sdk.dir=/Users/your_unix_name/android-sdk-macosx
- ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e
-
-## Run `npm install`
-
-This is needed to fetch the dependencies for the packager.
-
-```bash
-cd react-native
-npm install
-```
-
-## Building from the command line
-
-To build the framework code:
-
-```bash
-cd react-native
-./gradlew :ReactAndroid:assembleDebug
-```
-
-To install a snapshot version of the framework code in your local Maven repo:
-
-```bash
-./gradlew :ReactAndroid:installArchives
-```
-
-## Running the examples
-
-To run the UIExplorer app:
-
-```bash
-cd react-native
-./gradlew :Examples:UIExplorer:android:app:installDebug
-# Start the packager in a separate shell:
-# Make sure you ran npm install
-./packager/packager.sh
-# Open UIExplorer in your emulator, Menu button -> Reload JS should work
-```
-
-You can run any other sample app the same way, e.g.:
-
-```bash
-./gradlew :Examples:Movies:android:app:installDebug
-```
-
-## Building from Android Studio
-
-You'll need to do one additional step until we release the React Native Gradle plugin to Maven central. This is because Android Studio has its own local Maven repo:
-
- mkdir -p /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react
- cp -r ~/.m2/repository/com/facebook/react/gradleplugin /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react/
-
-Now, open Android Studio, click _Import Non-Android Studio project_ and find your `react-native` repo.
-
-In the configurations dropdown, _app_ should be selected. Click _Run_.
-
-## Installing the React Native .aar in your local Maven repo
-
-In some cases, for example when working on the `react-native-cli` it's useful to publish a snapshot version of React Native into your local Maven repo. This way, Gradle can pick it up when building projects that have a Maven dependency on React Native.
-
-Run:
-
-```bash
-cd react-native-android
-./gradlew :ReactAndroid:installArchives
-```
-
-## Troubleshooting
-
-Gradle build fails in `ndk-build`. See the section about `local.properties` file above.
-
-Gradle build fails "Could not find any version that matches com.facebook.react:gradleplugin:...". See the section about the React Native Gradle plugin above.
-
-Packager throws an error saying a module is not found. Try running `npm install` in the root of the repo.
+See [docs on the website](https://facebook.github.io/react-native/docs/android-building-from-source.html).
diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle
index 913dcbb73347d5..8f26d79976562e 100644
--- a/ReactAndroid/build.gradle
+++ b/ReactAndroid/build.gradle
@@ -204,6 +204,11 @@ task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {
into "$buildDir/react-ndk/exported"
}
+task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) {
+ from "$buildDir/react-ndk/exported"
+ into "src/main/jni/prebuilt/lib"
+}
+
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
@@ -219,7 +224,7 @@ android {
}
buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false'
- testApplicationId "com.facebook.react.tests"
+ testApplicationId "com.facebook.react.tests.gradle"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
diff --git a/ReactAndroid/src/androidTest/AndroidManifest.xml b/ReactAndroid/src/androidTest/AndroidManifest.xml
index 4275a7ea68567b..f6ce0fa0511cd0 100644
--- a/ReactAndroid/src/androidTest/AndroidManifest.xml
+++ b/ReactAndroid/src/androidTest/AndroidManifest.xml
@@ -1,6 +1,6 @@
@@ -10,10 +10,11 @@
-
+
diff --git a/ReactAndroid/src/androidTest/BUCK_temp b/ReactAndroid/src/androidTest/BUCK_temp
deleted file mode 100644
index d33209cedb5706..00000000000000
--- a/ReactAndroid/src/androidTest/BUCK_temp
+++ /dev/null
@@ -1,66 +0,0 @@
-include_defs('//ReactAndroid/DEFS')
-
-
-CATALYST_PRIMERY_DEX_PATTERNS = [
- '/CatalystAppShell^',
- '/CatalystApplicationImpl^',
- '^com/facebook/buck/android/support/exopackage/',
- '/FbInstrumentationTestRunner^',
- '/PrimaryDexFactories^',
- '/Screenshot^',
- '/DexmakerMockMaker^',
- '/AndroidJUnitRunner^',
- '/InstrumentationRunListener^',
- '/ExposedInstrumentationApi^',
- '/TestApplication^',
- '/ApplicationWithInjector^',
- '/RunListener^',
-]
-
-JS_BUNDLE_DEPS = [
- #':intern-schema',
-# '//java/com/facebook/graphql:schema',
-# '//java/com/facebook/graphql/graphql-data:locate_node'
-]
-
-
-# instrumentation tests that will run for tests located in GitHub open sourced folder
-android_binary (
- name = 'react_oss',
- manifest = 'AndroidManifest.xml',
- keystore = '//keystores:debug',
- use_split_dex = True,
- linear_alloc_hard_limit = 10 * 1024 * 1024,
- primary_dex_patterns = CATALYST_PRIMERY_DEX_PATTERNS,
- deps = [
-# ':integration_test_oss_bundle_js',
- react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
- # '//java/com/facebook/catalyst/js/react-native-github/ReactAndroid/src/main/res:shell',
- ],
-)
-
-# Building this rule will produce a file named messenger_test.apk
-android_instrumentation_apk(
- name = 'react_oss_test_apk',
- manifest = 'AndroidManifest.xml',
- apk = ':react_oss',
- deps = [
-# react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
- # react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
- # '//java/com/facebook/catalyst:integration_test_oss_bundle_js#dev',
- ],
-)
-
-
-
-android_instrumentation_test(
- name = 'react_oss_test',
- apk = ':react_oss_test_apk',
-)
-
-sh_binary(
- name = 'integration_test_oss_bundle_js',
- main = 'buildBundle.sh',
-# deps = [':InstallReactNativeNodeModules'],
- visibility = ['PUBLIC'],
-)
diff --git a/ReactAndroid/src/androidTest/assets/BUCK b/ReactAndroid/src/androidTest/assets/BUCK
new file mode 100644
index 00000000000000..4648c5ef94f5f3
--- /dev/null
+++ b/ReactAndroid/src/androidTest/assets/BUCK
@@ -0,0 +1,5 @@
+android_resource(
+ name = 'assets',
+ assets = '.',
+ visibility = ['PUBLIC'],
+)
diff --git a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
new file mode 100644
index 00000000000000..8fd01fc76607a3
--- /dev/null
+++ b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReactAndroid/src/androidTest/buck-runner/BUCK b/ReactAndroid/src/androidTest/buck-runner/BUCK
new file mode 100644
index 00000000000000..337e846c1e0541
--- /dev/null
+++ b/ReactAndroid/src/androidTest/buck-runner/BUCK
@@ -0,0 +1,23 @@
+include_defs('//ReactAndroid/DEFS')
+
+# We are running instrumentation tests in simple mode: app code and instrumentation are in the same APK
+# Currently you need to run these commands to execute tests:
+#
+# node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/assets/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
+# gradle :ReactAndroid:packageReactNdkLibsForBuck
+# buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests
+# ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests
+android_binary(
+ name = 'instrumentation-tests',
+ manifest = 'AndroidManifest.xml',
+ keystore = '//keystores:debug',
+ deps = [
+ react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
+ react_native_integration_tests_target('assets:assets'),
+ react_native_target('jni/prebuilt:reactnative-libs'),
+ react_native_target('jni/prebuilt:android-jsc'),
+ react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+ react_native_target('java/com/facebook/react/devsupport:devsupport'),
+ ],
+)
+
diff --git a/ReactAndroid/src/androidTest/buildBundle.sh b/ReactAndroid/src/androidTest/buildBundle.sh
deleted file mode 100755
index 6a61faa90ed204..00000000000000
--- a/ReactAndroid/src/androidTest/buildBundle.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-# TODO put output to temp folder?
-node ./local-cli/cli.js bundle --entry-file ReactAndroid/src/androidTest/assets/TestBundle.js --dev --platform android --bundle-output ReactAndroid/src/androidTest/assets/ReactAndroidTestBundle.js
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
index 9297f59a246035..37f0a4db395587 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
@@ -28,7 +28,7 @@
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.shell.MainReactPackage;
-
+import com.facebook.react.uimanager.UIImplementationProvider;
public class ReactAppTestActivity extends FragmentActivity implements
DefaultHardwareBackBtnHandler
@@ -75,7 +75,7 @@ protected void onPause() {
overridePendingTransition(0, 0);
if (mReactInstanceManager != null) {
- mReactInstanceManager.onPause();
+ mReactInstanceManager.onHostPause();
}
}
@@ -86,7 +86,7 @@ protected void onResume() {
mLifecycleState = LifecycleState.RESUMED;
if (mReactInstanceManager != null) {
- mReactInstanceManager.onResume(this, this);
+ mReactInstanceManager.onHostResume(this, this);
}
}
@@ -96,7 +96,7 @@ protected void onDestroy() {
mDestroyCountDownLatch.countDown();
if (mReactInstanceManager != null) {
- mReactInstanceManager.onDestroy();
+ mReactInstanceManager.destroy();
}
}
@@ -112,9 +112,17 @@ public void loadApp(String appKey, ReactInstanceSpecForTest spec, String bundleN
loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */);
}
+ public void loadApp(
+ String appKey,
+ ReactInstanceSpecForTest spec,
+ String bundleName,
+ UIImplementationProvider uiImplementationProvider) {
+ loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */, uiImplementationProvider);
+ }
+
public void resetRootViewForScreenshotTests() {
if (mReactInstanceManager != null) {
- mReactInstanceManager.onDestroy();
+ mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
mReactRootView = new ReactRootView(this);
@@ -128,6 +136,16 @@ public void loadApp(
@Nullable Bundle initialProps,
String bundleName,
boolean useDevSupport) {
+ loadApp(appKey, spec, initialProps, bundleName, useDevSupport, null);
+ }
+
+ public void loadApp(
+ String appKey,
+ ReactInstanceSpecForTest spec,
+ @Nullable Bundle initialProps,
+ String bundleName,
+ boolean useDevSupport,
+ UIImplementationProvider uiImplementationProvider) {
final CountDownLatch currentLayoutEvent = mLayoutEvent = new CountDownLatch(1);
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
@@ -145,10 +163,11 @@ public void loadApp(
.addPackage(new InstanceSpecForTestPackage(spec))
.setUseDeveloperSupport(useDevSupport)
.setBridgeIdleDebugListener(mBridgeIdleSignaler)
- .setInitialLifecycleState(mLifecycleState);
+ .setInitialLifecycleState(mLifecycleState)
+ .setUIImplementationProvider(uiImplementationProvider);
mReactInstanceManager = builder.build();
- mReactInstanceManager.onResume(this, this);
+ mReactInstanceManager.onHostResume(this, this);
Assertions.assertNotNull(mReactRootView).getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
index 5161a4aaab60d0..b8d85fe0ffdbb8 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
@@ -70,14 +70,17 @@ public void shutDownContext() {
mReactContext = null;
mInstance = null;
+ final SimpleSettableFuture semaphore = new SimpleSettableFuture<>();
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
if (contextToDestroy != null) {
- contextToDestroy.onDestroy();
+ contextToDestroy.destroy();
}
+ semaphore.set(null);
}
});
+ semaphore.getOrThrow();
}
}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
index fbe1c0db23954b..20afcb2341066d 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
@@ -12,6 +12,7 @@
import android.app.Instrumentation;
import android.content.Context;
+import android.support.test.InstrumentationRegistry;
import android.view.View;
import android.view.ViewGroup;
@@ -24,9 +25,9 @@
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NativeModuleRegistry;
+import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
-import android.support.test.InstrumentationRegistry;
import com.android.internal.util.Predicate;
public class ReactTestHelper {
@@ -60,7 +61,7 @@ public ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass) {
public CatalystInstance build() {
return new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
- .setJSExecutor(new JSCJavaScriptExecutor())
+ .setJSExecutor(new JSCJavaScriptExecutor(new WritableNativeMap()))
.setRegistry(mNativeModuleRegistryBuilder.build())
.setJSModulesConfig(mJSModulesConfigBuilder.build())
.setJSBundleLoader(JSBundleLoader.createFileLoader(
diff --git a/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK b/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK
new file mode 100644
index 00000000000000..8dd771536fa144
--- /dev/null
+++ b/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK
@@ -0,0 +1,10 @@
+include_defs('//ReactAndroid/DEFS')
+
+# used by ReactToolbarManager because of Gradle
+# TODO t10182713 will be replaced with res-for-appcompat when we stop using Gradle
+android_resource(
+ name = 'res-for-react-native',
+ res = react_native_dep('third-party/android/support/v7/appcompat-orig:res-unpacker-cmd'),
+ package = 'com.facebook.react',
+ visibility = ['//ReactAndroid/...',],
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java
index 711aed2ab2d5c7..5efd88e3b2a1ea 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;
@@ -258,7 +258,7 @@ private static void layoutNodeImpl(
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]]);
+ boolean isResolvedRowDimDefined = (!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0);
float width = CSSConstants.UNDEFINED;
if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
@@ -274,7 +274,7 @@ private static void layoutNodeImpl(
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]])) {
+ } else if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
height = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]];
} else {
height = parentMaxHeight -
@@ -320,8 +320,8 @@ private static void layoutNodeImpl(
float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])));
- boolean isMainDimDefined = !Float.isNaN(node.layout.dimensions[dim[mainAxis]]);
- boolean isCrossDimDefined = !Float.isNaN(node.layout.dimensions[dim[crossAxis]]);
+ boolean isMainDimDefined = (!Float.isNaN(node.layout.dimensions[dim[mainAxis]]) && node.layout.dimensions[dim[mainAxis]] >= 0.0);
+ boolean isCrossDimDefined = (!Float.isNaN(node.layout.dimensions[dim[crossAxis]]) && node.layout.dimensions[dim[crossAxis]] >= 0.0);
boolean isMainRowDirection = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE);
int i;
@@ -383,8 +383,8 @@ private static void layoutNodeImpl(
float mainDim = leadingPaddingAndBorderMain;
float crossDim = 0;
- float maxWidth;
- float maxHeight;
+ float maxWidth = CSSConstants.UNDEFINED;
+ float maxHeight = CSSConstants.UNDEFINED;
for (i = startLine; i < childCount; ++i) {
child = node.getChildAt(i);
child.lineIndex = linesCount;
@@ -421,7 +421,7 @@ private static void layoutNodeImpl(
// left and right or top and bottom).
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
- if (!Float.isNaN(node.layout.dimensions[dim[axis]]) &&
+ if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) &&
!(!Float.isNaN(child.style.dimensions[dim[axis]]) && child.style.dimensions[dim[axis]] >= 0.0) &&
!Float.isNaN(child.style.position[leading[axis]]) &&
!Float.isNaN(child.style.position[trailing[axis]])) {
@@ -468,7 +468,7 @@ private static void layoutNodeImpl(
maxHeight = CSSConstants.UNDEFINED;
if (!isMainRowDirection) {
- if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
+ if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else {
@@ -477,7 +477,7 @@ private static void layoutNodeImpl(
paddingAndBorderAxisResolvedRow;
}
} else {
- if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+ if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
paddingAndBorderAxisColumn;
} else {
@@ -528,7 +528,7 @@ private static void layoutNodeImpl(
if (isSimpleStackCross &&
(child.style.positionType != CSSPositionType.RELATIVE ||
(alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) ||
- Float.isNaN(child.layout.dimensions[dim[crossAxis]]))) {
+ (alignItem == CSSAlign.STRETCH && !isCrossDimDefined))) {
isSimpleStackCross = false;
firstComplexCross = i;
}
@@ -611,7 +611,7 @@ private static void layoutNodeImpl(
);
maxWidth = CSSConstants.UNDEFINED;
- if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
+ if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
paddingAndBorderAxisResolvedRow;
} else if (!isMainRowDirection) {
@@ -620,7 +620,7 @@ private static void layoutNodeImpl(
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)) {
+ if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
paddingAndBorderAxisColumn;
} else if (isMainRowDirection) {
@@ -738,15 +738,31 @@ private static void layoutNodeImpl(
CSSAlign alignItem = getAlignItem(node, child);
/*eslint-enable */
if (alignItem == CSSAlign.STRETCH) {
- // You can only stretch if the dimension has not already been set
+ // You can only stretch if the dimension has not already been defined
// previously.
- if (Float.isNaN(child.layout.dimensions[dim[crossAxis]])) {
+ if (!(!Float.isNaN(child.style.dimensions[dim[crossAxis]]) && child.style.dimensions[dim[crossAxis]] >= 0.0)) {
+ float dimCrossAxis = child.layout.dimensions[dim[crossAxis]];
child.layout.dimensions[dim[crossAxis]] = Math.max(
boundAxis(child, crossAxis, containerCrossAxis -
paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
// You never want to go smaller than padding
((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
);
+
+ // If the size has changed, and this child has children we need to re-layout this child
+ if (dimCrossAxis != child.layout.dimensions[dim[crossAxis]] && child.getChildCount() > 0) {
+ // Reset child margins before re-layout as they are added back in layoutNode and would be doubled
+ child.layout.position[leading[mainAxis]] -= child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
+ getRelativePosition(child, mainAxis);
+ child.layout.position[trailing[mainAxis]] -= child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) +
+ getRelativePosition(child, mainAxis);
+ child.layout.position[leading[crossAxis]] -= child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
+ getRelativePosition(child, crossAxis);
+ child.layout.position[trailing[crossAxis]] -= child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) +
+ getRelativePosition(child, crossAxis);
+
+ layoutNode(layoutContext, child, maxWidth, maxHeight, direction);
+ }
}
} else if (alignItem != CSSAlign.FLEX_START) {
// The remaining space between the parent dimensions+padding and child
@@ -824,7 +840,7 @@ private static void layoutNodeImpl(
if (child.lineIndex != i) {
break;
}
- if (!Float.isNaN(child.layout.dimensions[dim[crossAxis]])) {
+ if ((!Float.isNaN(child.layout.dimensions[dim[crossAxis]]) && child.layout.dimensions[dim[crossAxis]] >= 0.0)) {
lineHeight = Math.max(
lineHeight,
child.layout.dimensions[dim[crossAxis]] + (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))
@@ -917,7 +933,7 @@ private static void layoutNodeImpl(
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
- if (!Float.isNaN(node.layout.dimensions[dim[axis]]) &&
+ if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) &&
!(!Float.isNaN(currentAbsoluteChild.style.dimensions[dim[axis]]) && currentAbsoluteChild.style.dimensions[dim[axis]] >= 0.0) &&
!Float.isNaN(currentAbsoluteChild.style.position[leading[axis]]) &&
!Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]])) {
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README
index 955473ba0b5555..0de9f29a65f688 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/219bdaed15c16bbf7c1f2bab17ad629d04cc4199
+HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/3f0f4be2e868a9af4f3ed1e019f71efbc814e8a2
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 8d78b2695779ec..fc97204649102a 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/219bdaed15c16bbf7c1f2bab17ad629d04cc4199
+HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/3f0f4be2e868a9af4f3ed1e019f71efbc814e8a2
There is generated code in:
- README.facebook (this file)
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
index 3006da53a95c1d..a0c845dd62878a 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
index 18f754bf474999..13090a18ce30a3 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
index fa6e971f6d414d..45e9bfe0cee0dd 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
index 07b18b066b305f..d7b34e270acedc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
@@ -19,6 +19,7 @@
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
+import com.facebook.react.devsupport.HMRClient;
import com.facebook.react.modules.core.JSTimersExecution;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
import com.facebook.react.modules.core.Timing;
@@ -95,6 +96,7 @@ public List> createJSModules() {
RCTNativeAppEventEmitter.class,
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
+ HMRClient.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java b/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java
new file mode 100644
index 00000000000000..959a6f8d36c92e
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java
@@ -0,0 +1,12 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react;
+
+import com.facebook.react.bridge.WritableNativeMap;
+
+/**
+ * Interface for the configuration object that is passed to JSC.
+ */
+public interface JSCConfig {
+ public WritableNativeMap getConfigMap();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java b/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
index f8598e90884572..068bd063fb0c36 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
@@ -21,7 +21,7 @@
* RESUMED
*/
public enum LifecycleState {
-
+ BEFORE_CREATE,
BEFORE_RESUME,
RESUMED,
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
index 58f2833110e2de..e9fd7574895f3b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
@@ -2,20 +2,22 @@
package com.facebook.react;
-import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
import android.annotation.TargetApi;
-import android.app.Activity;
+import android.app.Application;
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 com.facebook.react.bridge.MemoryPressureListener;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
@@ -24,10 +26,14 @@
*/
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;
+ // am broadcast -a com.facebook.catalyst.ACTION_TRIM_MEMORY_MODERATE
+ private static final String ACTION_TRIM_MEMORY_MODERATE =
+ "com.facebook.rnfeed.ACTION_TRIM_MEMORY_MODERATE";
+ private static final String ACTION_TRIM_MEMORY_CRITICAL =
+ "com.facebook.rnfeed.ACTION_TRIM_MEMORY_CRITICAL";
+
+ private final Set mListeners =
+ Collections.synchronizedSet(new LinkedHashSet());
private final ComponentCallbacks2 mCallbacks = new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
@@ -44,11 +50,13 @@ public void onLowMemory() {
};
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- public static boolean handleDebugIntent(Activity activity, String action) {
+ public static boolean handleDebugIntent(Application application, String action) {
switch (action) {
- case ACTION_TRIM_MEMORY:
- simulateTrimMemory(activity, TRIM_MEMORY_MODERATE);
+ case ACTION_TRIM_MEMORY_MODERATE:
+ simulateTrimMemory(application, TRIM_MEMORY_MODERATE);
break;
+ case ACTION_TRIM_MEMORY_CRITICAL:
+ simulateTrimMemory(application, TRIM_MEMORY_COMPLETE);
default:
return false;
}
@@ -60,12 +68,18 @@ public static boolean handleDebugIntent(Activity activity, String action) {
context.getApplicationContext().registerComponentCallbacks(mCallbacks);
}
- public void onNewReactContextCreated(ReactContext reactContext) {
- mCatalystInstance = reactContext.getCatalystInstance();
+ /**
+ * Add a listener to be notified of memory pressure events.
+ */
+ public void addMemoryPressureListener(MemoryPressureListener listener) {
+ mListeners.add(listener);
}
- public void onReactInstanceDestroyed() {
- mCatalystInstance = null;
+ /**
+ * Remove a listener previously added with {@link #addMemoryPressureListener}.
+ */
+ public void removeMemoryPressureListener(MemoryPressureListener listener) {
+ mListeners.remove(listener);
}
public void destroy(Context context) {
@@ -81,13 +95,16 @@ private void trimMemory(int level) {
}
private void dispatchMemoryPressure(MemoryPressure level) {
- if (mCatalystInstance != null) {
- mCatalystInstance.handleMemoryPressure(level);
+ // copy listeners array to avoid ConcurrentModificationException if any of the listeners remove
+ // themselves in handleMemoryPressure()
+ MemoryPressureListener[] listeners =
+ mListeners.toArray(new MemoryPressureListener[mListeners.size()]);
+ for (MemoryPressureListener listener : listeners) {
+ listener.handleMemoryPressure(level);
}
}
- private static void simulateTrimMemory(Activity activity, int level) {
- activity.getApplication().onTrimMemory(level);
- activity.onTrimMemory(level);
+ private static void simulateTrimMemory(Application application, int level) {
+ application.onTrimMemory(level);
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
index c9679fafb96d71..d708e7dc2e6bad 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@@ -150,7 +150,7 @@ protected void onPause() {
mLifecycleState = LifecycleState.BEFORE_RESUME;
if (mReactInstanceManager != null) {
- mReactInstanceManager.onPause();
+ mReactInstanceManager.onHostPause();
}
}
@@ -161,7 +161,7 @@ protected void onResume() {
mLifecycleState = LifecycleState.RESUMED;
if (mReactInstanceManager != null) {
- mReactInstanceManager.onResume(this, this);
+ mReactInstanceManager.onHostResume(this, this);
}
}
@@ -170,7 +170,7 @@ protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
- mReactInstanceManager.onDestroy();
+ mReactInstanceManager.destroy();
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
index 17fb538b826d47..2e2ca160301f4a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
@@ -41,8 +41,8 @@
* The lifecycle of the instance of {@link ReactInstanceManager} 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}).
+ * owning activity's lifecycle events to the instance manager (see {@link #onHostPause},
+ * {@link #onHostDestroy} and {@link #onHostResume}).
*
* 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.
@@ -62,6 +62,8 @@ public interface ReactInstanceEventListener {
public abstract DevSupportManager getDevSupportManager();
+ public abstract MemoryPressureRouter getMemoryPressureRouter();
+
/**
* Trigger react context initialization asynchronously in a background async task. This enables
* applications to pre-load the application JS, and execute global code before
@@ -84,22 +86,32 @@ public interface ReactInstanceEventListener {
* consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
*/
public abstract void onBackPressed();
- public abstract void onPause();
+
+ /**
+ * Call this from {@link Activity#onPause()}. This notifies any listening modules so they can do
+ * any necessary cleanup.
+ */
+ public abstract void onHostPause();
/**
* 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
- * ReactInstanceManager}, so that once this instance receive {@link #onDestroy} event it will
+ * ReactInstanceManager}, so that once this instance receive {@link #onHostDestroy} event it will
* clear the reference to that defaultBackButtonImpl.
*
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
* this instance of {@link ReactInstanceManager}.
*/
- public abstract void onResume(
+ public abstract void onHostResume(
Activity activity,
DefaultHardwareBackBtnHandler defaultBackButtonImpl);
- public abstract void onDestroy();
+
+ /**
+ * Call this from {@link Activity#onDestroy()}. This notifies any listening modules so they can do
+ * any necessary cleanup.
+ */
+ public abstract void onHostDestroy();
public abstract void onActivityResult(int requestCode, int resultCode, Intent data);
public abstract void showDevOptionsDialog();
@@ -125,6 +137,11 @@ public abstract void onResume(
*/
public abstract void detachRootView(ReactRootView rootView);
+ /**
+ * Destroy this React instance and the attached JS context.
+ */
+ public abstract void destroy();
+
/**
* Uses configured {@link ReactPackage} instances to create all view managers
*/
@@ -136,9 +153,16 @@ public abstract List createAllViewManagers(
*/
public abstract void addReactInstanceEventListener(ReactInstanceEventListener listener);
+ /**
+ * Remove a listener previously added with {@link #addReactInstanceEventListener}.
+ */
+ public abstract void removeReactInstanceEventListener(ReactInstanceEventListener listener);
+
@VisibleForTesting
public abstract @Nullable ReactContext getCurrentReactContext();
+ public abstract LifecycleState getLifecycleState();
+
/**
* Creates a builder that is capable of creating an instance of {@link ReactInstanceManagerImpl}.
*/
@@ -161,6 +185,7 @@ public static class Builder {
protected @Nullable LifecycleState mInitialLifecycleState;
protected @Nullable UIImplementationProvider mUIImplementationProvider;
protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+ protected @Nullable JSCConfig mJSCConfig;
protected Builder() {
}
@@ -254,6 +279,11 @@ public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHand
return this;
}
+ public Builder setJSCConfig(JSCConfig jscConfig) {
+ mJSCConfig = jscConfig;
+ return this;
+ }
+
/**
* Instantiates a new {@link ReactInstanceManagerImpl}.
* Before calling {@code build}, the following must be called:
@@ -287,7 +317,8 @@ public ReactInstanceManager build() {
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mUIImplementationProvider,
- mNativeModuleCallExceptionHandler);
+ mNativeModuleCallExceptionHandler,
+ mJSCConfig);
}
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
index a92b151e1d17b0..0c480eb58680e8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
@@ -15,8 +15,9 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
import android.app.Activity;
import android.app.Application;
@@ -58,8 +59,7 @@
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.devsupport.DevServerHelper;
import com.facebook.react.devsupport.DevSupportManager;
-import com.facebook.react.devsupport.DevSupportManagerImpl;
-import com.facebook.react.devsupport.DisabledDevSupportManager;
+import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -71,7 +71,18 @@
import com.facebook.soloader.SoLoader;
import com.facebook.systrace.Systrace;
-import static com.facebook.react.bridge.ReactMarkerConstants.*;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START;
/**
* This class is managing instances of {@link CatalystInstance}. It expose a way to configure
@@ -85,8 +96,8 @@
* 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}).
+ * owning activity's lifecycle events to the instance manager (see {@link #onHostPause},
+ * {@link #onHostDestroy} and {@link #onHostResume}).
*
* To instantiate an instance of this class use {@link #builder}.
*/
@@ -111,11 +122,12 @@
private String mSourceUrl;
private @Nullable Activity mCurrentActivity;
private final Collection mReactInstanceEventListeners =
- new ConcurrentLinkedQueue<>();
+ Collections.synchronizedSet(new HashSet());
private volatile boolean mHasStartedCreatingInitialContext = false;
private final UIImplementationProvider mUIImplementationProvider;
private final MemoryPressureRouter mMemoryPressureRouter;
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+ private final @Nullable JSCConfig mJSCConfig;
private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() {
@@ -182,7 +194,9 @@ protected void onPreExecute() {
protected Result doInBackground(ReactContextInitParams... params) {
Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
try {
- JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
+ JavaScriptExecutor jsExecutor =
+ params[0].getJsExecutorFactory().create(
+ mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap());
return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
} catch (Exception e) {
// Pass exception to onPostExecute() so it can be handled on the main thread
@@ -263,7 +277,8 @@ public T get() throws Exception {
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState,
UIImplementationProvider uiImplementationProvider,
- NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
+ NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
+ @Nullable JSCConfig jscConfig) {
initializeSoLoaderIfNecessary(applicationContext);
// TODO(9577825): remove this
@@ -275,20 +290,17 @@ public T get() throws Exception {
mJSMainModuleName = jsMainModuleName;
mPackages = packages;
mUseDeveloperSupport = useDeveloperSupport;
- if (mUseDeveloperSupport) {
- mDevSupportManager = new DevSupportManagerImpl(
- applicationContext,
- mDevInterface,
- mJSMainModuleName,
- useDeveloperSupport);
- } else {
- mDevSupportManager = new DisabledDevSupportManager();
- }
+ mDevSupportManager = DevSupportManagerFactory.create(
+ applicationContext,
+ mDevInterface,
+ mJSMainModuleName,
+ useDeveloperSupport);
mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState;
mUIImplementationProvider = uiImplementationProvider;
mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+ mJSCConfig = jscConfig;
}
@Override
@@ -296,6 +308,11 @@ public DevSupportManager getDevSupportManager() {
return mDevSupportManager;
}
+ @Override
+ public MemoryPressureRouter getMemoryPressureRouter() {
+ return mMemoryPressureRouter;
+ }
+
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
@@ -308,8 +325,11 @@ private static void initializeSoLoaderIfNecessary(Context applicationContext) {
}
private static void setDisplayMetrics(Context context) {
- DisplayMetrics displayMetrics = new DisplayMetrics();
- displayMetrics.setTo(context.getResources().getDisplayMetrics());
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+
+ DisplayMetrics screenDisplayMetrics = new DisplayMetrics();
+ screenDisplayMetrics.setTo(displayMetrics);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
@@ -317,9 +337,8 @@ private static void setDisplayMetrics(Context context) {
// The real metrics include system decor elements (e.g. soft menu bar).
//
// See: http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
- if (Build.VERSION.SDK_INT >= 17){
- display.getRealMetrics(displayMetrics);
-
+ if (Build.VERSION.SDK_INT >= 17) {
+ display.getRealMetrics(screenDisplayMetrics);
} else {
// For 14 <= API level <= 16, we need to invoke getRawHeight and getRawWidth to get the real dimensions.
// Since react-native only supports API level 16+ we don't have to worry about other cases.
@@ -330,13 +349,13 @@ private static void setDisplayMetrics(Context context) {
try {
Method mGetRawH = Display.class.getMethod("getRawHeight");
Method mGetRawW = Display.class.getMethod("getRawWidth");
- displayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
- displayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
+ screenDisplayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
+ screenDisplayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException("Error getting real dimensions for API level < 17", e);
}
}
- DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+ DisplayMetricsHolder.setScreenDisplayMetrics(screenDisplayMetrics);
}
/**
@@ -458,20 +477,16 @@ private void toggleElementInspector() {
}
@Override
- public void onPause() {
+ public void onHostPause() {
UiThreadUtil.assertOnUiThread();
- mLifecycleState = LifecycleState.BEFORE_RESUME;
-
mDefaultBackButtonImpl = null;
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
}
+ moveToBeforeResumeLifecycleState();
mCurrentActivity = null;
- if (mCurrentReactContext != null) {
- mCurrentReactContext.onPause();
- }
}
/**
@@ -479,17 +494,16 @@ public void onPause() {
*
* 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
+ * ReactInstanceManagerImpl}, so that once this instance receive {@link #onHostDestroy} 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) {
+ public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
UiThreadUtil.assertOnUiThread();
- mLifecycleState = LifecycleState.RESUMED;
mDefaultBackButtonImpl = defaultBackButtonImpl;
if (mUseDeveloperSupport) {
@@ -497,32 +511,86 @@ public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBac
}
mCurrentActivity = activity;
- if (mCurrentReactContext != null) {
- mCurrentReactContext.onResume(activity);
+ moveToResumedLifecycleState(false);
+ }
+
+ @Override
+ public void onHostDestroy() {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(false);
}
+
+ moveToBeforeCreateLifecycleState();
+ mCurrentActivity = null;
}
@Override
- public void onDestroy() {
+ public void destroy() {
UiThreadUtil.assertOnUiThread();
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(false);
+ }
+
+ moveToBeforeCreateLifecycleState();
+
if (mReactContextInitAsyncTask != null) {
mReactContextInitAsyncTask.cancel(true);
}
mMemoryPressureRouter.destroy(mApplicationContext);
- if (mUseDeveloperSupport) {
- mDevSupportManager.setDevSupportEnabled(false);
- }
if (mCurrentReactContext != null) {
- mCurrentReactContext.onDestroy();
+ mCurrentReactContext.destroy();
mCurrentReactContext = null;
mHasStartedCreatingInitialContext = false;
}
mCurrentActivity = null;
}
+ private void moveToResumedLifecycleState(boolean force) {
+ if (mCurrentReactContext != null) {
+ // we currently don't have an onCreate callback so we call onResume for both transitions
+ if (force ||
+ mLifecycleState == LifecycleState.BEFORE_RESUME ||
+ mLifecycleState == LifecycleState.BEFORE_CREATE) {
+ mCurrentReactContext.onHostResume(mCurrentActivity);
+ }
+ }
+ mLifecycleState = LifecycleState.RESUMED;
+ }
+
+ private void moveToBeforeResumeLifecycleState() {
+ if (mCurrentReactContext != null) {
+ if (mLifecycleState == LifecycleState.BEFORE_CREATE) {
+ mCurrentReactContext.onHostResume(mCurrentActivity);
+ mCurrentReactContext.onHostPause();
+ } else if (mLifecycleState == LifecycleState.RESUMED) {
+ mCurrentReactContext.onHostPause();
+ }
+ }
+ mLifecycleState = LifecycleState.BEFORE_RESUME;
+ }
+
+ private void moveToBeforeCreateLifecycleState() {
+ if (mCurrentReactContext != null) {
+ if (mLifecycleState == LifecycleState.RESUMED) {
+ mCurrentReactContext.onHostPause();
+ mLifecycleState = LifecycleState.BEFORE_RESUME;
+ }
+ if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
+ mCurrentReactContext.onHostDestroy();
+ }
+ }
+ mLifecycleState = LifecycleState.BEFORE_CREATE;
+ }
+
+ public LifecycleState getLifecycleState() {
+ return mLifecycleState;
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mCurrentReactContext != null) {
@@ -602,6 +670,11 @@ public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
mReactInstanceEventListeners.add(listener);
}
+ @Override
+ public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
+ mReactInstanceEventListeners.remove(listener);
+ }
+
@VisibleForTesting
@Override
public @Nullable ReactContext getCurrentReactContext() {
@@ -651,14 +724,18 @@ private void setupReactContext(ReactApplicationContext reactContext) {
catalystInstance.initialize();
mDevSupportManager.onNewReactContextCreated(reactContext);
- mMemoryPressureRouter.onNewReactContextCreated(reactContext);
- moveReactContextToCurrentLifecycleState(reactContext);
+ mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
+ moveReactContextToCurrentLifecycleState();
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(rootView, catalystInstance);
}
- for (ReactInstanceEventListener listener : mReactInstanceEventListeners) {
+ ReactInstanceEventListener[] listeners =
+ new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
+ listeners = mReactInstanceEventListeners.toArray(listeners);
+
+ for (ReactInstanceEventListener listener : listeners) {
listener.onReactContextInitialized(reactContext);
}
}
@@ -697,14 +774,14 @@ private void detachViewFromInstance(
private void tearDownReactContext(ReactContext reactContext) {
UiThreadUtil.assertOnUiThread();
if (mLifecycleState == LifecycleState.RESUMED) {
- reactContext.onPause();
+ reactContext.onHostPause();
}
for (ReactRootView rootView : mAttachedRootViews) {
detachViewFromInstance(rootView, reactContext.getCatalystInstance());
}
- reactContext.onDestroy();
+ reactContext.destroy();
mDevSupportManager.onReactInstanceDestroyed(reactContext);
- mMemoryPressureRouter.onReactInstanceDestroyed();
+ mMemoryPressureRouter.removeMemoryPressureListener(reactContext.getCatalystInstance());
}
/**
@@ -822,9 +899,9 @@ private void processPackage(
}
}
- private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) {
+ private void moveReactContextToCurrentLifecycleState() {
if (mLifecycleState == LifecycleState.RESUMED) {
- reactContext.onResume(mCurrentActivity);
+ moveToResumedLifecycleState(true);
}
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
index 2271b20fd46a87..bf4b8b4bc8438f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
@@ -14,7 +14,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -28,6 +27,7 @@
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.SystemClock;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
@@ -151,7 +151,7 @@ private void handleTouchEvent(MotionEvent ev) {
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.START,
ev,
mTargetCoordinates[0],
@@ -173,7 +173,7 @@ private void handleTouchEvent(MotionEvent ev) {
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.END,
ev,
mTargetCoordinates[0],
@@ -184,7 +184,7 @@ private void handleTouchEvent(MotionEvent ev) {
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.MOVE,
ev,
mTargetCoordinates[0],
@@ -194,7 +194,7 @@ private void handleTouchEvent(MotionEvent ev) {
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.START,
ev,
mTargetCoordinates[0],
@@ -204,7 +204,7 @@ private void handleTouchEvent(MotionEvent ev) {
eventDispatcher.dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.END,
ev,
mTargetCoordinates[0],
@@ -255,7 +255,7 @@ private void dispatchCancelEvent(MotionEvent androidEvent) {
Assertions.assertNotNull(eventDispatcher).dispatchEvent(
TouchEvent.obtain(
mTargetTag,
- SystemClock.uptimeMillis(),
+ SystemClock.nanoTime(),
TouchEventType.CANCEL,
androidEvent,
mTargetCoordinates[0],
@@ -390,7 +390,7 @@ private KeyboardListener getKeyboardListener() {
private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final Rect mVisibleViewArea;
private final int mMinKeyboardHeightDetected;
-
+
private int mKeyboardHeight = 0;
/* package */ KeyboardListener() {
@@ -410,7 +410,7 @@ public void onGlobalLayout() {
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
final int heightDiff =
- DisplayMetricsHolder.getDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
+ DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
if (mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected) {
// keyboard is now showing, or the keyboard height has changed
mKeyboardHeight = heightDiff;
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 1a84c05fa4102f..afa121acb40232 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
@@ -57,14 +57,14 @@ public int getJSArgumentsNeeded() {
}
public abstract @Nullable T extractArgument(
- CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex);
+ CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex);
}
static final private ArgumentExtractor ARGUMENT_EXTRACTOR_BOOLEAN =
new ArgumentExtractor() {
@Override
public Boolean extractArgument(
- CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+ CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getBoolean(atIndex);
}
};
@@ -73,7 +73,7 @@ public Boolean extractArgument(
new ArgumentExtractor