From f463b731ee5fce75ccf1df43d6174527016a0379 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 15:40:52 -0700 Subject: [PATCH 01/20] Copy files from the react-native repo --- .../ReactIOS/IOSDefaultEventPluginOrder.js | 19 + .../ReactIOS/IOSNativeBridgeEventPlugin.js | 75 +++ .../native/ReactIOS/NativeMethodsMixin.js | 219 +++++++ src/renderers/native/ReactIOS/YellowBox.js | 364 ++++++++++++ .../ReactIOS/renderApplication.android.js | 139 +++++ .../native/ReactIOS/renderApplication.ios.js | 96 +++ .../native/ReactIOS/requireNativeComponent.js | 127 ++++ .../native/ReactIOS/verifyPropTypes.js | 61 ++ src/renderers/native/ReactNative/React.js | 14 + src/renderers/native/ReactNative/ReactDOM.js | 16 + .../native/ReactNative/ReactNative.js | 138 +++++ .../ReactNativeAttributePayload.js | 546 ++++++++++++++++++ .../ReactNative/ReactNativeBaseComponent.js | 239 ++++++++ .../ReactNativeBaseComponentEnvironment.js | 42 ++ .../ReactNative/ReactNativeDOMIDOperations.js | 102 ++++ .../ReactNativeDefaultInjection.js | 113 ++++ .../ReactNative/ReactNativeEventEmitter.js | 214 +++++++ .../ReactNativeGlobalInteractionHandler.js | 33 ++ .../ReactNativeGlobalResponderHandler.js | 30 + .../native/ReactNative/ReactNativeMount.js | 270 +++++++++ .../ReactNative/ReactNativePropRegistry.js | 44 ++ .../ReactNativeReconcileTransaction.js | 103 ++++ .../ReactNative/ReactNativeTagHandles.js | 113 ++++ .../ReactNative/ReactNativeTextComponent.js | 78 +++ .../ReactNative/UIManagerStatTracker.js | 57 ++ .../ReactNativeAttributePayload-test.js | 231 ++++++++ .../createReactNativeComponentClass.js | 46 ++ .../native/ReactNative/findNodeHandle.js | 112 ++++ .../browser/eventPlugins/PanResponder.js | 378 ++++++++++++ .../browser/eventPlugins/TouchHistoryMath.js | 122 ++++ .../native/vendor/react/core/clamp.js | 35 ++ .../vendor/react/platform/NodeHandle.js | 83 +++ .../worker/UniversalWorkerNodeHandle.js | 19 + .../core/ExecutionEnvironment.android.js | 51 ++ .../vendor/core/ExecutionEnvironment.ios.js | 48 ++ 35 files changed, 4377 insertions(+) create mode 100644 src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js create mode 100644 src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js create mode 100644 src/renderers/native/ReactIOS/NativeMethodsMixin.js create mode 100644 src/renderers/native/ReactIOS/YellowBox.js create mode 100644 src/renderers/native/ReactIOS/renderApplication.android.js create mode 100644 src/renderers/native/ReactIOS/renderApplication.ios.js create mode 100644 src/renderers/native/ReactIOS/requireNativeComponent.js create mode 100644 src/renderers/native/ReactIOS/verifyPropTypes.js create mode 100644 src/renderers/native/ReactNative/React.js create mode 100644 src/renderers/native/ReactNative/ReactDOM.js create mode 100644 src/renderers/native/ReactNative/ReactNative.js create mode 100644 src/renderers/native/ReactNative/ReactNativeAttributePayload.js create mode 100644 src/renderers/native/ReactNative/ReactNativeBaseComponent.js create mode 100644 src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js create mode 100644 src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js create mode 100644 src/renderers/native/ReactNative/ReactNativeDefaultInjection.js create mode 100644 src/renderers/native/ReactNative/ReactNativeEventEmitter.js create mode 100644 src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js create mode 100644 src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js create mode 100644 src/renderers/native/ReactNative/ReactNativeMount.js create mode 100644 src/renderers/native/ReactNative/ReactNativePropRegistry.js create mode 100644 src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js create mode 100644 src/renderers/native/ReactNative/ReactNativeTagHandles.js create mode 100644 src/renderers/native/ReactNative/ReactNativeTextComponent.js create mode 100644 src/renderers/native/ReactNative/UIManagerStatTracker.js create mode 100644 src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js create mode 100644 src/renderers/native/ReactNative/createReactNativeComponentClass.js create mode 100644 src/renderers/native/ReactNative/findNodeHandle.js create mode 100644 src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js create mode 100644 src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js create mode 100644 src/renderers/native/vendor/react/core/clamp.js create mode 100644 src/renderers/native/vendor/react/platform/NodeHandle.js create mode 100644 src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js create mode 100644 src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js create mode 100644 src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js diff --git a/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js b/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js new file mode 100644 index 0000000000000..80098fc414f30 --- /dev/null +++ b/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js @@ -0,0 +1,19 @@ +/** + * 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 IOSDefaultEventPluginOrder + * @flow + */ +'use strict'; + +var IOSDefaultEventPluginOrder = [ + 'ResponderEventPlugin', + 'IOSNativeBridgeEventPlugin' +]; + +module.exports = IOSDefaultEventPluginOrder; diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js new file mode 100644 index 0000000000000..d1ffc1d402e23 --- /dev/null +++ b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -0,0 +1,75 @@ +/** + * 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 IOSNativeBridgeEventPlugin + * @flow + */ +'use strict'; + +var EventPropagators = require('EventPropagators'); +var SyntheticEvent = require('SyntheticEvent'); +var UIManager = require('UIManager'); + +var merge = require('merge'); +var warning = require('fbjs/lib/warning'); + +var customBubblingEventTypes = UIManager.customBubblingEventTypes; +var customDirectEventTypes = UIManager.customDirectEventTypes; + +var allTypesByEventName = {}; + +for (var bubblingTypeName in customBubblingEventTypes) { + allTypesByEventName[bubblingTypeName] = customBubblingEventTypes[bubblingTypeName]; +} + +for (var directTypeName in customDirectEventTypes) { + warning( + !customBubblingEventTypes[directTypeName], + 'Event cannot be both direct and bubbling: %s', + directTypeName + ); + allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName]; +} + +var IOSNativeBridgeEventPlugin = { + + eventTypes: merge(customBubblingEventTypes, customDirectEventTypes), + + /** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {*} An accumulation of synthetic events. + * @see {EventPluginHub.extractEvents} + */ + extractEvents: function( + topLevelType: string, + topLevelTarget: EventTarget, + topLevelTargetID: string, + nativeEvent: Event + ): ?Object { + var bubbleDispatchConfig = customBubblingEventTypes[topLevelType]; + var directDispatchConfig = customDirectEventTypes[topLevelType]; + var event = SyntheticEvent.getPooled( + bubbleDispatchConfig || directDispatchConfig, + topLevelTargetID, + nativeEvent + ); + if (bubbleDispatchConfig) { + EventPropagators.accumulateTwoPhaseDispatches(event); + } else if (directDispatchConfig) { + EventPropagators.accumulateDirectDispatches(event); + } else { + return null; + } + return event; + } +}; + +module.exports = IOSNativeBridgeEventPlugin; diff --git a/src/renderers/native/ReactIOS/NativeMethodsMixin.js b/src/renderers/native/ReactIOS/NativeMethodsMixin.js new file mode 100644 index 0000000000000..88f451a58ca10 --- /dev/null +++ b/src/renderers/native/ReactIOS/NativeMethodsMixin.js @@ -0,0 +1,219 @@ +/** + * 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 NativeMethodsMixin + * @flow + */ +'use strict'; + +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var TextInputState = require('TextInputState'); +var UIManager = require('UIManager'); + +var findNodeHandle = require('findNodeHandle'); +var invariant = require('fbjs/lib/invariant'); + +type MeasureOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number +) => void + +type MeasureInWindowOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, +) => void + +type MeasureLayoutOnSuccessCallback = ( + left: number, + top: number, + width: number, + height: number +) => void + +function warnForStyleProps(props, validAttributes) { + for (var key in validAttributes.style) { + if (!(validAttributes[key] || props[key] === undefined)) { + console.error( + 'You are setting the style `{ ' + key + ': ... }` as a prop. You ' + + 'should nest it in a style object. ' + + 'E.g. `{ style: { ' + key + ': ... } }`' + ); + } + } +} + +/** + * `NativeMethodsMixin` provides methods to access the underlying native + * component directly. This can be useful in cases when you want to focus + * a view or measure its on-screen dimensions, for example. + * + * The methods described here are available on most of the default components + * provided by React Native. Note, however, that they are *not* available on + * composite components that aren't directly backed by a native view. This will + * generally include most components that you define in your own app. For more + * information, see [Direct + * Manipulation](docs/direct-manipulation.html). + */ +var NativeMethodsMixin = { + /** + * Determines the location on screen, width, and height of the given view and + * returns the values via an async callback. If successful, the callback will + * be called with the following arguments: + * + * - x + * - y + * - width + * - height + * - pageX + * - pageY + * + * Note that these measurements are not available until after the rendering + * has been completed in native. If you need the measurements as soon as + * possible, consider using the [`onLayout` + * prop](docs/view.html#onlayout) instead. + */ + measure: function(callback: MeasureOnSuccessCallback) { + UIManager.measure( + findNodeHandle(this), + mountSafeCallback(this, callback) + ); + }, + + /** + * Determines the location of the given view in the window and returns the + * values via an async callback. If the React root view is embedded in + * another native view, this will give you the absolute coordinates. If + * successful, the callback will be called with the following + * arguments: + * + * - x + * - y + * - width + * - height + * + * Note that these measurements are not available until after the rendering + * has been completed in native. + */ + measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) { + UIManager.measureInWindow( + findNodeHandle(this), + mountSafeCallback(this, callback) + ); + }, + + /** + * Like [`measure()`](#measure), but measures the view relative an ancestor, + * specified as `relativeToNativeNode`. This means that the returned x, y + * are relative to the origin x, y of the ancestor view. + * + * As always, to obtain a native node handle for a component, you can use + * `React.findNodeHandle(component)`. + */ + measureLayout: function( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail: () => void /* currently unused */ + ) { + UIManager.measureLayout( + findNodeHandle(this), + relativeToNativeNode, + mountSafeCallback(this, onFail), + mountSafeCallback(this, onSuccess) + ); + }, + + /** + * This function sends props straight to native. They will not participate in + * future diff process - this means that if you do not include them in the + * next render, they will remain active (see [Direct + * Manipulation](docs/direct-manipulation.html)). + */ + setNativeProps: function(nativeProps: Object) { + if (__DEV__) { + warnForStyleProps(nativeProps, this.viewConfig.validAttributes); + } + + var updatePayload = ReactNativeAttributePayload.create( + nativeProps, + this.viewConfig.validAttributes + ); + + UIManager.updateView( + findNodeHandle(this), + this.viewConfig.uiViewClassName, + updatePayload + ); + }, + + /** + * Requests focus for the given input or view. The exact behavior triggered + * will depend on the platform and type of view. + */ + focus: function() { + TextInputState.focusTextInput(findNodeHandle(this)); + }, + + /** + * Removes focus from an input or view. This is the opposite of `focus()`. + */ + blur: function() { + TextInputState.blurTextInput(findNodeHandle(this)); + } +}; + +function throwOnStylesProp(component, props) { + if (props.styles !== undefined) { + var owner = component._owner || null; + var name = component.constructor.displayName; + var msg = '`styles` is not a supported property of `' + name + '`, did ' + + 'you mean `style` (singular)?'; + if (owner && owner.constructor && owner.constructor.displayName) { + msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' + + ' component.'; + } + throw new Error(msg); + } +} +if (__DEV__) { + // hide this from Flow since we can't define these properties outside of + // __DEV__ without actually implementing them (setting them to undefined + // isn't allowed by ReactClass) + var NativeMethodsMixin_DEV = (NativeMethodsMixin: any); + invariant( + !NativeMethodsMixin_DEV.componentWillMount && + !NativeMethodsMixin_DEV.componentWillReceiveProps, + 'Do not override existing functions.' + ); + NativeMethodsMixin_DEV.componentWillMount = function () { + throwOnStylesProp(this, this.props); + }; + NativeMethodsMixin_DEV.componentWillReceiveProps = function (newProps) { + throwOnStylesProp(this, newProps); + }; +} + +/** + * In the future, we should cleanup callbacks by cancelling them instead of + * using this. + */ +var mountSafeCallback = function(context: ReactComponent, callback: ?Function): any { + return function() { + if (!callback || (context.isMounted && !context.isMounted())) { + return; + } + return callback.apply(context, arguments); + }; +}; + +module.exports = NativeMethodsMixin; diff --git a/src/renderers/native/ReactIOS/YellowBox.js b/src/renderers/native/ReactIOS/YellowBox.js new file mode 100644 index 0000000000000..e1dddc14541ef --- /dev/null +++ b/src/renderers/native/ReactIOS/YellowBox.js @@ -0,0 +1,364 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule YellowBox + * @flow + */ + +'use strict'; + +const EventEmitter = require('EventEmitter'); +import type EmitterSubscription from 'EmitterSubscription'; +const Platform = require('Platform'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); + +const _warningEmitter = new EventEmitter(); +const _warningMap = new Map(); + +/** + * YellowBox renders warnings at the bottom of the app being developed. + * + * Warnings help guard against subtle yet significant issues that can impact the + * quality of the app. This "in your face" style of warning allows developers to + * notice and correct these issues as quickly as possible. + * + * By default, the warning box is enabled in `__DEV__`. Set the following flag + * to disable it (and call `console.warn` to update any rendered ): + * + * console.disableYellowBox = true; + * console.warn('YellowBox is disabled.'); + * + * Warnings can be ignored programmatically by setting the array: + * + * console.ignoredYellowBox = ['Warning: ...']; + * + * Strings in `console.ignoredYellowBox` can be a prefix of the warning that + * should be ignored. + */ + +if (__DEV__) { + const {error, warn} = console; + console.error = function() { + error.apply(console, arguments); + // Show yellow box for the `warning` module. + if (typeof arguments[0] === 'string' && + arguments[0].startsWith('Warning: ')) { + updateWarningMap.apply(null, arguments); + } + }; + console.warn = function() { + warn.apply(console, arguments); + updateWarningMap.apply(null, arguments); + }; +} + +/** + * Simple function for formatting strings. + * + * Replaces placeholders with values passed as extra arguments + * + * @param {string} format the base string + * @param ...args the values to insert + * @return {string} the replaced string + */ +function sprintf(format, ...args) { + var index = 0; + return format.replace(/%s/g, match => args[index++]); +} + +function updateWarningMap(format, ...args): void { + const stringifySafe = require('stringifySafe'); + + format = String(format); + const argCount = (format.match(/%s/g) || []).length; + const warning = [ + sprintf(format, ...args.slice(0, argCount)), + ...args.slice(argCount).map(stringifySafe), + ].join(' '); + + const count = _warningMap.has(warning) ? _warningMap.get(warning) : 0; + _warningMap.set(warning, count + 1); + _warningEmitter.emit('warning', _warningMap); +} + +function isWarningIgnored(warning: string): boolean { + return ( + Array.isArray(console.ignoredYellowBox) && + console.ignoredYellowBox.some( + ignorePrefix => warning.startsWith(ignorePrefix) + ) + ); +} + +const WarningRow = ({count, warning, onPress}) => { + const Text = require('Text'); + const TouchableHighlight = require('TouchableHighlight'); + const View = require('View'); + + const countText = count > 1 ? + {'(' + count + ') '} : + null; + + return ( + + + + {countText} + {warning} + + + + ); +}; + +const WarningInspector = ({ + count, + warning, + onClose, + onDismiss, + onDismissAll, +}) => { + const ScrollView = require('ScrollView'); + const Text = require('Text'); + const TouchableHighlight = require('TouchableHighlight'); + const View = require('View'); + + const countSentence = + 'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.'; + + return ( + + + + {countSentence} + + + {warning} + + + + + Dismiss + + + + + Dismiss All + + + + + + ); +}; + +class YellowBox extends React.Component { + state: { + inspecting: ?string; + warningMap: Map; + }; + _listener: ?EmitterSubscription; + + constructor(props: mixed, context: mixed) { + super(props, context); + this.state = { + inspecting: null, + warningMap: _warningMap, + }; + this.dismissWarning = warning => { + const {inspecting, warningMap} = this.state; + if (warning) { + warningMap.delete(warning); + } else { + warningMap.clear(); + } + this.setState({ + inspecting: (warning && inspecting !== warning) ? inspecting : null, + warningMap, + }); + }; + } + + componentDidMount() { + let scheduled = null; + this._listener = _warningEmitter.addListener('warning', warningMap => { + // Use `setImmediate` because warnings often happen during render, but + // state cannot be set while rendering. + scheduled = scheduled || setImmediate(() => { + scheduled = null; + this.setState({ + warningMap, + }); + }); + }); + } + + componentWillUnmount() { + if (this._listener) { + this._listener.remove(); + } + } + + render() { + if (console.disableYellowBox || this.state.warningMap.size === 0) { + return null; + } + const ScrollView = require('ScrollView'); + const View = require('View'); + + const inspecting = this.state.inspecting; + const inspector = inspecting !== null ? + this.setState({inspecting: null})} + onDismiss={() => this.dismissWarning(inspecting)} + onDismissAll={() => this.dismissWarning(null)} + /> : + null; + + const rows = []; + this.state.warningMap.forEach((count, warning) => { + if (!isWarningIgnored(warning)) { + rows.push( + this.setState({inspecting: warning})} + onDismiss={() => this.dismissWarning(warning)} + /> + ); + } + }); + + const listStyle = [ + styles.list, + // Additional `0.4` so the 5th row can peek into view. + {height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)}, + ]; + return ( + + + {rows} + + {inspector} + + ); + } +} + +const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')'; +const textColor = 'white'; +const rowGutter = 1; +const rowHeight = 46; + +var styles = StyleSheet.create({ + fullScreen: { + backgroundColor: 'transparent', + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + inspector: { + backgroundColor: backgroundColor(0.95), + flex: 1, + }, + inspectorContainer: { + flex: 1, + }, + inspectorButtons: { + flexDirection: 'row', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, + inspectorButton: { + flex: 1, + padding: 22, + }, + inspectorButtonText: { + color: textColor, + fontSize: 14, + opacity: 0.8, + textAlign: 'center', + }, + inspectorContent: { + flex: 1, + paddingTop: 5, + }, + inspectorCount: { + padding: 15, + paddingBottom: 0, + }, + inspectorCountText: { + color: textColor, + fontSize: 14, + }, + inspectorWarning: { + padding: 15, + position: 'absolute', + top: 39, + bottom: 60, + }, + inspectorWarningText: { + color: textColor, + fontSize: 16, + fontWeight: '600', + }, + list: { + backgroundColor: 'transparent', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, + listRow: { + position: 'relative', + backgroundColor: backgroundColor(0.95), + flex: 1, + height: rowHeight, + marginTop: rowGutter, + }, + listRowContent: { + flex: 1, + }, + listRowCount: { + color: 'rgba(255, 255, 255, 0.5)', + }, + listRowText: { + color: textColor, + position: 'absolute', + left: 0, + top: Platform.OS === 'android' ? 5 : 7, + marginLeft: 15, + marginRight: 15, + }, +}); + +module.exports = YellowBox; diff --git a/src/renderers/native/ReactIOS/renderApplication.android.js b/src/renderers/native/ReactIOS/renderApplication.android.js new file mode 100644 index 0000000000000..03191bbc03785 --- /dev/null +++ b/src/renderers/native/ReactIOS/renderApplication.android.js @@ -0,0 +1,139 @@ +/** + * 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 renderApplication + */ + +'use strict'; + +var Inspector = require('Inspector'); +var Portal = require('Portal'); +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Subscribable = require('Subscribable'); +var View = require('View'); + +var invariant = require('fbjs/lib/invariant'); + +var YellowBox = __DEV__ ? require('YellowBox') : null; + +// require BackAndroid so it sets the default handler that exits the app if no listeners respond +require('BackAndroid'); + +var AppContainer = React.createClass({ + mixins: [Subscribable.Mixin], + + getInitialState: function() { + return { + enabled: __DEV__, + inspectorVisible: false, + rootNodeHandle: null, + rootImportanceForAccessibility: 'auto', + }; + }, + + toggleElementInspector: function() { + this.setState({ + inspectorVisible: !this.state.inspectorVisible, + rootNodeHandle: React.findNodeHandle(this.refs.main), + }); + }, + + componentDidMount: function() { + this.addListenerOn( + RCTDeviceEventEmitter, + 'toggleElementInspector', + this.toggleElementInspector + ); + + this._unmounted = false; + }, + + renderInspector: function() { + return this.state.inspectorVisible ? + : + null; + }, + + componentWillUnmount: function() { + this._unmounted = true; + }, + + setRootAccessibility: function(modalVisible) { + if (this._unmounted) { + return; + } + + this.setState({ + rootImportanceForAccessibility: modalVisible ? 'no-hide-descendants' : 'auto', + }); + }, + + render: function() { + var RootComponent = this.props.rootComponent; + var appView = + + + + ; + let yellowBox = null; + if (__DEV__) { + yellowBox = ; + } + return this.state.enabled ? + + {appView} + {yellowBox} + {this.renderInspector()} + : + appView; + } +}); + +function renderApplication( + RootComponent: ReactClass

, + initialProps: P, + rootTag: any +) { + invariant( + rootTag, + 'Expect to have a valid rootTag, instead got ', rootTag + ); + React.render( + , + rootTag + ); +} + +var styles = StyleSheet.create({ + // This is needed so the application covers the whole screen + // and therefore the contents of the Portal are not clipped. + appContainer: { + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + }, +}); + +module.exports = renderApplication; diff --git a/src/renderers/native/ReactIOS/renderApplication.ios.js b/src/renderers/native/ReactIOS/renderApplication.ios.js new file mode 100644 index 0000000000000..c3e4f8178044b --- /dev/null +++ b/src/renderers/native/ReactIOS/renderApplication.ios.js @@ -0,0 +1,96 @@ +/** + * 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 renderApplication + * @noflow + */ + +'use strict'; + +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Subscribable = require('Subscribable'); +var View = require('View'); + +var invariant = require('fbjs/lib/invariant'); + +var Inspector = __DEV__ ? require('Inspector') : null; +var YellowBox = __DEV__ ? require('YellowBox') : null; + +var AppContainer = React.createClass({ + mixins: [Subscribable.Mixin], + + getInitialState: function() { + return { inspector: null }; + }, + + toggleElementInspector: function() { + var inspector = !__DEV__ || this.state.inspector + ? null + : ; + this.setState({inspector}); + }, + + componentDidMount: function() { + this.addListenerOn( + RCTDeviceEventEmitter, + 'toggleElementInspector', + this.toggleElementInspector + ); + }, + + render: function() { + let yellowBox = null; + if (__DEV__) { + yellowBox = ; + } + return ( + + + {this.props.children} + + {yellowBox} + {this.state.inspector} + + ); + } +}); + +function renderApplication( + RootComponent: ReactClass

, + initialProps: P, + rootTag: any +) { + invariant( + rootTag, + 'Expect to have a valid rootTag, instead got ', rootTag + ); + /* eslint-disable jsx-no-undef-with-namespace */ + React.render( + + + , + rootTag + ); + /* eslint-enable jsx-no-undef-with-namespace */ +} + +var styles = StyleSheet.create({ + appContainer: { + flex: 1, + }, +}); + +module.exports = renderApplication; diff --git a/src/renderers/native/ReactIOS/requireNativeComponent.js b/src/renderers/native/ReactIOS/requireNativeComponent.js new file mode 100644 index 0000000000000..61993f799fb9b --- /dev/null +++ b/src/renderers/native/ReactIOS/requireNativeComponent.js @@ -0,0 +1,127 @@ +/** + * 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 requireNativeComponent + * @flow + */ +'use strict'; + +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +var UIManager = require('UIManager'); +var UnimplementedView = require('UnimplementedView'); + +var createReactNativeComponentClass = require('createReactNativeComponentClass'); + +var insetsDiffer = require('insetsDiffer'); +var pointsDiffer = require('pointsDiffer'); +var matricesDiffer = require('matricesDiffer'); +var processColor = require('processColor'); +var resolveAssetSource = require('resolveAssetSource'); +var sizesDiffer = require('sizesDiffer'); +var verifyPropTypes = require('verifyPropTypes'); +var warning = require('fbjs/lib/warning'); + +/** + * Used to create React components that directly wrap native component + * implementations. Config information is extracted from data exported from the + * UIManager module. You should also wrap the native component in a + * hand-written component with full propTypes definitions and other + * documentation - pass the hand-written component in as `componentInterface` to + * verify all the native props are documented via `propTypes`. + * + * If some native props shouldn't be exposed in the wrapper interface, you can + * pass null for `componentInterface` and call `verifyPropTypes` directly + * with `nativePropsToIgnore`; + * + * Common types are lined up with the appropriate prop differs with + * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`. + */ +import type { ComponentInterface } from 'verifyPropTypes'; + +function requireNativeComponent( + viewName: string, + componentInterface?: ?ComponentInterface, + extraConfig?: ?{nativeOnly?: Object}, +): Function { + var viewConfig = UIManager[viewName]; + if (!viewConfig || !viewConfig.NativeProps) { + warning(false, 'Native component for "%s" does not exist', viewName); + return UnimplementedView; + } + var nativeProps = { + ...UIManager.RCTView.NativeProps, + ...viewConfig.NativeProps, + }; + viewConfig.uiViewClassName = viewName; + viewConfig.validAttributes = {}; + viewConfig.propTypes = componentInterface && componentInterface.propTypes; + for (var key in nativeProps) { + var useAttribute = false; + var attribute = {}; + + var differ = TypeToDifferMap[nativeProps[key]]; + if (differ) { + attribute.diff = differ; + useAttribute = true; + } + + var processor = TypeToProcessorMap[nativeProps[key]]; + if (processor) { + attribute.process = processor; + useAttribute = true; + } + + viewConfig.validAttributes[key] = useAttribute ? attribute : true; + } + + // Unfortunately, the current set up puts the style properties on the top + // level props object. We also need to add the nested form for API + // compatibility. This allows these props on both the top level and the + // nested style level. TODO: Move these to nested declarations on the + // native side. + viewConfig.validAttributes.style = ReactNativeStyleAttributes; + + if (__DEV__) { + componentInterface && verifyPropTypes( + componentInterface, + viewConfig, + extraConfig && extraConfig.nativeOnly + ); + } + return createReactNativeComponentClass(viewConfig); +} + +var TypeToDifferMap = { + // iOS Types + CATransform3D: matricesDiffer, + CGPoint: pointsDiffer, + CGSize: sizesDiffer, + UIEdgeInsets: insetsDiffer, + // Android Types + // (not yet implemented) +}; + +function processColorArray(colors: []): [] { + return colors && colors.map(processColor); +} + +var TypeToProcessorMap = { + // iOS Types + CGColor: processColor, + CGColorArray: processColorArray, + UIColor: processColor, + UIColorArray: processColorArray, + CGImage: resolveAssetSource, + UIImage: resolveAssetSource, + RCTImageSource: resolveAssetSource, + // Android Types + Color: processColor, + ColorArray: processColorArray, +}; + +module.exports = requireNativeComponent; diff --git a/src/renderers/native/ReactIOS/verifyPropTypes.js b/src/renderers/native/ReactIOS/verifyPropTypes.js new file mode 100644 index 0000000000000..d284d60ec0aec --- /dev/null +++ b/src/renderers/native/ReactIOS/verifyPropTypes.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule verifyPropTypes + * @flow + */ +'use strict'; + +var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); + +export type ComponentInterface = ReactClass | { + name?: string; + displayName?: string; + propTypes: Object; +}; + +function verifyPropTypes( + componentInterface: ComponentInterface, + viewConfig: Object, + nativePropsToIgnore?: ?Object +) { + if (!viewConfig) { + return; // This happens for UnimplementedView. + } + var componentName = componentInterface.name || + componentInterface.displayName || + 'unknown'; + if (!componentInterface.propTypes) { + throw new Error( + '`' + componentName + '` has no propTypes defined`' + ); + } + + var nativeProps = viewConfig.NativeProps; + for (var prop in nativeProps) { + if (!componentInterface.propTypes[prop] && + !ReactNativeStyleAttributes[prop] && + (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { + var message; + if (componentInterface.propTypes.hasOwnProperty(prop)) { + message = '`' + componentName + '` has incorrectly defined propType for native prop `' + + viewConfig.uiViewClassName + '.' + prop + '` of native type `' + nativeProps[prop]; + } else { + message = '`' + componentName + '` has no propType for native prop `' + + viewConfig.uiViewClassName + '.' + prop + '` of native type `' + + nativeProps[prop] + '`'; + }; + message += '\nIf you haven\'t changed this prop yourself, this usually means that ' + + 'your versions of the native code and JavaScript code are out of sync. Updating both ' + + 'should make this error go away.'; + throw new Error(message); + } + } +} + +module.exports = verifyPropTypes; diff --git a/src/renderers/native/ReactNative/React.js b/src/renderers/native/ReactNative/React.js new file mode 100644 index 0000000000000..9cd4464f2860c --- /dev/null +++ b/src/renderers/native/ReactNative/React.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule React + * @flow + */ +'use strict'; + +module.exports = require('ReactNative'); diff --git a/src/renderers/native/ReactNative/ReactDOM.js b/src/renderers/native/ReactNative/ReactDOM.js new file mode 100644 index 0000000000000..7ac737c1323a5 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactDOM.js @@ -0,0 +1,16 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule ReactDOM + */ + +'use strict'; + +var ReactUpdates = require('ReactUpdates'); + +// Temporary shim required for ReactTestUtils and Relay. +var ReactDOM = { + unstable_batchedUpdates: ReactUpdates.batchedUpdates, +}; + +module.exports = ReactDOM; diff --git a/src/renderers/native/ReactNative/ReactNative.js b/src/renderers/native/ReactNative/ReactNative.js new file mode 100644 index 0000000000000..148a366663efd --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNative.js @@ -0,0 +1,138 @@ +/** + * 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 ReactNative + * @flow + */ +'use strict'; + +// Require ReactNativeDefaultInjection first for its side effects of setting up +// the JS environment +var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection'); + +var ReactChildren = require('ReactChildren'); +var ReactClass = require('ReactClass'); +var ReactComponent = require('ReactComponent'); +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactElement = require('ReactElement'); +var ReactElementValidator = require('ReactElementValidator'); +var ReactInstanceHandles = require('ReactInstanceHandles'); +var ReactNativeMount = require('ReactNativeMount'); +var ReactPropTypes = require('ReactPropTypes'); +var ReactUpdates = require('ReactUpdates'); + +var findNodeHandle = require('findNodeHandle'); +var invariant = require('fbjs/lib/invariant'); +var onlyChild = require('onlyChild'); +var warning = require('fbjs/lib/warning'); + +ReactNativeDefaultInjection.inject(); + +var createElement = ReactElement.createElement; +var createFactory = ReactElement.createFactory; +var cloneElement = ReactElement.cloneElement; + +if (__DEV__) { + createElement = ReactElementValidator.createElement; + createFactory = ReactElementValidator.createFactory; + cloneElement = ReactElementValidator.cloneElement; +} + +var resolveDefaultProps = function(element) { + // Could be optimized, but not currently in heavy use. + var defaultProps = element.type.defaultProps; + var props = element.props; + for (var propName in defaultProps) { + if (props[propName] === undefined) { + props[propName] = defaultProps[propName]; + } + } +}; + +// Experimental optimized element creation +var augmentElement = function(element: ReactElement): ReactElement { + if (__DEV__) { + invariant( + false, + 'This optimized path should never be used in DEV mode because ' + + 'it does not provide validation. Check your JSX transform.' + ); + } + element._owner = ReactCurrentOwner.current; + if (element.type.defaultProps) { + resolveDefaultProps(element); + } + return element; +}; + +var render = function( + element: ReactElement, + mountInto: number, + callback?: ?(() => void) +): ?ReactComponent { + return ReactNativeMount.renderComponent(element, mountInto, callback); +}; + +var ReactNative = { + hasReactNativeInitialized: false, + Children: { + map: ReactChildren.map, + forEach: ReactChildren.forEach, + count: ReactChildren.count, + toArray: ReactChildren.toArray, + only: onlyChild + }, + Component: ReactComponent, + PropTypes: ReactPropTypes, + createClass: ReactClass.createClass, + createElement: createElement, + createFactory: createFactory, + cloneElement: cloneElement, + _augmentElement: augmentElement, + findNodeHandle: findNodeHandle, + render: render, + unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, + + /* eslint-disable camelcase */ + unstable_batchedUpdates: ReactUpdates.batchedUpdates, + /* eslint-enable camelcase */ + + // Hook for JSX spread, don't use this for anything else. + __spread: Object.assign, + + unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, + isValidClass: ReactElement.isValidFactory, + isValidElement: ReactElement.isValidElement, + + // Deprecations (remove for 0.13) + renderComponent: function( + element: ReactElement, + mountInto: number, + callback?: ?(() => void) + ): ?ReactComponent { + warning('Use React.render instead of React.renderComponent'); + return ReactNative.render(element, mountInto, callback); + }, +}; + +// Inject the runtime into a devtools global hook regardless of browser. +// Allows for debugging when the hook is injected on the page. +/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */ +if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ + CurrentOwner: ReactCurrentOwner, + InstanceHandles: ReactInstanceHandles, + Mount: ReactNativeMount, + Reconciler: require('ReactReconciler'), + TextComponent: require('ReactNativeTextComponent'), + }); +} + +module.exports = ReactNative; diff --git a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js new file mode 100644 index 0000000000000..7adf0d27c7dc6 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js @@ -0,0 +1,546 @@ +/** + * 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 ReactNativeAttributePayload + * @flow + */ +'use strict'; + +var Platform = require('Platform'); +var ReactNativePropRegistry = require('ReactNativePropRegistry'); + +var deepDiffer = require('deepDiffer'); +var flattenStyle = require('flattenStyle'); + +var emptyObject = {}; + +/** + * Create a payload that contains all the updates between two sets of props. + * + * These helpers are all encapsulated into a single module, because they use + * mutation as a performance optimization which leads to subtle shared + * dependencies between the code paths. To avoid this mutable state leaking + * across modules, I've kept them isolated to this module. + */ + +type AttributeDiffer = (prevProp: mixed, nextProp: mixed) => boolean; +type AttributePreprocessor = (nextProp: mixed) => mixed; + +type CustomAttributeConfiguration = + { diff: AttributeDiffer, process: AttributePreprocessor } | + { diff: AttributeDiffer } | + { process: AttributePreprocessor }; + +type AttributeConfiguration = + { [key: string]: ( + CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/ + ) }; + +type NestedNode = Array | Object | number; + +// Tracks removed keys +var removedKeys = null; +var removedKeyCount = 0; + +function translateKey(propKey: string) : string { + if (propKey === 'transform') { + // We currently special case the key for `transform`. iOS uses the + // transformMatrix name and Android uses the decomposedMatrix name. + // TODO: We could unify these names and just use the name `transform` + // all the time. Just need to update the native side. + if (Platform.OS === 'android') { + return 'decomposedMatrix'; + } else { + return 'transformMatrix'; + } + } + return propKey; +} + +function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean { + if (typeof nextProp !== 'object' || nextProp === null) { + // Scalars have already been checked for equality + return true; + } else { + // For objects and arrays, the default diffing algorithm is a deep compare + return deepDiffer(prevProp, nextProp); + } +} + +function resolveObject(idOrObject: number | Object) : Object { + if (typeof idOrObject === 'number') { + return ReactNativePropRegistry.getByID(idOrObject); + } + return idOrObject; +} + +function restoreDeletedValuesInNestedArray( + updatePayload: Object, + node: NestedNode, + validAttributes: AttributeConfiguration +) { + if (Array.isArray(node)) { + var i = node.length; + while (i-- && removedKeyCount > 0) { + restoreDeletedValuesInNestedArray( + updatePayload, + node[i], + validAttributes + ); + } + } else if (node && removedKeyCount > 0) { + var obj = resolveObject(node); + for (var propKey in removedKeys) { + if (!removedKeys[propKey]) { + continue; + } + var nextProp = obj[propKey]; + if (nextProp === undefined) { + continue; + } + + var attributeConfig = validAttributes[propKey]; + if (!attributeConfig) { + continue; // not a valid native prop + } + + if (typeof nextProp === 'function') { + nextProp = true; + } + if (typeof nextProp === 'undefined') { + nextProp = null; + } + + if (typeof attributeConfig !== 'object') { + // case: !Object is the default case + updatePayload[propKey] = nextProp; + } else if (typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration + var nextValue = typeof attributeConfig.process === 'function' ? + attributeConfig.process(nextProp) : + nextProp; + updatePayload[propKey] = nextValue; + } + removedKeys[propKey] = false; + removedKeyCount--; + } + } +} + +function diffNestedArrayProperty( + updatePayload:? Object, + prevArray: Array, + nextArray: Array, + validAttributes: AttributeConfiguration +) : ?Object { + var minLength = prevArray.length < nextArray.length ? + prevArray.length : + nextArray.length; + var i; + for (i = 0; i < minLength; i++) { + // Diff any items in the array in the forward direction. Repeated keys + // will be overwritten by later values. + updatePayload = diffNestedProperty( + updatePayload, + prevArray[i], + nextArray[i], + validAttributes + ); + } + for (; i < prevArray.length; i++) { + // Clear out all remaining properties. + updatePayload = clearNestedProperty( + updatePayload, + prevArray[i], + validAttributes + ); + } + for (; i < nextArray.length; i++) { + // Add all remaining properties. + updatePayload = addNestedProperty( + updatePayload, + nextArray[i], + validAttributes + ); + } + return updatePayload; +} + +function diffNestedProperty( + updatePayload:? Object, + prevProp: NestedNode, + nextProp: NestedNode, + validAttributes: AttributeConfiguration +) : ?Object { + + if (!updatePayload && prevProp === nextProp) { + // If no properties have been added, then we can bail out quickly on object + // equality. + return updatePayload; + } + + if (!prevProp || !nextProp) { + if (nextProp) { + return addNestedProperty( + updatePayload, + nextProp, + validAttributes + ); + } + if (prevProp) { + return clearNestedProperty( + updatePayload, + prevProp, + validAttributes + ); + } + return updatePayload; + } + + if (!Array.isArray(prevProp) && !Array.isArray(nextProp)) { + // Both are leaves, we can diff the leaves. + return diffProperties( + updatePayload, + resolveObject(prevProp), + resolveObject(nextProp), + validAttributes + ); + } + + if (Array.isArray(prevProp) && Array.isArray(nextProp)) { + // Both are arrays, we can diff the arrays. + return diffNestedArrayProperty( + updatePayload, + prevProp, + nextProp, + validAttributes + ); + } + + if (Array.isArray(prevProp)) { + return diffProperties( + updatePayload, + // $FlowFixMe - We know that this is always an object when the input is. + flattenStyle(prevProp), + // $FlowFixMe - We know that this isn't an array because of above flow. + resolveObject(nextProp), + validAttributes + ); + } + + return diffProperties( + updatePayload, + resolveObject(prevProp), + // $FlowFixMe - We know that this is always an object when the input is. + flattenStyle(nextProp), + validAttributes + ); +} + +/** + * addNestedProperty takes a single set of props and valid attribute + * attribute configurations. It processes each prop and adds it to the + * updatePayload. + */ +function addNestedProperty( + updatePayload:? Object, + nextProp: NestedNode, + validAttributes: AttributeConfiguration +) { + if (!nextProp) { + return updatePayload; + } + + if (!Array.isArray(nextProp)) { + // Add each property of the leaf. + return addProperties( + updatePayload, + resolveObject(nextProp), + validAttributes + ); + } + + for (var i = 0; i < nextProp.length; i++) { + // Add all the properties of the array. + updatePayload = addNestedProperty( + updatePayload, + nextProp[i], + validAttributes + ); + } + + return updatePayload; +} + +/** + * clearNestedProperty takes a single set of props and valid attributes. It + * adds a null sentinel to the updatePayload, for each prop key. + */ +function clearNestedProperty( + updatePayload:? Object, + prevProp: NestedNode, + validAttributes: AttributeConfiguration +) : ?Object { + if (!prevProp) { + return updatePayload; + } + + if (!Array.isArray(prevProp)) { + // Add each property of the leaf. + return clearProperties( + updatePayload, + resolveObject(prevProp), + validAttributes + ); + } + + for (var i = 0; i < prevProp.length; i++) { + // Add all the properties of the array. + updatePayload = clearNestedProperty( + updatePayload, + prevProp[i], + validAttributes + ); + } + return updatePayload; +} + +/** + * diffProperties takes two sets of props and a set of valid attributes + * and write to updatePayload the values that changed or were deleted. + * If no updatePayload is provided, a new one is created and returned if + * anything changed. + */ +function diffProperties( + updatePayload: ?Object, + prevProps: Object, + nextProps: Object, + validAttributes: AttributeConfiguration +): ?Object { + var attributeConfig : ?(CustomAttributeConfiguration | AttributeConfiguration); + var nextProp; + var prevProp; + var altKey; + + for (var propKey in nextProps) { + attributeConfig = validAttributes[propKey]; + if (!attributeConfig) { + continue; // not a valid native prop + } + + altKey = translateKey(propKey); + if (!validAttributes[altKey]) { + // If there is no config for the alternative, bail out. Helps ART. + altKey = propKey; + } + + prevProp = prevProps[propKey]; + nextProp = nextProps[propKey]; + + // functions are converted to booleans as markers that the associated + // events should be sent from native. + if (typeof nextProp === 'function') { + nextProp = (true : any); + // If nextProp is not a function, then don't bother changing prevProp + // since nextProp will win and go into the updatePayload regardless. + if (typeof prevProp === 'function') { + prevProp = (true : any); + } + } + + // An explicit value of undefined is treated as a null because it overrides + // any other preceeding value. + if (typeof nextProp === 'undefined') { + nextProp = (null : any); + if (typeof prevProp === 'undefined') { + prevProp = (null : any); + } + } + + if (removedKeys) { + removedKeys[propKey] = false; + } + + if (updatePayload && updatePayload[altKey] !== undefined) { + // Something else already triggered an update to this key because another + // value diffed. Since we're now later in the nested arrays our value is + // more important so we need to calculate it and override the existing + // value. It doesn't matter if nothing changed, we'll set it anyway. + + // Pattern match on: attributeConfig + if (typeof attributeConfig !== 'object') { + // case: !Object is the default case + updatePayload[altKey] = nextProp; + } else if (typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration + var nextValue = typeof attributeConfig.process === 'function' ? + attributeConfig.process(nextProp) : + nextProp; + updatePayload[altKey] = nextValue; + } + continue; + } + + if (prevProp === nextProp) { + continue; // nothing changed + } + + // Pattern match on: attributeConfig + if (typeof attributeConfig !== 'object') { + // case: !Object is the default case + if (defaultDiffer(prevProp, nextProp)) { + // a normal leaf has changed + (updatePayload || (updatePayload = {}))[altKey] = nextProp; + } + } else if (typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + // case: CustomAttributeConfiguration + var shouldUpdate = prevProp === undefined || ( + typeof attributeConfig.diff === 'function' ? + attributeConfig.diff(prevProp, nextProp) : + defaultDiffer(prevProp, nextProp) + ); + if (shouldUpdate) { + var nextValue = typeof attributeConfig.process === 'function' ? + attributeConfig.process(nextProp) : + nextProp; + (updatePayload || (updatePayload = {}))[altKey] = nextValue; + } + } else { + // default: fallthrough case when nested properties are defined + removedKeys = null; + removedKeyCount = 0; + updatePayload = diffNestedProperty( + updatePayload, + prevProp, + nextProp, + attributeConfig + ); + if (removedKeyCount > 0 && updatePayload) { + restoreDeletedValuesInNestedArray( + updatePayload, + nextProp, + attributeConfig + ); + removedKeys = null; + } + } + } + + // Also iterate through all the previous props to catch any that have been + // removed and make sure native gets the signal so it can reset them to the + // default. + for (var propKey in prevProps) { + if (nextProps[propKey] !== undefined) { + continue; // we've already covered this key in the previous pass + } + attributeConfig = validAttributes[propKey]; + if (!attributeConfig) { + continue; // not a valid native prop + } + + altKey = translateKey(propKey); + if (!attributeConfig[altKey]) { + // If there is no config for the alternative, bail out. Helps ART. + altKey = propKey; + } + + if (updatePayload && updatePayload[altKey] !== undefined) { + // This was already updated to a diff result earlier. + continue; + } + + prevProp = prevProps[propKey]; + if (prevProp === undefined) { + continue; // was already empty anyway + } + // Pattern match on: attributeConfig + if (typeof attributeConfig !== 'object' || + typeof attributeConfig.diff === 'function' || + typeof attributeConfig.process === 'function') { + + // case: CustomAttributeConfiguration | !Object + // Flag the leaf property for removal by sending a sentinel. + (updatePayload || (updatePayload = {}))[altKey] = null; + if (!removedKeys) { + removedKeys = {}; + } + if (!removedKeys[propKey]) { + removedKeys[propKey] = true; + removedKeyCount++; + } + } else { + // default: + // This is a nested attribute configuration where all the properties + // were removed so we need to go through and clear out all of them. + updatePayload = clearNestedProperty( + updatePayload, + prevProp, + attributeConfig + ); + } + } + return updatePayload; +} + +/** + * addProperties adds all the valid props to the payload after being processed. + */ +function addProperties( + updatePayload: ?Object, + props: Object, + validAttributes: AttributeConfiguration +) : ?Object { + // TODO: Fast path + return diffProperties(updatePayload, emptyObject, props, validAttributes); +} + +/** + * clearProperties clears all the previous props by adding a null sentinel + * to the payload for each valid key. + */ +function clearProperties( + updatePayload: ?Object, + prevProps: Object, + validAttributes: AttributeConfiguration +) :? Object { + // TODO: Fast path + return diffProperties(updatePayload, prevProps, emptyObject, validAttributes); +} + +var ReactNativeAttributePayload = { + + create: function( + props: Object, + validAttributes: AttributeConfiguration + ) : ?Object { + return addProperties( + null, // updatePayload + props, + validAttributes + ); + }, + + diff: function( + prevProps: Object, + nextProps: Object, + validAttributes: AttributeConfiguration + ) : ?Object { + return diffProperties( + null, // updatePayload + prevProps, + nextProps, + validAttributes + ); + } + +}; + +module.exports = ReactNativeAttributePayload; diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js new file mode 100644 index 0000000000000..b879b45b6a51a --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -0,0 +1,239 @@ +/** + * 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 ReactNativeBaseComponent + * @flow + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactMultiChild = require('ReactMultiChild'); +var UIManager = require('UIManager'); + +var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); + +var registrationNames = ReactNativeEventEmitter.registrationNames; +var putListener = ReactNativeEventEmitter.putListener; +var deleteListener = ReactNativeEventEmitter.deleteListener; +var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners; + +type ReactNativeBaseComponentViewConfig = { + validAttributes: Object; + uiViewClassName: string; +} + +// require('UIManagerStatTracker').install(); // uncomment to enable + +/** + * @constructor ReactNativeBaseComponent + * @extends ReactComponent + * @extends ReactMultiChild + * @param {!object} UIKit View Configuration. + */ +var ReactNativeBaseComponent = function( + viewConfig: ReactNativeBaseComponentViewConfig +) { + this.viewConfig = viewConfig; +}; + +/** + * Mixin for containers that contain UIViews. NOTE: markup is rendered markup + * which is a `viewID` ... see the return value for `mountComponent` ! + */ +ReactNativeBaseComponent.Mixin = { + getPublicInstance: function() { + // TODO: This should probably use a composite wrapper + return this; + }, + + construct: function(element) { + this._currentElement = element; + }, + + unmountComponent: function() { + deleteAllListeners(this._rootNodeID); + this.unmountChildren(); + this._rootNodeID = null; + }, + + /** + * Every native component is responsible for allocating its own `tag`, and + * issuing the native `createView` command. But it is not responsible for + * recording the fact that its own `rootNodeID` is associated with a + * `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as + * a child of a container can confidently record that in + * `ReactNativeTagHandles`. + */ + initializeChildren: function(children, containerTag, transaction, context) { + var mountImages = this.mountChildren(children, transaction, context); + // In a well balanced tree, half of the nodes are in the bottom row and have + // no children - let's avoid calling out to the native bridge for a large + // portion of the children. + if (mountImages.length) { + + // TODO: Pool these per platform view class. Reusing the `mountImages` + // array would likely be a jit deopt. + var createdTags = []; + for (var i = 0, l = mountImages.length; i < l; i++) { + var mountImage = mountImages[i]; + var childTag = mountImage.tag; + var childID = mountImage.rootNodeID; + warning( + mountImage && mountImage.rootNodeID && mountImage.tag, + 'Mount image returned does not have required data' + ); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( + childID, + childTag + ); + createdTags[i] = mountImage.tag; + } + UIManager.setChildren(containerTag, createdTags); + } + }, + + /** + * Updates the component's currently mounted representation. + * + * @param {object} nextElement + * @param {ReactReconcileTransaction} transaction + * @param {object} context + * @internal + */ + receiveComponent: function(nextElement, transaction, context) { + var prevElement = this._currentElement; + this._currentElement = nextElement; + + if (__DEV__) { + for (var key in this.viewConfig.validAttributes) { + if (nextElement.props.hasOwnProperty(key)) { + deepFreezeAndThrowOnMutationInDev(nextElement.props[key]); + } + } + } + + var updatePayload = ReactNativeAttributePayload.diff( + prevElement.props, + nextElement.props, + this.viewConfig.validAttributes + ); + + if (updatePayload) { + UIManager.updateView( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID), + this.viewConfig.uiViewClassName, + updatePayload + ); + } + + this._reconcileListenersUponUpdate( + prevElement.props, + nextElement.props + ); + this.updateChildren(nextElement.props.children, transaction, context); + }, + + /** + * @param {object} initialProps Native component props. + */ + _registerListenersUponCreation: function(initialProps) { + for (var key in initialProps) { + // NOTE: The check for `!props[key]`, is only possible because this method + // registers listeners the *first* time a component is created. + if (registrationNames[key] && initialProps[key]) { + var listener = initialProps[key]; + putListener(this._rootNodeID, key, listener); + } + } + }, + + /** + * Reconciles event listeners, adding or removing if necessary. + * @param {object} prevProps Native component props including events. + * @param {object} nextProps Next native component props including events. + */ + _reconcileListenersUponUpdate: function(prevProps, nextProps) { + for (var key in nextProps) { + if (registrationNames[key] && (nextProps[key] !== prevProps[key])) { + if (nextProps[key]) { + putListener(this._rootNodeID, key, nextProps[key]); + } else { + deleteListener(this._rootNodeID, key); + } + } + } + }, + + /** + * @param {string} rootID Root ID of this subtree. + * @param {Transaction} transaction For creating/updating. + * @return {string} Unique iOS view tag. + */ + mountComponent: function(rootID, transaction, context) { + this._rootNodeID = rootID; + + var tag = ReactNativeTagHandles.allocateTag(); + + if (__DEV__) { + for (var key in this.viewConfig.validAttributes) { + if (this._currentElement.props.hasOwnProperty(key)) { + deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]); + } + } + } + + var updatePayload = ReactNativeAttributePayload.create( + this._currentElement.props, + this.viewConfig.validAttributes + ); + + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + if (nativeTopRootID == null) { + invariant( + false, + 'nativeTopRootID not found for tag ' + tag + ' view type ' + + this.viewConfig.uiViewClassName + ' with rootID ' + rootID); + } + UIManager.createView( + tag, + this.viewConfig.uiViewClassName, + ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID], + updatePayload + ); + + this._registerListenersUponCreation(this._currentElement.props); + this.initializeChildren( + this._currentElement.props.children, + tag, + transaction, + context + ); + return { + rootNodeID: rootID, + tag: tag + }; + } +}; + +/** + * Order of mixins is important. ReactNativeBaseComponent overrides methods in + * ReactMultiChild. + */ +Object.assign( + ReactNativeBaseComponent.prototype, + ReactMultiChild.Mixin, + ReactNativeBaseComponent.Mixin, + NativeMethodsMixin +); + +module.exports = ReactNativeBaseComponent; diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js b/src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js new file mode 100644 index 0000000000000..6e038620d66b8 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js @@ -0,0 +1,42 @@ +/** + * 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 ReactNativeComponentEnvironment + * @flow + */ +'use strict'; + +var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations'); +var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction'); + +var ReactNativeComponentEnvironment = { + + processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates, + + replaceNodeWithMarkupByID: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + + /** + * Nothing to do for UIKit bridge. + * + * @private + */ + unmountIDFromEnvironment: function(/*rootNodeID*/) { + + }, + + /** + * @param {DOMElement} Element to clear. + */ + clearNode: function(/*containerView*/) { + + }, + + ReactReconcileTransaction: ReactNativeReconcileTransaction, +}; + +module.exports = ReactNativeComponentEnvironment; diff --git a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js new file mode 100644 index 0000000000000..fbf90db503374 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeDOMIDOperations + * @flow + */ +'use strict'; + +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); +var ReactPerf = require('ReactPerf'); +var UIManager = require('UIManager'); + +/** + * Updates a component's children by processing a series of updates. + * For each of the update/create commands, the `fromIndex` refers to the index + * that the item existed at *before* any of the updates are applied, and the + * `toIndex` refers to the index after *all* of the updates are applied + * (including deletes/moves). TODO: refactor so this can be shared with + * DOMChildrenOperations. + * + * @param {array} updates List of update configurations. + * @param {array} markup List of markup strings - in the case of React + * IOS, the ids of new components assumed to be already created. + */ +var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { + if (!childrenUpdates.length) { + return; + } + var byContainerTag = {}; + // Group by parent ID - send them across the bridge in separate commands per + // containerID. + for (var i = 0; i < childrenUpdates.length; i++) { + var update = childrenUpdates[i]; + var containerTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(update.parentID); + var updates = byContainerTag[containerTag] || (byContainerTag[containerTag] = {}); + if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) { + (updates.moveFromIndices || (updates.moveFromIndices = [])).push(update.fromIndex); + (updates.moveToIndices || (updates.moveToIndices = [])).push(update.toIndex); + } else if (update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { + (updates.removeAtIndices || (updates.removeAtIndices = [])).push(update.fromIndex); + } else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) { + var mountImage = markupList[update.markupIndex]; + var tag = mountImage.tag; + var rootNodeID = mountImage.rootNodeID; + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(rootNodeID, tag); + (updates.addAtIndices || (updates.addAtIndices = [])).push(update.toIndex); + (updates.addChildTags || (updates.addChildTags = [])).push(tag); + } + } + // Note this enumeration order will be different on V8! Move `byContainerTag` + // to a sparse array as soon as we confirm there are not horrible perf + // penalties. + for (var updateParentTagString in byContainerTag) { + var updateParentTagNumber = +updateParentTagString; + var childUpdatesToSend = byContainerTag[updateParentTagNumber]; + UIManager.manageChildren( + updateParentTagNumber, + childUpdatesToSend.moveFromIndices, + childUpdatesToSend.moveToIndices, + childUpdatesToSend.addChildTags, + childUpdatesToSend.addAtIndices, + childUpdatesToSend.removeAtIndices + ); + } +}; + +/** + * Operations used to process updates to DOM nodes. This is made injectable via + * `ReactComponent.DOMIDOperations`. + */ +var ReactNativeDOMIDOperations = { + dangerouslyProcessChildrenUpdates: ReactPerf.measure( + // FIXME(frantic): #4441289 Hack to avoid modifying react-tools + 'ReactDOMIDOperations', + 'dangerouslyProcessChildrenUpdates', + dangerouslyProcessChildrenUpdates + ), + + /** + * Replaces a view that exists in the document with markup. + * + * @param {string} id ID of child to be replaced. + * @param {string} markup Mount image to replace child with id. + */ + dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure( + 'ReactDOMIDOperations', + 'dangerouslyReplaceNodeWithMarkupByID', + function(id, mountImage) { + var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id); + UIManager.replaceExistingNonRootView(oldTag, mountImage.tag); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag); + } + ), +}; + +module.exports = ReactNativeDOMIDOperations; diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js new file mode 100644 index 0000000000000..ee97c2560e61a --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -0,0 +1,113 @@ +/** + * 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 ReactNativeDefaultInjection + * @flow + */ +'use strict'; + +/** + * Make sure essential globals are available and are patched correctly. Please don't remove this + * line. Bundles created by react-packager `require` it before executing any application code. This + * ensures it exists in the dependency graph and can be `require`d. + */ +require('InitializeJavaScriptAppEngine'); + +var EventPluginHub = require('EventPluginHub'); +var EventPluginUtils = require('EventPluginUtils'); +var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder'); +var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin'); +var NodeHandle = require('NodeHandle'); +var ReactElement = require('ReactElement'); +var ReactComponentEnvironment = require('ReactComponentEnvironment'); +var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); +var ReactEmptyComponent = require('ReactEmptyComponent'); +var ReactInstanceHandles = require('ReactInstanceHandles'); +var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); +var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler'); +var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); +var ReactNativeMount = require('ReactNativeMount'); +var ReactNativeTextComponent = require('ReactNativeTextComponent'); +var ReactNativeComponent = require('ReactNativeComponent'); +var ReactUpdates = require('ReactUpdates'); +var ResponderEventPlugin = require('ResponderEventPlugin'); +var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); + +var invariant = require('fbjs/lib/invariant'); + +// Just to ensure this gets packaged, since its only caller is from Native. +require('RCTEventEmitter'); +require('RCTLog'); +require('JSTimersExecution'); + +function inject() { + /** + * Inject module for resolving DOM hierarchy and plugin ordering. + */ + EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder); + EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles); + + ResponderEventPlugin.injection.injectGlobalResponderHandler( + ReactNativeGlobalResponderHandler + ); + + ResponderEventPlugin.injection.injectGlobalInteractionHandler( + ReactNativeGlobalInteractionHandler + ); + + /** + * Some important event plugins included by default (without having to require + * them). + */ + EventPluginHub.injection.injectEventPluginsByName({ + 'ResponderEventPlugin': ResponderEventPlugin, + 'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin + }); + + ReactUpdates.injection.injectReconcileTransaction( + ReactNativeComponentEnvironment.ReactReconcileTransaction + ); + + ReactUpdates.injection.injectBatchingStrategy( + ReactDefaultBatchingStrategy + ); + + ReactComponentEnvironment.injection.injectEnvironment( + ReactNativeComponentEnvironment + ); + + var EmptyComponent = () => { + // Can't import View at the top because it depends on React to make its composite + var View = require('View'); + return ReactElement.createElement(View, { + collapsable: true, + style: { position: 'absolute' } + }); + }; + ReactEmptyComponent.injection.injectEmptyComponent(EmptyComponent); + + EventPluginUtils.injection.injectMount(ReactNativeMount); + + ReactNativeComponent.injection.injectTextComponentClass( + ReactNativeTextComponent + ); + ReactNativeComponent.injection.injectGenericComponentClass(function(tag) { + // Show a nicer error message for non-function tags + var info = ''; + if (typeof tag === 'string' && /^[a-z]/.test(tag)) { + info += ' Each component name should start with an uppercase letter.'; + } + invariant(false, 'Expected a component class, got %s.%s', tag, info); + }); + + NodeHandle.injection.injectImplementation(UniversalWorkerNodeHandle); +} + +module.exports = { + inject: inject, +}; diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js new file mode 100644 index 0000000000000..c7680257aff57 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -0,0 +1,214 @@ +/** + * 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 ReactNativeEventEmitter + * @flow + */ +'use strict'; + +var EventPluginHub = require('EventPluginHub'); +var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var NodeHandle = require('NodeHandle'); +var EventConstants = require('EventConstants'); + +var merge = require('merge'); +var warning = require('fbjs/lib/warning'); + +var topLevelTypes = EventConstants.topLevelTypes; + +/** + * Version of `ReactBrowserEventEmitter` that works on the receiving side of a + * serialized worker boundary. + */ + +// Shared default empty native event - conserve memory. +var EMPTY_NATIVE_EVENT = {}; + +/** + * Selects a subsequence of `Touch`es, without destroying `touches`. + * + * @param {Array} touches Deserialized touch objects. + * @param {Array} indices Indices by which to pull subsequence. + * @return {Array} Subsequence of touch objects. + */ +var touchSubsequence = function(touches, indices) { + var ret = []; + for (var i = 0; i < indices.length; i++) { + ret.push(touches[indices[i]]); + } + return ret; +}; + +/** + * TODO: Pool all of this. + * + * Destroys `touches` by removing touch objects at indices `indices`. This is + * to maintain compatibility with W3C touch "end" events, where the active + * touches don't include the set that has just been "ended". + * + * @param {Array} touches Deserialized touch objects. + * @param {Array} indices Indices to remove from `touches`. + * @return {Array} Subsequence of removed touch objects. + */ +var removeTouchesAtIndices = function( + touches: Array, + indices: Array +): Array { + var rippedOut = []; + // use an unsafe downcast to alias to nullable elements, + // so we can delete and then compact. + var temp: Array = (touches: Array); + for (var i = 0; i < indices.length; i++) { + var index = indices[i]; + rippedOut.push(touches[index]); + temp[index] = null; + } + var fillAt = 0; + for (var j = 0; j < temp.length; j++) { + var cur = temp[j]; + if (cur !== null) { + temp[fillAt++] = cur; + } + } + temp.length = fillAt; + return rippedOut; +}; + +/** + * `ReactNativeEventEmitter` is used to attach top-level event listeners. For example: + * + * ReactNativeEventEmitter.putListener('myID', 'onClick', myFunction); + * + * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'. + * + * @internal + */ +var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { + + registrationNames: EventPluginHub.registrationNameModules, + + putListener: EventPluginHub.putListener, + + getListener: EventPluginHub.getListener, + + deleteListener: EventPluginHub.deleteListener, + + deleteAllListeners: EventPluginHub.deleteAllListeners, + + /** + * Internal version of `receiveEvent` in terms of normalized (non-tag) + * `rootNodeID`. + * + * @see receiveEvent. + * + * @param {rootNodeID} rootNodeID React root node ID that event occurred on. + * @param {TopLevelType} topLevelType Top level type of event. + * @param {object} nativeEventParam Object passed from native. + */ + _receiveRootNodeIDEvent: function( + rootNodeID: ?string, + topLevelType: string, + nativeEventParam: Object + ) { + var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; + ReactNativeEventEmitter.handleTopLevel( + topLevelType, + rootNodeID, + rootNodeID, + nativeEvent, + nativeEvent.target + ); + }, + + /** + * Publicly exposed method on module for native objc to invoke when a top + * level event is extracted. + * @param {rootNodeID} rootNodeID React root node ID that event occurred on. + * @param {TopLevelType} topLevelType Top level type of event. + * @param {object} nativeEventParam Object passed from native. + */ + receiveEvent: function( + tag: number, + topLevelType: string, + nativeEventParam: Object + ) { + var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag]; + ReactNativeEventEmitter._receiveRootNodeIDEvent( + rootNodeID, + topLevelType, + nativeEventParam + ); + }, + + /** + * Simple multi-wrapper around `receiveEvent` that is intended to receive an + * efficient representation of `Touch` objects, and other information that + * can be used to construct W3C compliant `Event` and `Touch` lists. + * + * This may create dispatch behavior that differs than web touch handling. We + * loop through each of the changed touches and receive it as a single event. + * So two `touchStart`/`touchMove`s that occur simultaneously are received as + * two separate touch event dispatches - when they arguably should be one. + * + * This implementation reuses the `Touch` objects themselves as the `Event`s + * since we dispatch an event for each touch (though that might not be spec + * compliant). The main purpose of reusing them is to save allocations. + * + * TODO: Dispatch multiple changed touches in one event. The bubble path + * could be the first common ancestor of all the `changedTouches`. + * + * One difference between this behavior and W3C spec: cancelled touches will + * not appear in `.touches`, or in any future `.touches`, though they may + * still be "actively touching the surface". + * + * Web desktop polyfills only need to construct a fake touch event with + * identifier 0, also abandoning traditional click handlers. + */ + receiveTouches: function( + eventTopLevelType: string, + touches: Array, + changedIndices: Array + ) { + var changedTouches = + eventTopLevelType === topLevelTypes.topTouchEnd || + eventTopLevelType === topLevelTypes.topTouchCancel ? + removeTouchesAtIndices(touches, changedIndices) : + touchSubsequence(touches, changedIndices); + + for (var jj = 0; jj < changedTouches.length; jj++) { + var touch = changedTouches[jj]; + // Touch objects can fulfill the role of `DOM` `Event` objects if we set + // the `changedTouches`/`touches`. This saves allocations. + touch.changedTouches = changedTouches; + touch.touches = touches; + var nativeEvent = touch; + var rootNodeID = null; + var target = nativeEvent.target; + if (target !== null && target !== undefined) { + if (target < ReactNativeTagHandles.tagsStartAt) { + if (__DEV__) { + warning( + false, + 'A view is reporting that a touch occured on tag zero.' + ); + } + } else { + rootNodeID = NodeHandle.getRootNodeID(target); + } + } + ReactNativeEventEmitter._receiveRootNodeIDEvent( + rootNodeID, + eventTopLevelType, + nativeEvent + ); + } + } +}); + +module.exports = ReactNativeEventEmitter; diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js b/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js new file mode 100644 index 0000000000000..d5c9b95447791 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js @@ -0,0 +1,33 @@ +/** + * 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 ReactNativeGlobalInteractionHandler + * @flow + */ +'use strict'; + +var InteractionManager = require('InteractionManager'); + +// Interaction handle is created/cleared when responder is granted or +// released/terminated. +var interactionHandle = null; + +var ReactNativeGlobalInteractionHandler = { + onChange: function(numberActiveTouches: number) { + if (numberActiveTouches === 0) { + if (interactionHandle) { + InteractionManager.clearInteractionHandle(interactionHandle); + interactionHandle = null; + } + } else if (!interactionHandle) { + interactionHandle = InteractionManager.createInteractionHandle(); + } + } +}; + +module.exports = ReactNativeGlobalInteractionHandler; diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js new file mode 100644 index 0000000000000..1094e60451b89 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeGlobalResponderHandler + * @flow + */ +'use strict'; + +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var UIManager = require('UIManager'); + +var ReactNativeGlobalResponderHandler = { + onChange: function(from: string, to: string, blockNativeResponder: boolean) { + if (to !== null) { + UIManager.setJSResponder( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to), + blockNativeResponder + ); + } else { + UIManager.clearJSResponder(); + } + } +}; + +module.exports = ReactNativeGlobalResponderHandler; diff --git a/src/renderers/native/ReactNative/ReactNativeMount.js b/src/renderers/native/ReactNative/ReactNativeMount.js new file mode 100644 index 0000000000000..cf8c5bb336fdd --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeMount.js @@ -0,0 +1,270 @@ +/** + * 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 ReactNativeMount + * @flow + */ +'use strict'; + +var ReactElement = require('ReactElement'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var ReactPerf = require('ReactPerf'); +var ReactReconciler = require('ReactReconciler'); +var ReactUpdateQueue = require('ReactUpdateQueue'); +var ReactUpdates = require('ReactUpdates'); +var UIManager = require('UIManager'); + +var emptyObject = require('fbjs/lib/emptyObject'); +var instantiateReactComponent = require('instantiateReactComponent'); +var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); + +function instanceNumberToChildRootID(rootNodeID, instanceNumber) { + return rootNodeID + '[' + instanceNumber + ']'; +} + +/** + * Temporary (?) hack so that we can store all top-level pending updates on + * composites instead of having to worry about different types of components + * here. + */ +var TopLevelWrapper = function() {}; +TopLevelWrapper.prototype.isReactComponent = {}; +if (__DEV__) { + TopLevelWrapper.displayName = 'TopLevelWrapper'; +} +TopLevelWrapper.prototype.render = function() { + // this.props is actually a ReactElement + return this.props; +}; + +/** + * Mounts this component and inserts it into the DOM. + * + * @param {ReactComponent} componentInstance The instance to mount. + * @param {number} rootID ID of the root node. + * @param {number} container container element to mount into. + * @param {ReactReconcileTransaction} transaction + */ +function mountComponentIntoNode( + componentInstance, + rootID, + container, + transaction) { + var markup = ReactReconciler.mountComponent( + componentInstance, rootID, transaction, emptyObject + ); + componentInstance._renderedComponent._topLevelWrapper = componentInstance; + ReactNativeMount._mountImageIntoNode(markup, container); +} + +/** + * Batched mount. + * + * @param {ReactComponent} componentInstance The instance to mount. + * @param {number} rootID ID of the root node. + * @param {number} container container element to mount into. + */ +function batchedMountComponentIntoNode( + componentInstance, + rootID, + container) { + var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); + transaction.perform( + mountComponentIntoNode, + null, + componentInstance, + rootID, + container, + transaction + ); + ReactUpdates.ReactReconcileTransaction.release(transaction); +} + +/** + * As soon as `ReactMount` is refactored to not rely on the DOM, we can share + * code between the two. For now, we'll hard code the ID logic. + */ +var ReactNativeMount = { + instanceCount: 0, + + _instancesByContainerID: {}, + + // these two functions are needed by React Devtools + findNodeHandle: require('findNodeHandle'), + nativeTagToRootNodeID: function (nativeTag: number): string { + return ReactNativeTagHandles.tagToRootNodeID[nativeTag]; + }, + + /** + * @param {ReactComponent} instance Instance to render. + * @param {containerTag} containerView Handle to native view tag + */ + renderComponent: function( + nextElement: ReactElement, + containerTag: number, + callback?: ?(() => void) + ): ?ReactComponent { + var nextWrappedElement = new ReactElement( + TopLevelWrapper, + null, + null, + null, + null, + null, + nextElement + ); + + var topRootNodeID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; + if (topRootNodeID) { + var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID]; + if (prevComponent) { + var prevWrappedElement = prevComponent._currentElement; + var prevElement = prevWrappedElement.props; + if (shouldUpdateReactComponent(prevElement, nextElement)) { + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement); + if (callback) { + ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); + } + return prevComponent; + } else { + ReactNativeMount.unmountComponentAtNode(containerTag); + } + } + } + + if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) { + console.error('You cannot render into anything but a top root'); + return; + } + + var topRootNodeID = ReactNativeTagHandles.allocateRootNodeIDForTag(containerTag); + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( + topRootNodeID, + containerTag + ); + + var instance = instantiateReactComponent(nextWrappedElement); + ReactNativeMount._instancesByContainerID[topRootNodeID] = instance; + + var childRootNodeID = instanceNumberToChildRootID( + topRootNodeID, + ReactNativeMount.instanceCount++ + ); + + // The initial render is synchronous but any updates that happen during + // rendering, in componentWillMount or componentDidMount, will be batched + // according to the current batching strategy. + + ReactUpdates.batchedUpdates( + batchedMountComponentIntoNode, + instance, + childRootNodeID, + topRootNodeID + ); + var component = instance.getPublicInstance(); + if (callback) { + callback.call(component); + } + return component; + }, + + /** + * @param {View} view View tree image. + * @param {number} containerViewID View to insert sub-view into. + */ + _mountImageIntoNode: ReactPerf.measure( + // FIXME(frantic): #4441289 Hack to avoid modifying react-tools + 'ReactComponentBrowserEnvironment', + 'mountImageIntoNode', + function(mountImage, containerID) { + // Since we now know that the `mountImage` has been mounted, we can + // mark it as such. + ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( + mountImage.rootNodeID, + mountImage.tag + ); + UIManager.setChildren( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), + [mountImage.tag] + ); + } + ), + + /** + * Standard unmounting of the component that is rendered into `containerID`, + * but will also execute a command to remove the actual container view + * itself. This is useful when a client is cleaning up a React tree, and also + * knows that the container will no longer be needed. When executing + * asynchronously, it's easier to just have this method be the one that calls + * for removal of the view. + */ + unmountComponentAtNodeAndRemoveContainer: function( + containerTag: number + ) { + ReactNativeMount.unmountComponentAtNode(containerTag); + // call back into native to remove all of the subviews from this container + UIManager.removeRootView(containerTag); + }, + + /** + * Unmount component at container ID by iterating through each child component + * that has been rendered and unmounting it. There should just be one child + * component at this time. + */ + unmountComponentAtNode: function(containerTag: number): boolean { + if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) { + console.error('You cannot render into anything but a top root'); + return false; + } + + var containerID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; + var instance = ReactNativeMount._instancesByContainerID[containerID]; + if (!instance) { + return false; + } + ReactNativeMount.unmountComponentFromNode(instance, containerID); + delete ReactNativeMount._instancesByContainerID[containerID]; + return true; + }, + + /** + * Unmounts a component and sends messages back to iOS to remove its subviews. + * + * @param {ReactComponent} instance React component instance. + * @param {string} containerID ID of container we're removing from. + * @final + * @internal + * @see {ReactNativeMount.unmountComponentAtNode} + */ + unmountComponentFromNode: function( + instance: ReactComponent, + containerID: string + ) { + // Call back into native to remove all of the subviews from this container + ReactReconciler.unmountComponent(instance); + var containerTag = + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID); + UIManager.removeSubviewsFromContainerWithID(containerTag); + }, + + getNode: function(rootNodeID: string): number { + return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; + }, + + getID: function(nativeTag: number): string { + return ReactNativeTagHandles.tagToRootNodeID[nativeTag]; + } +}; + +ReactNativeMount.renderComponent = ReactPerf.measure( + 'ReactMount', + '_renderNewRootComponent', + ReactNativeMount.renderComponent +); + +module.exports = ReactNativeMount; diff --git a/src/renderers/native/ReactNative/ReactNativePropRegistry.js b/src/renderers/native/ReactNative/ReactNativePropRegistry.js new file mode 100644 index 0000000000000..3cffb9acf3b99 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativePropRegistry.js @@ -0,0 +1,44 @@ +/** + * 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 ReactNativePropRegistry + * @flow + */ +'use strict'; + +var objects = {}; +var uniqueID = 1; +var emptyObject = {}; + +class ReactNativePropRegistry { + static register(object: Object): number { + var id = ++uniqueID; + if (__DEV__) { + Object.freeze(object); + } + objects[id] = object; + return id; + } + + static getByID(id: number): Object { + if (!id) { + // Used in the style={[condition && id]} pattern, + // we want it to be a no-op when the value is false or null + return emptyObject; + } + + var object = objects[id]; + if (!object) { + console.warn('Invalid style with id `' + id + '`. Skipping ...'); + return emptyObject; + } + return object; + } +} + +module.exports = ReactNativePropRegistry; diff --git a/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js b/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js new file mode 100644 index 0000000000000..b6721964988f0 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeReconcileTransaction + * @flow + */ +'use strict'; + +var CallbackQueue = require('CallbackQueue'); +var PooledClass = require('PooledClass'); +var Transaction = require('Transaction'); + +/** + * Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during + * the performing of the transaction. + */ +var ON_DOM_READY_QUEUEING = { + /** + * Initializes the internal `onDOMReady` queue. + */ + initialize: function() { + this.reactMountReady.reset(); + }, + + /** + * After DOM is flushed, invoke all registered `onDOMReady` callbacks. + */ + close: function() { + this.reactMountReady.notifyAll(); + } +}; + +/** + * Executed within the scope of the `Transaction` instance. Consider these as + * being member methods, but with an implied ordering while being isolated from + * each other. + */ +var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING]; + +/** + * Currently: + * - The order that these are listed in the transaction is critical: + * - Suppresses events. + * - Restores selection range. + * + * Future: + * - Restore document/overflow scroll positions that were unintentionally + * modified via DOM insertions above the top viewport boundary. + * - Implement/integrate with customized constraint based layout system and keep + * track of which dimensions must be remeasured. + * + * @class ReactNativeReconcileTransaction + */ +function ReactNativeReconcileTransaction() { + this.reinitializeTransaction(); + this.reactMountReady = CallbackQueue.getPooled(null); +} + +var Mixin = { + /** + * @see Transaction + * @abstract + * @final + * @return {array} List of operation wrap procedures. + * TODO: convert to array + */ + getTransactionWrappers: function() { + return TRANSACTION_WRAPPERS; + }, + + /** + * @return {object} The queue to collect `onDOMReady` callbacks with. + * TODO: convert to ReactMountReady + */ + getReactMountReady: function() { + return this.reactMountReady; + }, + + /** + * `PooledClass` looks for this, and will invoke this before allowing this + * instance to be reused. + */ + destructor: function() { + CallbackQueue.release(this.reactMountReady); + this.reactMountReady = null; + } +}; + +Object.assign( + ReactNativeReconcileTransaction.prototype, + Transaction.Mixin, + ReactNativeReconcileTransaction, + Mixin +); + +PooledClass.addPoolingTo(ReactNativeReconcileTransaction); + +module.exports = ReactNativeReconcileTransaction; diff --git a/src/renderers/native/ReactNative/ReactNativeTagHandles.js b/src/renderers/native/ReactNative/ReactNativeTagHandles.js new file mode 100644 index 0000000000000..5a0de4cda393c --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeTagHandles.js @@ -0,0 +1,113 @@ +/** + * 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 ReactNativeTagHandles + * @flow + */ +'use strict'; + +var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); + +/** + * Keeps track of allocating and associating native "tags" which are numeric, + * unique view IDs. All the native tags are negative numbers, to avoid + * collisions, but in the JS we keep track of them as positive integers to store + * them effectively in Arrays. So we must refer to them as "inverses" of the + * native tags (that are * normally negative). + * + * It *must* be the case that every `rootNodeID` always maps to the exact same + * `tag` forever. The easiest way to accomplish this is to never delete + * anything from this table. + * Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to + * unmount a component with a `rootNodeID`, then mount a new one in its place, + */ +var INITIAL_TAG_COUNT = 1; +var NATIVE_TOP_ROOT_ID_SEPARATOR = '{TOP_LEVEL}'; +var ReactNativeTagHandles = { + tagsStartAt: INITIAL_TAG_COUNT, + tagCount: INITIAL_TAG_COUNT, + + allocateTag: function(): number { + // Skip over root IDs as those are reserved for native + while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) { + ReactNativeTagHandles.tagCount++; + } + var tag = ReactNativeTagHandles.tagCount; + ReactNativeTagHandles.tagCount++; + return tag; + }, + + /** + * This associates the *last* observed *native* mounting between `rootNodeID` + * and some `tag`. This association doesn't imply that `rootNodeID` is still + * natively mounted as `tag`. The only reason why we don't clear the + * association when the `rootNodeID` is unmounted, is that we don't have a + * convenient time to disassociate them (otherwise we would). + * `unmountComponent` isn't the correct time because that doesn't imply that + * the native node has been natively unmounted. + */ + associateRootNodeIDWithMountedNodeHandle: function( + rootNodeID: ?string, + tag: ?number + ) { + warning(rootNodeID && tag, 'Root node or tag is null when associating'); + if (rootNodeID && tag) { + ReactNativeTagHandles.tagToRootNodeID[tag] = rootNodeID; + ReactNativeTagHandles.rootNodeIDToTag[rootNodeID] = tag; + } + }, + + allocateRootNodeIDForTag: function(tag: number): string { + invariant( + this.reactTagIsNativeTopRootID(tag), + 'Expect a native root tag, instead got ', tag + ); + return '.r[' + tag + ']' + NATIVE_TOP_ROOT_ID_SEPARATOR; + }, + + reactTagIsNativeTopRootID: function(reactTag: number): bool { + // We reserve all tags that are 1 mod 10 for native root views + return reactTag % 10 === 1; + }, + + getNativeTopRootIDFromNodeID: function(nodeID: ?string): ?string { + if (!nodeID) { + return null; + } + var index = nodeID.indexOf(NATIVE_TOP_ROOT_ID_SEPARATOR); + if (index === -1) { + return null; + } + return nodeID.substr(0, index + NATIVE_TOP_ROOT_ID_SEPARATOR.length); + }, + + /** + * Returns the native `nodeHandle` (`tag`) that was most recently *natively* + * mounted at the `rootNodeID`. Just because a React component has been + * mounted, that doesn't mean that its native node has been mounted. The + * native node is mounted when we actually make the call to insert the + * `nodeHandle` (`tag`) into the native hierarchy. + * + * @param {string} rootNodeID Root node ID to find most recently mounted tag + * for. Again, this doesn't imply that it is still currently mounted. + * @return {number} Tag ID of native view for most recent mounting of + * `rootNodeID`. + */ + mostRecentMountedNodeHandleForRootNodeID: function( + rootNodeID: string + ): number { + return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; + }, + + tagToRootNodeID: ([] : Array), + + rootNodeIDToTag: ({} : {[key: string]: number}) +}; + +module.exports = ReactNativeTagHandles; diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNative/ReactNativeTextComponent.js new file mode 100644 index 0000000000000..7bd97467f26b6 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeTextComponent.js @@ -0,0 +1,78 @@ +/** + * 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 ReactNativeTextComponent + */ + +'use strict'; + +var ReactNativeTagHandles = require('ReactNativeTagHandles'); +var UIManager = require('UIManager'); + +var invariant = require('fbjs/lib/invariant'); + +var ReactNativeTextComponent = function(props) { + // This constructor and its argument is currently used by mocks. +}; + +Object.assign(ReactNativeTextComponent.prototype, { + + construct: function(text) { + // This is really a ReactText (ReactNode), not a ReactElement + this._currentElement = text; + this._stringText = '' + text; + this._rootNodeID = null; + }, + + mountComponent: function(rootID, transaction, context) { + invariant( + context.isInAParentText, + 'RawText "' + this._stringText + '" must be wrapped in an explicit ' + + ' component.' + ); + this._rootNodeID = rootID; + var tag = ReactNativeTagHandles.allocateTag(); + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + UIManager.createView( + tag, + 'RCTRawText', + nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, + {text: this._stringText} + ); + return { + rootNodeID: rootID, + tag: tag, + }; + }, + + receiveComponent: function(nextText, transaction, context) { + if (nextText !== this._currentElement) { + this._currentElement = nextText; + var nextStringText = '' + nextText; + if (nextStringText !== this._stringText) { + this._stringText = nextStringText; + UIManager.updateView( + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID( + this._rootNodeID + ), + 'RCTRawText', + {text: this._stringText} + ); + } + } + }, + + unmountComponent: function() { + this._currentElement = null; + this._stringText = null; + this._rootNodeID = null; + } + +}); + +module.exports = ReactNativeTextComponent; diff --git a/src/renderers/native/ReactNative/UIManagerStatTracker.js b/src/renderers/native/ReactNative/UIManagerStatTracker.js new file mode 100644 index 0000000000000..a3b8779234f5e --- /dev/null +++ b/src/renderers/native/ReactNative/UIManagerStatTracker.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule UIManagerStatTracker + * @flow + */ +'use strict'; + +var UIManager = require('UIManager'); + +var installed = false; +var UIManagerStatTracker = { + install: function() { + if (installed) { + return; + } + installed = true; + var statLogHandle; + var stats = {}; + function printStats() { + console.log({UIManagerStatTracker: stats}); + statLogHandle = null; + } + function incStat(key: string, increment: number) { + stats[key] = (stats[key] || 0) + increment; + if (!statLogHandle) { + statLogHandle = setImmediate(printStats); + } + } + var createViewOrig = UIManager.createView; + UIManager.createView = function(tag, className, rootTag, props) { + incStat('createView', 1); + incStat('setProp', Object.keys(props || []).length); + createViewOrig(tag, className, rootTag, props); + }; + var updateViewOrig = UIManager.updateView; + UIManager.updateView = function(tag, className, props) { + incStat('updateView', 1); + incStat('setProp', Object.keys(props || []).length); + updateViewOrig(tag, className, props); + }; + var manageChildrenOrig = UIManager.manageChildren; + UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) { + incStat('manageChildren', 1); + incStat('move', Object.keys(moveFrom || []).length); + incStat('remove', Object.keys(remove || []).length); + manageChildrenOrig(tag, moveFrom, moveTo, addTags, addIndices, remove); + }; + }, +}; + +module.exports = UIManagerStatTracker; diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js new file mode 100644 index 0000000000000..6cbaa46a30081 --- /dev/null +++ b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ +'use strict'; + +jest.dontMock('ReactNativeAttributePayload'); +jest.dontMock('ReactNativePropRegistry'); +jest.dontMock('deepDiffer'); +jest.dontMock('flattenStyle'); + +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var ReactNativePropRegistry = require('ReactNativePropRegistry'); + +var diff = ReactNativeAttributePayload.diff; + +describe('ReactNativeAttributePayload', function() { + + it('should work with simple example', () => { + expect(diff( + {a: 1, c: 3}, + {b: 2, c: 3}, + {a: true, b: true} + )).toEqual({a: null, b: 2}); + }); + + it('should skip fields that are equal', () => { + expect(diff( + {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0}, + {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0}, + {a: true, b: true, c: true, d: true, e: true, f: true} + )).toEqual(null); + }); + + it('should remove fields', () => { + expect(diff( + {a: 1}, + {}, + {a: true} + )).toEqual({a: null}); + }); + + it('should remove fields that are set to undefined', () => { + expect(diff( + {a: 1}, + {a: undefined}, + {a: true} + )).toEqual({a: null}); + }); + + it('should ignore invalid fields', () => { + expect(diff( + {a: 1}, + {b: 2}, + {} + )).toEqual(null); + }); + + it('should use the diff attribute', () => { + var diffA = jest.genMockFunction().mockImpl((a, b) => true); + var diffB = jest.genMockFunction().mockImpl((a, b) => false); + expect(diff( + {a: [1], b: [3]}, + {a: [2], b: [4]}, + {a: {diff: diffA}, b: {diff: diffB}} + )).toEqual({a: [2]}); + expect(diffA).toBeCalledWith([1], [2]); + expect(diffB).toBeCalledWith([3], [4]); + }); + + it('should not use the diff attribute on addition/removal', () => { + var diffA = jest.genMockFunction(); + var diffB = jest.genMockFunction(); + expect(diff( + {a: [1]}, + {b: [2]}, + {a: {diff: diffA}, b: {diff: diffB}} + )).toEqual({a: null, b: [2]}); + expect(diffA).not.toBeCalled(); + expect(diffB).not.toBeCalled(); + }); + + it('should do deep diffs of Objects by default', () => { + expect(diff( + {a: [1], b: {k: [3,4]}, c: {k: [4,4]} }, + {a: [2], b: {k: [3,4]}, c: {k: [4,5]} }, + {a: true, b: true, c: true} + )).toEqual({a: [2], c: {k: [4,5]}}); + }); + + it('should work with undefined styles', () => { + expect(diff( + { style: { a: '#ffffff', b: 1 } }, + { style: undefined }, + { style: { b: true } } + )).toEqual({ b: null }); + expect(diff( + { style: undefined }, + { style: { a: '#ffffff', b: 1 } }, + { style: { b: true } } + )).toEqual({ b: 1 }); + expect(diff( + { style: undefined }, + { style: undefined }, + { style: { b: true } } + )).toEqual(null); + }); + + it('should work with empty styles', () => { + expect(diff( + {a: 1, c: 3}, + {}, + {a: true, b: true} + )).toEqual({a: null}); + expect(diff( + {}, + {a: 1, c: 3}, + {a: true, b: true} + )).toEqual({a: 1}); + expect(diff( + {}, + {}, + {a: true, b: true} + )).toEqual(null); + }); + + it('should flatten nested styles and predefined styles', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + {}, + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 1, bar: 2 }); + + expect(diff( + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + {}, + validStyleAttribute + )).toEqual({ foo: null, bar: null }); + + var barStyle = ReactNativePropRegistry.register({ + bar: 3, + }); + + expect(diff( + {}, + { someStyle: [[{ foo: 1 }, { foo: 2 }], barStyle]}, + validStyleAttribute + )).toEqual({ foo: 2, bar: 3 }); + }); + + it('should reset a value to a previous if it is removed', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: 3 }]}, + { someStyle: [{ foo: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 1, bar: 2 }); + }); + + it('should not clear removed props if they are still in another slot', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 3 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: 3 }); // this should ideally be null. heuristic tradeoff. + + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 1, bar: 1 }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ bar: 2, foo: 1 }); + }); + + it('should clear a prop if a later style is explicit null/undefined', () => { + var validStyleAttribute = { someStyle: { foo: true, bar: true } }; + expect(diff( + { someStyle: [{}, { foo: 3, bar: 2 }]}, + { someStyle: [{ foo: 1 }, { bar: 2, foo: null }]}, + validStyleAttribute + )).toEqual({ foo: null }); + + expect(diff( + { someStyle: [{ foo: 3 }, { foo: null, bar: 2 }]}, + { someStyle: [{ foo: null }, { bar: 2 }]}, + validStyleAttribute + )).toEqual({ foo: null }); + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: null }]}, + { someStyle: [{ foo: 2 }, { foo: null }]}, + validStyleAttribute + )).toEqual({ foo: null }); // this should ideally be null. heuristic. + + // Test the same case with object equality because an early bailout doesn't + // work in this case. + var fooObj = { foo: 3 }; + expect(diff( + { someStyle: [{ foo: 1 }, fooObj]}, + { someStyle: [{ foo: 2 }, fooObj]}, + validStyleAttribute + )).toEqual({ foo: 3 }); // this should ideally be null. heuristic. + + expect(diff( + { someStyle: [{ foo: 1 }, { foo: 3 }]}, + { someStyle: [{ foo: 2 }, { foo: undefined }]}, + validStyleAttribute + )).toEqual({ foo: null }); // this should ideally be null. heuristic. + }); + + // Function properties are just markers to native that events should be sent. + it('should convert functions to booleans', () => { + // Note that if the property changes from one function to another, we don't + // need to send an update. + expect(diff( + {a: function() { return 1; }, b: function() { return 2; }, c: 3}, + {b: function() { return 9; }, c: function() { return 3; }, }, + {a: true, b: true, c: true} + )).toEqual({a: null, c: true}); + }); + + }); diff --git a/src/renderers/native/ReactNative/createReactNativeComponentClass.js b/src/renderers/native/ReactNative/createReactNativeComponentClass.js new file mode 100644 index 0000000000000..199e268891a0d --- /dev/null +++ b/src/renderers/native/ReactNative/createReactNativeComponentClass.js @@ -0,0 +1,46 @@ +/** + * 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 createReactNativeComponentClass + * @flow + */ + +'use strict'; + +var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); + +// See also ReactNativeBaseComponent +type ReactNativeBaseComponentViewConfig = { + validAttributes: Object; + uiViewClassName: string; + propTypes?: Object, +} + +/** + * @param {string} config iOS View configuration. + * @private + */ +var createReactNativeComponentClass = function( + viewConfig: ReactNativeBaseComponentViewConfig +): ReactClass { + var Constructor = function(element) { + this._currentElement = element; + + this._rootNodeID = null; + this._renderedChildren = null; + }; + Constructor.displayName = viewConfig.uiViewClassName; + Constructor.viewConfig = viewConfig; + Constructor.propTypes = viewConfig.propTypes; + Constructor.prototype = new ReactNativeBaseComponent(viewConfig); + Constructor.prototype.constructor = Constructor; + + return ((Constructor: any): ReactClass); +}; + +module.exports = createReactNativeComponentClass; diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/ReactNative/findNodeHandle.js new file mode 100644 index 0000000000000..dd3191b82d921 --- /dev/null +++ b/src/renderers/native/ReactNative/findNodeHandle.js @@ -0,0 +1,112 @@ +/** + * 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 findNodeHandle + * @flow + */ + +'use strict'; + +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactInstanceMap = require('ReactInstanceMap'); +var ReactNativeTagHandles = require('ReactNativeTagHandles'); + +var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); + +/** + * ReactNative vs ReactWeb + * ----------------------- + * React treats some pieces of data opaquely. This means that the information + * is first class (it can be passed around), but cannot be inspected. This + * allows us to build infrastructure that reasons about resources, without + * making assumptions about the nature of those resources, and this allows that + * infra to be shared across multiple platforms, where the resources are very + * different. General infra (such as `ReactMultiChild`) reasons opaquely about + * the data, but platform specific code (such as `ReactNativeBaseComponent`) can + * make assumptions about the data. + * + * + * `rootNodeID`, uniquely identifies a position in the generated native view + * tree. Many layers of composite components (created with `React.createClass`) + * can all share the same `rootNodeID`. + * + * `nodeHandle`: A sufficiently unambiguous way to refer to a lower level + * resource (dom node, native view etc). The `rootNodeID` is sufficient for web + * `nodeHandle`s, because the position in a tree is always enough to uniquely + * identify a DOM node (we never have nodes in some bank outside of the + * document). The same would be true for `ReactNative`, but we must maintain a + * mapping that we can send efficiently serializable + * strings across native boundaries. + * + * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative + * ---------------------------------------------------------------------------- + * nodeHandle N/A rootNodeID tag + */ + +function findNodeHandle(componentOrHandle: any): ?number { + if (__DEV__) { + var owner = ReactCurrentOwner.current; + if (owner !== null) { + warning( + owner._warnedAboutRefsInRender, + '%s is accessing findNodeHandle inside its render(). ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + owner.getName() || 'A component' + ); + owner._warnedAboutRefsInRender = true; + } + } + if (componentOrHandle == null) { + return null; + } + if (typeof componentOrHandle === 'number') { + // Already a node handle + return componentOrHandle; + } + + var component = componentOrHandle; + + // TODO (balpert): Wrap iOS native components in a composite wrapper, then + // ReactInstanceMap.get here will always succeed for mounted components + var internalInstance = ReactInstanceMap.get(component); + if (internalInstance) { + return ReactNativeTagHandles.rootNodeIDToTag[internalInstance._rootNodeID]; + } else { + var rootNodeID = component._rootNodeID; + if (rootNodeID) { + return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; + } else { + invariant( + ( + // Native + typeof component === 'object' && + '_rootNodeID' in component + ) || ( + // Composite + component.render != null && + typeof component.render === 'function' + ), + 'findNodeHandle(...): Argument is not a component ' + + '(type: %s, keys: %s)', + typeof component, + Object.keys(component) + ); + invariant( + false, + 'findNodeHandle(...): Unable to find node handle for unmounted ' + + 'component.' + ); + } + } +} + +module.exports = findNodeHandle; diff --git a/src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js b/src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js new file mode 100644 index 0000000000000..c633dd724be79 --- /dev/null +++ b/src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js @@ -0,0 +1,378 @@ +/** + * @providesModule PanResponder + */ + +"use strict"; + +var TouchHistoryMath = require('TouchHistoryMath'); + +var currentCentroidXOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; +var currentCentroidYOfTouchesChangedAfter = + TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; +var previousCentroidXOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidXOfTouchesChangedAfter; +var previousCentroidYOfTouchesChangedAfter = + TouchHistoryMath.previousCentroidYOfTouchesChangedAfter; +var currentCentroidX = TouchHistoryMath.currentCentroidX; +var currentCentroidY = TouchHistoryMath.currentCentroidY; + +/** + * `PanResponder` reconciles several touches into a single gesture. It makes + * single-touch gestures resilient to extra touches, and can be used to + * recognize simple multi-touch gestures. + * + * It provides a predictable wrapper of the responder handlers provided by the + * [gesture responder system](docs/gesture-responder-system.html). + * For each handler, it provides a new `gestureState` object alongside the + * native event object: + * + * ``` + * onPanResponderMove: (event, gestureState) => {} + * ``` + * + * A native event is a synthetic touch event with the following form: + * + * - `nativeEvent` + * + `changedTouches` - Array of all touch events that have changed since the last event + * + `identifier` - The ID of the touch + * + `locationX` - The X position of the touch, relative to the element + * + `locationY` - The Y position of the touch, relative to the element + * + `pageX` - The X position of the touch, relative to the root element + * + `pageY` - The Y position of the touch, relative to the root element + * + `target` - The node id of the element receiving the touch event + * + `timestamp` - A time identifier for the touch, useful for velocity calculation + * + `touches` - Array of all current touches on the screen + * + * A `gestureState` object has the following: + * + * - `stateID` - ID of the gestureState- persisted as long as there at least + * one touch on screen + * - `moveX` - the latest screen coordinates of the recently-moved touch + * - `moveY` - the latest screen coordinates of the recently-moved touch + * - `x0` - the screen coordinates of the responder grant + * - `y0` - the screen coordinates of the responder grant + * - `dx` - accumulated distance of the gesture since the touch started + * - `dy` - accumulated distance of the gesture since the touch started + * - `vx` - current velocity of the gesture + * - `vy` - current velocity of the gesture + * - `numberActiveTouches` - Number of touches currently on screen + * + * ### Basic Usage + * + * ``` + * componentWillMount: function() { + * this._panResponder = PanResponder.create({ + * // Ask to be the responder: + * onStartShouldSetPanResponder: (evt, gestureState) => true, + * onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + * onMoveShouldSetPanResponder: (evt, gestureState) => true, + * onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + * + * onPanResponderGrant: (evt, gestureState) => { + * // The guesture has started. Show visual feedback so the user knows + * // what is happening! + * + * // gestureState.{x,y}0 will be set to zero now + * }, + * onPanResponderMove: (evt, gestureState) => { + * // The most recent move distance is gestureState.move{X,Y} + * + * // The accumulated gesture distance since becoming responder is + * // gestureState.d{x,y} + * }, + * onPanResponderTerminationRequest: (evt, gestureState) => true, + * onPanResponderRelease: (evt, gestureState) => { + * // The user has released all touches while this view is the + * // responder. This typically means a gesture has succeeded + * }, + * onPanResponderTerminate: (evt, gestureState) => { + * // Another component has become the responder, so this gesture + * // should be cancelled + * }, + * onShouldBlockNativeResponder: (evt, gestureState) => { + * // Returns whether this component should block native components from becoming the JS + * // responder. Returns true by default. Is currently only supported on android. + * return true; + * }, + * }); + * }, + * + * render: function() { + * return ( + * + * ); + * }, + * + * ``` + * + * ### Working Example + * + * To see it in action, try the + * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js) + */ + +var PanResponder = { + + /** + * + * A graphical explanation of the touch data flow: + * + * +----------------------------+ +--------------------------------+ + * | ResponderTouchHistoryStore | |TouchHistoryMath | + * +----------------------------+ +----------+---------------------+ + * |Global store of touchHistory| |Allocation-less math util | + * |including activeness, start | |on touch history (centroids | + * |position, prev/cur position.| |and multitouch movement etc) | + * | | | | + * +----^-----------------------+ +----^---------------------------+ + * | | + * | (records relevant history | + * | of touches relevant for | + * | implementing higher level | + * | gestures) | + * | | + * +----+-----------------------+ +----|---------------------------+ + * | ResponderEventPlugin | | | Your App/Component | + * +----------------------------+ +----|---------------------------+ + * |Negotiates which view gets | Low level | | High level | + * |onResponderMove events. | events w/ | +-+-------+ events w/ | + * |Also records history into | touchHistory| | Pan | multitouch + | + * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative| + * +----------------------------+ attached to | | | distance and | + * each event | +---------+ velocity. | + * | | + * | | + * +--------------------------------+ + * + * + * + * Gesture that calculates cumulative movement over time in a way that just + * "does the right thing" for multiple touches. The "right thing" is very + * nuanced. When moving two touches in opposite directions, the cumulative + * distance is zero in each dimension. When two touches move in parallel five + * pixels in the same direction, the cumulative distance is five, not ten. If + * two touches start, one moves five in a direction, then stops and the other + * touch moves fives in the same direction, the cumulative distance is ten. + * + * This logic requires a kind of processing of time "clusters" of touch events + * so that two touch moves that essentially occur in parallel but move every + * other frame respectively, are considered part of the same movement. + * + * Explanation of some of the non-obvious fields: + * + * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is + * invalid. If a move event has been observed, `(moveX, moveY)` is the + * centroid of the most recently moved "cluster" of active touches. + * (Currently all move have the same timeStamp, but later we should add some + * threshold for what is considered to be "moving"). If a palm is + * accidentally counted as a touch, but a finger is moving greatly, the palm + * will move slightly, but we only want to count the single moving touch. + * - x0/y0: Centroid location (non-cumulative) at the time of becoming + * responder. + * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch + * distance. Accounts for touch moves that are clustered together in time, + * moving the same direction. Only valid when currently responder (otherwise, + * it only represents the drag distance below the threshold). + * - vx/vy: Velocity. + */ + + _initializeGestureState: function(gestureState) { + gestureState.moveX = 0; + gestureState.moveY = 0; + gestureState.x0 = 0; + gestureState.y0 = 0; + gestureState.dx = 0; + gestureState.dy = 0; + gestureState.vx = 0; + gestureState.vy = 0; + gestureState.numberActiveTouches = 0; + // All `gestureState` accounts for timeStamps up until: + gestureState._accountsForMovesUpTo = 0; + }, + + /** + * This is nuanced and is necessary. It is incorrect to continuously take all + * active *and* recently moved touches, find the centroid, and track how that + * result changes over time. Instead, we must take all recently moved + * touches, and calculate how the centroid has changed just for those + * recently moved touches, and append that change to an accumulator. This is + * to (at least) handle the case where the user is moving three fingers, and + * then one of the fingers stops but the other two continue. + * + * This is very different than taking all of the recently moved touches and + * storing their centroid as `dx/dy`. For correctness, we must *accumulate + * changes* in the centroid of recently moved touches. + * + * There is also some nuance with how we handle multiple moved touches in a + * single event. With the way `ReactNativeEventEmitter` dispatches touches as + * individual events, multiple touches generate two 'move' events, each of + * them triggering `onResponderMove`. But with the way `PanResponder` works, + * all of the gesture inference is performed on the first dispatch, since it + * looks at all of the touches (even the ones for which there hasn't been a + * native dispatch yet). Therefore, `PanResponder` does not call + * `onResponderMove` passed the first dispatch. This diverges from the + * typical responder callback pattern (without using `PanResponder`), but + * avoids more dispatches than necessary. + */ + _updateGestureStateOnMove: function(gestureState, touchHistory) { + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + gestureState.moveX = currentCentroidXOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo + ); + gestureState.moveY = currentCentroidYOfTouchesChangedAfter( + touchHistory, + gestureState._accountsForMovesUpTo + ); + var movedAfter = gestureState._accountsForMovesUpTo; + var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter); + var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter); + var nextDX = gestureState.dx + (x - prevX); + var nextDY = gestureState.dy + (y - prevY); + + // TODO: This must be filtered intelligently. + var dt = + (touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo); + gestureState.vx = (nextDX - gestureState.dx) / dt; + gestureState.vy = (nextDY - gestureState.dy) / dt; + + gestureState.dx = nextDX; + gestureState.dy = nextDY; + gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp; + }, + + /** + * @param {object} config Enhanced versions of all of the responder callbacks + * that provide not only the typical `ResponderSyntheticEvent`, but also the + * `PanResponder` gesture state. Simply replace the word `Responder` with + * `PanResponder` in each of the typical `onResponder*` callbacks. For + * example, the `config` object would look like: + * + * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}` + * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}` + * - `onStartShouldSetPanResponder: (e, gestureState) => {...}` + * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}` + * - `onPanResponderReject: (e, gestureState) => {...}` + * - `onPanResponderGrant: (e, gestureState) => {...}` + * - `onPanResponderStart: (e, gestureState) => {...}` + * - `onPanResponderEnd: (e, gestureState) => {...}` + * - `onPanResponderRelease: (e, gestureState) => {...}` + * - `onPanResponderMove: (e, gestureState) => {...}` + * - `onPanResponderTerminate: (e, gestureState) => {...}` + * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` + * - `onShouldBlockNativeResponder: (e, gestureState) => {...}` + * + * In general, for events that have capture equivalents, we update the + * gestureState once in the capture phase and can use it in the bubble phase + * as well. + * + * Be careful with onStartShould* callbacks. They only reflect updated + * `gestureState` for start/end events that bubble/capture to the Node. + * Once the node is the responder, you can rely on every start/end event + * being processed by the gesture and `gestureState` being updated + * accordingly. (numberActiveTouches) may not be totally accurate unless you + * are the responder. + */ + create: function(config) { + var gestureState = { + // Useful for debugging + stateID: Math.random(), + }; + PanResponder._initializeGestureState(gestureState); + var panHandlers = { + onStartShouldSetResponder: function(e) { + return config.onStartShouldSetPanResponder === undefined ? false : + config.onStartShouldSetPanResponder(e, gestureState); + }, + onMoveShouldSetResponder: function(e) { + return config.onMoveShouldSetPanResponder === undefined ? false : + config.onMoveShouldSetPanResponder(e, gestureState); + }, + onStartShouldSetResponderCapture: function(e) { + // TODO: Actually, we should reinitialize the state any time + // touches.length increases from 0 active to > 0 active. + if (e.nativeEvent.touches.length === 1) { + PanResponder._initializeGestureState(gestureState); + } + gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; + return config.onStartShouldSetPanResponderCapture !== undefined ? + config.onStartShouldSetPanResponderCapture(e, gestureState) : false; + }, + + onMoveShouldSetResponderCapture: function(e) { + var touchHistory = e.touchHistory; + // Responder system incorrectly dispatches should* to current responder + // Filter out any touch moves past the first one - we would have + // already processed multi-touch geometry during the first event. + if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + return false; + } + PanResponder._updateGestureStateOnMove(gestureState, touchHistory); + return config.onMoveShouldSetPanResponderCapture ? + config.onMoveShouldSetPanResponderCapture(e, gestureState) : false; + }, + + onResponderGrant: function(e) { + gestureState.x0 = currentCentroidX(e.touchHistory); + gestureState.y0 = currentCentroidY(e.touchHistory); + gestureState.dx = 0; + gestureState.dy = 0; + config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); + // TODO: t7467124 investigate if this can be removed + return config.onShouldBlockNativeResponder === undefined ? true : + config.onShouldBlockNativeResponder(); + }, + + onResponderReject: function(e) { + config.onPanResponderReject && config.onPanResponderReject(e, gestureState); + }, + + onResponderRelease: function(e) { + config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState); + PanResponder._initializeGestureState(gestureState); + }, + + onResponderStart: function(e) { + var touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + config.onPanResponderStart && config.onPanResponderStart(e, gestureState); + }, + + onResponderMove: function(e) { + var touchHistory = e.touchHistory; + // Guard against the dispatch of two touch moves when there are two + // simultaneously changed touches. + if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) { + return; + } + // Filter out any touch moves past the first one - we would have + // already processed multi-touch geometry during the first event. + PanResponder._updateGestureStateOnMove(gestureState, touchHistory); + config.onPanResponderMove && config.onPanResponderMove(e, gestureState); + }, + + onResponderEnd: function(e) { + var touchHistory = e.touchHistory; + gestureState.numberActiveTouches = touchHistory.numberActiveTouches; + config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState); + }, + + onResponderTerminate: function(e) { + config.onPanResponderTerminate && + config.onPanResponderTerminate(e, gestureState); + PanResponder._initializeGestureState(gestureState); + }, + + onResponderTerminationRequest: function(e) { + return config.onPanResponderTerminationRequest === undefined ? true : + config.onPanResponderTerminationRequest(e, gestureState); + }, + }; + return {panHandlers: panHandlers}; + }, +}; + +module.exports = PanResponder; diff --git a/src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js b/src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js new file mode 100644 index 0000000000000..1507d21f5618b --- /dev/null +++ b/src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js @@ -0,0 +1,122 @@ +/** + * @providesModule TouchHistoryMath + */ + +"use strict"; + +var TouchHistoryMath = { + /** + * This code is optimized and not intended to look beautiful. This allows + * computing of touch centroids that have moved after `touchesChangedAfter` + * timeStamp. You can compute the current centroid involving all touches + * moves after `touchesChangedAfter`, or you can compute the previous + * centroid of all touches that were moved after `touchesChangedAfter`. + * + * @param {TouchHistoryMath} touchHistory Standard Responder touch track + * data. + * @param {number} touchesChangedAfter timeStamp after which moved touches + * are considered "actively moving" - not just "active". + * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension. + * @param {boolean} ofCurrent Compute current centroid for actively moving + * touches vs. previous centroid of now actively moving touches. + * @return {number} value of centroid in specified dimension. + */ + centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) { + var touchBank = touchHistory.touchBank; + var total = 0; + var count = 0; + + var oneTouchData = touchHistory.numberActiveTouches === 1 ? + touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null; + + if (oneTouchData !== null) { + if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) { + total += ofCurrent && isXAxis ? oneTouchData.currentPageX : + ofCurrent && !isXAxis ? oneTouchData.currentPageY : + !ofCurrent && isXAxis ? oneTouchData.previousPageX : + oneTouchData.previousPageY; + count = 1; + } + } else { + for (var i = 0; i < touchBank.length; i++) { + var touchTrack = touchBank[i]; + if (touchTrack !== null && + touchTrack !== undefined && + touchTrack.touchActive && + touchTrack.currentTimeStamp >= touchesChangedAfter) { + var toAdd; // Yuck, program temporarily in invalid state. + if (ofCurrent && isXAxis) { + toAdd = touchTrack.currentPageX; + } else if (ofCurrent && !isXAxis) { + toAdd = touchTrack.currentPageY; + } else if (!ofCurrent && isXAxis) { + toAdd = touchTrack.previousPageX; + } else { + toAdd = touchTrack.previousPageY; + } + total += toAdd; + count++; + } + } + } + return count > 0 ? total / count : TouchHistoryMath.noCentroid; + }, + + currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + true // ofCurrent + ); + }, + + currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + true // ofCurrent + ); + }, + + previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + false // ofCurrent + ); + }, + + previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + false // ofCurrent + ); + }, + + currentCentroidX: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + true, // isXAxis + true // ofCurrent + ); + }, + + currentCentroidY: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + false, // isXAxis + true // ofCurrent + ); + }, + + noCentroid: -1, +}; + +module.exports = TouchHistoryMath; diff --git a/src/renderers/native/vendor/react/core/clamp.js b/src/renderers/native/vendor/react/core/clamp.js new file mode 100644 index 0000000000000..cb7578ceecce7 --- /dev/null +++ b/src/renderers/native/vendor/react/core/clamp.js @@ -0,0 +1,35 @@ +/** + * @generated SignedSource<> + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !! 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. !! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * @providesModule clamp + * @typechecks + */ + + /** + * @param {number} value + * @param {number} min + * @param {number} max + * @return {number} + */ +function clamp(min, value, max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} + +module.exports = clamp; diff --git a/src/renderers/native/vendor/react/platform/NodeHandle.js b/src/renderers/native/vendor/react/platform/NodeHandle.js new file mode 100644 index 0000000000000..c7c93545bf031 --- /dev/null +++ b/src/renderers/native/vendor/react/platform/NodeHandle.js @@ -0,0 +1,83 @@ +/** + * @providesModule NodeHandle + */ + + /** + * A "handle" is a serializable representation of the underlying platform's + * native node abstraction. This allows reasoning about nodes behind a thread + * (worker) boundary. On some platforms (DOM main thread) the node handle *is* + * an actual DOM node - `NodeHandle` (and potentially other libraries) + * abstract away those differences so you can write code that doesn't depend + * on whether or not you are running in a worker. For example, you could write + * application code: + * + * + * SomeLibrary.measureNodeHandle(myNodeHandle, cb) + * + * Where `measureNodeHandle` knows how to handle actual DOM nodes if running + * in a worker thread, and knows how to handle numeric IDs if running in a + * worker thread. + * + * The only other requirement of a platform/environment is that it always be + * possible to extract the React rootNodeID in a blocking manner (see + * `getRootNodeID`). + * + * +------------------+ +------------------+ +------------------+ + * | | | | | | + * | ReactJS | | YourUtilities | | Animation Utils | + * | | | | | | + * +------------------+ +------------------+ +------------------+ + * + * +------------------------------------------------------------+ + * | Async Platform Independent Node Interface | + * +------------------------------------------------------------+ + * | | + * | NodeIterface: | + * | -measure(nodeHandle, cb) | + * | -setProperties(nodeHandle, cb) | + * | -manageChildren(nodeHandle, nodeHandles, cb) | + * | ... | + * | | + * | Note: This may be a simplification. We could break up much | + * | of this functionality into several smaller libraries, each | + * | one requiring a . | + * +------------------------------------------------------------+ + * + * +------------------------------------------------------------+ + * | Platform Implementations | + * | ----------------------------------------- | + * | React Canvas | React DOM Worker | React DOM main | + * +------------------------------------------------------------+ + * | | | | + * |-measure(..) |-measure(..) |-measure(..) | + * |-setProperties(..) |-setProperties(..) |-setProperties(..) | + * |-manageChildren(..)|-manageChildren(..) |-manageChildren(..)| + * | ... | ... | ... | + * +-----------------------------o------------------------------+ + * | Worker simply ^ + * | marshals commands | + * | to Web DOM thread. | + * +---------------------+ + */ +var NodeHandle = { + /** + * Injection + */ + injection: { + injectImplementation: function(Impl) { + NodeHandle._Implementation = Impl; + } + }, + + _Implementation: null, + + /** + * @param {NodeHandle} nodeHandle The handle to the low level resource. + * @return {string} React root node ID. + */ + getRootNodeID: function(nodeHandle) { + return NodeHandle._Implementation.getRootNodeID(nodeHandle); + } +}; + +module.exports = NodeHandle; diff --git a/src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js new file mode 100644 index 0000000000000..ca7faa7fecfec --- /dev/null +++ b/src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js @@ -0,0 +1,19 @@ +/** + * @providesModule UniversalWorkerNodeHandle + */ + +var ReactNativeTagHandles = require('ReactNativeTagHandles'); + +var invariant = require('fbjs/lib/invariant'); + +var UniversalWorkerNodeHandle = { + getRootNodeID: function(nodeHandle) { + invariant( + nodeHandle !== undefined && nodeHandle !== null && nodeHandle !== 0, + 'No node handle defined' + ); + return ReactNativeTagHandles.tagToRootNodeID[nodeHandle]; + } +}; + +module.exports = UniversalWorkerNodeHandle; diff --git a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js new file mode 100644 index 0000000000000..23116e61cd10e --- /dev/null +++ b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js @@ -0,0 +1,51 @@ +/** + * Copyright 2013-2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule ExecutionEnvironment + * + * NB: This is a temporary override that has not yet been merged upstream. It is NOT actually part + * of react-core (yet) + * + * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>> + */ + +/*jslint evil: true */ + +"use strict"; + +var canUseDOM = false; + +/** + * Simple, lightweight module assisting with the detection and context of + * Worker. Helps avoid circular dependencies and allows code to reason about + * whether or not they are in a Worker, even if they never include the main + * `ReactWorker` dependency. + */ +var ExecutionEnvironment = { + + canUseDOM: canUseDOM, + + canUseWorkers: typeof Worker !== 'undefined', + + canUseEventListeners: + canUseDOM && !!(window.addEventListener || window.attachEvent), + + canUseViewport: canUseDOM && !!window.screen, + + isInWorker: !canUseDOM // For now, this is true - might change in the future. + +}; + +module.exports = ExecutionEnvironment; diff --git a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js new file mode 100644 index 0000000000000..3a59accdcded4 --- /dev/null +++ b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js @@ -0,0 +1,48 @@ +/** + * Copyright 2013-2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule ExecutionEnvironment + * + * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>> + */ + +/*jslint evil: true */ + +"use strict"; + +var canUseDOM = false; + +/** + * Simple, lightweight module assisting with the detection and context of + * Worker. Helps avoid circular dependencies and allows code to reason about + * whether or not they are in a Worker, even if they never include the main + * `ReactWorker` dependency. + */ +var ExecutionEnvironment = { + + canUseDOM: canUseDOM, + + canUseWorkers: typeof Worker !== 'undefined', + + canUseEventListeners: + canUseDOM && !!(window.addEventListener || window.attachEvent), + + canUseViewport: canUseDOM && !!window.screen, + + isInWorker: !canUseDOM // For now, this is true - might change in the future. + +}; + +module.exports = ExecutionEnvironment; From 6c885d28c51ea30af0d8a4031dedcea98ef4114c Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 15:47:48 -0700 Subject: [PATCH 02/20] Remove duplicates and move files out of native/vendor Moving the event plugins into their dedicated folder Removing the ExecutionEnvironment override. I will just have to fix where needed. Probably related to the Chrome debugger? --- .../platform => NodeHandle}/NodeHandle.js | 0 .../UniversalWorkerNodeHandle.js | 0 src/renderers/native/ReactNative/React.js | 14 ----- src/renderers/native/ReactNative/ReactDOM.js | 16 ------ .../core/ExecutionEnvironment.android.js | 51 ------------------- .../vendor/core/ExecutionEnvironment.ios.js | 48 ----------------- .../event}/eventPlugins/PanResponder.js | 0 .../event}/eventPlugins/TouchHistoryMath.js | 0 8 files changed, 129 deletions(-) rename src/renderers/native/{vendor/react/platform => NodeHandle}/NodeHandle.js (100%) rename src/renderers/native/{vendor/react/platformImplementations/universal/worker => NodeHandle}/UniversalWorkerNodeHandle.js (100%) delete mode 100644 src/renderers/native/ReactNative/React.js delete mode 100644 src/renderers/native/ReactNative/ReactDOM.js delete mode 100644 src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js delete mode 100644 src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js rename src/renderers/{native/vendor/react/browser => shared/event}/eventPlugins/PanResponder.js (100%) rename src/renderers/{native/vendor/react/browser => shared/event}/eventPlugins/TouchHistoryMath.js (100%) diff --git a/src/renderers/native/vendor/react/platform/NodeHandle.js b/src/renderers/native/NodeHandle/NodeHandle.js similarity index 100% rename from src/renderers/native/vendor/react/platform/NodeHandle.js rename to src/renderers/native/NodeHandle/NodeHandle.js diff --git a/src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js similarity index 100% rename from src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js rename to src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js diff --git a/src/renderers/native/ReactNative/React.js b/src/renderers/native/ReactNative/React.js deleted file mode 100644 index 9cd4464f2860c..0000000000000 --- a/src/renderers/native/ReactNative/React.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule React - * @flow - */ -'use strict'; - -module.exports = require('ReactNative'); diff --git a/src/renderers/native/ReactNative/ReactDOM.js b/src/renderers/native/ReactNative/ReactDOM.js deleted file mode 100644 index 7ac737c1323a5..0000000000000 --- a/src/renderers/native/ReactNative/ReactDOM.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule ReactDOM - */ - -'use strict'; - -var ReactUpdates = require('ReactUpdates'); - -// Temporary shim required for ReactTestUtils and Relay. -var ReactDOM = { - unstable_batchedUpdates: ReactUpdates.batchedUpdates, -}; - -module.exports = ReactDOM; diff --git a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js deleted file mode 100644 index 23116e61cd10e..0000000000000 --- a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ExecutionEnvironment - * - * NB: This is a temporary override that has not yet been merged upstream. It is NOT actually part - * of react-core (yet) - * - * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>> - */ - -/*jslint evil: true */ - -"use strict"; - -var canUseDOM = false; - -/** - * Simple, lightweight module assisting with the detection and context of - * Worker. Helps avoid circular dependencies and allows code to reason about - * whether or not they are in a Worker, even if they never include the main - * `ReactWorker` dependency. - */ -var ExecutionEnvironment = { - - canUseDOM: canUseDOM, - - canUseWorkers: typeof Worker !== 'undefined', - - canUseEventListeners: - canUseDOM && !!(window.addEventListener || window.attachEvent), - - canUseViewport: canUseDOM && !!window.screen, - - isInWorker: !canUseDOM // For now, this is true - might change in the future. - -}; - -module.exports = ExecutionEnvironment; diff --git a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js b/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js deleted file mode 100644 index 3a59accdcded4..0000000000000 --- a/src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2013-2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @providesModule ExecutionEnvironment - * - * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>> - */ - -/*jslint evil: true */ - -"use strict"; - -var canUseDOM = false; - -/** - * Simple, lightweight module assisting with the detection and context of - * Worker. Helps avoid circular dependencies and allows code to reason about - * whether or not they are in a Worker, even if they never include the main - * `ReactWorker` dependency. - */ -var ExecutionEnvironment = { - - canUseDOM: canUseDOM, - - canUseWorkers: typeof Worker !== 'undefined', - - canUseEventListeners: - canUseDOM && !!(window.addEventListener || window.attachEvent), - - canUseViewport: canUseDOM && !!window.screen, - - isInWorker: !canUseDOM // For now, this is true - might change in the future. - -}; - -module.exports = ExecutionEnvironment; diff --git a/src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js b/src/renderers/shared/event/eventPlugins/PanResponder.js similarity index 100% rename from src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js rename to src/renderers/shared/event/eventPlugins/PanResponder.js diff --git a/src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js b/src/renderers/shared/event/eventPlugins/TouchHistoryMath.js similarity index 100% rename from src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js rename to src/renderers/shared/event/eventPlugins/TouchHistoryMath.js From fe395def652b65847de3912d64181b24f96f70fe Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 16:08:34 -0700 Subject: [PATCH 03/20] Move React Native platform files back These files are really only related to the platform itself and not the React integration so I'll move them back. --- src/renderers/native/ReactIOS/YellowBox.js | 364 ------------------ .../ReactIOS/renderApplication.android.js | 139 ------- .../native/ReactIOS/renderApplication.ios.js | 96 ----- .../native/ReactIOS/requireNativeComponent.js | 127 ------ .../native/ReactIOS/verifyPropTypes.js | 61 --- .../ReactNative/UIManagerStatTracker.js | 57 --- .../native/vendor/react/core/clamp.js | 35 -- 7 files changed, 879 deletions(-) delete mode 100644 src/renderers/native/ReactIOS/YellowBox.js delete mode 100644 src/renderers/native/ReactIOS/renderApplication.android.js delete mode 100644 src/renderers/native/ReactIOS/renderApplication.ios.js delete mode 100644 src/renderers/native/ReactIOS/requireNativeComponent.js delete mode 100644 src/renderers/native/ReactIOS/verifyPropTypes.js delete mode 100644 src/renderers/native/ReactNative/UIManagerStatTracker.js delete mode 100644 src/renderers/native/vendor/react/core/clamp.js diff --git a/src/renderers/native/ReactIOS/YellowBox.js b/src/renderers/native/ReactIOS/YellowBox.js deleted file mode 100644 index e1dddc14541ef..0000000000000 --- a/src/renderers/native/ReactIOS/YellowBox.js +++ /dev/null @@ -1,364 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule YellowBox - * @flow - */ - -'use strict'; - -const EventEmitter = require('EventEmitter'); -import type EmitterSubscription from 'EmitterSubscription'; -const Platform = require('Platform'); -const React = require('React'); -const StyleSheet = require('StyleSheet'); - -const _warningEmitter = new EventEmitter(); -const _warningMap = new Map(); - -/** - * YellowBox renders warnings at the bottom of the app being developed. - * - * Warnings help guard against subtle yet significant issues that can impact the - * quality of the app. This "in your face" style of warning allows developers to - * notice and correct these issues as quickly as possible. - * - * By default, the warning box is enabled in `__DEV__`. Set the following flag - * to disable it (and call `console.warn` to update any rendered ): - * - * console.disableYellowBox = true; - * console.warn('YellowBox is disabled.'); - * - * Warnings can be ignored programmatically by setting the array: - * - * console.ignoredYellowBox = ['Warning: ...']; - * - * Strings in `console.ignoredYellowBox` can be a prefix of the warning that - * should be ignored. - */ - -if (__DEV__) { - const {error, warn} = console; - console.error = function() { - error.apply(console, arguments); - // Show yellow box for the `warning` module. - if (typeof arguments[0] === 'string' && - arguments[0].startsWith('Warning: ')) { - updateWarningMap.apply(null, arguments); - } - }; - console.warn = function() { - warn.apply(console, arguments); - updateWarningMap.apply(null, arguments); - }; -} - -/** - * Simple function for formatting strings. - * - * Replaces placeholders with values passed as extra arguments - * - * @param {string} format the base string - * @param ...args the values to insert - * @return {string} the replaced string - */ -function sprintf(format, ...args) { - var index = 0; - return format.replace(/%s/g, match => args[index++]); -} - -function updateWarningMap(format, ...args): void { - const stringifySafe = require('stringifySafe'); - - format = String(format); - const argCount = (format.match(/%s/g) || []).length; - const warning = [ - sprintf(format, ...args.slice(0, argCount)), - ...args.slice(argCount).map(stringifySafe), - ].join(' '); - - const count = _warningMap.has(warning) ? _warningMap.get(warning) : 0; - _warningMap.set(warning, count + 1); - _warningEmitter.emit('warning', _warningMap); -} - -function isWarningIgnored(warning: string): boolean { - return ( - Array.isArray(console.ignoredYellowBox) && - console.ignoredYellowBox.some( - ignorePrefix => warning.startsWith(ignorePrefix) - ) - ); -} - -const WarningRow = ({count, warning, onPress}) => { - const Text = require('Text'); - const TouchableHighlight = require('TouchableHighlight'); - const View = require('View'); - - const countText = count > 1 ? - {'(' + count + ') '} : - null; - - return ( - - - - {countText} - {warning} - - - - ); -}; - -const WarningInspector = ({ - count, - warning, - onClose, - onDismiss, - onDismissAll, -}) => { - const ScrollView = require('ScrollView'); - const Text = require('Text'); - const TouchableHighlight = require('TouchableHighlight'); - const View = require('View'); - - const countSentence = - 'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.'; - - return ( - - - - {countSentence} - - - {warning} - - - - - Dismiss - - - - - Dismiss All - - - - - - ); -}; - -class YellowBox extends React.Component { - state: { - inspecting: ?string; - warningMap: Map; - }; - _listener: ?EmitterSubscription; - - constructor(props: mixed, context: mixed) { - super(props, context); - this.state = { - inspecting: null, - warningMap: _warningMap, - }; - this.dismissWarning = warning => { - const {inspecting, warningMap} = this.state; - if (warning) { - warningMap.delete(warning); - } else { - warningMap.clear(); - } - this.setState({ - inspecting: (warning && inspecting !== warning) ? inspecting : null, - warningMap, - }); - }; - } - - componentDidMount() { - let scheduled = null; - this._listener = _warningEmitter.addListener('warning', warningMap => { - // Use `setImmediate` because warnings often happen during render, but - // state cannot be set while rendering. - scheduled = scheduled || setImmediate(() => { - scheduled = null; - this.setState({ - warningMap, - }); - }); - }); - } - - componentWillUnmount() { - if (this._listener) { - this._listener.remove(); - } - } - - render() { - if (console.disableYellowBox || this.state.warningMap.size === 0) { - return null; - } - const ScrollView = require('ScrollView'); - const View = require('View'); - - const inspecting = this.state.inspecting; - const inspector = inspecting !== null ? - this.setState({inspecting: null})} - onDismiss={() => this.dismissWarning(inspecting)} - onDismissAll={() => this.dismissWarning(null)} - /> : - null; - - const rows = []; - this.state.warningMap.forEach((count, warning) => { - if (!isWarningIgnored(warning)) { - rows.push( - this.setState({inspecting: warning})} - onDismiss={() => this.dismissWarning(warning)} - /> - ); - } - }); - - const listStyle = [ - styles.list, - // Additional `0.4` so the 5th row can peek into view. - {height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)}, - ]; - return ( - - - {rows} - - {inspector} - - ); - } -} - -const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')'; -const textColor = 'white'; -const rowGutter = 1; -const rowHeight = 46; - -var styles = StyleSheet.create({ - fullScreen: { - backgroundColor: 'transparent', - position: 'absolute', - left: 0, - right: 0, - top: 0, - bottom: 0, - }, - inspector: { - backgroundColor: backgroundColor(0.95), - flex: 1, - }, - inspectorContainer: { - flex: 1, - }, - inspectorButtons: { - flexDirection: 'row', - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - }, - inspectorButton: { - flex: 1, - padding: 22, - }, - inspectorButtonText: { - color: textColor, - fontSize: 14, - opacity: 0.8, - textAlign: 'center', - }, - inspectorContent: { - flex: 1, - paddingTop: 5, - }, - inspectorCount: { - padding: 15, - paddingBottom: 0, - }, - inspectorCountText: { - color: textColor, - fontSize: 14, - }, - inspectorWarning: { - padding: 15, - position: 'absolute', - top: 39, - bottom: 60, - }, - inspectorWarningText: { - color: textColor, - fontSize: 16, - fontWeight: '600', - }, - list: { - backgroundColor: 'transparent', - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - }, - listRow: { - position: 'relative', - backgroundColor: backgroundColor(0.95), - flex: 1, - height: rowHeight, - marginTop: rowGutter, - }, - listRowContent: { - flex: 1, - }, - listRowCount: { - color: 'rgba(255, 255, 255, 0.5)', - }, - listRowText: { - color: textColor, - position: 'absolute', - left: 0, - top: Platform.OS === 'android' ? 5 : 7, - marginLeft: 15, - marginRight: 15, - }, -}); - -module.exports = YellowBox; diff --git a/src/renderers/native/ReactIOS/renderApplication.android.js b/src/renderers/native/ReactIOS/renderApplication.android.js deleted file mode 100644 index 03191bbc03785..0000000000000 --- a/src/renderers/native/ReactIOS/renderApplication.android.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule renderApplication - */ - -'use strict'; - -var Inspector = require('Inspector'); -var Portal = require('Portal'); -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Subscribable = require('Subscribable'); -var View = require('View'); - -var invariant = require('fbjs/lib/invariant'); - -var YellowBox = __DEV__ ? require('YellowBox') : null; - -// require BackAndroid so it sets the default handler that exits the app if no listeners respond -require('BackAndroid'); - -var AppContainer = React.createClass({ - mixins: [Subscribable.Mixin], - - getInitialState: function() { - return { - enabled: __DEV__, - inspectorVisible: false, - rootNodeHandle: null, - rootImportanceForAccessibility: 'auto', - }; - }, - - toggleElementInspector: function() { - this.setState({ - inspectorVisible: !this.state.inspectorVisible, - rootNodeHandle: React.findNodeHandle(this.refs.main), - }); - }, - - componentDidMount: function() { - this.addListenerOn( - RCTDeviceEventEmitter, - 'toggleElementInspector', - this.toggleElementInspector - ); - - this._unmounted = false; - }, - - renderInspector: function() { - return this.state.inspectorVisible ? - : - null; - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - setRootAccessibility: function(modalVisible) { - if (this._unmounted) { - return; - } - - this.setState({ - rootImportanceForAccessibility: modalVisible ? 'no-hide-descendants' : 'auto', - }); - }, - - render: function() { - var RootComponent = this.props.rootComponent; - var appView = - - - - ; - let yellowBox = null; - if (__DEV__) { - yellowBox = ; - } - return this.state.enabled ? - - {appView} - {yellowBox} - {this.renderInspector()} - : - appView; - } -}); - -function renderApplication( - RootComponent: ReactClass

, - initialProps: P, - rootTag: any -) { - invariant( - rootTag, - 'Expect to have a valid rootTag, instead got ', rootTag - ); - React.render( - , - rootTag - ); -} - -var styles = StyleSheet.create({ - // This is needed so the application covers the whole screen - // and therefore the contents of the Portal are not clipped. - appContainer: { - position: 'absolute', - left: 0, - top: 0, - right: 0, - bottom: 0, - }, -}); - -module.exports = renderApplication; diff --git a/src/renderers/native/ReactIOS/renderApplication.ios.js b/src/renderers/native/ReactIOS/renderApplication.ios.js deleted file mode 100644 index c3e4f8178044b..0000000000000 --- a/src/renderers/native/ReactIOS/renderApplication.ios.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule renderApplication - * @noflow - */ - -'use strict'; - -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Subscribable = require('Subscribable'); -var View = require('View'); - -var invariant = require('fbjs/lib/invariant'); - -var Inspector = __DEV__ ? require('Inspector') : null; -var YellowBox = __DEV__ ? require('YellowBox') : null; - -var AppContainer = React.createClass({ - mixins: [Subscribable.Mixin], - - getInitialState: function() { - return { inspector: null }; - }, - - toggleElementInspector: function() { - var inspector = !__DEV__ || this.state.inspector - ? null - : ; - this.setState({inspector}); - }, - - componentDidMount: function() { - this.addListenerOn( - RCTDeviceEventEmitter, - 'toggleElementInspector', - this.toggleElementInspector - ); - }, - - render: function() { - let yellowBox = null; - if (__DEV__) { - yellowBox = ; - } - return ( - - - {this.props.children} - - {yellowBox} - {this.state.inspector} - - ); - } -}); - -function renderApplication( - RootComponent: ReactClass

, - initialProps: P, - rootTag: any -) { - invariant( - rootTag, - 'Expect to have a valid rootTag, instead got ', rootTag - ); - /* eslint-disable jsx-no-undef-with-namespace */ - React.render( - - - , - rootTag - ); - /* eslint-enable jsx-no-undef-with-namespace */ -} - -var styles = StyleSheet.create({ - appContainer: { - flex: 1, - }, -}); - -module.exports = renderApplication; diff --git a/src/renderers/native/ReactIOS/requireNativeComponent.js b/src/renderers/native/ReactIOS/requireNativeComponent.js deleted file mode 100644 index 61993f799fb9b..0000000000000 --- a/src/renderers/native/ReactIOS/requireNativeComponent.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule requireNativeComponent - * @flow - */ -'use strict'; - -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); -var UIManager = require('UIManager'); -var UnimplementedView = require('UnimplementedView'); - -var createReactNativeComponentClass = require('createReactNativeComponentClass'); - -var insetsDiffer = require('insetsDiffer'); -var pointsDiffer = require('pointsDiffer'); -var matricesDiffer = require('matricesDiffer'); -var processColor = require('processColor'); -var resolveAssetSource = require('resolveAssetSource'); -var sizesDiffer = require('sizesDiffer'); -var verifyPropTypes = require('verifyPropTypes'); -var warning = require('fbjs/lib/warning'); - -/** - * Used to create React components that directly wrap native component - * implementations. Config information is extracted from data exported from the - * UIManager module. You should also wrap the native component in a - * hand-written component with full propTypes definitions and other - * documentation - pass the hand-written component in as `componentInterface` to - * verify all the native props are documented via `propTypes`. - * - * If some native props shouldn't be exposed in the wrapper interface, you can - * pass null for `componentInterface` and call `verifyPropTypes` directly - * with `nativePropsToIgnore`; - * - * Common types are lined up with the appropriate prop differs with - * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`. - */ -import type { ComponentInterface } from 'verifyPropTypes'; - -function requireNativeComponent( - viewName: string, - componentInterface?: ?ComponentInterface, - extraConfig?: ?{nativeOnly?: Object}, -): Function { - var viewConfig = UIManager[viewName]; - if (!viewConfig || !viewConfig.NativeProps) { - warning(false, 'Native component for "%s" does not exist', viewName); - return UnimplementedView; - } - var nativeProps = { - ...UIManager.RCTView.NativeProps, - ...viewConfig.NativeProps, - }; - viewConfig.uiViewClassName = viewName; - viewConfig.validAttributes = {}; - viewConfig.propTypes = componentInterface && componentInterface.propTypes; - for (var key in nativeProps) { - var useAttribute = false; - var attribute = {}; - - var differ = TypeToDifferMap[nativeProps[key]]; - if (differ) { - attribute.diff = differ; - useAttribute = true; - } - - var processor = TypeToProcessorMap[nativeProps[key]]; - if (processor) { - attribute.process = processor; - useAttribute = true; - } - - viewConfig.validAttributes[key] = useAttribute ? attribute : true; - } - - // Unfortunately, the current set up puts the style properties on the top - // level props object. We also need to add the nested form for API - // compatibility. This allows these props on both the top level and the - // nested style level. TODO: Move these to nested declarations on the - // native side. - viewConfig.validAttributes.style = ReactNativeStyleAttributes; - - if (__DEV__) { - componentInterface && verifyPropTypes( - componentInterface, - viewConfig, - extraConfig && extraConfig.nativeOnly - ); - } - return createReactNativeComponentClass(viewConfig); -} - -var TypeToDifferMap = { - // iOS Types - CATransform3D: matricesDiffer, - CGPoint: pointsDiffer, - CGSize: sizesDiffer, - UIEdgeInsets: insetsDiffer, - // Android Types - // (not yet implemented) -}; - -function processColorArray(colors: []): [] { - return colors && colors.map(processColor); -} - -var TypeToProcessorMap = { - // iOS Types - CGColor: processColor, - CGColorArray: processColorArray, - UIColor: processColor, - UIColorArray: processColorArray, - CGImage: resolveAssetSource, - UIImage: resolveAssetSource, - RCTImageSource: resolveAssetSource, - // Android Types - Color: processColor, - ColorArray: processColorArray, -}; - -module.exports = requireNativeComponent; diff --git a/src/renderers/native/ReactIOS/verifyPropTypes.js b/src/renderers/native/ReactIOS/verifyPropTypes.js deleted file mode 100644 index d284d60ec0aec..0000000000000 --- a/src/renderers/native/ReactIOS/verifyPropTypes.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule verifyPropTypes - * @flow - */ -'use strict'; - -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); - -export type ComponentInterface = ReactClass | { - name?: string; - displayName?: string; - propTypes: Object; -}; - -function verifyPropTypes( - componentInterface: ComponentInterface, - viewConfig: Object, - nativePropsToIgnore?: ?Object -) { - if (!viewConfig) { - return; // This happens for UnimplementedView. - } - var componentName = componentInterface.name || - componentInterface.displayName || - 'unknown'; - if (!componentInterface.propTypes) { - throw new Error( - '`' + componentName + '` has no propTypes defined`' - ); - } - - var nativeProps = viewConfig.NativeProps; - for (var prop in nativeProps) { - if (!componentInterface.propTypes[prop] && - !ReactNativeStyleAttributes[prop] && - (!nativePropsToIgnore || !nativePropsToIgnore[prop])) { - var message; - if (componentInterface.propTypes.hasOwnProperty(prop)) { - message = '`' + componentName + '` has incorrectly defined propType for native prop `' + - viewConfig.uiViewClassName + '.' + prop + '` of native type `' + nativeProps[prop]; - } else { - message = '`' + componentName + '` has no propType for native prop `' + - viewConfig.uiViewClassName + '.' + prop + '` of native type `' + - nativeProps[prop] + '`'; - }; - message += '\nIf you haven\'t changed this prop yourself, this usually means that ' + - 'your versions of the native code and JavaScript code are out of sync. Updating both ' + - 'should make this error go away.'; - throw new Error(message); - } - } -} - -module.exports = verifyPropTypes; diff --git a/src/renderers/native/ReactNative/UIManagerStatTracker.js b/src/renderers/native/ReactNative/UIManagerStatTracker.js deleted file mode 100644 index a3b8779234f5e..0000000000000 --- a/src/renderers/native/ReactNative/UIManagerStatTracker.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule UIManagerStatTracker - * @flow - */ -'use strict'; - -var UIManager = require('UIManager'); - -var installed = false; -var UIManagerStatTracker = { - install: function() { - if (installed) { - return; - } - installed = true; - var statLogHandle; - var stats = {}; - function printStats() { - console.log({UIManagerStatTracker: stats}); - statLogHandle = null; - } - function incStat(key: string, increment: number) { - stats[key] = (stats[key] || 0) + increment; - if (!statLogHandle) { - statLogHandle = setImmediate(printStats); - } - } - var createViewOrig = UIManager.createView; - UIManager.createView = function(tag, className, rootTag, props) { - incStat('createView', 1); - incStat('setProp', Object.keys(props || []).length); - createViewOrig(tag, className, rootTag, props); - }; - var updateViewOrig = UIManager.updateView; - UIManager.updateView = function(tag, className, props) { - incStat('updateView', 1); - incStat('setProp', Object.keys(props || []).length); - updateViewOrig(tag, className, props); - }; - var manageChildrenOrig = UIManager.manageChildren; - UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) { - incStat('manageChildren', 1); - incStat('move', Object.keys(moveFrom || []).length); - incStat('remove', Object.keys(remove || []).length); - manageChildrenOrig(tag, moveFrom, moveTo, addTags, addIndices, remove); - }; - }, -}; - -module.exports = UIManagerStatTracker; diff --git a/src/renderers/native/vendor/react/core/clamp.js b/src/renderers/native/vendor/react/core/clamp.js deleted file mode 100644 index cb7578ceecce7..0000000000000 --- a/src/renderers/native/vendor/react/core/clamp.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @generated SignedSource<> - * - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * !! 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. !! - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * - * @providesModule clamp - * @typechecks - */ - - /** - * @param {number} value - * @param {number} min - * @param {number} max - * @return {number} - */ -function clamp(min, value, max) { - if (value < min) { - return min; - } - if (value > max) { - return max; - } - return value; -} - -module.exports = clamp; From 8806463a66cea3caacb7ecd94c54bf9124ae650b Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 16:26:34 -0700 Subject: [PATCH 04/20] Add Object Spread Support to Our Babel Config Won't really use this in prod code yet but I have a benchmark that uses it. --- .babelrc | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/.babelrc b/.babelrc index 73803b050c24c..2ff815b4253f3 100644 --- a/.babelrc +++ b/.babelrc @@ -4,6 +4,7 @@ "plugins": [ "fbjs-scripts/babel-6/dev-expression", "syntax-trailing-function-commas", + "babel-plugin-transform-object-rest-spread", "transform-es2015-template-literals", "transform-es2015-literals", "transform-es2015-arrow-functions", diff --git a/package.json b/package.json index 11a716f9ccf4c..c651733353281 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "babel-plugin-transform-es2015-template-literals": "^6.5.2", "babel-plugin-transform-es3-member-expression-literals": "^6.5.0", "babel-plugin-transform-es3-property-literals": "^6.5.0", + "babel-plugin-transform-object-rest-spread": "^6.6.5", "babel-preset-react": "^6.5.0", "browserify": "^13.0.0", "bundle-collapser": "^1.1.1", From caa6abaecf6e7ab1511862c9e5faf868ff93c96e Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 12 Aug 2015 01:41:24 -0700 Subject: [PATCH 05/20] Build an dedicated npm package for react-native-renderer --- .travis.yml | 1 + Gruntfile.js | 4 ++++ grunt/tasks/npm-react-native.js | 21 +++++++++++++++++++ grunt/tasks/version-check.js | 2 ++ packages/react-native-renderer/README.md | 5 +++++ packages/react-native-renderer/index.js | 3 +++ packages/react-native-renderer/package.json | 23 +++++++++++++++++++++ 7 files changed, 59 insertions(+) create mode 100644 grunt/tasks/npm-react-native.js create mode 100644 packages/react-native-renderer/README.md create mode 100644 packages/react-native-renderer/index.js create mode 100644 packages/react-native-renderer/package.json diff --git a/.travis.yml b/.travis.yml index bfbdfeaa20a1a..634cf98e55c4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ script: -F "react-dom-server.min=@build/react-dom-server.min.js" \ -F "npm-react=@build/packages/react.tgz" \ -F "npm-react-dom=@build/packages/react-dom.tgz" \ + -F "npm-react-native=@build/packages/react-native-renderer.tgz" \ -F "commit=$TRAVIS_COMMIT" \ -F "date=`git log --format='%ct' -1`" \ -F "pull_request=$TRAVIS_PULL_REQUEST" \ diff --git a/Gruntfile.js b/Gruntfile.js index c3ee8c20d6441..d3591ec8edd40 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -74,6 +74,9 @@ module.exports = function(grunt) { grunt.registerTask('npm-react-dom:release', npmReactDOMTasks.buildRelease); grunt.registerTask('npm-react-dom:pack', npmReactDOMTasks.packRelease); + var npmReactNativeTasks = require('./grunt/tasks/npm-react-native'); + grunt.registerTask('npm-react-native:pack', npmReactNativeTasks.packRelease); + var npmReactAddonsTasks = require('./grunt/tasks/npm-react-addons'); grunt.registerTask('npm-react-addons:release', npmReactAddonsTasks.buildReleases); grunt.registerTask('npm-react-addons:pack', npmReactAddonsTasks.packReleases); @@ -127,6 +130,7 @@ module.exports = function(grunt) { 'npm-react:pack', 'npm-react-dom:release', 'npm-react-dom:pack', + 'npm-react-native:pack', 'npm-react-addons:release', 'npm-react-addons:pack', 'compare_size', diff --git a/grunt/tasks/npm-react-native.js b/grunt/tasks/npm-react-native.js new file mode 100644 index 0000000000000..132ae2d9e3d89 --- /dev/null +++ b/grunt/tasks/npm-react-native.js @@ -0,0 +1,21 @@ +'use strict'; + +var fs = require('fs'); +var grunt = require('grunt'); + +function packRelease() { + var done = this.async(); + var spawnCmd = { + cmd: 'npm', + args: ['pack', 'packages/react-native-renderer'], + }; + grunt.util.spawn(spawnCmd, function() { + var buildSrc = 'react-native-renderer-' + grunt.config.data.pkg.version + '.tgz'; + var buildDest = 'build/packages/react-native-renderer.tgz'; + fs.rename(buildSrc, buildDest, done); + }); +} + +module.exports = { + packRelease: packRelease, +}; diff --git a/grunt/tasks/version-check.js b/grunt/tasks/version-check.js index ff6da818c0de0..99f55feac010e 100644 --- a/grunt/tasks/version-check.js +++ b/grunt/tasks/version-check.js @@ -11,6 +11,8 @@ module.exports = function() { grunt.file.readJSON('./packages/react/package.json').version, 'packages/react-dom/package.json': grunt.file.readJSON('./packages/react-dom/package.json').version, + 'packages/react-native-renderer/package.json': + grunt.file.readJSON('./packages/react-native-renderer/package.json').version, 'packages/react-addons/package.json (version)': addonsData.version, // Get the "version" without the range bit 'packages/react-addons/package.json (react dependency)': addonsData.peerDependencies.react.slice(1), diff --git a/packages/react-native-renderer/README.md b/packages/react-native-renderer/README.md new file mode 100644 index 0000000000000..db3e7d3a5981d --- /dev/null +++ b/packages/react-native-renderer/README.md @@ -0,0 +1,5 @@ +# `react-native-renderer` + +This package is the renderer that is used by the react-native package. +It is intended to be used inside the react-native environment. It is not +intended to be used stand alone. diff --git a/packages/react-native-renderer/index.js b/packages/react-native-renderer/index.js new file mode 100644 index 0000000000000..6f35853e7c530 --- /dev/null +++ b/packages/react-native-renderer/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('react/lib/ReactNative'); diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json new file mode 100644 index 0000000000000..5d1fee2e6db8f --- /dev/null +++ b/packages/react-native-renderer/package.json @@ -0,0 +1,23 @@ +{ + "name": "react-native-renderer", + "version": "15.0.0-rc.2", + "description": "React package for use inside react-native.", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "keywords": [ + "react", + "react-native" + ], + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "homepage": "https://facebook.github.io/react-native/", + "dependencies": { + "fbjs": "^0.8.0-alpha.2", + "react": "^15.0.0-rc.2" + } +} From 240dfae28c81a05dab137a84e81e48c5ccecdcec Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 22:55:58 -0700 Subject: [PATCH 06/20] Add React Native Modules to module map + fix fbjs Our module rewrite whitelist needs to ignore providesModule files that are going to be required from the global React Native environment. We also need to add ReactDOM to providesModule since we removed it from React Native. --- gulpfile.js | 38 ++++++++++++++----- packages/react/lib/ReactDOM.native.js | 4 ++ .../NodeHandle/UniversalWorkerNodeHandle.js | 2 +- .../ReactIOS/IOSNativeBridgeEventPlugin.js | 2 +- .../native/ReactIOS/NativeMethodsMixin.js | 2 +- .../ReactNative/ReactNativeBaseComponent.js | 4 +- .../ReactNativeDefaultInjection.js | 2 +- .../ReactNative/ReactNativeEventEmitter.js | 2 +- .../native/ReactNative/ReactNativeMount.js | 2 +- .../ReactNative/ReactNativeTagHandles.js | 4 +- .../ReactNative/ReactNativeTextComponent.js | 2 +- .../native/ReactNative/findNodeHandle.js | 4 +- 12 files changed, 46 insertions(+), 22 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f885ea349a670..2c2a7c98239ee 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,6 +20,7 @@ var paths = { react: { src: [ 'src/**/*.js', + '!src/**/__benchmarks__/**/*.js', '!src/**/__tests__/**/*.js', '!src/**/__mocks__/**/*.js', '!src/shared/vendor/**/*.js', @@ -28,17 +29,36 @@ var paths = { }, }; +var fbjsModuleMap = require('fbjs/module-map'); +var moduleMap = {}; +for (var key in fbjsModuleMap) { + moduleMap[key] = fbjsModuleMap[key]; +} +var whiteListNames = [ + 'deepDiffer', + 'deepFreezeAndThrowOnMutationInDev', + 'flattenStyle', + 'InitializeJavaScriptAppEngine', + 'InteractionManager', + 'JSTimersExecution', + 'merge', + 'Platform', + 'RCTEventEmitter', + 'RCTLog', + 'TextInputState', + 'UIManager', + 'View', +]; + +whiteListNames.forEach(function(name) { + moduleMap[name] = name; +}); + +moduleMap['object-assign'] = 'object-assign'; + var babelOpts = { plugins: [ - [babelPluginModules, { - map: Object.assign( - {}, - require('fbjs/module-map'), - { - 'object-assign': 'object-assign', - } - ), - }], + [babelPluginModules, { map: moduleMap }], ], }; diff --git a/packages/react/lib/ReactDOM.native.js b/packages/react/lib/ReactDOM.native.js index bdd9044a8b1c7..2c4b436a7e852 100644 --- a/packages/react/lib/ReactDOM.native.js +++ b/packages/react/lib/ReactDOM.native.js @@ -1,3 +1,7 @@ +/** + * @providesModule ReactDOM + */ + 'use strict'; var ReactUpdates = require('./ReactUpdates'); diff --git a/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js b/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js index ca7faa7fecfec..b91feba9b1e2a 100644 --- a/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js +++ b/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js @@ -4,7 +4,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var invariant = require('fbjs/lib/invariant'); +var invariant = require('invariant'); var UniversalWorkerNodeHandle = { getRootNodeID: function(nodeHandle) { diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js index d1ffc1d402e23..6c1d887e555de 100644 --- a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -16,7 +16,7 @@ var SyntheticEvent = require('SyntheticEvent'); var UIManager = require('UIManager'); var merge = require('merge'); -var warning = require('fbjs/lib/warning'); +var warning = require('warning'); var customBubblingEventTypes = UIManager.customBubblingEventTypes; var customDirectEventTypes = UIManager.customDirectEventTypes; diff --git a/src/renderers/native/ReactIOS/NativeMethodsMixin.js b/src/renderers/native/ReactIOS/NativeMethodsMixin.js index 88f451a58ca10..f9247a14195ee 100644 --- a/src/renderers/native/ReactIOS/NativeMethodsMixin.js +++ b/src/renderers/native/ReactIOS/NativeMethodsMixin.js @@ -16,7 +16,7 @@ var TextInputState = require('TextInputState'); var UIManager = require('UIManager'); var findNodeHandle = require('findNodeHandle'); -var invariant = require('fbjs/lib/invariant'); +var invariant = require('invariant'); type MeasureOnSuccessCallback = ( x: number, diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index b879b45b6a51a..19afca80362e9 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -19,8 +19,8 @@ var ReactMultiChild = require('ReactMultiChild'); var UIManager = require('UIManager'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); +var invariant = require('invariant'); +var warning = require('warning'); var registrationNames = ReactNativeEventEmitter.registrationNames; var putListener = ReactNativeEventEmitter.putListener; diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js index ee97c2560e61a..0d55d670c40e1 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -38,7 +38,7 @@ var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); -var invariant = require('fbjs/lib/invariant'); +var invariant = require('invariant'); // Just to ensure this gets packaged, since its only caller is from Native. require('RCTEventEmitter'); diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index c7680257aff57..ac8854b6cb65d 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -18,7 +18,7 @@ var NodeHandle = require('NodeHandle'); var EventConstants = require('EventConstants'); var merge = require('merge'); -var warning = require('fbjs/lib/warning'); +var warning = require('warning'); var topLevelTypes = EventConstants.topLevelTypes; diff --git a/src/renderers/native/ReactNative/ReactNativeMount.js b/src/renderers/native/ReactNative/ReactNativeMount.js index cf8c5bb336fdd..2a38c3dbba8c0 100644 --- a/src/renderers/native/ReactNative/ReactNativeMount.js +++ b/src/renderers/native/ReactNative/ReactNativeMount.js @@ -19,7 +19,7 @@ var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); var UIManager = require('UIManager'); -var emptyObject = require('fbjs/lib/emptyObject'); +var emptyObject = require('emptyObject'); var instantiateReactComponent = require('instantiateReactComponent'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); diff --git a/src/renderers/native/ReactNative/ReactNativeTagHandles.js b/src/renderers/native/ReactNative/ReactNativeTagHandles.js index 5a0de4cda393c..ab350817c6e90 100644 --- a/src/renderers/native/ReactNative/ReactNativeTagHandles.js +++ b/src/renderers/native/ReactNative/ReactNativeTagHandles.js @@ -11,8 +11,8 @@ */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); +var invariant = require('invariant'); +var warning = require('warning'); /** * Keeps track of allocating and associating native "tags" which are numeric, diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNative/ReactNativeTextComponent.js index 7bd97467f26b6..07463ff905b39 100644 --- a/src/renderers/native/ReactNative/ReactNativeTextComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeTextComponent.js @@ -14,7 +14,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); var UIManager = require('UIManager'); -var invariant = require('fbjs/lib/invariant'); +var invariant = require('invariant'); var ReactNativeTextComponent = function(props) { // This constructor and its argument is currently used by mocks. diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/ReactNative/findNodeHandle.js index dd3191b82d921..37c772760c503 100644 --- a/src/renderers/native/ReactNative/findNodeHandle.js +++ b/src/renderers/native/ReactNative/findNodeHandle.js @@ -16,8 +16,8 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); +var invariant = require('invariant'); +var warning = require('warning'); /** * ReactNative vs ReactWeb From 91e62c718b4fc47fd67b18697dc495275bb64550 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 12 Aug 2015 01:55:41 -0700 Subject: [PATCH 07/20] Strip isomorphic stuff from the ReactNative module Also, add a shim for the isomorphic module for ios and android so that react-native doesn't pull in react-dom when React is required. --- .../native/ReactNative/ReactNative.js | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/src/renderers/native/ReactNative/ReactNative.js b/src/renderers/native/ReactNative/ReactNative.js index 148a366663efd..b331912c98900 100644 --- a/src/renderers/native/ReactNative/ReactNative.js +++ b/src/renderers/native/ReactNative/ReactNative.js @@ -15,61 +15,16 @@ // the JS environment var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection'); -var ReactChildren = require('ReactChildren'); -var ReactClass = require('ReactClass'); -var ReactComponent = require('ReactComponent'); var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); -var ReactElementValidator = require('ReactElementValidator'); var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactNativeMount = require('ReactNativeMount'); -var ReactPropTypes = require('ReactPropTypes'); var ReactUpdates = require('ReactUpdates'); var findNodeHandle = require('findNodeHandle'); -var invariant = require('fbjs/lib/invariant'); -var onlyChild = require('onlyChild'); -var warning = require('fbjs/lib/warning'); ReactNativeDefaultInjection.inject(); -var createElement = ReactElement.createElement; -var createFactory = ReactElement.createFactory; -var cloneElement = ReactElement.cloneElement; - -if (__DEV__) { - createElement = ReactElementValidator.createElement; - createFactory = ReactElementValidator.createFactory; - cloneElement = ReactElementValidator.cloneElement; -} - -var resolveDefaultProps = function(element) { - // Could be optimized, but not currently in heavy use. - var defaultProps = element.type.defaultProps; - var props = element.props; - for (var propName in defaultProps) { - if (props[propName] === undefined) { - props[propName] = defaultProps[propName]; - } - } -}; - -// Experimental optimized element creation -var augmentElement = function(element: ReactElement): ReactElement { - if (__DEV__) { - invariant( - false, - 'This optimized path should never be used in DEV mode because ' + - 'it does not provide validation. Check your JSX transform.' - ); - } - element._owner = ReactCurrentOwner.current; - if (element.type.defaultProps) { - resolveDefaultProps(element); - } - return element; -}; - var render = function( element: ReactElement, mountInto: number, @@ -80,20 +35,6 @@ var render = function( var ReactNative = { hasReactNativeInitialized: false, - Children: { - map: ReactChildren.map, - forEach: ReactChildren.forEach, - count: ReactChildren.count, - toArray: ReactChildren.toArray, - only: onlyChild - }, - Component: ReactComponent, - PropTypes: ReactPropTypes, - createClass: ReactClass.createClass, - createElement: createElement, - createFactory: createFactory, - cloneElement: cloneElement, - _augmentElement: augmentElement, findNodeHandle: findNodeHandle, render: render, unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, @@ -102,12 +43,7 @@ var ReactNative = { unstable_batchedUpdates: ReactUpdates.batchedUpdates, /* eslint-enable camelcase */ - // Hook for JSX spread, don't use this for anything else. - __spread: Object.assign, - unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, - isValidClass: ReactElement.isValidFactory, - isValidElement: ReactElement.isValidElement, // Deprecations (remove for 0.13) renderComponent: function( From d8e8ea5cbbfae10f1eb0ccfaeb7e8b0668020083 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 18:58:47 -0700 Subject: [PATCH 08/20] Add test and mocks Mock UIManager Comment out dontMock that actually needs mocking --- .../InitializeJavaScriptAppEngine.js | 14 +++++ .../__mocks__/InteractionManager.js | 16 +++++ .../__mocks__/JSTimersExecution.js | 14 +++++ .../native/ReactNative/__mocks__/Platform.js | 16 +++++ .../ReactNative/__mocks__/RCTEventEmitter.js | 14 +++++ .../native/ReactNative/__mocks__/RCTLog.js | 14 +++++ .../ReactNative/__mocks__/TextInputState.js | 17 +++++ .../native/ReactNative/__mocks__/UIManager.js | 21 +++++++ .../ReactNative/__mocks__/deepDiffer.js | 63 +++++++++++++++++++ .../deepFreezeAndThrowOnMutationInDev.js | 16 +++++ .../ReactNative/__mocks__/flattenStyle.js | 16 +++++ .../native/ReactNative/__mocks__/merge.js | 18 ++++++ .../ReactNativeAttributePayload-test.js | 4 +- .../__tests__/ReactNativeMount-test.js | 34 ++++++++++ 14 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js create mode 100644 src/renderers/native/ReactNative/__mocks__/InteractionManager.js create mode 100644 src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js create mode 100644 src/renderers/native/ReactNative/__mocks__/Platform.js create mode 100644 src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js create mode 100644 src/renderers/native/ReactNative/__mocks__/RCTLog.js create mode 100644 src/renderers/native/ReactNative/__mocks__/TextInputState.js create mode 100644 src/renderers/native/ReactNative/__mocks__/UIManager.js create mode 100644 src/renderers/native/ReactNative/__mocks__/deepDiffer.js create mode 100644 src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js create mode 100644 src/renderers/native/ReactNative/__mocks__/flattenStyle.js create mode 100644 src/renderers/native/ReactNative/__mocks__/merge.js create mode 100644 src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js diff --git a/src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js b/src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js new file mode 100644 index 0000000000000..3f4b4fdd0fac0 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js @@ -0,0 +1,14 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Noop + +// TODO: Move all initialization callers back into react-native diff --git a/src/renderers/native/ReactNative/__mocks__/InteractionManager.js b/src/renderers/native/ReactNative/__mocks__/InteractionManager.js new file mode 100644 index 0000000000000..de72e164a6cf9 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/InteractionManager.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// TODO: Figure out a way to drop this dependency + +var InteractionManager = {}; + +module.exports = InteractionManager; diff --git a/src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js b/src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js new file mode 100644 index 0000000000000..3f4b4fdd0fac0 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js @@ -0,0 +1,14 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Noop + +// TODO: Move all initialization callers back into react-native diff --git a/src/renderers/native/ReactNative/__mocks__/Platform.js b/src/renderers/native/ReactNative/__mocks__/Platform.js new file mode 100644 index 0000000000000..fc04c24549a48 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/Platform.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Mock of the Native Hooks + +var Platform = {}; + +module.exports = Platform; diff --git a/src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js b/src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js new file mode 100644 index 0000000000000..3f4b4fdd0fac0 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js @@ -0,0 +1,14 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Noop + +// TODO: Move all initialization callers back into react-native diff --git a/src/renderers/native/ReactNative/__mocks__/RCTLog.js b/src/renderers/native/ReactNative/__mocks__/RCTLog.js new file mode 100644 index 0000000000000..3f4b4fdd0fac0 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/RCTLog.js @@ -0,0 +1,14 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Noop + +// TODO: Move all initialization callers back into react-native diff --git a/src/renderers/native/ReactNative/__mocks__/TextInputState.js b/src/renderers/native/ReactNative/__mocks__/TextInputState.js new file mode 100644 index 0000000000000..a0b4e576d77b3 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/TextInputState.js @@ -0,0 +1,17 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Mock of the Native Hooks +// TODO: Should this move into the components themselves? E.g. focusable + +var TextInputState = {}; + +module.exports = TextInputState; diff --git a/src/renderers/native/ReactNative/__mocks__/UIManager.js b/src/renderers/native/ReactNative/__mocks__/UIManager.js new file mode 100644 index 0000000000000..0c51ab6e6b331 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/UIManager.js @@ -0,0 +1,21 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// Mock of the Native Hooks + +var RCTUIManager = { + createView: jest.genMockFunction(), + setChildren: jest.genMockFunction(), + manageChildren: jest.genMockFunction(), + updateView: jest.genMockFunction(), +}; + +module.exports = RCTUIManager; diff --git a/src/renderers/native/ReactNative/__mocks__/deepDiffer.js b/src/renderers/native/ReactNative/__mocks__/deepDiffer.js new file mode 100644 index 0000000000000..cc58e4b328e54 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/deepDiffer.js @@ -0,0 +1,63 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// TODO: Move deepDiffer into react + +var deepDiffer = function(one: any, two: any): bool { + if (one === two) { + // Short circuit on identical object references instead of traversing them. + return false; + } + if ((typeof one === 'function') && (typeof two === 'function')) { + // We consider all functions equal + return false; + } + if ((typeof one !== 'object') || (one === null)) { + // Primitives can be directly compared + return one !== two; + } + if ((typeof two !== 'object') || (two === null)) { + // We know they are different because the previous case would have triggered + // otherwise. + return true; + } + if (one.constructor !== two.constructor) { + return true; + } + if (Array.isArray(one)) { + // We know two is also an array because the constructors are equal + var len = one.length; + if (two.length !== len) { + return true; + } + for (var ii = 0; ii < len; ii++) { + if (deepDiffer(one[ii], two[ii])) { + return true; + } + } + } else { + for (var key in one) { + if (deepDiffer(one[key], two[key])) { + return true; + } + } + for (var twoKey in two) { + // The only case we haven't checked yet is keys that are in two but aren't + // in one, which means they are different. + if (one[twoKey] === undefined && two[twoKey] !== undefined) { + return true; + } + } + } + return false; +}; + +module.exports = deepDiffer; diff --git a/src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js b/src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js new file mode 100644 index 0000000000000..ebb81c3335a07 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// TODO: move into react or fbjs + +var deepFreezeAndThrowOnMutationInDev = function() { }; + +module.exports = deepFreezeAndThrowOnMutationInDev; diff --git a/src/renderers/native/ReactNative/__mocks__/flattenStyle.js b/src/renderers/native/ReactNative/__mocks__/flattenStyle.js new file mode 100644 index 0000000000000..a766c705fb054 --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/flattenStyle.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// TODO: Move flattenStyle into react + +var flattenStyle = function() { }; + +module.exports = flattenStyle; diff --git a/src/renderers/native/ReactNative/__mocks__/merge.js b/src/renderers/native/ReactNative/__mocks__/merge.js new file mode 100644 index 0000000000000..0c2dc8bf5de7a --- /dev/null +++ b/src/renderers/native/ReactNative/__mocks__/merge.js @@ -0,0 +1,18 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +// TODO: Replace all callers with spread + +var merge = function(a, b) { + return {...a, ...b}; +}; + +module.exports = merge; diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js index 6cbaa46a30081..d325ca0919739 100644 --- a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js +++ b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -11,8 +11,8 @@ jest.dontMock('ReactNativeAttributePayload'); jest.dontMock('ReactNativePropRegistry'); -jest.dontMock('deepDiffer'); -jest.dontMock('flattenStyle'); +// jest.dontMock('deepDiffer'); +// jest.dontMock('flattenStyle'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); var ReactNativePropRegistry = require('ReactNativePropRegistry'); diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js b/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js new file mode 100644 index 0000000000000..b35ff4068e8c1 --- /dev/null +++ b/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js @@ -0,0 +1,34 @@ +/** + * Copyright 2013-2015, 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. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactNative; +var createReactNativeComponentClass; + +describe('ReactNative', function() { + beforeEach(function() { + React = require('React'); + ReactNative = require('ReactNative'); + createReactNativeComponentClass = require('createReactNativeComponentClass'); + }); + + it('should be able to create and render a native component', function() { + var View = createReactNativeComponentClass({ + validAttributes: { foo: true }, + uiViewClassName: 'View', + }); + + ReactNative.render(, 1); + }); + +}); From f8335168b6257a8e908c2e9d4529134da5748fee Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 19:00:51 -0700 Subject: [PATCH 09/20] Fix 0.15 compatibility --- .../ReactNative/ReactNativeBaseComponent.js | 13 ++++++++---- .../ReactNativeDefaultInjection.js | 20 +++++++++---------- .../ReactNative/ReactNativeEventEmitter.js | 3 ++- .../createReactNativeComponentClass.js | 1 + 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index 19afca80362e9..11c1cb754c0e8 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -56,10 +56,6 @@ ReactNativeBaseComponent.Mixin = { return this; }, - construct: function(element) { - this._currentElement = element; - }, - unmountComponent: function() { deleteAllListeners(this._rootNodeID); this.unmountChildren(); @@ -174,6 +170,15 @@ ReactNativeBaseComponent.Mixin = { } }, + /** + * Currently this still uses IDs for reconciliation so this can return null. + * + * @return {null} Null. + */ + getNativeNode: function() { + return null; + }, + /** * @param {string} rootID Root ID of this subtree. * @param {Transaction} transaction For creating/updating. diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js index 0d55d670c40e1..85170abb16201 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -27,13 +27,12 @@ var ReactElement = require('ReactElement'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactEmptyComponent = require('ReactEmptyComponent'); -var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler'); var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); -var ReactNativeMount = require('ReactNativeMount'); var ReactNativeTextComponent = require('ReactNativeTextComponent'); var ReactNativeComponent = require('ReactNativeComponent'); +var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); @@ -50,7 +49,6 @@ function inject() { * Inject module for resolving DOM hierarchy and plugin ordering. */ EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder); - EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles); ResponderEventPlugin.injection.injectGlobalResponderHandler( ReactNativeGlobalResponderHandler @@ -81,17 +79,19 @@ function inject() { ReactNativeComponentEnvironment ); - var EmptyComponent = () => { + var EmptyComponent = (instantiate) => { // Can't import View at the top because it depends on React to make its composite var View = require('View'); - return ReactElement.createElement(View, { - collapsable: true, - style: { position: 'absolute' } - }); + return new ReactSimpleEmptyComponent( + ReactElement.createElement(View, { + collapsable: true, + style: { position: 'absolute' } + }), + instantiate + ); }; - ReactEmptyComponent.injection.injectEmptyComponent(EmptyComponent); - EventPluginUtils.injection.injectMount(ReactNativeMount); + ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent); ReactNativeComponent.injection.injectTextComponentClass( ReactNativeTextComponent diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index ac8854b6cb65d..5ff5e72bf4cb9 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -12,6 +12,7 @@ 'use strict'; var EventPluginHub = require('EventPluginHub'); +var EventPluginRegistry = require('EventPluginRegistry'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var NodeHandle = require('NodeHandle'); @@ -91,7 +92,7 @@ var removeTouchesAtIndices = function( */ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { - registrationNames: EventPluginHub.registrationNameModules, + registrationNames: EventPluginRegistry.registrationNameModules, putListener: EventPluginHub.putListener, diff --git a/src/renderers/native/ReactNative/createReactNativeComponentClass.js b/src/renderers/native/ReactNative/createReactNativeComponentClass.js index 199e268891a0d..9a8250fbead18 100644 --- a/src/renderers/native/ReactNative/createReactNativeComponentClass.js +++ b/src/renderers/native/ReactNative/createReactNativeComponentClass.js @@ -30,6 +30,7 @@ var createReactNativeComponentClass = function( ): ReactClass { var Constructor = function(element) { this._currentElement = element; + this._topLevelWrapper = null; this._rootNodeID = null; this._renderedChildren = null; From 97b079b364e3c0400ce90abe06e97f857a4fd7c8 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 24 Mar 2016 22:59:32 -0700 Subject: [PATCH 10/20] Fix ReactNativeComponentEnvironment providesModule This has a different file name from its providesModule. Screws up our build scripts. --- ...ComponentEnvironment.js => ReactNativeComponentEnvironment.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/renderers/native/ReactNative/{ReactNativeBaseComponentEnvironment.js => ReactNativeComponentEnvironment.js} (100%) diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js b/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js rename to src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js From 75cec60e48c305fe89c4b528e56c780aec3524c7 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 29 Mar 2016 14:57:18 -0700 Subject: [PATCH 11/20] Get rid of rootNodeIDs they're just tags now --- src/renderers/native/NodeHandle/NodeHandle.js | 83 -------------- .../NodeHandle/UniversalWorkerNodeHandle.js | 19 ---- .../ReactNative/ReactNativeBaseComponent.js | 41 +++---- .../ReactNativeComponentEnvironment.js | 2 +- .../ReactNative/ReactNativeContainerInfo.js | 21 ++++ .../ReactNative/ReactNativeDOMIDOperations.js | 11 +- .../ReactNativeDefaultInjection.js | 4 - .../ReactNative/ReactNativeEventEmitter.js | 5 +- .../ReactNativeGlobalResponderHandler.js | 5 +- .../native/ReactNative/ReactNativeMount.js | 102 ++++++------------ .../ReactNative/ReactNativeTagHandles.js | 58 +--------- .../ReactNative/ReactNativeTextComponent.js | 39 ++++--- .../__tests__/ReactNativeMount-test.js | 27 +++++ .../createReactNativeComponentClass.js | 3 +- .../native/ReactNative/findNodeHandle.js | 4 +- 15 files changed, 131 insertions(+), 293 deletions(-) delete mode 100644 src/renderers/native/NodeHandle/NodeHandle.js delete mode 100644 src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js create mode 100644 src/renderers/native/ReactNative/ReactNativeContainerInfo.js diff --git a/src/renderers/native/NodeHandle/NodeHandle.js b/src/renderers/native/NodeHandle/NodeHandle.js deleted file mode 100644 index c7c93545bf031..0000000000000 --- a/src/renderers/native/NodeHandle/NodeHandle.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @providesModule NodeHandle - */ - - /** - * A "handle" is a serializable representation of the underlying platform's - * native node abstraction. This allows reasoning about nodes behind a thread - * (worker) boundary. On some platforms (DOM main thread) the node handle *is* - * an actual DOM node - `NodeHandle` (and potentially other libraries) - * abstract away those differences so you can write code that doesn't depend - * on whether or not you are running in a worker. For example, you could write - * application code: - * - * - * SomeLibrary.measureNodeHandle(myNodeHandle, cb) - * - * Where `measureNodeHandle` knows how to handle actual DOM nodes if running - * in a worker thread, and knows how to handle numeric IDs if running in a - * worker thread. - * - * The only other requirement of a platform/environment is that it always be - * possible to extract the React rootNodeID in a blocking manner (see - * `getRootNodeID`). - * - * +------------------+ +------------------+ +------------------+ - * | | | | | | - * | ReactJS | | YourUtilities | | Animation Utils | - * | | | | | | - * +------------------+ +------------------+ +------------------+ - * - * +------------------------------------------------------------+ - * | Async Platform Independent Node Interface | - * +------------------------------------------------------------+ - * | | - * | NodeIterface: | - * | -measure(nodeHandle, cb) | - * | -setProperties(nodeHandle, cb) | - * | -manageChildren(nodeHandle, nodeHandles, cb) | - * | ... | - * | | - * | Note: This may be a simplification. We could break up much | - * | of this functionality into several smaller libraries, each | - * | one requiring a . | - * +------------------------------------------------------------+ - * - * +------------------------------------------------------------+ - * | Platform Implementations | - * | ----------------------------------------- | - * | React Canvas | React DOM Worker | React DOM main | - * +------------------------------------------------------------+ - * | | | | - * |-measure(..) |-measure(..) |-measure(..) | - * |-setProperties(..) |-setProperties(..) |-setProperties(..) | - * |-manageChildren(..)|-manageChildren(..) |-manageChildren(..)| - * | ... | ... | ... | - * +-----------------------------o------------------------------+ - * | Worker simply ^ - * | marshals commands | - * | to Web DOM thread. | - * +---------------------+ - */ -var NodeHandle = { - /** - * Injection - */ - injection: { - injectImplementation: function(Impl) { - NodeHandle._Implementation = Impl; - } - }, - - _Implementation: null, - - /** - * @param {NodeHandle} nodeHandle The handle to the low level resource. - * @return {string} React root node ID. - */ - getRootNodeID: function(nodeHandle) { - return NodeHandle._Implementation.getRootNodeID(nodeHandle); - } -}; - -module.exports = NodeHandle; diff --git a/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js b/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js deleted file mode 100644 index b91feba9b1e2a..0000000000000 --- a/src/renderers/native/NodeHandle/UniversalWorkerNodeHandle.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @providesModule UniversalWorkerNodeHandle - */ - -var ReactNativeTagHandles = require('ReactNativeTagHandles'); - -var invariant = require('invariant'); - -var UniversalWorkerNodeHandle = { - getRootNodeID: function(nodeHandle) { - invariant( - nodeHandle !== undefined && nodeHandle !== null && nodeHandle !== 0, - 'No node handle defined' - ); - return ReactNativeTagHandles.tagToRootNodeID[nodeHandle]; - } -}; - -module.exports = UniversalWorkerNodeHandle; diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index 11c1cb754c0e8..f16aff4f7c596 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -82,17 +82,8 @@ ReactNativeBaseComponent.Mixin = { var createdTags = []; for (var i = 0, l = mountImages.length; i < l; i++) { var mountImage = mountImages[i]; - var childTag = mountImage.tag; - var childID = mountImage.rootNodeID; - warning( - mountImage && mountImage.rootNodeID && mountImage.tag, - 'Mount image returned does not have required data' - ); - ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( - childID, - childTag - ); - createdTags[i] = mountImage.tag; + var childTag = mountImage; + createdTags[i] = childTag; } UIManager.setChildren(containerTag, createdTags); } @@ -126,7 +117,7 @@ ReactNativeBaseComponent.Mixin = { if (updatePayload) { UIManager.updateView( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID), + this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload ); @@ -176,7 +167,7 @@ ReactNativeBaseComponent.Mixin = { * @return {null} Null. */ getNativeNode: function() { - return null; + return this._rootNodeID; }, /** @@ -184,11 +175,13 @@ ReactNativeBaseComponent.Mixin = { * @param {Transaction} transaction For creating/updating. * @return {string} Unique iOS view tag. */ - mountComponent: function(rootID, transaction, context) { - this._rootNodeID = rootID; - + mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) { var tag = ReactNativeTagHandles.allocateTag(); + this._rootNodeID = tag; + this._nativeParent = nativeParent; + this._nativeContainerInfo = nativeContainerInfo; + if (__DEV__) { for (var key in this.viewConfig.validAttributes) { if (this._currentElement.props.hasOwnProperty(key)) { @@ -202,17 +195,12 @@ ReactNativeBaseComponent.Mixin = { this.viewConfig.validAttributes ); - var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); - if (nativeTopRootID == null) { - invariant( - false, - 'nativeTopRootID not found for tag ' + tag + ' view type ' + - this.viewConfig.uiViewClassName + ' with rootID ' + rootID); - } + var nativeTopRootTag = nativeContainerInfo._tag; + console.log('mountInCmp', nativeContainerInfo, nativeTopRootTag); UIManager.createView( tag, this.viewConfig.uiViewClassName, - ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID], + nativeTopRootTag, updatePayload ); @@ -223,10 +211,7 @@ ReactNativeBaseComponent.Mixin = { transaction, context ); - return { - rootNodeID: rootID, - tag: tag - }; + return tag; } }; diff --git a/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js b/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js index 6e038620d66b8..c8cff21b2e32a 100644 --- a/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js +++ b/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js @@ -18,7 +18,7 @@ var ReactNativeComponentEnvironment = { processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates, - replaceNodeWithMarkupByID: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + replaceNodeWithMarkup: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, /** * Nothing to do for UIKit bridge. diff --git a/src/renderers/native/ReactNative/ReactNativeContainerInfo.js b/src/renderers/native/ReactNative/ReactNativeContainerInfo.js new file mode 100644 index 0000000000000..d3de99316c804 --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeContainerInfo.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeContainerInfo + * @flow + */ +'use strict'; + +function ReactNativeContainerInfo(tag) { + var info = { + _tag: tag + }; + return info; +} + +module.exports = ReactNativeContainerInfo; diff --git a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js index fbf90db503374..0c6c842dae4c4 100644 --- a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js +++ b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js @@ -37,7 +37,8 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { // containerID. for (var i = 0; i < childrenUpdates.length; i++) { var update = childrenUpdates[i]; - var containerTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(update.parentID); + var containerTag = update.parentID; + throw new Error('parentID is borked. See changes to multiChild'); var updates = byContainerTag[containerTag] || (byContainerTag[containerTag] = {}); if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) { (updates.moveFromIndices || (updates.moveFromIndices = [])).push(update.fromIndex); @@ -46,9 +47,8 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { (updates.removeAtIndices || (updates.removeAtIndices = [])).push(update.fromIndex); } else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) { var mountImage = markupList[update.markupIndex]; - var tag = mountImage.tag; + var tag = mountImage; var rootNodeID = mountImage.rootNodeID; - ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(rootNodeID, tag); (updates.addAtIndices || (updates.addAtIndices = [])).push(update.toIndex); (updates.addChildTags || (updates.addChildTags = [])).push(tag); } @@ -92,9 +92,8 @@ var ReactNativeDOMIDOperations = { 'ReactDOMIDOperations', 'dangerouslyReplaceNodeWithMarkupByID', function(id, mountImage) { - var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id); - UIManager.replaceExistingNonRootView(oldTag, mountImage.tag); - ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag); + var oldTag = id; + UIManager.replaceExistingNonRootView(oldTag, mountImage); } ), }; diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js index 85170abb16201..18ddc86f974c5 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -22,7 +22,6 @@ var EventPluginHub = require('EventPluginHub'); var EventPluginUtils = require('EventPluginUtils'); var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder'); var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin'); -var NodeHandle = require('NodeHandle'); var ReactElement = require('ReactElement'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); @@ -35,7 +34,6 @@ var ReactNativeComponent = require('ReactNativeComponent'); var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); -var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle'); var invariant = require('invariant'); @@ -104,8 +102,6 @@ function inject() { } invariant(false, 'Expected a component class, got %s.%s', tag, info); }); - - NodeHandle.injection.injectImplementation(UniversalWorkerNodeHandle); } module.exports = { diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index 5ff5e72bf4cb9..eeea701f6e0e6 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -15,7 +15,6 @@ var EventPluginHub = require('EventPluginHub'); var EventPluginRegistry = require('EventPluginRegistry'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); -var NodeHandle = require('NodeHandle'); var EventConstants = require('EventConstants'); var merge = require('merge'); @@ -139,7 +138,7 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { topLevelType: string, nativeEventParam: Object ) { - var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag]; + var rootNodeID = tag; ReactNativeEventEmitter._receiveRootNodeIDEvent( rootNodeID, topLevelType, @@ -200,7 +199,7 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { ); } } else { - rootNodeID = NodeHandle.getRootNodeID(target); + rootNodeID = target; } } ReactNativeEventEmitter._receiveRootNodeIDEvent( diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js index 1094e60451b89..1fb45e783fa1e 100644 --- a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js +++ b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactNativeGlobalResponderHandler - * @flow */ 'use strict'; @@ -15,10 +14,10 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); var UIManager = require('UIManager'); var ReactNativeGlobalResponderHandler = { - onChange: function(from: string, to: string, blockNativeResponder: boolean) { + onChange: function(from, to, blockNativeResponder) { if (to !== null) { UIManager.setJSResponder( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to), + to._rootNodeID, blockNativeResponder ); } else { diff --git a/src/renderers/native/ReactNative/ReactNativeMount.js b/src/renderers/native/ReactNative/ReactNativeMount.js index 2a38c3dbba8c0..f0e9988e35da0 100644 --- a/src/renderers/native/ReactNative/ReactNativeMount.js +++ b/src/renderers/native/ReactNative/ReactNativeMount.js @@ -12,6 +12,7 @@ 'use strict'; var ReactElement = require('ReactElement'); +var ReactNativeContainerInfo = require('ReactNativeContainerInfo'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); @@ -23,10 +24,6 @@ var emptyObject = require('emptyObject'); var instantiateReactComponent = require('instantiateReactComponent'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); -function instanceNumberToChildRootID(rootNodeID, instanceNumber) { - return rootNodeID + '[' + instanceNumber + ']'; -} - /** * Temporary (?) hack so that we can store all top-level pending updates on * composites instead of having to worry about different types of components @@ -47,19 +44,22 @@ TopLevelWrapper.prototype.render = function() { * * @param {ReactComponent} componentInstance The instance to mount. * @param {number} rootID ID of the root node. - * @param {number} container container element to mount into. + * @param {number} containerTag container element to mount into. * @param {ReactReconcileTransaction} transaction */ function mountComponentIntoNode( componentInstance, - rootID, - container, + containerTag, transaction) { var markup = ReactReconciler.mountComponent( - componentInstance, rootID, transaction, emptyObject + componentInstance, + transaction, + null, + ReactNativeContainerInfo(containerTag), + emptyObject ); componentInstance._renderedComponent._topLevelWrapper = componentInstance; - ReactNativeMount._mountImageIntoNode(markup, container); + ReactNativeMount._mountImageIntoNode(markup, containerTag); } /** @@ -67,19 +67,17 @@ function mountComponentIntoNode( * * @param {ReactComponent} componentInstance The instance to mount. * @param {number} rootID ID of the root node. - * @param {number} container container element to mount into. + * @param {number} containerTag container element to mount into. */ function batchedMountComponentIntoNode( componentInstance, - rootID, - container) { + containerTag) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform( mountComponentIntoNode, null, componentInstance, - rootID, - container, + containerTag, transaction ); ReactUpdates.ReactReconcileTransaction.release(transaction); @@ -90,15 +88,10 @@ function batchedMountComponentIntoNode( * code between the two. For now, we'll hard code the ID logic. */ var ReactNativeMount = { - instanceCount: 0, - _instancesByContainerID: {}, // these two functions are needed by React Devtools findNodeHandle: require('findNodeHandle'), - nativeTagToRootNodeID: function (nativeTag: number): string { - return ReactNativeTagHandles.tagToRootNodeID[nativeTag]; - }, /** * @param {ReactComponent} instance Instance to render. @@ -119,21 +112,19 @@ var ReactNativeMount = { nextElement ); - var topRootNodeID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; - if (topRootNodeID) { - var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID]; - if (prevComponent) { - var prevWrappedElement = prevComponent._currentElement; - var prevElement = prevWrappedElement.props; - if (shouldUpdateReactComponent(prevElement, nextElement)) { - ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement); - if (callback) { - ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); - } - return prevComponent; - } else { - ReactNativeMount.unmountComponentAtNode(containerTag); + var topRootNodeID = containerTag; + var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID]; + if (prevComponent) { + var prevWrappedElement = prevComponent._currentElement; + var prevElement = prevWrappedElement.props; + if (shouldUpdateReactComponent(prevElement, nextElement)) { + ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement); + if (callback) { + ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); } + return prevComponent; + } else { + ReactNativeMount.unmountComponentAtNode(containerTag); } } @@ -142,19 +133,10 @@ var ReactNativeMount = { return; } - var topRootNodeID = ReactNativeTagHandles.allocateRootNodeIDForTag(containerTag); - ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( - topRootNodeID, - containerTag - ); + ReactNativeTagHandles.assertRootTag(containerTag); var instance = instantiateReactComponent(nextWrappedElement); - ReactNativeMount._instancesByContainerID[topRootNodeID] = instance; - - var childRootNodeID = instanceNumberToChildRootID( - topRootNodeID, - ReactNativeMount.instanceCount++ - ); + ReactNativeMount._instancesByContainerID[containerTag] = instance; // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched @@ -163,8 +145,7 @@ var ReactNativeMount = { ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, instance, - childRootNodeID, - topRootNodeID + containerTag ); var component = instance.getPublicInstance(); if (callback) { @@ -184,13 +165,10 @@ var ReactNativeMount = { function(mountImage, containerID) { // Since we now know that the `mountImage` has been mounted, we can // mark it as such. - ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle( - mountImage.rootNodeID, - mountImage.tag - ); + var childTag = mountImage; UIManager.setChildren( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID), - [mountImage.tag] + containerID, + [childTag] ); } ), @@ -222,13 +200,12 @@ var ReactNativeMount = { return false; } - var containerID = ReactNativeTagHandles.tagToRootNodeID[containerTag]; - var instance = ReactNativeMount._instancesByContainerID[containerID]; + var instance = ReactNativeMount._instancesByContainerID[containerTag]; if (!instance) { return false; } - ReactNativeMount.unmountComponentFromNode(instance, containerID); - delete ReactNativeMount._instancesByContainerID[containerID]; + ReactNativeMount.unmountComponentFromNode(instance, containerTag); + delete ReactNativeMount._instancesByContainerID[containerTag]; return true; }, @@ -247,18 +224,9 @@ var ReactNativeMount = { ) { // Call back into native to remove all of the subviews from this container ReactReconciler.unmountComponent(instance); - var containerTag = - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID); - UIManager.removeSubviewsFromContainerWithID(containerTag); - }, - - getNode: function(rootNodeID: string): number { - return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; - }, - - getID: function(nativeTag: number): string { - return ReactNativeTagHandles.tagToRootNodeID[nativeTag]; + UIManager.removeSubviewsFromContainerWithID(containerID); } + }; ReactNativeMount.renderComponent = ReactPerf.measure( diff --git a/src/renderers/native/ReactNative/ReactNativeTagHandles.js b/src/renderers/native/ReactNative/ReactNativeTagHandles.js index ab350817c6e90..f0079fdabc0b7 100644 --- a/src/renderers/native/ReactNative/ReactNativeTagHandles.js +++ b/src/renderers/native/ReactNative/ReactNativeTagHandles.js @@ -43,71 +43,17 @@ var ReactNativeTagHandles = { return tag; }, - /** - * This associates the *last* observed *native* mounting between `rootNodeID` - * and some `tag`. This association doesn't imply that `rootNodeID` is still - * natively mounted as `tag`. The only reason why we don't clear the - * association when the `rootNodeID` is unmounted, is that we don't have a - * convenient time to disassociate them (otherwise we would). - * `unmountComponent` isn't the correct time because that doesn't imply that - * the native node has been natively unmounted. - */ - associateRootNodeIDWithMountedNodeHandle: function( - rootNodeID: ?string, - tag: ?number - ) { - warning(rootNodeID && tag, 'Root node or tag is null when associating'); - if (rootNodeID && tag) { - ReactNativeTagHandles.tagToRootNodeID[tag] = rootNodeID; - ReactNativeTagHandles.rootNodeIDToTag[rootNodeID] = tag; - } - }, - - allocateRootNodeIDForTag: function(tag: number): string { + assertRootTag: function(tag: number): void { invariant( this.reactTagIsNativeTopRootID(tag), 'Expect a native root tag, instead got ', tag ); - return '.r[' + tag + ']' + NATIVE_TOP_ROOT_ID_SEPARATOR; }, reactTagIsNativeTopRootID: function(reactTag: number): bool { // We reserve all tags that are 1 mod 10 for native root views return reactTag % 10 === 1; - }, - - getNativeTopRootIDFromNodeID: function(nodeID: ?string): ?string { - if (!nodeID) { - return null; - } - var index = nodeID.indexOf(NATIVE_TOP_ROOT_ID_SEPARATOR); - if (index === -1) { - return null; - } - return nodeID.substr(0, index + NATIVE_TOP_ROOT_ID_SEPARATOR.length); - }, - - /** - * Returns the native `nodeHandle` (`tag`) that was most recently *natively* - * mounted at the `rootNodeID`. Just because a React component has been - * mounted, that doesn't mean that its native node has been mounted. The - * native node is mounted when we actually make the call to insert the - * `nodeHandle` (`tag`) into the native hierarchy. - * - * @param {string} rootNodeID Root node ID to find most recently mounted tag - * for. Again, this doesn't imply that it is still currently mounted. - * @return {number} Tag ID of native view for most recent mounting of - * `rootNodeID`. - */ - mostRecentMountedNodeHandleForRootNodeID: function( - rootNodeID: string - ): number { - return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; - }, - - tagToRootNodeID: ([] : Array), - - rootNodeIDToTag: ({} : {[key: string]: number}) + } }; module.exports = ReactNativeTagHandles; diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNative/ReactNativeTextComponent.js index 07463ff905b39..534828e161901 100644 --- a/src/renderers/native/ReactNative/ReactNativeTextComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeTextComponent.js @@ -16,38 +16,38 @@ var UIManager = require('UIManager'); var invariant = require('invariant'); -var ReactNativeTextComponent = function(props) { - // This constructor and its argument is currently used by mocks. +var ReactNativeTextComponent = function(text) { + // This is really a ReactText (ReactNode), not a ReactElement + this._currentElement = text; + this._stringText = '' + text; + this._nativeParent = null; + this._rootNodeID = null; }; Object.assign(ReactNativeTextComponent.prototype, { - construct: function(text) { - // This is really a ReactText (ReactNode), not a ReactElement - this._currentElement = text; - this._stringText = '' + text; - this._rootNodeID = null; - }, - - mountComponent: function(rootID, transaction, context) { + mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) { + // TODO: nativeParent should have this context already. Stop abusing context. invariant( context.isInAParentText, 'RawText "' + this._stringText + '" must be wrapped in an explicit ' + ' component.' ); - this._rootNodeID = rootID; + this._nativeParent = nativeParent; var tag = ReactNativeTagHandles.allocateTag(); - var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + this._rootNodeID = tag; + var nativeTopRootTag = nativeContainerInfo._tag; UIManager.createView( tag, 'RCTRawText', - nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, + nativeTopRootTag, {text: this._stringText} ); - return { - rootNodeID: rootID, - tag: tag, - }; + return tag; + }, + + getNativeNode: function() { + return this._rootNodeID; }, receiveComponent: function(nextText, transaction, context) { @@ -56,10 +56,9 @@ Object.assign(ReactNativeTextComponent.prototype, { var nextStringText = '' + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; + console.log('receiveComponent', this, this._rootNodeID); UIManager.updateView( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID( - this._rootNodeID - ), + this._rootNodeID, 'RCTRawText', {text: this._stringText} ); diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js b/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js index b35ff4068e8c1..74f4f7f57fa0b 100644 --- a/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js +++ b/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js @@ -14,11 +14,13 @@ var React; var ReactNative; var createReactNativeComponentClass; +var UIManager; describe('ReactNative', function() { beforeEach(function() { React = require('React'); ReactNative = require('ReactNative'); + UIManager = require('UIManager'); createReactNativeComponentClass = require('createReactNativeComponentClass'); }); @@ -29,6 +31,31 @@ describe('ReactNative', function() { }); ReactNative.render(, 1); + expect(UIManager.createView).toBeCalled(); + expect(UIManager.setChildren).toBeCalled(); + expect(UIManager.manageChildren).not.toBeCalled(); + expect(UIManager.updateView).not.toBeCalled(); + }); + + it('should be able to create and update a native component', function() { + var View = createReactNativeComponentClass({ + validAttributes: { foo: true }, + uiViewClassName: 'View', + }); + + ReactNative.render(, 11); + + expect(UIManager.createView.mock.calls.length).toBe(2); + expect(UIManager.setChildren.mock.calls.length).toBe(2); + expect(UIManager.manageChildren).not.toBeCalled(); + expect(UIManager.updateView).not.toBeCalled(); + + ReactNative.render(, 11); + + expect(UIManager.createView.mock.calls.length).toBe(2); + expect(UIManager.setChildren.mock.calls.length).toBe(2); + expect(UIManager.manageChildren).not.toBeCalled(); + expect(UIManager.updateView).toBeCalledWith(3, 'View', { foo: 'bar' }); }); }); diff --git a/src/renderers/native/ReactNative/createReactNativeComponentClass.js b/src/renderers/native/ReactNative/createReactNativeComponentClass.js index 9a8250fbead18..82d071b0876d9 100644 --- a/src/renderers/native/ReactNative/createReactNativeComponentClass.js +++ b/src/renderers/native/ReactNative/createReactNativeComponentClass.js @@ -31,7 +31,8 @@ var createReactNativeComponentClass = function( var Constructor = function(element) { this._currentElement = element; this._topLevelWrapper = null; - + this._nativeParent = null; + this._nativeContainerInfo = null; this._rootNodeID = null; this._renderedChildren = null; }; diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/ReactNative/findNodeHandle.js index 37c772760c503..1c7636282e9d3 100644 --- a/src/renderers/native/ReactNative/findNodeHandle.js +++ b/src/renderers/native/ReactNative/findNodeHandle.js @@ -79,11 +79,11 @@ function findNodeHandle(componentOrHandle: any): ?number { // ReactInstanceMap.get here will always succeed for mounted components var internalInstance = ReactInstanceMap.get(component); if (internalInstance) { - return ReactNativeTagHandles.rootNodeIDToTag[internalInstance._rootNodeID]; + return internalInstance._rootNodeID; } else { var rootNodeID = component._rootNodeID; if (rootNodeID) { - return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID]; + return rootNodeID; } else { invariant( ( From 3287d932351638ed5d9c658bc071189efba227d0 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 30 Mar 2016 14:35:46 -0700 Subject: [PATCH 12/20] Build up a native component cache for event dispatching Changes to event overloading structure --- .../ReactIOS/IOSNativeBridgeEventPlugin.js | 18 +-- .../ReactNative/ReactNativeBaseComponent.js | 5 +- .../ReactNative/ReactNativeComponentTree.js | 66 +++++++++ .../ReactNativeDefaultInjection.js | 4 + .../ReactNative/ReactNativeEventEmitter.js | 7 +- .../ReactNative/ReactNativeTextComponent.js | 6 +- .../ReactNative/ReactNativeTreeTraversal.js | 126 ++++++++++++++++++ .../native/ReactNative/findNodeHandle.js | 2 +- 8 files changed, 217 insertions(+), 17 deletions(-) create mode 100644 src/renderers/native/ReactNative/ReactNativeComponentTree.js create mode 100644 src/renderers/native/ReactNative/ReactNativeTreeTraversal.js diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js index 6c1d887e555de..844e47536ba2a 100644 --- a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -41,25 +41,21 @@ var IOSNativeBridgeEventPlugin = { eventTypes: merge(customBubblingEventTypes, customDirectEventTypes), /** - * @param {string} topLevelType Record from `EventConstants`. - * @param {DOMEventTarget} topLevelTarget The listening component root node. - * @param {string} topLevelTargetID ID of `topLevelTarget`. - * @param {object} nativeEvent Native browser event. - * @return {*} An accumulation of synthetic events. * @see {EventPluginHub.extractEvents} */ extractEvents: function( - topLevelType: string, - topLevelTarget: EventTarget, - topLevelTargetID: string, - nativeEvent: Event + topLevelType, + targetInst, + nativeEvent, + nativeEventTarget ): ?Object { var bubbleDispatchConfig = customBubblingEventTypes[topLevelType]; var directDispatchConfig = customDirectEventTypes[topLevelType]; var event = SyntheticEvent.getPooled( bubbleDispatchConfig || directDispatchConfig, - topLevelTargetID, - nativeEvent + targetInst, + nativeEvent, + nativeEventTarget ); if (bubbleDispatchConfig) { EventPropagators.accumulateTwoPhaseDispatches(event); diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index f16aff4f7c596..ed21216ce6e30 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -13,6 +13,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChild = require('ReactMultiChild'); @@ -57,6 +58,7 @@ ReactNativeBaseComponent.Mixin = { }, unmountComponent: function() { + ReactNativeComponentTree.uncacheNode(this); deleteAllListeners(this._rootNodeID); this.unmountChildren(); this._rootNodeID = null; @@ -196,7 +198,6 @@ ReactNativeBaseComponent.Mixin = { ); var nativeTopRootTag = nativeContainerInfo._tag; - console.log('mountInCmp', nativeContainerInfo, nativeTopRootTag); UIManager.createView( tag, this.viewConfig.uiViewClassName, @@ -204,6 +205,8 @@ ReactNativeBaseComponent.Mixin = { updatePayload ); + ReactNativeComponentTree.precacheNode(this, tag); + this._registerListenersUponCreation(this._currentElement.props); this.initializeChildren( this._currentElement.props.children, diff --git a/src/renderers/native/ReactNative/ReactNativeComponentTree.js b/src/renderers/native/ReactNative/ReactNativeComponentTree.js new file mode 100644 index 0000000000000..82963ad4b1b4d --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeComponentTree.js @@ -0,0 +1,66 @@ +/** + * Copyright 2013-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 ReactNativeComponentTree + */ + +'use strict'; + +var invariant = require('invariant'); + +var instanceCache = {}; + +/** + * Drill down (through composites and empty components) until we get a native or + * native text component. + * + * This is pretty polymorphic but unavoidable with the current structure we have + * for `_renderedChildren`. + */ +function getRenderedNativeOrTextFromComponent(component) { + var rendered; + while ((rendered = component._renderedComponent)) { + component = rendered; + } + return component; +} + +/** + * Populate `_nativeNode` on the rendered native/text component with the given + * DOM node. The passed `inst` can be a composite. + */ +function precacheNode(inst, tag) { + var nativeInst = getRenderedNativeOrTextFromComponent(inst); + instanceCache[tag] = nativeInst; +} + +function uncacheNode(inst) { + var tag = inst._rootNodeID; + if (tag) { + delete instanceCache[tag]; + } +} + +function getInstanceFromTag(tag) { + return instanceCache[tag] || null; +} + +function getTagFromInstance(inst) { + invariant(inst._rootNodeID, 'All native instances should have a tag.'); + return inst._rootNodeID; +} + +var ReactNativeComponentTree = { + getClosestInstanceFromNode: getInstanceFromTag, + getInstanceFromNode: getInstanceFromTag, + getNodeFromInstance: getTagFromInstance, + precacheNode: precacheNode, + uncacheNode: uncacheNode, +}; + +module.exports = ReactNativeComponentTree; diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js index 18ddc86f974c5..c95e300be2825 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -30,7 +30,9 @@ var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment') var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler'); var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); var ReactNativeTextComponent = require('ReactNativeTextComponent'); +var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal'); var ReactNativeComponent = require('ReactNativeComponent'); +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); @@ -47,6 +49,8 @@ function inject() { * Inject module for resolving DOM hierarchy and plugin ordering. */ EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder); + EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree); + EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal); ResponderEventPlugin.injection.injectGlobalResponderHandler( ReactNativeGlobalResponderHandler diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index eeea701f6e0e6..67d18d11cb48e 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -14,6 +14,7 @@ var EventPluginHub = require('EventPluginHub'); var EventPluginRegistry = require('EventPluginRegistry'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var EventConstants = require('EventConstants'); @@ -112,15 +113,15 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { * @param {object} nativeEventParam Object passed from native. */ _receiveRootNodeIDEvent: function( - rootNodeID: ?string, + rootNodeID: number, topLevelType: string, nativeEventParam: Object ) { var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT; + var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID); ReactNativeEventEmitter.handleTopLevel( topLevelType, - rootNodeID, - rootNodeID, + inst, nativeEvent, nativeEvent.target ); diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNative/ReactNativeTextComponent.js index 534828e161901..434e4b8885f5f 100644 --- a/src/renderers/native/ReactNative/ReactNativeTextComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeTextComponent.js @@ -11,6 +11,7 @@ 'use strict'; +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var UIManager = require('UIManager'); @@ -43,6 +44,9 @@ Object.assign(ReactNativeTextComponent.prototype, { nativeTopRootTag, {text: this._stringText} ); + + ReactNativeComponentTree.precacheNode(this, tag); + return tag; }, @@ -56,7 +60,6 @@ Object.assign(ReactNativeTextComponent.prototype, { var nextStringText = '' + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; - console.log('receiveComponent', this, this._rootNodeID); UIManager.updateView( this._rootNodeID, 'RCTRawText', @@ -67,6 +70,7 @@ Object.assign(ReactNativeTextComponent.prototype, { }, unmountComponent: function() { + ReactNativeComponentTree.uncacheNode(this); this._currentElement = null; this._stringText = null; this._rootNodeID = null; diff --git a/src/renderers/native/ReactNative/ReactNativeTreeTraversal.js b/src/renderers/native/ReactNative/ReactNativeTreeTraversal.js new file mode 100644 index 0000000000000..58a3a9e479a3f --- /dev/null +++ b/src/renderers/native/ReactNative/ReactNativeTreeTraversal.js @@ -0,0 +1,126 @@ +/** + * Copyright 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 ReactNativeTreeTraversal + */ + +'use strict'; + +// Same as ReactDOMTreeTraversal without the invariants. + +/** + * Return the lowest common ancestor of A and B, or null if they are in + * different trees. + */ +function getLowestCommonAncestor(instA, instB) { + var depthA = 0; + for (var tempA = instA; tempA; tempA = tempA._nativeParent) { + depthA++; + } + var depthB = 0; + for (var tempB = instB; tempB; tempB = tempB._nativeParent) { + depthB++; + } + + // If A is deeper, crawl up. + while (depthA - depthB > 0) { + instA = instA._nativeParent; + depthA--; + } + + // If B is deeper, crawl up. + while (depthB - depthA > 0) { + instB = instB._nativeParent; + depthB--; + } + + // Walk in lockstep until we find a match. + var depth = depthA; + while (depth--) { + if (instA === instB) { + return instA; + } + instA = instA._nativeParent; + instB = instB._nativeParent; + } + return null; +} + +/** + * Return if A is an ancestor of B. + */ +function isAncestor(instA, instB) { + while (instB) { + if (instB === instA) { + return true; + } + instB = instB._nativeParent; + } + return false; +} + +/** + * Return the parent instance of the passed-in instance. + */ +function getParentInstance(inst) { + return inst._nativeParent; +} + +/** + * Simulates the traversal of a two-phase, capture/bubble event dispatch. + */ +function traverseTwoPhase(inst, fn, arg) { + var path = []; + while (inst) { + path.push(inst); + inst = inst._nativeParent; + } + var i; + for (i = path.length; i-- > 0;) { + fn(path[i], false, arg); + } + for (i = 0; i < path.length; i++) { + fn(path[i], true, arg); + } +} + +/** + * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that + * should would receive a `mouseEnter` or `mouseLeave` event. + * + * Does not invoke the callback on the nearest common ancestor because nothing + * "entered" or "left" that element. + */ +function traverseEnterLeave(from, to, fn, argFrom, argTo) { + var common = from && to ? getLowestCommonAncestor(from, to) : null; + var pathFrom = []; + while (from && from !== common) { + pathFrom.push(from); + from = from._nativeParent; + } + var pathTo = []; + while (to && to !== common) { + pathTo.push(to); + to = to._nativeParent; + } + var i; + for (i = 0; i < pathFrom.length; i++) { + fn(pathFrom[i], true, argFrom); + } + for (i = pathTo.length; i-- > 0;) { + fn(pathTo[i], false, argTo); + } +} + +module.exports = { + isAncestor: isAncestor, + getLowestCommonAncestor: getLowestCommonAncestor, + getParentInstance: getParentInstance, + traverseTwoPhase: traverseTwoPhase, + traverseEnterLeave: traverseEnterLeave, +}; diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/ReactNative/findNodeHandle.js index 1c7636282e9d3..15f709e7e9a8c 100644 --- a/src/renderers/native/ReactNative/findNodeHandle.js +++ b/src/renderers/native/ReactNative/findNodeHandle.js @@ -79,7 +79,7 @@ function findNodeHandle(componentOrHandle: any): ?number { // ReactInstanceMap.get here will always succeed for mounted components var internalInstance = ReactInstanceMap.get(component); if (internalInstance) { - return internalInstance._rootNodeID; + return internalInstance.getNativeNode(); } else { var rootNodeID = component._rootNodeID; if (rootNodeID) { From 4fcdf02068b71b5873a890fda22c62471fd47d95 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 4 Apr 2016 19:44:31 -0700 Subject: [PATCH 13/20] Listeners are not attached by ID in the API ...even though they technically still are attached by. --- .../native/ReactNative/ReactNativeBaseComponent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index ed21216ce6e30..4a87bc2d76654 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -59,7 +59,7 @@ ReactNativeBaseComponent.Mixin = { unmountComponent: function() { ReactNativeComponentTree.uncacheNode(this); - deleteAllListeners(this._rootNodeID); + deleteAllListeners(this); this.unmountChildren(); this._rootNodeID = null; }, @@ -141,7 +141,7 @@ ReactNativeBaseComponent.Mixin = { // registers listeners the *first* time a component is created. if (registrationNames[key] && initialProps[key]) { var listener = initialProps[key]; - putListener(this._rootNodeID, key, listener); + putListener(this, key, listener); } } }, @@ -155,9 +155,9 @@ ReactNativeBaseComponent.Mixin = { for (var key in nextProps) { if (registrationNames[key] && (nextProps[key] !== prevProps[key])) { if (nextProps[key]) { - putListener(this._rootNodeID, key, nextProps[key]); + putListener(this, key, nextProps[key]); } else { - deleteListener(this._rootNodeID, key); + deleteListener(this, key); } } } From dc188dd5216257568f97700741382bbb8d13d243 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 5 Apr 2016 01:12:04 -0700 Subject: [PATCH 14/20] Update ReactNativeDOMIDOperations to deal with a single parent node This is the new protocol. --- .../ReactNative/ReactNativeDOMIDOperations.js | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js index 0c6c842dae4c4..c2a995ee761df 100644 --- a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js +++ b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js @@ -11,6 +11,7 @@ */ 'use strict'; +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactPerf = require('ReactPerf'); @@ -24,50 +25,46 @@ var UIManager = require('UIManager'); * (including deletes/moves). TODO: refactor so this can be shared with * DOMChildrenOperations. * - * @param {array} updates List of update configurations. + * @param {ReactNativeBaseComponent} updates List of update configurations. * @param {array} markup List of markup strings - in the case of React * IOS, the ids of new components assumed to be already created. */ -var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) { +var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) { if (!childrenUpdates.length) { return; } - var byContainerTag = {}; - // Group by parent ID - send them across the bridge in separate commands per - // containerID. + + var containerTag = ReactNativeComponentTree.getNodeFromInstance(inst); + + var moveFromIndices; + var moveToIndices; + var addChildTags; + var addAtIndices; + var removeAtIndices; + for (var i = 0; i < childrenUpdates.length; i++) { var update = childrenUpdates[i]; - var containerTag = update.parentID; - throw new Error('parentID is borked. See changes to multiChild'); - var updates = byContainerTag[containerTag] || (byContainerTag[containerTag] = {}); if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) { - (updates.moveFromIndices || (updates.moveFromIndices = [])).push(update.fromIndex); - (updates.moveToIndices || (updates.moveToIndices = [])).push(update.toIndex); + (moveFromIndices || (moveFromIndices = [])).push(update.fromIndex); + (moveToIndices || (moveToIndices = [])).push(update.toIndex); } else if (update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { - (updates.removeAtIndices || (updates.removeAtIndices = [])).push(update.fromIndex); + (removeAtIndices || (removeAtIndices = [])).push(update.fromIndex); } else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) { - var mountImage = markupList[update.markupIndex]; + var mountImage = update.content; var tag = mountImage; - var rootNodeID = mountImage.rootNodeID; - (updates.addAtIndices || (updates.addAtIndices = [])).push(update.toIndex); - (updates.addChildTags || (updates.addChildTags = [])).push(tag); + (addAtIndices || (addAtIndices = [])).push(update.toIndex); + (addChildTags || (addChildTags = [])).push(tag); } } - // Note this enumeration order will be different on V8! Move `byContainerTag` - // to a sparse array as soon as we confirm there are not horrible perf - // penalties. - for (var updateParentTagString in byContainerTag) { - var updateParentTagNumber = +updateParentTagString; - var childUpdatesToSend = byContainerTag[updateParentTagNumber]; - UIManager.manageChildren( - updateParentTagNumber, - childUpdatesToSend.moveFromIndices, - childUpdatesToSend.moveToIndices, - childUpdatesToSend.addChildTags, - childUpdatesToSend.addAtIndices, - childUpdatesToSend.removeAtIndices - ); - } + + UIManager.manageChildren( + containerTag, + moveFromIndices, + moveToIndices, + addChildTags, + addAtIndices, + removeAtIndices + ); }; /** From 064092102d1034c59e1ce959651a5cbe5af9e07d Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 12 Apr 2016 18:41:51 -0700 Subject: [PATCH 15/20] Bump package versions --- packages/react-native-renderer/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json index 5d1fee2e6db8f..a9c487be12d3e 100644 --- a/packages/react-native-renderer/package.json +++ b/packages/react-native-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-native-renderer", - "version": "15.0.0-rc.2", + "version": "16.0.0-alpha", "description": "React package for use inside react-native.", "main": "index.js", "repository": { @@ -17,7 +17,7 @@ }, "homepage": "https://facebook.github.io/react-native/", "dependencies": { - "fbjs": "^0.8.0-alpha.2", - "react": "^15.0.0-rc.2" + "fbjs": "^0.8.0", + "react": "^16.0.0-alpha" } } From 22a8f9964ea4095f4ede4fef0e65f3576ffe9b4a Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 13 Apr 2016 13:29:40 -0700 Subject: [PATCH 16/20] Don't try to get the target node for responder if there is no listener This can happen in edge cases where he listeners are already unmounted or not mounted yet or something. --- src/renderers/shared/event/EventPluginUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/shared/event/EventPluginUtils.js b/src/renderers/shared/event/EventPluginUtils.js index f7e8efe12f5ec..33019d27d6043 100644 --- a/src/renderers/shared/event/EventPluginUtils.js +++ b/src/renderers/shared/event/EventPluginUtils.js @@ -204,7 +204,7 @@ function executeDirectDispatch(event) { !Array.isArray(dispatchListener), 'executeDirectDispatch(...): Invalid `event`.' ); - event.currentTarget = EventPluginUtils.getNodeFromInstance(dispatchInstance); + event.currentTarget = dispatchListener ? EventPluginUtils.getNodeFromInstance(dispatchInstance) : null; var res = dispatchListener ? dispatchListener(event) : null; event.currentTarget = null; event._dispatchListeners = null; From c7d90e2b352e881bfefa480a9f1c2df0e5e0b8a5 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 13 Apr 2016 15:21:05 -0700 Subject: [PATCH 17/20] Ensure react-native-renderer package gets copied --- Gruntfile.js | 2 ++ grunt/tasks/npm-react-native.js | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Gruntfile.js b/Gruntfile.js index d3591ec8edd40..f1f3c435670d3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -75,6 +75,7 @@ module.exports = function(grunt) { grunt.registerTask('npm-react-dom:pack', npmReactDOMTasks.packRelease); var npmReactNativeTasks = require('./grunt/tasks/npm-react-native'); + grunt.registerTask('npm-react-native:release', npmReactNativeTasks.buildRelease); grunt.registerTask('npm-react-native:pack', npmReactNativeTasks.packRelease); var npmReactAddonsTasks = require('./grunt/tasks/npm-react-addons'); @@ -130,6 +131,7 @@ module.exports = function(grunt) { 'npm-react:pack', 'npm-react-dom:release', 'npm-react-dom:pack', + 'npm-react-native:release', 'npm-react-native:pack', 'npm-react-addons:release', 'npm-react-addons:pack', diff --git a/grunt/tasks/npm-react-native.js b/grunt/tasks/npm-react-native.js index 132ae2d9e3d89..7157e08f6bb3b 100644 --- a/grunt/tasks/npm-react-native.js +++ b/grunt/tasks/npm-react-native.js @@ -3,6 +3,30 @@ var fs = require('fs'); var grunt = require('grunt'); +var src = 'packages/react-native-renderer/'; +var dest = 'build/packages/react-native-renderer/'; + +function buildRelease() { + if (grunt.file.exists(dest)) { + grunt.file.delete(dest); + } + + // Copy to build/packages/react-native-renderer + var mappings = [].concat( + grunt.file.expandMapping('**/*', dest, {cwd: src}), + grunt.file.expandMapping('{LICENSE,PATENTS}', dest) + ); + mappings.forEach(function(mapping) { + var mappingSrc = mapping.src[0]; + var mappingDest = mapping.dest; + if (grunt.file.isDir(mappingSrc)) { + grunt.file.mkdir(mappingDest); + } else { + grunt.file.copy(mappingSrc, mappingDest); + } + }); +} + function packRelease() { var done = this.async(); var spawnCmd = { @@ -17,5 +41,6 @@ function packRelease() { } module.exports = { + buildRelease: buildRelease, packRelease: packRelease, }; From 5470972f6a61111da661b0092d71645751bf1259 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Apr 2016 21:47:49 +0100 Subject: [PATCH 18/20] Update devtools injection This isn't actually used right now so I can't test it. Because the Chrome devtools are broken for React Native. The Nuclide integration is in the react-native repo. --- .../native/ReactNative/ReactNative.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/renderers/native/ReactNative/ReactNative.js b/src/renderers/native/ReactNative/ReactNative.js index b331912c98900..8ab2391606836 100644 --- a/src/renderers/native/ReactNative/ReactNative.js +++ b/src/renderers/native/ReactNative/ReactNative.js @@ -13,6 +13,7 @@ // Require ReactNativeDefaultInjection first for its side effects of setting up // the JS environment +var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection'); var ReactCurrentOwner = require('ReactCurrentOwner'); @@ -63,11 +64,24 @@ if ( typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ - CurrentOwner: ReactCurrentOwner, - InstanceHandles: ReactInstanceHandles, + ComponentTree: { + getClosestInstanceFromNode: function(node) { + return ReactNativeComponentTree.getClosestInstanceFromNode(node); + }, + getNodeFromInstance: function(inst) { + // inst is an internal instance (but could be a composite) + while (inst._renderedComponent) { + inst = inst._renderedComponent; + } + if (inst) { + return ReactNativeComponentTree.getNodeFromInstance(inst); + } else { + return null; + } + }, + }, Mount: ReactNativeMount, Reconciler: require('ReactReconciler'), - TextComponent: require('ReactNativeTextComponent'), }); } From 744548ad143b7350e7e1d7d39e72bf2f4108fee6 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 19 Apr 2016 00:11:31 +0100 Subject: [PATCH 19/20] Fix lint for moved ReactNative --- .../ReactIOS/IOSDefaultEventPluginOrder.js | 2 +- .../ReactIOS/IOSNativeBridgeEventPlugin.js | 2 +- .../native/ReactIOS/NativeMethodsMixin.js | 8 +++--- .../native/ReactNative/ReactNative.js | 12 --------- .../ReactNativeAttributePayload.js | 10 +++---- .../ReactNative/ReactNativeBaseComponent.js | 4 +-- .../ReactNative/ReactNativeContainerInfo.js | 2 +- .../ReactNative/ReactNativeDOMIDOperations.js | 1 - .../ReactNativeDefaultInjection.js | 4 +-- .../ReactNative/ReactNativeEventEmitter.js | 2 +- .../ReactNativeGlobalInteractionHandler.js | 2 +- .../ReactNativeGlobalResponderHandler.js | 3 +-- .../native/ReactNative/ReactNativeMount.js | 4 +-- .../ReactNativeReconcileTransaction.js | 4 +-- .../ReactNative/ReactNativeTagHandles.js | 6 ++--- .../ReactNative/ReactNativeTextComponent.js | 6 ++--- .../ReactNativeAttributePayload-test.js | 27 ++++++++++++++----- .../native/ReactNative/findNodeHandle.js | 1 - .../shared/event/eventPlugins/PanResponder.js | 17 ++++++------ .../event/eventPlugins/TouchHistoryMath.js | 2 +- 20 files changed, 58 insertions(+), 61 deletions(-) diff --git a/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js b/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js index 80098fc414f30..eae211c40fcea 100644 --- a/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js +++ b/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js @@ -13,7 +13,7 @@ var IOSDefaultEventPluginOrder = [ 'ResponderEventPlugin', - 'IOSNativeBridgeEventPlugin' + 'IOSNativeBridgeEventPlugin', ]; module.exports = IOSDefaultEventPluginOrder; diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js index 844e47536ba2a..564e10f28c95b 100644 --- a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -65,7 +65,7 @@ var IOSNativeBridgeEventPlugin = { return null; } return event; - } + }, }; module.exports = IOSNativeBridgeEventPlugin; diff --git a/src/renderers/native/ReactIOS/NativeMethodsMixin.js b/src/renderers/native/ReactIOS/NativeMethodsMixin.js index f9247a14195ee..0d06e11fef227 100644 --- a/src/renderers/native/ReactIOS/NativeMethodsMixin.js +++ b/src/renderers/native/ReactIOS/NativeMethodsMixin.js @@ -169,7 +169,7 @@ var NativeMethodsMixin = { */ blur: function() { TextInputState.blurTextInput(findNodeHandle(this)); - } + }, }; function throwOnStylesProp(component, props) { @@ -195,10 +195,10 @@ if (__DEV__) { !NativeMethodsMixin_DEV.componentWillReceiveProps, 'Do not override existing functions.' ); - NativeMethodsMixin_DEV.componentWillMount = function () { + NativeMethodsMixin_DEV.componentWillMount = function() { throwOnStylesProp(this, this.props); }; - NativeMethodsMixin_DEV.componentWillReceiveProps = function (newProps) { + NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) { throwOnStylesProp(this, newProps); }; } @@ -210,7 +210,7 @@ if (__DEV__) { var mountSafeCallback = function(context: ReactComponent, callback: ?Function): any { return function() { if (!callback || (context.isMounted && !context.isMounted())) { - return; + return undefined; } return callback.apply(context, arguments); }; diff --git a/src/renderers/native/ReactNative/ReactNative.js b/src/renderers/native/ReactNative/ReactNative.js index 8ab2391606836..e40c6b7576571 100644 --- a/src/renderers/native/ReactNative/ReactNative.js +++ b/src/renderers/native/ReactNative/ReactNative.js @@ -16,9 +16,7 @@ var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection'); -var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); -var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactNativeMount = require('ReactNativeMount'); var ReactUpdates = require('ReactUpdates'); @@ -45,16 +43,6 @@ var ReactNative = { /* eslint-enable camelcase */ unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, - - // Deprecations (remove for 0.13) - renderComponent: function( - element: ReactElement, - mountInto: number, - callback?: ?(() => void) - ): ?ReactComponent { - warning('Use React.render instead of React.renderComponent'); - return ReactNative.render(element, mountInto, callback); - }, }; // Inject the runtime into a devtools global hook regardless of browser. diff --git a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js index 7adf0d27c7dc6..ca1cd57030408 100644 --- a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js +++ b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js @@ -408,9 +408,9 @@ function diffProperties( defaultDiffer(prevProp, nextProp) ); if (shouldUpdate) { - var nextValue = typeof attributeConfig.process === 'function' ? - attributeConfig.process(nextProp) : - nextProp; + nextValue = typeof attributeConfig.process === 'function' ? + attributeConfig.process(nextProp) : + nextProp; (updatePayload || (updatePayload = {}))[altKey] = nextValue; } } else { @@ -437,7 +437,7 @@ function diffProperties( // Also iterate through all the previous props to catch any that have been // removed and make sure native gets the signal so it can reset them to the // default. - for (var propKey in prevProps) { + for (propKey in prevProps) { if (nextProps[propKey] !== undefined) { continue; // we've already covered this key in the previous pass } @@ -539,7 +539,7 @@ var ReactNativeAttributePayload = { nextProps, validAttributes ); - } + }, }; diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js index 4a87bc2d76654..a15dae5bba4af 100644 --- a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeBaseComponent.js @@ -20,8 +20,6 @@ var ReactMultiChild = require('ReactMultiChild'); var UIManager = require('UIManager'); var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); -var invariant = require('invariant'); -var warning = require('warning'); var registrationNames = ReactNativeEventEmitter.registrationNames; var putListener = ReactNativeEventEmitter.putListener; @@ -215,7 +213,7 @@ ReactNativeBaseComponent.Mixin = { context ); return tag; - } + }, }; /** diff --git a/src/renderers/native/ReactNative/ReactNativeContainerInfo.js b/src/renderers/native/ReactNative/ReactNativeContainerInfo.js index d3de99316c804..cc37ede4a09d1 100644 --- a/src/renderers/native/ReactNative/ReactNativeContainerInfo.js +++ b/src/renderers/native/ReactNative/ReactNativeContainerInfo.js @@ -13,7 +13,7 @@ function ReactNativeContainerInfo(tag) { var info = { - _tag: tag + _tag: tag, }; return info; } diff --git a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js index c2a995ee761df..0bb92fef55211 100644 --- a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js +++ b/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js @@ -12,7 +12,6 @@ 'use strict'; var ReactNativeComponentTree = require('ReactNativeComponentTree'); -var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactPerf = require('ReactPerf'); var UIManager = require('UIManager'); diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js index c95e300be2825..2c603ec9aa6a3 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js @@ -66,7 +66,7 @@ function inject() { */ EventPluginHub.injection.injectEventPluginsByName({ 'ResponderEventPlugin': ResponderEventPlugin, - 'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin + 'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin, }); ReactUpdates.injection.injectReconcileTransaction( @@ -87,7 +87,7 @@ function inject() { return new ReactSimpleEmptyComponent( ReactElement.createElement(View, { collapsable: true, - style: { position: 'absolute' } + style: { position: 'absolute' }, }), instantiate ); diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index 67d18d11cb48e..1b252c699553e 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -209,7 +209,7 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { nativeEvent ); } - } + }, }); module.exports = ReactNativeEventEmitter; diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js b/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js index d5c9b95447791..b84408a0d5399 100644 --- a/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js +++ b/src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js @@ -27,7 +27,7 @@ var ReactNativeGlobalInteractionHandler = { } else if (!interactionHandle) { interactionHandle = InteractionManager.createInteractionHandle(); } - } + }, }; module.exports = ReactNativeGlobalInteractionHandler; diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js index 1fb45e783fa1e..84bd45fb6f1f6 100644 --- a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js +++ b/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js @@ -10,7 +10,6 @@ */ 'use strict'; -var ReactNativeTagHandles = require('ReactNativeTagHandles'); var UIManager = require('UIManager'); var ReactNativeGlobalResponderHandler = { @@ -23,7 +22,7 @@ var ReactNativeGlobalResponderHandler = { } else { UIManager.clearJSResponder(); } - } + }, }; module.exports = ReactNativeGlobalResponderHandler; diff --git a/src/renderers/native/ReactNative/ReactNativeMount.js b/src/renderers/native/ReactNative/ReactNativeMount.js index f0e9988e35da0..50d300923a2a9 100644 --- a/src/renderers/native/ReactNative/ReactNativeMount.js +++ b/src/renderers/native/ReactNative/ReactNativeMount.js @@ -130,7 +130,7 @@ var ReactNativeMount = { if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) { console.error('You cannot render into anything but a top root'); - return; + return null; } ReactNativeTagHandles.assertRootTag(containerTag); @@ -225,7 +225,7 @@ var ReactNativeMount = { // Call back into native to remove all of the subviews from this container ReactReconciler.unmountComponent(instance); UIManager.removeSubviewsFromContainerWithID(containerID); - } + }, }; diff --git a/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js b/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js index b6721964988f0..1c40be74bc4e2 100644 --- a/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js +++ b/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js @@ -32,7 +32,7 @@ var ON_DOM_READY_QUEUEING = { */ close: function() { this.reactMountReady.notifyAll(); - } + }, }; /** @@ -88,7 +88,7 @@ var Mixin = { destructor: function() { CallbackQueue.release(this.reactMountReady); this.reactMountReady = null; - } + }, }; Object.assign( diff --git a/src/renderers/native/ReactNative/ReactNativeTagHandles.js b/src/renderers/native/ReactNative/ReactNativeTagHandles.js index f0079fdabc0b7..2a4d7bd01065a 100644 --- a/src/renderers/native/ReactNative/ReactNativeTagHandles.js +++ b/src/renderers/native/ReactNative/ReactNativeTagHandles.js @@ -12,7 +12,6 @@ 'use strict'; var invariant = require('invariant'); -var warning = require('warning'); /** * Keeps track of allocating and associating native "tags" which are numeric, @@ -28,7 +27,6 @@ var warning = require('warning'); * unmount a component with a `rootNodeID`, then mount a new one in its place, */ var INITIAL_TAG_COUNT = 1; -var NATIVE_TOP_ROOT_ID_SEPARATOR = '{TOP_LEVEL}'; var ReactNativeTagHandles = { tagsStartAt: INITIAL_TAG_COUNT, tagCount: INITIAL_TAG_COUNT, @@ -46,14 +44,14 @@ var ReactNativeTagHandles = { assertRootTag: function(tag: number): void { invariant( this.reactTagIsNativeTopRootID(tag), - 'Expect a native root tag, instead got ', tag + 'Expect a native root tag, instead got %s', tag ); }, reactTagIsNativeTopRootID: function(reactTag: number): bool { // We reserve all tags that are 1 mod 10 for native root views return reactTag % 10 === 1; - } + }, }; module.exports = ReactNativeTagHandles; diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNative/ReactNativeTextComponent.js index 434e4b8885f5f..3de8a82dd21d5 100644 --- a/src/renderers/native/ReactNative/ReactNativeTextComponent.js +++ b/src/renderers/native/ReactNative/ReactNativeTextComponent.js @@ -31,8 +31,8 @@ Object.assign(ReactNativeTextComponent.prototype, { // TODO: nativeParent should have this context already. Stop abusing context. invariant( context.isInAParentText, - 'RawText "' + this._stringText + '" must be wrapped in an explicit ' + - ' component.' + 'RawText "%s" must be wrapped in an explicit component.', + this._stringText ); this._nativeParent = nativeParent; var tag = ReactNativeTagHandles.allocateTag(); @@ -74,7 +74,7 @@ Object.assign(ReactNativeTextComponent.prototype, { this._currentElement = null; this._stringText = null; this._rootNodeID = null; - } + }, }); diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js index d325ca0919739..be65d175a2830 100644 --- a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js +++ b/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js @@ -87,10 +87,10 @@ describe('ReactNativeAttributePayload', function() { it('should do deep diffs of Objects by default', () => { expect(diff( - {a: [1], b: {k: [3,4]}, c: {k: [4,4]} }, - {a: [2], b: {k: [3,4]}, c: {k: [4,5]} }, + {a: [1], b: {k: [3, 4]}, c: {k: [4, 4]} }, + {a: [2], b: {k: [3, 4]}, c: {k: [4, 5]} }, {a: true, b: true, c: true} - )).toEqual({a: [2], c: {k: [4,5]}}); + )).toEqual({a: [2], c: {k: [4, 5]}}); }); it('should work with undefined styles', () => { @@ -222,10 +222,25 @@ describe('ReactNativeAttributePayload', function() { // Note that if the property changes from one function to another, we don't // need to send an update. expect(diff( - {a: function() { return 1; }, b: function() { return 2; }, c: 3}, - {b: function() { return 9; }, c: function() { return 3; }, }, + { + a: function() { + return 1; + }, + b: function() { + return 2; + }, + c: 3, + }, + { + b: function() { + return 9; + }, + c: function() { + return 3; + }, + }, {a: true, b: true, c: true} )).toEqual({a: null, c: true}); }); - }); +}); diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/ReactNative/findNodeHandle.js index 15f709e7e9a8c..2979490de7a6a 100644 --- a/src/renderers/native/ReactNative/findNodeHandle.js +++ b/src/renderers/native/ReactNative/findNodeHandle.js @@ -14,7 +14,6 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactInstanceMap = require('ReactInstanceMap'); -var ReactNativeTagHandles = require('ReactNativeTagHandles'); var invariant = require('invariant'); var warning = require('warning'); diff --git a/src/renderers/shared/event/eventPlugins/PanResponder.js b/src/renderers/shared/event/eventPlugins/PanResponder.js index c633dd724be79..a102747fe6bcb 100644 --- a/src/renderers/shared/event/eventPlugins/PanResponder.js +++ b/src/renderers/shared/event/eventPlugins/PanResponder.js @@ -2,7 +2,7 @@ * @providesModule PanResponder */ -"use strict"; +'use strict'; var TouchHistoryMath = require('TouchHistoryMath'); @@ -320,25 +320,25 @@ var PanResponder = { gestureState.y0 = currentCentroidY(e.touchHistory); gestureState.dx = 0; gestureState.dy = 0; - config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); + if (config.onPanResponderGrant) config.onPanResponderGrant(e, gestureState); // TODO: t7467124 investigate if this can be removed return config.onShouldBlockNativeResponder === undefined ? true : config.onShouldBlockNativeResponder(); }, onResponderReject: function(e) { - config.onPanResponderReject && config.onPanResponderReject(e, gestureState); + if (config.onPanResponderReject) config.onPanResponderReject(e, gestureState); }, onResponderRelease: function(e) { - config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState); + if (config.onPanResponderRelease) config.onPanResponderRelease(e, gestureState); PanResponder._initializeGestureState(gestureState); }, onResponderStart: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderStart && config.onPanResponderStart(e, gestureState); + if (config.onPanResponderStart) config.onPanResponderStart(e, gestureState); }, onResponderMove: function(e) { @@ -351,18 +351,19 @@ var PanResponder = { // Filter out any touch moves past the first one - we would have // already processed multi-touch geometry during the first event. PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - config.onPanResponderMove && config.onPanResponderMove(e, gestureState); + if (config.onPanResponderMove) config.onPanResponderMove(e, gestureState); }, onResponderEnd: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState); + if (config.onPanResponderEnd) config.onPanResponderEnd(e, gestureState); }, onResponderTerminate: function(e) { - config.onPanResponderTerminate && + if (config.onPanResponderTerminate) { config.onPanResponderTerminate(e, gestureState); + } PanResponder._initializeGestureState(gestureState); }, diff --git a/src/renderers/shared/event/eventPlugins/TouchHistoryMath.js b/src/renderers/shared/event/eventPlugins/TouchHistoryMath.js index 1507d21f5618b..a2a725bd2fa62 100644 --- a/src/renderers/shared/event/eventPlugins/TouchHistoryMath.js +++ b/src/renderers/shared/event/eventPlugins/TouchHistoryMath.js @@ -2,7 +2,7 @@ * @providesModule TouchHistoryMath */ -"use strict"; +'use strict'; var TouchHistoryMath = { /** From ada60c4fd8dc8bb4e1259ce798979e520d236e59 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 19 Apr 2016 01:34:02 +0100 Subject: [PATCH 20/20] Remove ReactDOM.native shim This is causing build errors. This should be in the downstream repo if anything. Relay has its own shim that should be preferred. --- packages/react/lib/ReactDOM.native.js | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/react/lib/ReactDOM.native.js diff --git a/packages/react/lib/ReactDOM.native.js b/packages/react/lib/ReactDOM.native.js deleted file mode 100644 index 2c4b436a7e852..0000000000000 --- a/packages/react/lib/ReactDOM.native.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @providesModule ReactDOM - */ - -'use strict'; - -var ReactUpdates = require('./ReactUpdates'); - -// TODO: In React Native, ReactTestUtils depends on ./ReactDOM (for -// renderIntoDocument, which should never be called) and Relay depends on -// react-dom (for batching). Once those are fixed, nothing in RN should import -// this module and this file can go away. - -module.exports = { - unstable_batchedUpdates: ReactUpdates.batchedUpdates, -};