RN: Update React (2/2)
Reviewed By: kentaromiura Differential Revision: D4026114 fbshipit-source-id: 67808af91454d95941fea01eef58a4d9086f46e1
This commit is contained in:
Родитель
b76ab8e3a1
Коммит
3683beb88a
|
@ -17,6 +17,7 @@
|
|||
|
||||
# Ignore unexpected extra @providesModule
|
||||
.*/node_modules/commoner/test/source/widget/share.js
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
|
||||
# Ignore duplicate module providers
|
||||
# For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root
|
||||
|
|
|
@ -47,7 +47,9 @@ class AppEventsTest extends React.Component {
|
|||
throw new Error('Received wrong event: ' + JSON.stringify(event));
|
||||
}
|
||||
var elapsed = (Date.now() - event.ts) + 'ms';
|
||||
this.setState({received: event, elapsed}, TestModule.markTestCompleted);
|
||||
this.setState({received: event, elapsed}, () => {
|
||||
TestModule.markTestCompleted();
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -174,7 +174,9 @@ class AsyncStorageTest extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
done = () => this.setState({done: true}, TestModule.markTestCompleted);
|
||||
done = () => this.setState({done: true}, () => {
|
||||
TestModule.markTestCompleted();
|
||||
});
|
||||
updateMessage = (msg) => {
|
||||
this.setState({messages: this.state.messages.concat('\n' + msg)});
|
||||
DEBUG && console.log(msg);
|
||||
|
|
|
@ -51,7 +51,9 @@ class IntegrationTestHarnessTest extends React.Component {
|
|||
} else if (!TestModule.markTestCompleted) {
|
||||
throw new Error('RCTTestModule.markTestCompleted not defined.');
|
||||
}
|
||||
this.setState({done: true}, TestModule.markTestCompleted);
|
||||
this.setState({done: true}, () => {
|
||||
TestModule.markTestCompleted();
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -120,7 +120,9 @@ var TimersTest = React.createClass({
|
|||
},
|
||||
|
||||
done() {
|
||||
this.setState({done: true}, TestModule.markTestCompleted);
|
||||
this.setState({done: true}, () => {
|
||||
TestModule.markTestCompleted();
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
|
|
|
@ -17,7 +17,7 @@ var Transform = require('art/core/transform');
|
|||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
|
||||
var createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');
|
||||
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
|
||||
// Diff Helpers
|
||||
|
|
|
@ -20,7 +20,7 @@ var Set = require('Set');
|
|||
var SpringConfig = require('SpringConfig');
|
||||
var ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
var findNodeHandle = require('react/lib/findNodeHandle');
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const ReactNativeMount = require('react/lib/ReactNativeMount');
|
||||
const ReactNativeMount = require('ReactNativeMount');
|
||||
const getReactData = require('getReactData');
|
||||
|
||||
const INDENTATION_SIZE = 2;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
const ColorPropType = require('ColorPropType');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const Platform = require('Platform');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
'use strict';
|
||||
|
||||
var ColorPropType = require('ColorPropType');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var Platform = require('Platform');
|
||||
var React = require('React');
|
||||
var ReactNative = require('ReactNative');
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
const ColorPropType = require('ColorPropType');
|
||||
const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
const Image = require('Image');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var View = require('View');
|
||||
var ColorPropType = require('ColorPropType');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var Image = require('Image');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
const ColorPropType = require('ColorPropType');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const Platform = require('Platform');
|
||||
const React = require('React');
|
||||
const View = require('View');
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var Image = require('Image');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var Platform = require('Platform');
|
||||
var React = require('React');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var ColorPropType = require('ColorPropType');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var Platform = require('Platform');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
const ColorPropType = require('ColorPropType');
|
||||
const DocumentSelectionState = require('DocumentSelectionState');
|
||||
const EventEmitter = require('EventEmitter');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const Platform = require('Platform');
|
||||
const React = require('React');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
var Image = require('Image');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var UIManager = require('UIManager');
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
var Animated = require('Animated');
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var Touchable = require('Touchable');
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var ColorPropType = require('ColorPropType');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var Animated = require('Animated');
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
jest.disableAutomock();
|
||||
|
||||
const React = require('React');
|
||||
const ReactTestRenderer = require('react/lib/ReactTestRenderer');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
const Text = require('Text');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const NativeModules = require('NativeModules');
|
||||
const React = require('React');
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
|
|
|
@ -72,7 +72,7 @@ function setupDevtools() {
|
|||
return;
|
||||
}
|
||||
// This is breaking encapsulation of the React package. Move plz.
|
||||
var ReactNativeComponentTree = require('react/lib/ReactNativeComponentTree');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
|
||||
ComponentTree: {
|
||||
getClosestInstanceFromNode: function (node) {
|
||||
|
@ -90,8 +90,8 @@ function setupDevtools() {
|
|||
}
|
||||
}
|
||||
},
|
||||
Mount: require('react/lib/ReactNativeMount'),
|
||||
Reconciler: require('react/lib/ReactReconciler')
|
||||
Mount: require('ReactNativeMount'),
|
||||
Reconciler: require('ReactReconciler')
|
||||
});
|
||||
ws.onmessage = handleMessage;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var ImageResizeMode = require('ImageResizeMode');
|
||||
var ImageStylePropTypes = require('ImageStylePropTypes');
|
||||
|
|
|
@ -15,7 +15,7 @@ const EdgeInsetsPropType = require('EdgeInsetsPropType');
|
|||
const ImageResizeMode = require('ImageResizeMode');
|
||||
const ImageSourcePropType = require('ImageSourcePropType');
|
||||
const ImageStylePropTypes = require('ImageStylePropTypes');
|
||||
const NativeMethodsMixin = require('react/lib/NativeMethodsMixin');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const NativeModules = require('NativeModules');
|
||||
const React = require('React');
|
||||
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('react/lib/ReactNativeComponentTree');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
|
||||
function traverseOwnerTreeUp(hierarchy, instance) {
|
||||
if (instance) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
'use strict';
|
||||
|
||||
const InteractionManager = require('./InteractionManager');
|
||||
const TouchHistoryMath = require('react/lib/TouchHistoryMath');
|
||||
const TouchHistoryMath = require('TouchHistoryMath');
|
||||
|
||||
const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('react/lib/ReactDebugTool');
|
||||
var ReactPerf = require('react/lib/ReactPerf');
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var performanceNow = require('fbjs/lib/performanceNow');
|
||||
|
|
|
@ -67,10 +67,10 @@ const Systrace = {
|
|||
if (__DEV__) {
|
||||
if (enabled) {
|
||||
global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS);
|
||||
require('react/lib/ReactDebugTool').addDevtool(ReactSystraceDevtool);
|
||||
require('ReactDebugTool').addDevtool(ReactSystraceDevtool);
|
||||
} else {
|
||||
global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS);
|
||||
require('react/lib/ReactDebugTool').removeDevtool(ReactSystraceDevtool);
|
||||
require('ReactDebugTool').removeDevtool(ReactSystraceDevtool);
|
||||
}
|
||||
}
|
||||
_enabled = enabled;
|
||||
|
|
|
@ -15,7 +15,7 @@ const NativeModules = require('NativeModules');
|
|||
const Platform = require('Platform');
|
||||
|
||||
const defineLazyObjectProperty = require('defineLazyObjectProperty');
|
||||
const findNodeHandle = require('react/lib/findNodeHandle');
|
||||
const findNodeHandle = require('findNodeHandle');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type React from 'react';
|
||||
|
|
|
@ -15,7 +15,7 @@ const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
|||
const UIManager = require('UIManager');
|
||||
const UnimplementedView = require('UnimplementedView');
|
||||
|
||||
const createReactNativeComponentClass = require('react/lib/createReactNativeComponentClass');
|
||||
const createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
const insetsDiffer = require('insetsDiffer');
|
||||
const matricesDiffer = require('matricesDiffer');
|
||||
const pointsDiffer = require('pointsDiffer');
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# React Native Renderer
|
||||
|
||||
This is a downstream copy of React's renderer code to render into React Native.
|
||||
The source of truth is the React repo. Please submit any changes upstream to
|
||||
the [React Core GitHub repository](https://github.com/facebook/react).
|
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule ReactNative
|
||||
* @providesModule ReactVersion
|
||||
*/
|
||||
'use strict';
|
||||
module.exports = require('react/lib/ReactNative');
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = '16.0.0-alpha';
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
'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.
|
||||
*/
|
||||
function mountSafeCallback(
|
||||
context: ReactComponent<any, any, any>,
|
||||
callback: ?Function
|
||||
): any {
|
||||
return function() {
|
||||
if (!callback || (context.isMounted && !context.isMounted())) {
|
||||
return undefined;
|
||||
}
|
||||
return callback.apply(context, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = NativeMethodsMixin;
|
|
@ -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 ReactNative
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Require ReactNativeDefaultInjection first for its side effects of setting up
|
||||
// the JS environment
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
|
||||
|
||||
var ReactNativeMount = require('ReactNativeMount');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
|
||||
ReactNativeDefaultInjection.inject();
|
||||
|
||||
var render = function(
|
||||
element: ReactElement<any>,
|
||||
mountInto: number,
|
||||
callback?: ?(() => void)
|
||||
): ?ReactComponent<any, any, any> {
|
||||
return ReactNativeMount.renderComponent(element, mountInto, callback);
|
||||
};
|
||||
|
||||
var ReactNative = {
|
||||
hasReactNativeInitialized: false,
|
||||
findNodeHandle: findNodeHandle,
|
||||
render: render,
|
||||
unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode,
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer,
|
||||
};
|
||||
|
||||
// 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({
|
||||
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'),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ReactNative;
|
|
@ -0,0 +1,520 @@
|
|||
/**
|
||||
* 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 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<NestedNode> | Object | number;
|
||||
|
||||
// Tracks removed keys
|
||||
var removedKeys = null;
|
||||
var removedKeyCount = 0;
|
||||
|
||||
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<NestedNode>,
|
||||
nextArray: Array<NestedNode>,
|
||||
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;
|
||||
|
||||
for (var propKey in nextProps) {
|
||||
attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
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[propKey] !== 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[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;
|
||||
}
|
||||
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 = {}))[propKey] = 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) {
|
||||
nextValue = typeof attributeConfig.process === 'function' ?
|
||||
attributeConfig.process(nextProp) :
|
||||
nextProp;
|
||||
(updatePayload || (updatePayload = {}))[propKey] = nextValue;
|
||||
}
|
||||
} else {
|
||||
// default: fallthrough case when nested properties are defined
|
||||
removedKeys = null;
|
||||
removedKeyCount = 0;
|
||||
// We think that attributeConfig is not CustomAttributeConfiguration at
|
||||
// this point so we assume it must be AttributeConfiguration.
|
||||
// $FlowFixMe
|
||||
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 (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
|
||||
}
|
||||
|
||||
if (updatePayload && updatePayload[propKey] !== 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 = {}))[propKey] = 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;
|
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* 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 ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
deleteAllListeners(this);
|
||||
this.unmountChildren();
|
||||
this._rootNodeID = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
createdTags[i] = childTag;
|
||||
}
|
||||
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(
|
||||
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, 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, key, nextProps[key]);
|
||||
} else {
|
||||
deleteListener(this, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Currently this still uses IDs for reconciliation so this can return null.
|
||||
*
|
||||
* @return {null} Null.
|
||||
*/
|
||||
getHostNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ReactNativeReconcileTransaction} transaction
|
||||
* @param {?ReactNativeBaseComponent} the parent component instance
|
||||
* @param {?object} info about the host container
|
||||
* @param {object} context
|
||||
* @return {string} Unique iOS view tag.
|
||||
*/
|
||||
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
|
||||
this._rootNodeID = tag;
|
||||
this._hostParent = hostParent;
|
||||
this._hostContainerInfo = hostContainerInfo;
|
||||
|
||||
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 nativeTopRootTag = hostContainerInfo._tag;
|
||||
UIManager.createView(
|
||||
tag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
nativeTopRootTag,
|
||||
updatePayload
|
||||
);
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
this._registerListenersUponCreation(this._currentElement.props);
|
||||
this.initializeChildren(
|
||||
this._currentElement.props.children,
|
||||
tag,
|
||||
transaction,
|
||||
context
|
||||
);
|
||||
return tag;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Order of mixins is important. ReactNativeBaseComponent overrides methods in
|
||||
* ReactMultiChild.
|
||||
*/
|
||||
Object.assign(
|
||||
ReactNativeBaseComponent.prototype,
|
||||
ReactMultiChild,
|
||||
ReactNativeBaseComponent.Mixin,
|
||||
NativeMethodsMixin
|
||||
);
|
||||
|
||||
module.exports = ReactNativeBaseComponent;
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* 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 ReactNativeBridgeEventPlugin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
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 ReactNativeBridgeEventPlugin = {
|
||||
|
||||
eventTypes: { ...customBubblingEventTypes, ...customDirectEventTypes },
|
||||
|
||||
/**
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType: string,
|
||||
targetInst: Object,
|
||||
nativeEvent: Event,
|
||||
nativeEventTarget: Object
|
||||
): ?Object {
|
||||
var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
|
||||
var directDispatchConfig = customDirectEventTypes[topLevelType];
|
||||
var event = SyntheticEvent.getPooled(
|
||||
bubbleDispatchConfig || directDispatchConfig,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
if (bubbleDispatchConfig) {
|
||||
EventPropagators.accumulateTwoPhaseDispatches(event);
|
||||
} else if (directDispatchConfig) {
|
||||
EventPropagators.accumulateDirectDispatches(event);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeBridgeEventPlugin;
|
|
@ -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 ReactNativeComponentEnvironment
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations');
|
||||
var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction');
|
||||
|
||||
var ReactNativeComponentEnvironment = {
|
||||
|
||||
processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates,
|
||||
|
||||
replaceNodeWithMarkup: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID,
|
||||
|
||||
/**
|
||||
* @param {DOMElement} Element to clear.
|
||||
*/
|
||||
clearNode: function(/*containerView*/) {
|
||||
|
||||
},
|
||||
|
||||
ReactReconcileTransaction: ReactNativeReconcileTransaction,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentEnvironment;
|
|
@ -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('fbjs/lib/invariant');
|
||||
|
||||
var instanceCache = {};
|
||||
|
||||
/**
|
||||
* Drill down (through composites and empty components) until we get a host or
|
||||
* host text component.
|
||||
*
|
||||
* This is pretty polymorphic but unavoidable with the current structure we have
|
||||
* for `_renderedChildren`.
|
||||
*/
|
||||
function getRenderedHostOrTextFromComponent(component) {
|
||||
var rendered;
|
||||
while ((rendered = component._renderedComponent)) {
|
||||
component = rendered;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate `_hostNode` on the rendered host/text component with the given
|
||||
* DOM node. The passed `inst` can be a composite.
|
||||
*/
|
||||
function precacheNode(inst, tag) {
|
||||
var nativeInst = getRenderedHostOrTextFromComponent(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;
|
|
@ -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: number) {
|
||||
var info = {
|
||||
_tag: tag,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
module.exports = ReactNativeContainerInfo;
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeDOMIDOperations
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
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 {ReactNativeBaseComponent} updates List of update configurations.
|
||||
* @param {array<string>} markup List of markup strings - in the case of React
|
||||
* IOS, the ids of new components assumed to be already created.
|
||||
*/
|
||||
var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) {
|
||||
if (!childrenUpdates.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
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];
|
||||
if (update.type === 'MOVE_EXISTING') {
|
||||
(moveFromIndices || (moveFromIndices = [])).push(update.fromIndex);
|
||||
(moveToIndices || (moveToIndices = [])).push(update.toIndex);
|
||||
} else if (update.type === 'REMOVE_NODE') {
|
||||
(removeAtIndices || (removeAtIndices = [])).push(update.fromIndex);
|
||||
} else if (update.type === 'INSERT_MARKUP') {
|
||||
var mountImage = update.content;
|
||||
var tag = mountImage;
|
||||
(addAtIndices || (addAtIndices = [])).push(update.toIndex);
|
||||
(addChildTags || (addChildTags = [])).push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
UIManager.manageChildren(
|
||||
containerTag,
|
||||
moveFromIndices,
|
||||
moveToIndices,
|
||||
addChildTags,
|
||||
addAtIndices,
|
||||
removeAtIndices
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Operations used to process updates to DOM nodes. This is made injectable via
|
||||
* `ReactComponent.DOMIDOperations`.
|
||||
*/
|
||||
var ReactNativeDOMIDOperations = {
|
||||
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: function(id, mountImage) {
|
||||
var oldTag = id;
|
||||
UIManager.replaceExistingNonRootView(oldTag, mountImage);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeDOMIDOperations;
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* 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.
|
||||
* TODO: require this in packager, not in React #10932517
|
||||
*/
|
||||
require('InitializeCore');
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var RCTEventEmitter = require('RCTEventEmitter');
|
||||
var React = require('React');
|
||||
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin');
|
||||
var ReactHostComponent = require('ReactHostComponent');
|
||||
var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder');
|
||||
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
|
||||
var ReactNativeTextComponent = require('ReactNativeTextComponent');
|
||||
var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal');
|
||||
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var ResponderEventPlugin = require('ResponderEventPlugin');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
function inject() {
|
||||
/**
|
||||
* Register the event emitter with the native bridge
|
||||
*/
|
||||
RCTEventEmitter.register(ReactNativeEventEmitter);
|
||||
|
||||
/**
|
||||
* Inject module for resolving DOM hierarchy and plugin ordering.
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
|
||||
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
|
||||
EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);
|
||||
|
||||
ResponderEventPlugin.injection.injectGlobalResponderHandler(
|
||||
ReactNativeGlobalResponderHandler
|
||||
);
|
||||
|
||||
/**
|
||||
* Some important event plugins included by default (without having to require
|
||||
* them).
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
'ResponderEventPlugin': ResponderEventPlugin,
|
||||
'ReactNativeBridgeEventPlugin': ReactNativeBridgeEventPlugin,
|
||||
});
|
||||
|
||||
ReactUpdates.injection.injectReconcileTransaction(
|
||||
ReactNativeComponentEnvironment.ReactReconcileTransaction
|
||||
);
|
||||
|
||||
ReactUpdates.injection.injectBatchingStrategy(
|
||||
ReactDefaultBatchingStrategy
|
||||
);
|
||||
|
||||
ReactComponentEnvironment.injection.injectEnvironment(
|
||||
ReactNativeComponentEnvironment
|
||||
);
|
||||
|
||||
var EmptyComponent = (instantiate) => {
|
||||
// Can't import View at the top because it depends on React to make its composite
|
||||
var View = require('View');
|
||||
return new ReactSimpleEmptyComponent(
|
||||
React.createElement(View, {
|
||||
collapsable: true,
|
||||
style: { position: 'absolute' },
|
||||
}),
|
||||
instantiate
|
||||
);
|
||||
};
|
||||
|
||||
ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent);
|
||||
|
||||
ReactHostComponent.injection.injectTextComponentClass(
|
||||
ReactNativeTextComponent
|
||||
);
|
||||
ReactHostComponent.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);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inject: inject,
|
||||
};
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* 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 EventPluginRegistry = require('EventPluginRegistry');
|
||||
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
/**
|
||||
* 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<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices by which to pull subsequence.
|
||||
* @return {Array<Touch>} 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<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices to remove from `touches`.
|
||||
* @return {Array<Touch>} Subsequence of removed touch objects.
|
||||
*/
|
||||
var removeTouchesAtIndices = function(
|
||||
touches: Array<Object>,
|
||||
indices: Array<number>
|
||||
): Array<Object> {
|
||||
var rippedOut = [];
|
||||
// use an unsafe downcast to alias to nullable elements,
|
||||
// so we can delete and then compact.
|
||||
var temp: Array<?Object> = (touches: Array<any>);
|
||||
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 = {
|
||||
|
||||
...ReactEventEmitterMixin,
|
||||
|
||||
registrationNames: EventPluginRegistry.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: number,
|
||||
topLevelType: string,
|
||||
nativeEventParam: Object
|
||||
) {
|
||||
var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
|
||||
var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
|
||||
if (!inst) {
|
||||
// If the original instance is already gone, we don't have to dispatch
|
||||
// any events.
|
||||
return;
|
||||
}
|
||||
ReactUpdates.batchedUpdates(function() {
|
||||
ReactNativeEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
inst,
|
||||
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 = 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<Object>,
|
||||
changedIndices: Array<number>
|
||||
) {
|
||||
var changedTouches =
|
||||
eventTopLevelType === 'topTouchEnd' ||
|
||||
eventTopLevelType === '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 = target;
|
||||
}
|
||||
}
|
||||
ReactNativeEventEmitter._receiveRootNodeIDEvent(
|
||||
rootNodeID,
|
||||
eventTopLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeEventEmitter;
|
|
@ -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 ReactNativeEventPluginOrder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeEventPluginOrder = [
|
||||
'ResponderEventPlugin',
|
||||
'ReactNativeBridgeEventPlugin',
|
||||
];
|
||||
|
||||
module.exports = ReactNativeEventPluginOrder;
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var ReactNativeGlobalResponderHandler = {
|
||||
onChange: function(from, to, blockNativeResponder) {
|
||||
if (to !== null) {
|
||||
UIManager.setJSResponder(
|
||||
to._rootNodeID,
|
||||
blockNativeResponder
|
||||
);
|
||||
} else {
|
||||
UIManager.clearJSResponder();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeGlobalResponderHandler;
|
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* 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 React = require('React');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactNativeContainerInfo = require('ReactNativeContainerInfo');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
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');
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return this.props.child;
|
||||
};
|
||||
TopLevelWrapper.isReactTopLevelWrapper = true;
|
||||
|
||||
/**
|
||||
* 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} containerTag container element to mount into.
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
*/
|
||||
function mountComponentIntoNode(
|
||||
componentInstance,
|
||||
containerTag,
|
||||
transaction) {
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance,
|
||||
transaction,
|
||||
null,
|
||||
ReactNativeContainerInfo(containerTag),
|
||||
emptyObject,
|
||||
0 /* parentDebugID */
|
||||
);
|
||||
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
|
||||
ReactNativeMount._mountImageIntoNode(markup, containerTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batched mount.
|
||||
*
|
||||
* @param {ReactComponent} componentInstance The instance to mount.
|
||||
* @param {number} rootID ID of the root node.
|
||||
* @param {number} containerTag container element to mount into.
|
||||
*/
|
||||
function batchedMountComponentIntoNode(
|
||||
componentInstance,
|
||||
containerTag) {
|
||||
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
|
||||
transaction.perform(
|
||||
mountComponentIntoNode,
|
||||
null,
|
||||
componentInstance,
|
||||
containerTag,
|
||||
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 = {
|
||||
_instancesByContainerID: {},
|
||||
|
||||
// these two functions are needed by React Devtools
|
||||
findNodeHandle: require('findNodeHandle'),
|
||||
|
||||
/**
|
||||
* @param {ReactComponent} instance Instance to render.
|
||||
* @param {containerTag} containerView Handle to native view tag
|
||||
*/
|
||||
renderComponent: function(
|
||||
nextElement: ReactElement<*>,
|
||||
containerTag: number,
|
||||
callback?: ?(() => void)
|
||||
): ?ReactComponent<any, any, any> {
|
||||
var nextWrappedElement = React.createElement(
|
||||
TopLevelWrapper,
|
||||
{ child: nextElement }
|
||||
);
|
||||
|
||||
var topRootNodeID = containerTag;
|
||||
var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID];
|
||||
if (prevComponent) {
|
||||
var prevWrappedElement = prevComponent._currentElement;
|
||||
var prevElement = prevWrappedElement.props.child;
|
||||
if (shouldUpdateReactComponent(prevElement, nextElement)) {
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement, emptyObject);
|
||||
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 null;
|
||||
}
|
||||
|
||||
ReactNativeTagHandles.assertRootTag(containerTag);
|
||||
|
||||
var instance = instantiateReactComponent(nextWrappedElement, false);
|
||||
ReactNativeMount._instancesByContainerID[containerTag] = instance;
|
||||
|
||||
// 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,
|
||||
containerTag
|
||||
);
|
||||
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: function(mountImage : number, containerID : number) {
|
||||
// Since we now know that the `mountImage` has been mounted, we can
|
||||
// mark it as such.
|
||||
var childTag = mountImage;
|
||||
UIManager.setChildren(
|
||||
containerID,
|
||||
[childTag]
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 instance = ReactNativeMount._instancesByContainerID[containerTag];
|
||||
if (!instance) {
|
||||
return false;
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onBeginFlush();
|
||||
}
|
||||
ReactNativeMount.unmountComponentFromNode(instance, containerTag);
|
||||
delete ReactNativeMount._instancesByContainerID[containerTag];
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onEndFlush();
|
||||
}
|
||||
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<any, any, any>,
|
||||
containerID: number
|
||||
) {
|
||||
// Call back into native to remove all of the subviews from this container
|
||||
ReactReconciler.unmountComponent(instance);
|
||||
UIManager.removeSubviewsFromContainerWithID(containerID);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactNativeMount;
|
|
@ -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;
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
if (__DEV__) {
|
||||
TRANSACTION_WRAPPERS.push({
|
||||
initialize: ReactInstrumentation.debugTool.onBeginFlush,
|
||||
close: ReactInstrumentation.debugTool.onEndFlush,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<object>} List of operation wrap procedures.
|
||||
* TODO: convert to array<TransactionWrapper>
|
||||
*/
|
||||
getTransactionWrappers: function() {
|
||||
return TRANSACTION_WRAPPERS;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect `onDOMReady` callbacks with.
|
||||
* TODO: convert to ReactMountReady
|
||||
*/
|
||||
getReactMountReady: function() {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect React async events.
|
||||
*/
|
||||
getUpdateQueue: function() {
|
||||
return ReactUpdateQueue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save current transaction state -- if the return value from this method is
|
||||
* passed to `rollback`, the transaction will be reset to that state.
|
||||
*/
|
||||
checkpoint: function() {
|
||||
// reactMountReady is the our only stateful wrapper
|
||||
return this.reactMountReady.checkpoint();
|
||||
},
|
||||
|
||||
rollback: function(checkpoint) {
|
||||
this.reactMountReady.rollback(checkpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* `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,
|
||||
ReactNativeReconcileTransaction,
|
||||
Mixin
|
||||
);
|
||||
|
||||
PooledClass.addPoolingTo(ReactNativeReconcileTransaction);
|
||||
|
||||
module.exports = ReactNativeReconcileTransaction;
|
|
@ -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 ReactNativeTagHandles
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
},
|
||||
|
||||
assertRootTag: function(tag: number): void {
|
||||
invariant(
|
||||
this.reactTagIsNativeTopRootID(tag),
|
||||
'Expect a native root tag, instead got %s', tag
|
||||
);
|
||||
},
|
||||
|
||||
reactTagIsNativeTopRootID: function(reactTag: number): boolean {
|
||||
// We reserve all tags that are 1 mod 10 for native root views
|
||||
return reactTag % 10 === 1;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeTagHandles;
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* 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 ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
var ReactNativeTextComponent = function(text) {
|
||||
// This is really a ReactText (ReactNode), not a ReactElement
|
||||
this._currentElement = text;
|
||||
this._stringText = '' + text;
|
||||
this._hostParent = null;
|
||||
this._rootNodeID = 0;
|
||||
};
|
||||
|
||||
Object.assign(ReactNativeTextComponent.prototype, {
|
||||
|
||||
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
|
||||
// TODO: hostParent should have this context already. Stop abusing context.
|
||||
invariant(
|
||||
context.isInAParentText,
|
||||
'RawText "%s" must be wrapped in an explicit <Text> component.',
|
||||
this._stringText
|
||||
);
|
||||
this._hostParent = hostParent;
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
this._rootNodeID = tag;
|
||||
var nativeTopRootTag = hostContainerInfo._tag;
|
||||
UIManager.createView(
|
||||
tag,
|
||||
'RCTRawText',
|
||||
nativeTopRootTag,
|
||||
{text: this._stringText}
|
||||
);
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
return tag;
|
||||
},
|
||||
|
||||
getHostNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
receiveComponent: function(nextText, transaction, context) {
|
||||
if (nextText !== this._currentElement) {
|
||||
this._currentElement = nextText;
|
||||
var nextStringText = '' + nextText;
|
||||
if (nextStringText !== this._stringText) {
|
||||
this._stringText = nextStringText;
|
||||
UIManager.updateView(
|
||||
this._rootNodeID,
|
||||
'RCTRawText',
|
||||
{text: this._stringText}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
this._currentElement = null;
|
||||
this._stringText = null;
|
||||
this._rootNodeID = 0;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactNativeTextComponent;
|
|
@ -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._hostParent) {
|
||||
depthA++;
|
||||
}
|
||||
var depthB = 0;
|
||||
for (var tempB = instB; tempB; tempB = tempB._hostParent) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
instA = instA._hostParent;
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
instB = instB._hostParent;
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
var depth = depthA;
|
||||
while (depth--) {
|
||||
if (instA === instB) {
|
||||
return instA;
|
||||
}
|
||||
instA = instA._hostParent;
|
||||
instB = instB._hostParent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if A is an ancestor of B.
|
||||
*/
|
||||
function isAncestor(instA, instB) {
|
||||
while (instB) {
|
||||
if (instB === instA) {
|
||||
return true;
|
||||
}
|
||||
instB = instB._hostParent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent instance of the passed-in instance.
|
||||
*/
|
||||
function getParentInstance(inst) {
|
||||
return inst._hostParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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._hostParent;
|
||||
}
|
||||
var i;
|
||||
for (i = path.length; i-- > 0;) {
|
||||
fn(path[i], 'captured', arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], 'bubbled', 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._hostParent;
|
||||
}
|
||||
var pathTo = [];
|
||||
while (to && to !== common) {
|
||||
pathTo.push(to);
|
||||
to = to._hostParent;
|
||||
}
|
||||
var i;
|
||||
for (i = 0; i < pathFrom.length; i++) {
|
||||
fn(pathFrom[i], 'bubbled', argFrom);
|
||||
}
|
||||
for (i = pathTo.length; i-- > 0;) {
|
||||
fn(pathTo[i], 'captured', argTo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAncestor: isAncestor,
|
||||
getLowestCommonAncestor: getLowestCommonAncestor,
|
||||
getParentInstance: getParentInstance,
|
||||
traverseTwoPhase: traverseTwoPhase,
|
||||
traverseEnterLeave: traverseEnterLeave,
|
||||
};
|
|
@ -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 #10932517: Move all initialization callers back into react-native
|
|
@ -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';
|
||||
|
||||
var RCTEventEmitter = {
|
||||
register: jest.fn(),
|
||||
};
|
||||
|
||||
module.exports = RCTEventEmitter;
|
|
@ -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;
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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.fn(),
|
||||
setChildren: jest.fn(),
|
||||
manageChildren: jest.fn(),
|
||||
updateView: jest.fn(),
|
||||
removeSubviewsFromContainerWithID: jest.fn(),
|
||||
replaceExistingNonRootView: jest.fn(),
|
||||
};
|
||||
|
||||
module.exports = RCTUIManager;
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
module.exports = View;
|
|
@ -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): boolean {
|
||||
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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* 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<any> {
|
||||
var Constructor = function(element) {
|
||||
this._currentElement = element;
|
||||
this._topLevelWrapper = null;
|
||||
this._hostParent = null;
|
||||
this._hostContainerInfo = null;
|
||||
this._rootNodeID = 0;
|
||||
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<any>);
|
||||
};
|
||||
|
||||
module.exports = createReactNativeComponentClass;
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
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 internalInstance.getHostNode();
|
||||
} else {
|
||||
var rootNodeID = component._rootNodeID;
|
||||
if (rootNodeID) {
|
||||
return 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;
|
|
@ -0,0 +1,423 @@
|
|||
/**
|
||||
* Copyright 2016-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 ReactDebugTool
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
|
||||
var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
|
||||
var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
|
||||
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
|
||||
|
||||
var performanceNow = require('fbjs/lib/performanceNow');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
import type { ReactElement } from 'ReactElementType';
|
||||
import type { DebugID } from 'ReactInstanceType';
|
||||
import type { Operation } from 'ReactHostOperationHistoryHook';
|
||||
|
||||
type Hook = any;
|
||||
|
||||
type TimerType =
|
||||
'ctor' |
|
||||
'render' |
|
||||
'componentWillMount' |
|
||||
'componentWillUnmount' |
|
||||
'componentWillReceiveProps' |
|
||||
'shouldComponentUpdate' |
|
||||
'componentWillUpdate' |
|
||||
'componentDidUpdate' |
|
||||
'componentDidMount';
|
||||
|
||||
type Measurement = {
|
||||
timerType: TimerType,
|
||||
instanceID: DebugID,
|
||||
duration: number,
|
||||
};
|
||||
|
||||
type TreeSnapshot = {
|
||||
[key: DebugID]: {
|
||||
displayName: string,
|
||||
text: string,
|
||||
updateCount: number,
|
||||
childIDs: Array<DebugID>,
|
||||
ownerID: DebugID,
|
||||
parentID: DebugID,
|
||||
}
|
||||
};
|
||||
|
||||
type HistoryItem = {
|
||||
duration: number,
|
||||
measurements: Array<Measurement>,
|
||||
operations: Array<Operation>,
|
||||
treeSnapshot: TreeSnapshot,
|
||||
};
|
||||
|
||||
export type FlushHistory = Array<HistoryItem>;
|
||||
|
||||
var hooks = [];
|
||||
var didHookThrowForEvent = {};
|
||||
|
||||
function callHook(event, fn, context, arg1, arg2, arg3, arg4, arg5) {
|
||||
try {
|
||||
fn.call(context, arg1, arg2, arg3, arg4, arg5);
|
||||
} catch (e) {
|
||||
warning(
|
||||
didHookThrowForEvent[event],
|
||||
'Exception thrown by hook while handling %s: %s',
|
||||
event,
|
||||
e + '\n' + e.stack
|
||||
);
|
||||
didHookThrowForEvent[event] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function emitEvent(event, arg1, arg2, arg3, arg4, arg5) {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
var hook = hooks[i];
|
||||
var fn = hook[event];
|
||||
if (fn) {
|
||||
callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isProfiling = false;
|
||||
var flushHistory = [];
|
||||
var lifeCycleTimerStack = [];
|
||||
var currentFlushNesting = 0;
|
||||
var currentFlushMeasurements = [];
|
||||
var currentFlushStartTime = 0;
|
||||
var currentTimerDebugID = null;
|
||||
var currentTimerStartTime = 0;
|
||||
var currentTimerNestedFlushDuration = 0;
|
||||
var currentTimerType = null;
|
||||
|
||||
var lifeCycleTimerHasWarned = false;
|
||||
|
||||
function clearHistory() {
|
||||
ReactComponentTreeHook.purgeUnmountedComponents();
|
||||
ReactHostOperationHistoryHook.clearHistory();
|
||||
}
|
||||
|
||||
function getTreeSnapshot(registeredIDs) {
|
||||
return registeredIDs.reduce((tree, id) => {
|
||||
var ownerID = ReactComponentTreeHook.getOwnerID(id);
|
||||
var parentID = ReactComponentTreeHook.getParentID(id);
|
||||
tree[id] = {
|
||||
displayName: ReactComponentTreeHook.getDisplayName(id),
|
||||
text: ReactComponentTreeHook.getText(id),
|
||||
updateCount: ReactComponentTreeHook.getUpdateCount(id),
|
||||
childIDs: ReactComponentTreeHook.getChildIDs(id),
|
||||
// Text nodes don't have owners but this is close enough.
|
||||
ownerID: ownerID ||
|
||||
parentID && ReactComponentTreeHook.getOwnerID(parentID) ||
|
||||
0,
|
||||
parentID,
|
||||
};
|
||||
return tree;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function resetMeasurements() {
|
||||
var previousStartTime = currentFlushStartTime;
|
||||
var previousMeasurements = currentFlushMeasurements;
|
||||
var previousOperations = ReactHostOperationHistoryHook.getHistory();
|
||||
|
||||
if (currentFlushNesting === 0) {
|
||||
currentFlushStartTime = 0;
|
||||
currentFlushMeasurements = [];
|
||||
clearHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousMeasurements.length || previousOperations.length) {
|
||||
var registeredIDs = ReactComponentTreeHook.getRegisteredIDs();
|
||||
flushHistory.push({
|
||||
duration: performanceNow() - previousStartTime,
|
||||
measurements: previousMeasurements || [],
|
||||
operations: previousOperations || [],
|
||||
treeSnapshot: getTreeSnapshot(registeredIDs),
|
||||
});
|
||||
}
|
||||
|
||||
clearHistory();
|
||||
currentFlushStartTime = performanceNow();
|
||||
currentFlushMeasurements = [];
|
||||
}
|
||||
|
||||
function checkDebugID(debugID, allowRoot = false) {
|
||||
if (allowRoot && debugID === 0) {
|
||||
return;
|
||||
}
|
||||
if (!debugID) {
|
||||
warning(false, 'ReactDebugTool: debugID may not be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
function beginLifeCycleTimer(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code. ' +
|
||||
'Did not expect %s timer to start while %s timer is still in ' +
|
||||
'progress for %s instance.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
(debugID === currentTimerDebugID) ? 'the same' : 'another'
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
currentTimerStartTime = performanceNow();
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
}
|
||||
|
||||
function endLifeCycleTimer(debugID, timerType) {
|
||||
if (currentFlushNesting === 0) {
|
||||
return;
|
||||
}
|
||||
if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) {
|
||||
warning(
|
||||
false,
|
||||
'There is an internal error in the React performance measurement code. ' +
|
||||
'We did not expect %s timer to stop while %s timer is still in ' +
|
||||
'progress for %s instance. Please report this as a bug in React.',
|
||||
timerType,
|
||||
currentTimerType || 'no',
|
||||
(debugID === currentTimerDebugID) ? 'the same' : 'another'
|
||||
);
|
||||
lifeCycleTimerHasWarned = true;
|
||||
}
|
||||
if (isProfiling) {
|
||||
currentFlushMeasurements.push({
|
||||
timerType,
|
||||
instanceID: debugID,
|
||||
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
|
||||
});
|
||||
}
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
}
|
||||
|
||||
function pauseCurrentLifeCycleTimer() {
|
||||
var currentTimer = {
|
||||
startTime: currentTimerStartTime,
|
||||
nestedFlushStartTime: performanceNow(),
|
||||
debugID: currentTimerDebugID,
|
||||
timerType: currentTimerType,
|
||||
};
|
||||
lifeCycleTimerStack.push(currentTimer);
|
||||
currentTimerStartTime = 0;
|
||||
currentTimerNestedFlushDuration = 0;
|
||||
currentTimerDebugID = null;
|
||||
currentTimerType = null;
|
||||
}
|
||||
|
||||
function resumeCurrentLifeCycleTimer() {
|
||||
var {startTime, nestedFlushStartTime, debugID, timerType} = lifeCycleTimerStack.pop();
|
||||
var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
|
||||
currentTimerStartTime = startTime;
|
||||
currentTimerNestedFlushDuration += nestedFlushDuration;
|
||||
currentTimerDebugID = debugID;
|
||||
currentTimerType = timerType;
|
||||
}
|
||||
|
||||
var lastMarkTimeStamp = 0;
|
||||
var canUsePerformanceMeasure: boolean =
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/2345
|
||||
typeof performance !== 'undefined' &&
|
||||
typeof performance.mark === 'function' &&
|
||||
typeof performance.clearMarks === 'function' &&
|
||||
typeof performance.measure === 'function' &&
|
||||
typeof performance.clearMeasures === 'function';
|
||||
|
||||
function shouldMark(debugID) {
|
||||
if (!isProfiling || !canUsePerformanceMeasure) {
|
||||
return false;
|
||||
}
|
||||
var element = ReactComponentTreeHook.getElement(debugID);
|
||||
if (element == null || typeof element !== 'object') {
|
||||
return false;
|
||||
}
|
||||
var isHostElement = typeof element.type === 'string';
|
||||
if (isHostElement) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function markBegin(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
lastMarkTimeStamp = performanceNow();
|
||||
performance.mark(markName);
|
||||
}
|
||||
|
||||
function markEnd(debugID, markType) {
|
||||
if (!shouldMark(debugID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var markName = `${debugID}::${markType}`;
|
||||
var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown';
|
||||
|
||||
// Chrome has an issue of dropping markers recorded too fast:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=640652
|
||||
// To work around this, we will not report very small measurements.
|
||||
// I determined the magic number by tweaking it back and forth.
|
||||
// 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe.
|
||||
// When the bug is fixed, we can `measure()` unconditionally if we want to.
|
||||
var timeStamp = performanceNow();
|
||||
if (timeStamp - lastMarkTimeStamp > 0.1) {
|
||||
var measurementName = `${displayName} [${markType}]`;
|
||||
performance.measure(measurementName, markName);
|
||||
}
|
||||
|
||||
performance.clearMarks(markName);
|
||||
performance.clearMeasures(measurementName);
|
||||
}
|
||||
|
||||
var ReactDebugTool = {
|
||||
addHook(hook: Hook): void {
|
||||
hooks.push(hook);
|
||||
},
|
||||
removeHook(hook: Hook): void {
|
||||
for (var i = 0; i < hooks.length; i++) {
|
||||
if (hooks[i] === hook) {
|
||||
hooks.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
},
|
||||
isProfiling(): boolean {
|
||||
return isProfiling;
|
||||
},
|
||||
beginProfiling(): void {
|
||||
if (isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = true;
|
||||
flushHistory.length = 0;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.addHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
endProfiling(): void {
|
||||
if (!isProfiling) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfiling = false;
|
||||
resetMeasurements();
|
||||
ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
|
||||
},
|
||||
getFlushHistory(): FlushHistory {
|
||||
return flushHistory;
|
||||
},
|
||||
onBeginFlush(): void {
|
||||
currentFlushNesting++;
|
||||
resetMeasurements();
|
||||
pauseCurrentLifeCycleTimer();
|
||||
emitEvent('onBeginFlush');
|
||||
},
|
||||
onEndFlush(): void {
|
||||
resetMeasurements();
|
||||
currentFlushNesting--;
|
||||
resumeCurrentLifeCycleTimer();
|
||||
emitEvent('onEndFlush');
|
||||
},
|
||||
onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
|
||||
markBegin(debugID, timerType);
|
||||
beginLifeCycleTimer(debugID, timerType);
|
||||
},
|
||||
onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
|
||||
checkDebugID(debugID);
|
||||
endLifeCycleTimer(debugID, timerType);
|
||||
markEnd(debugID, timerType);
|
||||
emitEvent('onEndLifeCycleTimer', debugID, timerType);
|
||||
},
|
||||
onBeginProcessingChildContext(): void {
|
||||
emitEvent('onBeginProcessingChildContext');
|
||||
},
|
||||
onEndProcessingChildContext(): void {
|
||||
emitEvent('onEndProcessingChildContext');
|
||||
},
|
||||
onHostOperation(operation: Operation) {
|
||||
checkDebugID(operation.instanceID);
|
||||
emitEvent('onHostOperation', operation);
|
||||
},
|
||||
onSetState(): void {
|
||||
emitEvent('onSetState');
|
||||
},
|
||||
onSetChildren(debugID: DebugID, childDebugIDs: Array<DebugID>) {
|
||||
checkDebugID(debugID);
|
||||
childDebugIDs.forEach(checkDebugID);
|
||||
emitEvent('onSetChildren', debugID, childDebugIDs);
|
||||
},
|
||||
onBeforeMountComponent(debugID: DebugID, element: ReactElement, parentDebugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
checkDebugID(parentDebugID, true);
|
||||
emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
|
||||
markBegin(debugID, 'mount');
|
||||
},
|
||||
onMountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'mount');
|
||||
emitEvent('onMountComponent', debugID);
|
||||
},
|
||||
onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUpdateComponent', debugID, element);
|
||||
markBegin(debugID, 'update');
|
||||
},
|
||||
onUpdateComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'update');
|
||||
emitEvent('onUpdateComponent', debugID);
|
||||
},
|
||||
onBeforeUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
emitEvent('onBeforeUnmountComponent', debugID);
|
||||
markBegin(debugID, 'unmount');
|
||||
},
|
||||
onUnmountComponent(debugID: DebugID): void {
|
||||
checkDebugID(debugID);
|
||||
markEnd(debugID, 'unmount');
|
||||
emitEvent('onUnmountComponent', debugID);
|
||||
},
|
||||
onTestEvent(): void {
|
||||
emitEvent('onTestEvent');
|
||||
},
|
||||
};
|
||||
|
||||
// TODO remove these when RN/www gets updated
|
||||
(ReactDebugTool: any).addDevtool = ReactDebugTool.addHook;
|
||||
(ReactDebugTool: any).removeDevtool = ReactDebugTool.removeHook;
|
||||
|
||||
ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
|
||||
ReactDebugTool.addHook(ReactComponentTreeHook);
|
||||
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
|
||||
if ((/[?&]react_perf\b/).test(url)) {
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
|
||||
module.exports = ReactDebugTool;
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright 2016-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 ReactInstrumentation
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Trust the developer to only use ReactInstrumentation with a __DEV__ check
|
||||
var debugTool = ((null: any): typeof ReactDebugTool);
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
debugTool = ReactDebugTool;
|
||||
}
|
||||
|
||||
module.exports = {debugTool};
|
|
@ -0,0 +1,459 @@
|
|||
/**
|
||||
* Copyright 2016-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 ReactPerf
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDebugTool = require('ReactDebugTool');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
var alreadyWarned = false;
|
||||
|
||||
import type { FlushHistory } from 'ReactDebugTool';
|
||||
|
||||
function roundFloat(val, base = 2) {
|
||||
var n = Math.pow(10, base);
|
||||
return Math.floor(val * n) / n;
|
||||
}
|
||||
|
||||
// Flow type definition of console.table is too strict right now, see
|
||||
// https://github.com/facebook/flow/pull/2353 for updates
|
||||
function consoleTable(table: Array<{[key: string]: any}>): void {
|
||||
console.table((table: any));
|
||||
}
|
||||
|
||||
function warnInProduction() {
|
||||
if (alreadyWarned) {
|
||||
return;
|
||||
}
|
||||
alreadyWarned = true;
|
||||
if (typeof console !== 'undefined') {
|
||||
console.error(
|
||||
'ReactPerf is not supported in the production builds of React. ' +
|
||||
'To collect measurements, please use the development build of React instead.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getLastMeasurements() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
return ReactDebugTool.getFlushHistory();
|
||||
}
|
||||
|
||||
function getExclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) {
|
||||
var {displayName} = treeSnapshot[instanceID];
|
||||
var key = displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
counts: {},
|
||||
durations: {},
|
||||
totalDuration: 0,
|
||||
};
|
||||
}
|
||||
if (!stats.durations[timerType]) {
|
||||
stats.durations[timerType] = 0;
|
||||
}
|
||||
if (!stats.counts[timerType]) {
|
||||
stats.counts[timerType] = 0;
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => {
|
||||
stats.totalDuration += duration;
|
||||
stats.durations[timerType] += duration;
|
||||
stats.counts[timerType]++;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.totalDuration - a.totalDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getInclusive(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
var isCompositeByID = {};
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
isCompositeByID[instanceID] = true;
|
||||
});
|
||||
});
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot} = flush;
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// As we traverse parents, only count inclusive time towards composites.
|
||||
// We know something is a composite if its render() was called.
|
||||
if (isCompositeByID[nextParentID]) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.inclusiveRenderDuration - a.inclusiveRenderDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getWasted(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregatedStats = {};
|
||||
var affectedIDs = {};
|
||||
|
||||
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
var stats = aggregatedStats[key];
|
||||
if (!stats) {
|
||||
affectedIDs[key] = {};
|
||||
stats = aggregatedStats[key] = {
|
||||
key,
|
||||
instanceCount: 0,
|
||||
inclusiveRenderDuration: 0,
|
||||
renderCount: 0,
|
||||
};
|
||||
}
|
||||
affectedIDs[key][instanceID] = true;
|
||||
applyUpdate(stats);
|
||||
}
|
||||
|
||||
flushHistory.forEach(flush => {
|
||||
var {measurements, treeSnapshot, operations} = flush;
|
||||
var isDefinitelyNotWastedByID = {};
|
||||
|
||||
// Find host components associated with an operation in this batch.
|
||||
// Mark all components in their parent tree as definitely not wasted.
|
||||
operations.forEach(operation => {
|
||||
var {instanceID} = operation;
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
isDefinitelyNotWastedByID[nextParentID] = true;
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
|
||||
// Find composite components that rendered in this batch.
|
||||
// These are potential candidates for being wasted renders.
|
||||
var renderedCompositeIDs = {};
|
||||
measurements.forEach(measurement => {
|
||||
var {instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
renderedCompositeIDs[instanceID] = true;
|
||||
});
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
var {duration, instanceID, timerType} = measurement;
|
||||
if (timerType !== 'render') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was a DOM update below this component, or it has just been
|
||||
// mounted, its render() is not considered wasted.
|
||||
var { updateCount } = treeSnapshot[instanceID];
|
||||
if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We consider this render() wasted.
|
||||
updateAggregatedStats(treeSnapshot, instanceID, stats => {
|
||||
stats.renderCount++;
|
||||
});
|
||||
|
||||
var nextParentID = instanceID;
|
||||
while (nextParentID) {
|
||||
// Any parents rendered during this batch are considered wasted
|
||||
// unless we previously marked them as dirty.
|
||||
var isWasted =
|
||||
renderedCompositeIDs[nextParentID] &&
|
||||
!isDefinitelyNotWastedByID[nextParentID];
|
||||
if (isWasted) {
|
||||
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
|
||||
stats.inclusiveRenderDuration += duration;
|
||||
});
|
||||
}
|
||||
nextParentID = treeSnapshot[nextParentID].parentID;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(aggregatedStats)
|
||||
.map(key => ({
|
||||
...aggregatedStats[key],
|
||||
instanceCount: Object.keys(affectedIDs[key]).length,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.inclusiveRenderDuration - a.inclusiveRenderDuration
|
||||
);
|
||||
}
|
||||
|
||||
function getOperations(flushHistory = getLastMeasurements()) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return [];
|
||||
}
|
||||
|
||||
var stats = [];
|
||||
flushHistory.forEach((flush, flushIndex) => {
|
||||
var {operations, treeSnapshot} = flush;
|
||||
operations.forEach(operation => {
|
||||
var {instanceID, type, payload} = operation;
|
||||
var {displayName, ownerID} = treeSnapshot[instanceID];
|
||||
var owner = treeSnapshot[ownerID];
|
||||
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
|
||||
|
||||
stats.push({
|
||||
flushIndex,
|
||||
instanceID,
|
||||
key,
|
||||
type,
|
||||
ownerID,
|
||||
payload,
|
||||
});
|
||||
});
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
|
||||
function printExclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getExclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, totalDuration} = item;
|
||||
var renderCount = item.counts.render || 0;
|
||||
var renderDuration = item.durations.render || 0;
|
||||
return {
|
||||
'Component': key,
|
||||
'Total time (ms)': roundFloat(totalDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Total render time (ms)': roundFloat(renderDuration),
|
||||
'Average render time (ms)': renderCount ?
|
||||
roundFloat(renderDuration / renderCount) :
|
||||
undefined,
|
||||
'Render count': renderCount,
|
||||
'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration),
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printInclusive(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getInclusive(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printWasted(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getWasted(flushHistory);
|
||||
var table = stats.map(item => {
|
||||
var {key, instanceCount, inclusiveRenderDuration, renderCount} = item;
|
||||
return {
|
||||
'Owner > Component': key,
|
||||
'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration),
|
||||
'Instance count': instanceCount,
|
||||
'Render count': renderCount,
|
||||
};
|
||||
});
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
function printOperations(flushHistory?: FlushHistory) {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
var stats = getOperations(flushHistory);
|
||||
var table = stats.map(stat => ({
|
||||
'Owner > Node': stat.key,
|
||||
'Operation': stat.type,
|
||||
'Payload': typeof stat.payload === 'object' ?
|
||||
JSON.stringify(stat.payload) :
|
||||
stat.payload,
|
||||
'Flush index': stat.flushIndex,
|
||||
'Owner Component ID': stat.ownerID,
|
||||
'DOM Component ID': stat.instanceID,
|
||||
}));
|
||||
consoleTable(table);
|
||||
}
|
||||
|
||||
var warnedAboutPrintDOM = false;
|
||||
function printDOM(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutPrintDOM,
|
||||
'`ReactPerf.printDOM(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.printOperations(...)` instead.'
|
||||
);
|
||||
warnedAboutPrintDOM = true;
|
||||
return printOperations(measurements);
|
||||
}
|
||||
|
||||
var warnedAboutGetMeasurementsSummaryMap = false;
|
||||
function getMeasurementsSummaryMap(measurements: FlushHistory) {
|
||||
warning(
|
||||
warnedAboutGetMeasurementsSummaryMap,
|
||||
'`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' +
|
||||
'`ReactPerf.getWasted(...)` instead.'
|
||||
);
|
||||
warnedAboutGetMeasurementsSummaryMap = true;
|
||||
return getWasted(measurements);
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.beginProfiling();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDebugTool.endProfiling();
|
||||
}
|
||||
|
||||
function isRunning() {
|
||||
if (!__DEV__) {
|
||||
warnInProduction();
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReactDebugTool.isProfiling();
|
||||
}
|
||||
|
||||
var ReactPerfAnalysis = {
|
||||
getLastMeasurements,
|
||||
getExclusive,
|
||||
getInclusive,
|
||||
getWasted,
|
||||
getOperations,
|
||||
printExclusive,
|
||||
printInclusive,
|
||||
printWasted,
|
||||
printOperations,
|
||||
start,
|
||||
stop,
|
||||
isRunning,
|
||||
// Deprecated:
|
||||
printDOM,
|
||||
getMeasurementsSummaryMap,
|
||||
};
|
||||
|
||||
module.exports = ReactPerfAnalysis;
|
|
@ -0,0 +1,267 @@
|
|||
/**
|
||||
* 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 ReactChildFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
import type { ReactNodeList } from 'ReactTypes';
|
||||
|
||||
var REACT_ELEMENT_TYPE = require('ReactElementSymbol');
|
||||
var {
|
||||
REACT_COROUTINE_TYPE,
|
||||
REACT_YIELD_TYPE,
|
||||
} = require('ReactCoroutine');
|
||||
|
||||
var ReactFiber = require('ReactFiber');
|
||||
var ReactReifiedYield = require('ReactReifiedYield');
|
||||
|
||||
const {
|
||||
cloneFiber,
|
||||
createFiberFromElement,
|
||||
createFiberFromCoroutine,
|
||||
createFiberFromYield,
|
||||
} = ReactFiber;
|
||||
|
||||
const {
|
||||
createReifiedYield,
|
||||
} = ReactReifiedYield;
|
||||
|
||||
const isArray = Array.isArray;
|
||||
|
||||
function ChildReconciler(shouldClone) {
|
||||
|
||||
function createSubsequentChild(
|
||||
returnFiber : Fiber,
|
||||
existingChild : ?Fiber,
|
||||
previousSibling : Fiber,
|
||||
newChildren,
|
||||
priority : PriorityLevel
|
||||
) : Fiber {
|
||||
if (typeof newChildren !== 'object' || newChildren === null) {
|
||||
return previousSibling;
|
||||
}
|
||||
|
||||
switch (newChildren.$$typeof) {
|
||||
case REACT_ELEMENT_TYPE: {
|
||||
const element = (newChildren : ReactElement<any>);
|
||||
if (existingChild &&
|
||||
element.type === existingChild.type &&
|
||||
element.key === existingChild.key) {
|
||||
// TODO: This is not sufficient since previous siblings could be new.
|
||||
// Will fix reconciliation properly later.
|
||||
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
|
||||
if (!shouldClone) {
|
||||
// TODO: This might be lowering the priority of nested unfinished work.
|
||||
clone.pendingWorkPriority = priority;
|
||||
}
|
||||
clone.pendingProps = element.props;
|
||||
clone.sibling = null;
|
||||
clone.return = returnFiber;
|
||||
previousSibling.sibling = clone;
|
||||
return clone;
|
||||
}
|
||||
const child = createFiberFromElement(element, priority);
|
||||
previousSibling.sibling = child;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_COROUTINE_TYPE: {
|
||||
const coroutine = (newChildren : ReactCoroutine);
|
||||
const child = createFiberFromCoroutine(coroutine, priority);
|
||||
previousSibling.sibling = child;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_YIELD_TYPE: {
|
||||
const yieldNode = (newChildren : ReactYield);
|
||||
const reifiedYield = createReifiedYield(yieldNode);
|
||||
const child = createFiberFromYield(yieldNode, priority);
|
||||
child.output = reifiedYield;
|
||||
previousSibling.sibling = child;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChildren)) {
|
||||
let prev : Fiber = previousSibling;
|
||||
let existing : ?Fiber = existingChild;
|
||||
for (var i = 0; i < newChildren.length; i++) {
|
||||
var nextExisting = existing && existing.sibling;
|
||||
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||||
if (prev && existing) {
|
||||
// TODO: This is not correct because there could've been more
|
||||
// than one sibling consumed but I don't want to return a tuple.
|
||||
existing = nextExisting;
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
} else {
|
||||
// TODO: Throw for unknown children.
|
||||
return previousSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function createFirstChild(returnFiber, existingChild, newChildren, priority) {
|
||||
if (typeof newChildren !== 'object' || newChildren === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (newChildren.$$typeof) {
|
||||
case REACT_ELEMENT_TYPE: {
|
||||
/* $FlowFixMe(>=0.31.0): This is an unsafe cast. Consider adding a type
|
||||
* annotation to the `newChildren` param of this
|
||||
* function.
|
||||
*/
|
||||
const element = (newChildren : ReactElement<any>);
|
||||
if (existingChild &&
|
||||
element.type === existingChild.type &&
|
||||
element.key === existingChild.key) {
|
||||
// Get the clone of the existing fiber.
|
||||
const clone = shouldClone ? cloneFiber(existingChild, priority) : existingChild;
|
||||
if (!shouldClone) {
|
||||
// TODO: This might be lowering the priority of nested unfinished work.
|
||||
clone.pendingWorkPriority = priority;
|
||||
}
|
||||
clone.pendingProps = element.props;
|
||||
clone.sibling = null;
|
||||
clone.return = returnFiber;
|
||||
return clone;
|
||||
}
|
||||
const child = createFiberFromElement(element, priority);
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_COROUTINE_TYPE: {
|
||||
/* $FlowFixMe(>=0.31.0): No 'handler' property found in object type
|
||||
*/
|
||||
const coroutine = (newChildren : ReactCoroutine);
|
||||
const child = createFiberFromCoroutine(coroutine, priority);
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
|
||||
case REACT_YIELD_TYPE: {
|
||||
// A yield results in a fragment fiber whose output is the continuation.
|
||||
// TODO: When there is only a single child, we can optimize this to avoid
|
||||
// the fragment.
|
||||
/* $FlowFixMe(>=0.31.0): No 'continuation' property found in object
|
||||
* type
|
||||
*/
|
||||
const yieldNode = (newChildren : ReactYield);
|
||||
const reifiedYield = createReifiedYield(yieldNode);
|
||||
const child = createFiberFromYield(yieldNode, priority);
|
||||
child.output = reifiedYield;
|
||||
child.return = returnFiber;
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChildren)) {
|
||||
var first : ?Fiber = null;
|
||||
var prev : ?Fiber = null;
|
||||
var existing : ?Fiber = existingChild;
|
||||
/* $FlowIssue(>=0.31.0) #12747709
|
||||
*
|
||||
* `Array.isArray` is matched syntactically for now until predicate
|
||||
* support is complete.
|
||||
*/
|
||||
for (var i = 0; i < newChildren.length; i++) {
|
||||
var nextExisting = existing && existing.sibling;
|
||||
if (prev == null) {
|
||||
prev = createFirstChild(returnFiber, existing, newChildren[i], priority);
|
||||
first = prev;
|
||||
} else {
|
||||
prev = createSubsequentChild(returnFiber, existing, prev, newChildren[i], priority);
|
||||
}
|
||||
if (prev && existing) {
|
||||
// TODO: This is not correct because there could've been more
|
||||
// than one sibling consumed but I don't want to return a tuple.
|
||||
existing = nextExisting;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
} else {
|
||||
// TODO: Throw for unknown children.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This API won't work because we'll need to transfer the side-effects of
|
||||
// unmounting children to the returnFiber.
|
||||
function reconcileChildFibers(
|
||||
returnFiber : Fiber,
|
||||
currentFirstChild : ?Fiber,
|
||||
newChildren : ReactNodeList,
|
||||
priority : PriorityLevel
|
||||
) : ?Fiber {
|
||||
return createFirstChild(returnFiber, currentFirstChild, newChildren, priority);
|
||||
}
|
||||
|
||||
return reconcileChildFibers;
|
||||
}
|
||||
|
||||
exports.reconcileChildFibers = ChildReconciler(true);
|
||||
|
||||
exports.reconcileChildFibersInPlace = ChildReconciler(false);
|
||||
|
||||
|
||||
function cloneSiblings(current : Fiber, workInProgress : Fiber, returnFiber : Fiber) {
|
||||
workInProgress.return = returnFiber;
|
||||
while (current.sibling) {
|
||||
current = current.sibling;
|
||||
workInProgress = workInProgress.sibling = cloneFiber(
|
||||
current,
|
||||
current.pendingWorkPriority
|
||||
);
|
||||
workInProgress.return = returnFiber;
|
||||
}
|
||||
workInProgress.sibling = null;
|
||||
}
|
||||
|
||||
exports.cloneChildFibers = function(current : ?Fiber, workInProgress : Fiber) {
|
||||
if (!workInProgress.child) {
|
||||
return;
|
||||
}
|
||||
if (current && workInProgress.child === current.child) {
|
||||
// We use workInProgress.child since that lets Flow know that it can't be
|
||||
// null since we validated that already. However, as the line above suggests
|
||||
// they're actually the same thing.
|
||||
const currentChild = workInProgress.child;
|
||||
// TODO: This used to reset the pending priority. Not sure if that is needed.
|
||||
// workInProgress.pendingWorkPriority = current.pendingWorkPriority;
|
||||
// TODO: The below priority used to be set to NoWork which would've
|
||||
// dropped work. This is currently unobservable but will become
|
||||
// observable when the first sibling has lower priority work remaining
|
||||
// than the next sibling. At that point we should add tests that catches
|
||||
// this.
|
||||
const newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
|
||||
workInProgress.child = newChild;
|
||||
cloneSiblings(currentChild, newChild, workInProgress);
|
||||
}
|
||||
|
||||
// If there is no alternate, then we don't need to clone the children.
|
||||
// If the children of the alternate fiber is a different set, then we don't
|
||||
// need to clone. We need to reset the return fiber though since we'll
|
||||
// traverse down into them.
|
||||
let child = workInProgress.child;
|
||||
while (child) {
|
||||
child.return = workInProgress;
|
||||
child = child.sibling;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,281 @@
|
|||
/**
|
||||
* 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 ReactFiber
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
import type { TypeOfWork } from 'ReactTypeOfWork';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
YieldComponent,
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
var {
|
||||
NoWork,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
// An Instance is shared between all versions of a component. We can easily
|
||||
// break this out into a separate object to avoid copying so much to the
|
||||
// alternate versions of the tree. We put this on a single object for now to
|
||||
// minimize the number of objects created during the initial render.
|
||||
type Instance = {
|
||||
|
||||
// Tag identifying the type of fiber.
|
||||
tag: TypeOfWork,
|
||||
|
||||
// Unique identifier of this child.
|
||||
key: null | string,
|
||||
|
||||
// The function/class/module associated with this fiber.
|
||||
type: any,
|
||||
|
||||
// The local state associated with this fiber.
|
||||
stateNode: any,
|
||||
|
||||
// Conceptual aliases
|
||||
// parent : Instance -> return The parent happens to be the same as the
|
||||
// return fiber since we've merged the fiber and instance.
|
||||
|
||||
};
|
||||
|
||||
// A Fiber is work on a Component that needs to be done or was done. There can
|
||||
// be more than one per component.
|
||||
export type Fiber = Instance & {
|
||||
|
||||
// The Fiber to return to after finishing processing this one.
|
||||
// This is effectively the parent, but there can be multiple parents (two)
|
||||
// so this is only the parent of the thing we're currently processing.
|
||||
// It is conceptually the same as the return address of a stack frame.
|
||||
return: ?Fiber,
|
||||
|
||||
// Singly Linked List Tree Structure.
|
||||
child: ?Fiber,
|
||||
sibling: ?Fiber,
|
||||
|
||||
// The ref last used to attach this node.
|
||||
// I'll avoid adding an owner field for prod and model that as functions.
|
||||
ref: null | (handle : ?Object) => void,
|
||||
|
||||
// Input is the data coming into process this fiber. Arguments. Props.
|
||||
pendingProps: any, // This type will be more specific once we overload the tag.
|
||||
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
|
||||
memoizedProps: any, // The props used to create the output.
|
||||
// A queue of local state updates.
|
||||
updateQueue: ?UpdateQueue,
|
||||
// The state used to create the output. This is a full state object.
|
||||
memoizedState: any,
|
||||
// Linked list of callbacks to call after updates are committed.
|
||||
callbackList: ?UpdateQueue,
|
||||
// Output is the return value of this fiber, or a linked list of return values
|
||||
// if this returns multiple values. Such as a fragment.
|
||||
output: any, // This type will be more specific once we overload the tag.
|
||||
|
||||
// Singly linked list fast path to the next fiber with side-effects.
|
||||
nextEffect: ?Fiber,
|
||||
|
||||
// The first and last fiber with side-effect within this subtree. This allows
|
||||
// us to reuse a slice of the linked list when we reuse the work done within
|
||||
// this fiber.
|
||||
firstEffect: ?Fiber,
|
||||
lastEffect: ?Fiber,
|
||||
|
||||
// This will be used to quickly determine if a subtree has no pending changes.
|
||||
pendingWorkPriority: PriorityLevel,
|
||||
|
||||
// This value represents the priority level that was last used to process this
|
||||
// component. This indicates whether it is better to continue from the
|
||||
// progressed work or if it is better to continue from the current state.
|
||||
progressedPriority: PriorityLevel,
|
||||
|
||||
// If work bails out on a Fiber that already had some work started at a lower
|
||||
// priority, then we need to store the progressed work somewhere. This holds
|
||||
// the started child set until we need to get back to working on it. It may
|
||||
// or may not be the same as the "current" child.
|
||||
progressedChild: ?Fiber,
|
||||
|
||||
// This is a pooled version of a Fiber. Every fiber that gets updated will
|
||||
// eventually have a pair. There are cases when we can clean up pairs to save
|
||||
// memory if we need to.
|
||||
alternate: ?Fiber,
|
||||
|
||||
// Conceptual aliases
|
||||
// workInProgress : Fiber -> alternate The alternate used for reuse happens
|
||||
// to be the same as work in progress.
|
||||
|
||||
};
|
||||
|
||||
// This is a constructor of a POJO instead of a constructor function for a few
|
||||
// reasons:
|
||||
// 1) Nobody should add any instance methods on this. Instance methods can be
|
||||
// more difficult to predict when they get optimized and they are almost
|
||||
// never inlined properly in static compilers.
|
||||
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
|
||||
// always know when it is a fiber.
|
||||
// 3) We can easily go from a createFiber call to calling a constructor if that
|
||||
// is faster. The opposite is not true.
|
||||
// 4) We might want to experiment with using numeric keys since they are easier
|
||||
// to optimize in a non-JIT environment.
|
||||
// 5) It should be easy to port this to a C struct and keep a C implementation
|
||||
// compatible.
|
||||
var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
|
||||
return {
|
||||
|
||||
// Instance
|
||||
|
||||
tag: tag,
|
||||
|
||||
key: key,
|
||||
|
||||
type: null,
|
||||
|
||||
stateNode: null,
|
||||
|
||||
// Fiber
|
||||
|
||||
return: null,
|
||||
|
||||
child: null,
|
||||
sibling: null,
|
||||
|
||||
ref: null,
|
||||
|
||||
pendingProps: null,
|
||||
memoizedProps: null,
|
||||
updateQueue: null,
|
||||
memoizedState: null,
|
||||
callbackList: null,
|
||||
output: null,
|
||||
|
||||
nextEffect: null,
|
||||
firstEffect: null,
|
||||
lastEffect: null,
|
||||
|
||||
pendingWorkPriority: NoWork,
|
||||
progressedPriority: NoWork,
|
||||
progressedChild: null,
|
||||
|
||||
alternate: null,
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
function shouldConstruct(Component) {
|
||||
return !!(Component.prototype && Component.prototype.isReactComponent);
|
||||
}
|
||||
|
||||
// This is used to create an alternate fiber to do work on.
|
||||
// TODO: Rename to createWorkInProgressFiber or something like that.
|
||||
exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fiber {
|
||||
// We clone to get a work in progress. That means that this fiber is the
|
||||
// current. To make it safe to reuse that fiber later on as work in progress
|
||||
// we need to reset its work in progress flag now. We don't have an
|
||||
// opportunity to do this earlier since we don't traverse the tree when
|
||||
// the work in progress tree becomes the current tree.
|
||||
// fiber.progressedPriority = NoWork;
|
||||
// fiber.progressedChild = null;
|
||||
|
||||
// We use a double buffering pooling technique because we know that we'll only
|
||||
// ever need at most two versions of a tree. We pool the "other" unused node
|
||||
// that we're free to reuse. This is lazily created to avoid allocating extra
|
||||
// objects for things that are never updated. It also allow us to reclaim the
|
||||
// extra memory if needed.
|
||||
let alt = fiber.alternate;
|
||||
if (alt) {
|
||||
// Whenever we clone, we do so to get a new work in progress.
|
||||
// This ensures that we've reset these in the new tree.
|
||||
alt.nextEffect = null;
|
||||
alt.firstEffect = null;
|
||||
alt.lastEffect = null;
|
||||
} else {
|
||||
// This should not have an alternate already
|
||||
alt = createFiber(fiber.tag, fiber.key);
|
||||
alt.type = fiber.type;
|
||||
|
||||
alt.progressedChild = fiber.progressedChild;
|
||||
alt.progressedPriority = fiber.progressedPriority;
|
||||
|
||||
alt.alternate = fiber;
|
||||
fiber.alternate = alt;
|
||||
}
|
||||
|
||||
alt.stateNode = fiber.stateNode;
|
||||
alt.child = fiber.child;
|
||||
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
|
||||
alt.ref = fiber.ref;
|
||||
// pendingProps is here for symmetry but is unnecessary in practice for now.
|
||||
// TODO: Pass in the new pendingProps as an argument maybe?
|
||||
alt.pendingProps = fiber.pendingProps;
|
||||
alt.updateQueue = fiber.updateQueue;
|
||||
alt.callbackList = fiber.callbackList;
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
alt.memoizedProps = fiber.memoizedProps;
|
||||
alt.output = fiber.output;
|
||||
|
||||
return alt;
|
||||
};
|
||||
|
||||
exports.createHostContainerFiber = function() {
|
||||
const fiber = createFiber(HostContainer, null);
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromElement = function(element : ReactElement<*>, priorityLevel : PriorityLevel) {
|
||||
// $FlowFixMe: ReactElement.key is currently defined as ?string but should be defined as null | string in Flow.
|
||||
const fiber = createFiberFromElementType(element.type, element.key);
|
||||
fiber.pendingProps = element.props;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
function createFiberFromElementType(type : mixed, key : null | string) {
|
||||
let fiber;
|
||||
if (typeof type === 'function') {
|
||||
fiber = shouldConstruct(type) ?
|
||||
createFiber(ClassComponent, key) :
|
||||
createFiber(IndeterminateComponent, key);
|
||||
fiber.type = type;
|
||||
} else if (typeof type === 'string') {
|
||||
fiber = createFiber(HostComponent, key);
|
||||
fiber.type = type;
|
||||
} else if (typeof type === 'object' && type !== null) {
|
||||
// Currently assumed to be a continuation and therefore is a fiber already.
|
||||
fiber = type;
|
||||
} else {
|
||||
throw new Error('Unknown component type: ' + typeof type);
|
||||
}
|
||||
return fiber;
|
||||
}
|
||||
|
||||
exports.createFiberFromElementType = createFiberFromElementType;
|
||||
|
||||
exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priorityLevel : PriorityLevel) {
|
||||
const fiber = createFiber(CoroutineComponent, coroutine.key);
|
||||
fiber.type = coroutine.handler;
|
||||
fiber.pendingProps = coroutine;
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
return fiber;
|
||||
};
|
||||
|
||||
exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) {
|
||||
const fiber = createFiber(YieldComponent, yieldNode.key);
|
||||
fiber.pendingProps = {};
|
||||
return fiber;
|
||||
};
|
|
@ -0,0 +1,423 @@
|
|||
/**
|
||||
* 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 ReactFiberBeginWork
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactCoroutine } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { Scheduler } from 'ReactFiberScheduler';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var {
|
||||
reconcileChildFibers,
|
||||
reconcileChildFibersInPlace,
|
||||
cloneChildFibers,
|
||||
} = require('ReactChildFiber');
|
||||
var { LowPriority } = require('ReactPriorityLevel');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
} = ReactTypeOfWork;
|
||||
var {
|
||||
NoWork,
|
||||
OffscreenPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
var {
|
||||
createUpdateQueue,
|
||||
addToQueue,
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
// We now have clones. Let's store them as the currently progressed work.
|
||||
workInProgress.progressedChild = workInProgress.child;
|
||||
workInProgress.progressedPriority = priorityLevel;
|
||||
if (current) {
|
||||
// We also store it on the current. When the alternate swaps in we can
|
||||
// continue from this point.
|
||||
current.progressedChild = workInProgress.progressedChild;
|
||||
current.progressedPriority = workInProgress.progressedPriority;
|
||||
}
|
||||
}
|
||||
|
||||
function reconcileChildren(current, workInProgress, nextChildren) {
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
|
||||
}
|
||||
|
||||
function reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel) {
|
||||
// At this point any memoization is no longer valid since we'll have changed
|
||||
// the children.
|
||||
workInProgress.memoizedProps = null;
|
||||
if (current && current.child === workInProgress.child) {
|
||||
// If the current child is the same as the work in progress, it means that
|
||||
// we haven't yet started any work on these children. Therefore, we use
|
||||
// the clone algorithm to create a copy of all the current children.
|
||||
workInProgress.child = reconcileChildFibers(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel
|
||||
);
|
||||
} else {
|
||||
// If, on the other hand, we don't have a current fiber or if it is
|
||||
// already using a clone, that means we've already begun some work on this
|
||||
// tree and we can continue where we left off by reconciling against the
|
||||
// existing children.
|
||||
workInProgress.child = reconcileChildFibersInPlace(
|
||||
workInProgress,
|
||||
workInProgress.child,
|
||||
nextChildren,
|
||||
priorityLevel
|
||||
);
|
||||
}
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
}
|
||||
|
||||
function updateFunctionalComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
|
||||
// TODO: Disable this before release, since it is not part of the public API
|
||||
// I use this for testing to compare the relative overhead of classes.
|
||||
if (typeof fn.shouldComponentUpdate === 'function') {
|
||||
if (workInProgress.memoizedProps !== null) {
|
||||
if (!fn.shouldComponentUpdate(workInProgress.memoizedProps, props)) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nextChildren = fn(props);
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void {
|
||||
const { scheduleDeferredWork } = getScheduler();
|
||||
fiber.updateQueue = updateQueue;
|
||||
// Schedule update on the alternate as well, since we don't know which tree
|
||||
// is current.
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.updateQueue = updateQueue;
|
||||
}
|
||||
while (true) {
|
||||
if (fiber.pendingWorkPriority === NoWork ||
|
||||
fiber.pendingWorkPriority >= priorityLevel) {
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
}
|
||||
if (fiber.alternate) {
|
||||
if (fiber.alternate.pendingWorkPriority === NoWork ||
|
||||
fiber.alternate.pendingWorkPriority >= priorityLevel) {
|
||||
fiber.alternate.pendingWorkPriority = priorityLevel;
|
||||
}
|
||||
}
|
||||
// Duck type root
|
||||
if (fiber.stateNode && fiber.stateNode.containerInfo) {
|
||||
const root : FiberRoot = (fiber.stateNode : any);
|
||||
scheduleDeferredWork(root, priorityLevel);
|
||||
return;
|
||||
}
|
||||
if (!fiber.return) {
|
||||
throw new Error('No root!');
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
enqueueSetState(instance, partialState) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = fiber.updateQueue ?
|
||||
addToQueue(fiber.updateQueue, partialState) :
|
||||
createUpdateQueue(partialState);
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueReplaceState(instance, state) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = createUpdateQueue(state);
|
||||
updateQueue.isReplace = true;
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueForceUpdate(instance) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
|
||||
updateQueue.isForced = true;
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueCallback(instance, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
let updateQueue = fiber.updateQueue ?
|
||||
fiber.updateQueue :
|
||||
createUpdateQueue(null);
|
||||
addCallbackToQueue(updateQueue, callback);
|
||||
fiber.updateQueue = updateQueue;
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.updateQueue = updateQueue;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
|
||||
// A class component update is the result of either new props or new state.
|
||||
// Account for the possibly of missing pending props by falling back to the
|
||||
// memoized props.
|
||||
var props = workInProgress.pendingProps;
|
||||
if (!props && current) {
|
||||
props = current.memoizedProps;
|
||||
}
|
||||
// Compute the state using the memoized state and the update queue.
|
||||
var updateQueue = workInProgress.updateQueue;
|
||||
var previousState = current ? current.memoizedState : null;
|
||||
var state = updateQueue ?
|
||||
mergeUpdateQueue(updateQueue, previousState, props) :
|
||||
previousState;
|
||||
|
||||
var instance = workInProgress.stateNode;
|
||||
if (!instance) {
|
||||
var ctor = workInProgress.type;
|
||||
workInProgress.stateNode = instance = new ctor(props);
|
||||
state = instance.state || null;
|
||||
// The initial state must be added to the update queue in case
|
||||
// setState is called before the initial render.
|
||||
if (state !== null) {
|
||||
workInProgress.updateQueue = createUpdateQueue(state);
|
||||
}
|
||||
// The instance needs access to the fiber so that it can schedule updates
|
||||
ReactInstanceMap.set(instance, workInProgress);
|
||||
instance.updater = updater;
|
||||
} else if (typeof instance.shouldComponentUpdate === 'function' &&
|
||||
!(updateQueue && updateQueue.isForced)) {
|
||||
if (workInProgress.memoizedProps !== null) {
|
||||
// Reset the props, in case this is a ping-pong case rather than a
|
||||
// completed update case. For the completed update case, the instance
|
||||
// props will already be the memoizedProps.
|
||||
instance.props = workInProgress.memoizedProps;
|
||||
instance.state = workInProgress.memoizedState;
|
||||
if (!instance.shouldComponentUpdate(props, state)) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
var nextChildren = instance.render();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateHostComponent(current, workInProgress) {
|
||||
const nextChildren = workInProgress.pendingProps.children;
|
||||
if (workInProgress.pendingProps.hidden &&
|
||||
workInProgress.pendingWorkPriority !== OffscreenPriority) {
|
||||
// If this host component is hidden, we can bail out on the children.
|
||||
// We'll rerender the children later at the lower priority.
|
||||
|
||||
// It is unfortunate that we have to do the reconciliation of these
|
||||
// children already since that will add them to the tree even though
|
||||
// they are not actually done yet. If this is a large set it is also
|
||||
// confusing that this takes time to do right now instead of later.
|
||||
|
||||
if (workInProgress.progressedPriority === OffscreenPriority) {
|
||||
// If we already made some progress on the offscreen priority before,
|
||||
// then we should continue from where we left off.
|
||||
workInProgress.child = workInProgress.progressedChild;
|
||||
}
|
||||
|
||||
// Reconcile the children and stash them for later work.
|
||||
reconcileChildrenAtPriority(current, workInProgress, nextChildren, OffscreenPriority);
|
||||
workInProgress.child = current ? current.child : null;
|
||||
// Abort and don't process children yet.
|
||||
return null;
|
||||
} else {
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
|
||||
function mountIndeterminateComponent(current, workInProgress) {
|
||||
var fn = workInProgress.type;
|
||||
var props = workInProgress.pendingProps;
|
||||
var value = fn(props);
|
||||
if (typeof value === 'object' && value && typeof value.render === 'function') {
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
if (current) {
|
||||
current.tag = ClassComponent;
|
||||
}
|
||||
value = value.render();
|
||||
} else {
|
||||
// Proceed under the assumption that this is a functional component
|
||||
workInProgress.tag = FunctionalComponent;
|
||||
if (current) {
|
||||
current.tag = FunctionalComponent;
|
||||
}
|
||||
}
|
||||
reconcileChildren(current, workInProgress, value);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateCoroutineComponent(current, workInProgress) {
|
||||
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
}
|
||||
reconcileChildren(current, workInProgress, coroutine.children);
|
||||
}
|
||||
|
||||
/*
|
||||
function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
|
||||
let child = firstChild;
|
||||
do {
|
||||
// Ensure that the first and last effect of the parent corresponds
|
||||
// to the children's first and last effect.
|
||||
if (!returnFiber.firstEffect) {
|
||||
returnFiber.firstEffect = child.firstEffect;
|
||||
}
|
||||
if (child.lastEffect) {
|
||||
if (returnFiber.lastEffect) {
|
||||
returnFiber.lastEffect.nextEffect = child.firstEffect;
|
||||
}
|
||||
returnFiber.lastEffect = child.lastEffect;
|
||||
}
|
||||
} while (child = child.sibling);
|
||||
}
|
||||
*/
|
||||
|
||||
function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : ?Fiber {
|
||||
const priorityLevel = workInProgress.pendingWorkPriority;
|
||||
|
||||
// TODO: We should ideally be able to bail out early if the children have no
|
||||
// more work to do. However, since we don't have a separation of this
|
||||
// Fiber's priority and its children yet - we don't know without doing lots
|
||||
// of the same work we do anyway. Once we have that separation we can just
|
||||
// bail out here if the children has no more work at this priority level.
|
||||
// if (workInProgress.priorityOfChildren <= priorityLevel) {
|
||||
// // If there are side-effects in these children that have not yet been
|
||||
// // committed we need to ensure that they get properly transferred up.
|
||||
// if (current && current.child !== workInProgress.child) {
|
||||
// reuseChildrenEffects(workInProgress, child);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
cloneChildFibers(current, workInProgress);
|
||||
markChildAsProgressed(current, workInProgress, priorityLevel);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function bailoutOnLowPriority(current, workInProgress) {
|
||||
if (current) {
|
||||
workInProgress.child = current.child;
|
||||
workInProgress.memoizedProps = current.memoizedProps;
|
||||
workInProgress.output = current.output;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
|
||||
if (workInProgress.pendingWorkPriority === NoWork ||
|
||||
workInProgress.pendingWorkPriority > priorityLevel) {
|
||||
return bailoutOnLowPriority(current, workInProgress);
|
||||
}
|
||||
|
||||
if (workInProgress.progressedPriority === priorityLevel) {
|
||||
// If we have progressed work on this priority level already, we can
|
||||
// proceed this that as the child.
|
||||
workInProgress.child = workInProgress.progressedChild;
|
||||
}
|
||||
|
||||
if ((workInProgress.pendingProps === null || (
|
||||
workInProgress.memoizedProps !== null &&
|
||||
workInProgress.pendingProps === workInProgress.memoizedProps
|
||||
)) &&
|
||||
workInProgress.updateQueue === null) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
case IndeterminateComponent:
|
||||
return mountIndeterminateComponent(current, workInProgress);
|
||||
case FunctionalComponent:
|
||||
return updateFunctionalComponent(current, workInProgress);
|
||||
case ClassComponent:
|
||||
return updateClassComponent(current, workInProgress);
|
||||
case HostContainer:
|
||||
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
if (workInProgress.child) {
|
||||
return beginWork(
|
||||
workInProgress.child.alternate,
|
||||
workInProgress.child,
|
||||
priorityLevel
|
||||
);
|
||||
}
|
||||
return null;
|
||||
case HostComponent:
|
||||
if (workInProgress.stateNode && config.beginUpdate) {
|
||||
config.beginUpdate(workInProgress.stateNode);
|
||||
}
|
||||
return updateHostComponent(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
// This is a restart. Reset the tag to the initial phase.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
// Intentionally fall through since this is now the same.
|
||||
case CoroutineComponent:
|
||||
updateCoroutineComponent(current, workInProgress);
|
||||
// This doesn't take arbitrary time so we could synchronously just begin
|
||||
// eagerly do the work of workInProgress.child as an optimization.
|
||||
if (workInProgress.child) {
|
||||
return beginWork(
|
||||
workInProgress.child.alternate,
|
||||
workInProgress.child,
|
||||
priorityLevel
|
||||
);
|
||||
}
|
||||
return workInProgress.child;
|
||||
case YieldComponent:
|
||||
// A yield component is just a placeholder, we can just run through the
|
||||
// next one immediately.
|
||||
if (workInProgress.sibling) {
|
||||
return beginWork(
|
||||
workInProgress.sibling.alternate,
|
||||
workInProgress.sibling,
|
||||
priorityLevel
|
||||
);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
beginWork,
|
||||
};
|
||||
|
||||
};
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* 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 ReactFiberCommitWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
} = ReactTypeOfWork;
|
||||
var { callCallbacks } = require('ReactFiberUpdateQueue');
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
const updateContainer = config.updateContainer;
|
||||
const commitUpdate = config.commitUpdate;
|
||||
|
||||
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
// Clear updates from current fiber. This must go before the callbacks
|
||||
// are reset, in case an update is triggered from inside a callback. Is
|
||||
// this safe? Relies on the assumption that work is only committed if
|
||||
// the update queue is empty.
|
||||
if (finishedWork.alternate) {
|
||||
finishedWork.alternate.updateQueue = null;
|
||||
}
|
||||
if (finishedWork.callbackList) {
|
||||
const { callbackList } = finishedWork;
|
||||
finishedWork.callbackList = null;
|
||||
callCallbacks(callbackList, finishedWork.stateNode);
|
||||
}
|
||||
// TODO: Fire componentDidMount/componentDidUpdate, update refs
|
||||
return;
|
||||
}
|
||||
case HostContainer: {
|
||||
// TODO: Attach children to root container.
|
||||
const children = finishedWork.output;
|
||||
const root : FiberRoot = finishedWork.stateNode;
|
||||
const containerInfo : C = root.containerInfo;
|
||||
updateContainer(containerInfo, children);
|
||||
return;
|
||||
}
|
||||
case HostComponent: {
|
||||
if (finishedWork.stateNode == null || !current) {
|
||||
throw new Error('This should only be done during updates.');
|
||||
}
|
||||
// Commit the work prepared earlier.
|
||||
const child = finishedWork.child;
|
||||
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
|
||||
const newProps = finishedWork.memoizedProps;
|
||||
const oldProps = current.memoizedProps;
|
||||
const instance : I = finishedWork.stateNode;
|
||||
commitUpdate(instance, oldProps, newProps, children);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new Error('This unit of work tag should not have side-effects.');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
commitWork,
|
||||
};
|
||||
|
||||
};
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* 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 ReactFiberCompleteWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactCoroutine } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { ReifiedYield } from 'ReactReifiedYield';
|
||||
|
||||
var { reconcileChildFibers } = require('ReactChildFiber');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
FunctionalComponent,
|
||||
ClassComponent,
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
CoroutineComponent,
|
||||
CoroutineHandlerPhase,
|
||||
YieldComponent,
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
const createInstance = config.createInstance;
|
||||
const prepareUpdate = config.prepareUpdate;
|
||||
|
||||
function markForPreEffect(workInProgress : Fiber) {
|
||||
// Schedule a side-effect on this fiber, BEFORE the children's side-effects.
|
||||
if (workInProgress.firstEffect) {
|
||||
workInProgress.nextEffect = workInProgress.firstEffect;
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
} else {
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
workInProgress.lastEffect = workInProgress;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: It's possible this will create layout thrash issues because mutations
|
||||
// of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
|
||||
// of a sibling reads, then the next sibling updates and reads etc.
|
||||
function markForPostEffect(workInProgress : Fiber) {
|
||||
// Schedule a side-effect on this fiber, AFTER the children's side-effects.
|
||||
if (workInProgress.lastEffect) {
|
||||
workInProgress.lastEffect.nextEffect = workInProgress;
|
||||
} else {
|
||||
workInProgress.firstEffect = workInProgress;
|
||||
}
|
||||
workInProgress.lastEffect = workInProgress;
|
||||
}
|
||||
|
||||
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
|
||||
// If we have a single result, we just pass that through as the output to
|
||||
// avoid unnecessary traversal. When we have multiple output, we just pass
|
||||
// the linked list of fibers that has the individual output values.
|
||||
returnFiber.output = (child && !child.sibling) ? child.output : child;
|
||||
returnFiber.memoizedProps = returnFiber.pendingProps;
|
||||
}
|
||||
|
||||
function recursivelyFillYields(yields, output : ?Fiber | ?ReifiedYield) {
|
||||
if (!output) {
|
||||
// Ignore nulls etc.
|
||||
} else if (output.tag !== undefined) { // TODO: Fix this fragile duck test.
|
||||
// Detect if this is a fiber, if so it is a fragment result.
|
||||
// $FlowFixMe: Refinement issue.
|
||||
var item = (output : Fiber);
|
||||
do {
|
||||
recursivelyFillYields(yields, item.output);
|
||||
item = item.sibling;
|
||||
} while (item);
|
||||
} else {
|
||||
// $FlowFixMe: Refinement issue. If it is not a Fiber or null, it is a yield
|
||||
yields.push(output);
|
||||
}
|
||||
}
|
||||
|
||||
function moveCoroutineToHandlerPhase(current : ?Fiber, workInProgress : Fiber) {
|
||||
var coroutine = (workInProgress.pendingProps : ?ReactCoroutine);
|
||||
if (!coroutine) {
|
||||
throw new Error('Should be resolved by now');
|
||||
}
|
||||
|
||||
// First step of the coroutine has completed. Now we need to do the second.
|
||||
// TODO: It would be nice to have a multi stage coroutine represented by a
|
||||
// single component, or at least tail call optimize nested ones. Currently
|
||||
// that requires additional fields that we don't want to add to the fiber.
|
||||
// So this requires nested handlers.
|
||||
// Note: This doesn't mutate the alternate node. I don't think it needs to
|
||||
// since this stage is reset for every pass.
|
||||
workInProgress.tag = CoroutineHandlerPhase;
|
||||
|
||||
// Build up the yields.
|
||||
// TODO: Compare this to a generator or opaque helpers like Children.
|
||||
var yields : Array<ReifiedYield> = [];
|
||||
var child = workInProgress.child;
|
||||
while (child) {
|
||||
recursivelyFillYields(yields, child.output);
|
||||
child = child.sibling;
|
||||
}
|
||||
var fn = coroutine.handler;
|
||||
var props = coroutine.props;
|
||||
var nextChildren = fn(props, yields);
|
||||
|
||||
var currentFirstChild = current ? current.stateNode : null;
|
||||
// Inherit the priority of the returnFiber.
|
||||
const priority = workInProgress.pendingWorkPriority;
|
||||
workInProgress.stateNode = reconcileChildFibers(
|
||||
workInProgress,
|
||||
currentFirstChild,
|
||||
nextChildren,
|
||||
priority
|
||||
);
|
||||
return workInProgress.stateNode;
|
||||
}
|
||||
|
||||
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
|
||||
switch (workInProgress.tag) {
|
||||
case FunctionalComponent:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
return null;
|
||||
case ClassComponent:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// Don't use the state queue to compute the memoized state. We already
|
||||
// merged it and assigned it to the instance. Transfer it from there.
|
||||
// Also need to transfer the props, because pendingProps will be null
|
||||
// in the case of an update
|
||||
const { state, props } = workInProgress.stateNode;
|
||||
workInProgress.memoizedState = state;
|
||||
workInProgress.memoizedProps = props;
|
||||
// Transfer update queue to callbackList field so callbacks can be
|
||||
// called during commit phase.
|
||||
workInProgress.callbackList = workInProgress.updateQueue;
|
||||
markForPostEffect(workInProgress);
|
||||
return null;
|
||||
case HostContainer:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// We don't know if a container has updated any children so we always
|
||||
// need to update it right now. We schedule this side-effect before
|
||||
// all the other side-effects in the subtree. We need to schedule it
|
||||
// before so that the entire tree is up-to-date before the life-cycles
|
||||
// are invoked.
|
||||
markForPreEffect(workInProgress);
|
||||
return null;
|
||||
case HostComponent:
|
||||
let newProps = workInProgress.pendingProps;
|
||||
const child = workInProgress.child;
|
||||
const children = (child && !child.sibling) ? (child.output : ?Fiber | I) : child;
|
||||
if (current && workInProgress.stateNode != null) {
|
||||
// If we have an alternate, that means this is an update and we need to
|
||||
// schedule a side-effect to do the updates.
|
||||
const oldProps = current.memoizedProps;
|
||||
// If we get updated because one of our children updated, we don't
|
||||
// have newProps so we'll have to reuse them.
|
||||
// TODO: Split the update API as separate for the props vs. children.
|
||||
// Even better would be if children weren't special cased at all tho.
|
||||
if (!newProps) {
|
||||
newProps = oldProps;
|
||||
}
|
||||
const instance : I = workInProgress.stateNode;
|
||||
if (prepareUpdate(instance, oldProps, newProps, children)) {
|
||||
// This returns true if there was something to update.
|
||||
markForPreEffect(workInProgress);
|
||||
}
|
||||
// TODO: Is this actually ever going to change? Why set it every time?
|
||||
workInProgress.output = instance;
|
||||
} else {
|
||||
if (!newProps) {
|
||||
if (workInProgress.stateNode === null) {
|
||||
throw new Error('We must have new props for new mounts.');
|
||||
} else {
|
||||
// This can happen when we abort work.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const instance = createInstance(workInProgress.type, newProps, children);
|
||||
// TODO: This seems like unnecessary duplication.
|
||||
workInProgress.stateNode = instance;
|
||||
workInProgress.output = instance;
|
||||
}
|
||||
workInProgress.memoizedProps = newProps;
|
||||
return null;
|
||||
case CoroutineComponent:
|
||||
return moveCoroutineToHandlerPhase(current, workInProgress);
|
||||
case CoroutineHandlerPhase:
|
||||
transferOutput(workInProgress.stateNode, workInProgress);
|
||||
// Reset the tag to now be a first phase coroutine.
|
||||
workInProgress.tag = CoroutineComponent;
|
||||
return null;
|
||||
case YieldComponent:
|
||||
// Does nothing.
|
||||
return null;
|
||||
|
||||
// Error cases
|
||||
case IndeterminateComponent:
|
||||
throw new Error('An indeterminate component should have become determinate before completing.');
|
||||
default:
|
||||
throw new Error('Unknown unit of work tag');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
completeWork,
|
||||
};
|
||||
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* 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 ReactFiberReconciler
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { TypeOfWork } from 'ReactTypeOfWork';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
var { createFiberRoot } = require('ReactFiberRoot');
|
||||
var ReactFiberScheduler = require('ReactFiberScheduler');
|
||||
|
||||
type Deadline = {
|
||||
timeRemaining : () => number
|
||||
};
|
||||
|
||||
type HostChildNode<I> = { tag: TypeOfWork, output: HostChildren<I>, sibling: any };
|
||||
|
||||
export type HostChildren<I> = null | void | I | HostChildNode<I>;
|
||||
|
||||
export type HostConfig<T, P, I, C> = {
|
||||
|
||||
// TODO: We don't currently have a quick way to detect that children didn't
|
||||
// reorder so we host will always need to check the set. We should make a flag
|
||||
// or something so that it can bailout easily.
|
||||
|
||||
updateContainer(containerInfo : C, children : HostChildren<I>) : void;
|
||||
|
||||
createInstance(type : T, props : P, children : HostChildren<I>) : I,
|
||||
prepareUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : boolean,
|
||||
commitUpdate(instance : I, oldProps : P, newProps : P, children : HostChildren<I>) : void,
|
||||
deleteInstance(instance : I) : void,
|
||||
|
||||
scheduleAnimationCallback(callback : () => void) : void,
|
||||
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void
|
||||
|
||||
};
|
||||
|
||||
type OpaqueNode = Fiber;
|
||||
|
||||
export type Reconciler<C> = {
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
|
||||
unmountContainer(container : OpaqueNode) : void,
|
||||
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
|
||||
|
||||
// Used to extract the return value from the initial render. Legacy API.
|
||||
getPublicRootInstance(container : OpaqueNode) : (C | null),
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) : Reconciler<C> {
|
||||
|
||||
var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);
|
||||
|
||||
return {
|
||||
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode {
|
||||
const root = createFiberRoot(containerInfo);
|
||||
const container = root.current;
|
||||
// TODO: Use pending work/state instead of props.
|
||||
// TODO: This should not override the pendingWorkPriority if there is
|
||||
// higher priority work in the subtree.
|
||||
container.pendingProps = element;
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
// It may seem strange that we don't return the root here, but that will
|
||||
// allow us to have containers that are in the middle of the tree instead
|
||||
// of being roots.
|
||||
return container;
|
||||
},
|
||||
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = element;
|
||||
|
||||
scheduleWork(root);
|
||||
},
|
||||
|
||||
unmountContainer(container : OpaqueNode) : void {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = [];
|
||||
|
||||
scheduleWork(root);
|
||||
},
|
||||
|
||||
performWithPriority,
|
||||
|
||||
getPublicRootInstance(container : OpaqueNode) : (C | null) {
|
||||
return null;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* 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 ReactFiberRoot
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
const { createHostContainerFiber } = require('ReactFiber');
|
||||
|
||||
export type FiberRoot = {
|
||||
// Any additional information from the host associated with this root.
|
||||
containerInfo: any,
|
||||
// The currently active root fiber. This is the mutable root of the tree.
|
||||
current: Fiber,
|
||||
// Determines if this root has already been added to the schedule for work.
|
||||
isScheduled: boolean,
|
||||
// The work schedule is a linked list.
|
||||
nextScheduledRoot: ?FiberRoot,
|
||||
};
|
||||
|
||||
exports.createFiberRoot = function(containerInfo : any) : FiberRoot {
|
||||
// Cyclic construction. This cheats the type system right now because
|
||||
// stateNode is any.
|
||||
const uninitializedFiber = createHostContainerFiber();
|
||||
const root = {
|
||||
current: uninitializedFiber,
|
||||
containerInfo: containerInfo,
|
||||
isScheduled: false,
|
||||
nextScheduledRoot: null,
|
||||
};
|
||||
uninitializedFiber.stateNode = root;
|
||||
return root;
|
||||
};
|
|
@ -0,0 +1,348 @@
|
|||
/**
|
||||
* 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 ReactFiberScheduler
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
|
||||
var ReactFiberBeginWork = require('ReactFiberBeginWork');
|
||||
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
|
||||
var ReactFiberCommitWork = require('ReactFiberCommitWork');
|
||||
|
||||
var { cloneFiber } = require('ReactFiber');
|
||||
|
||||
var {
|
||||
NoWork,
|
||||
LowPriority,
|
||||
AnimationPriority,
|
||||
SynchronousPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
|
||||
export type Scheduler = {
|
||||
scheduleDeferredWork: (root : FiberRoot, priority : PriorityLevel) => void
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
// Use a closure to circumvent the circular dependency between the scheduler
|
||||
// and ReactFiberBeginWork. Don't know if there's a better way to do this.
|
||||
let scheduler;
|
||||
function getScheduler(): Scheduler {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
const { beginWork } = ReactFiberBeginWork(config, getScheduler);
|
||||
const { completeWork } = ReactFiberCompleteWork(config);
|
||||
const { commitWork } = ReactFiberCommitWork(config);
|
||||
|
||||
const scheduleAnimationCallback = config.scheduleAnimationCallback;
|
||||
const scheduleDeferredCallback = config.scheduleDeferredCallback;
|
||||
|
||||
// The default priority to use for updates.
|
||||
let defaultPriority : PriorityLevel = LowPriority;
|
||||
|
||||
// The next work in progress fiber that we're currently working on.
|
||||
let nextUnitOfWork : ?Fiber = null;
|
||||
let nextPriorityLevel : PriorityLevel = NoWork;
|
||||
|
||||
// Linked list of roots with scheduled work on them.
|
||||
let nextScheduledRoot : ?FiberRoot = null;
|
||||
let lastScheduledRoot : ?FiberRoot = null;
|
||||
|
||||
function findNextUnitOfWork() {
|
||||
// Clear out roots with no more work on them.
|
||||
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
|
||||
nextScheduledRoot.isScheduled = false;
|
||||
if (nextScheduledRoot === lastScheduledRoot) {
|
||||
nextScheduledRoot = null;
|
||||
lastScheduledRoot = null;
|
||||
nextPriorityLevel = NoWork;
|
||||
return null;
|
||||
}
|
||||
nextScheduledRoot = nextScheduledRoot.nextScheduledRoot;
|
||||
}
|
||||
// TODO: This is scanning one root at a time. It should be scanning all
|
||||
// roots for high priority work before moving on to lower priorities.
|
||||
let root = nextScheduledRoot;
|
||||
let highestPriorityRoot = null;
|
||||
let highestPriorityLevel = NoWork;
|
||||
while (root) {
|
||||
if (highestPriorityLevel === NoWork ||
|
||||
highestPriorityLevel > root.current.pendingWorkPriority) {
|
||||
highestPriorityLevel = root.current.pendingWorkPriority;
|
||||
highestPriorityRoot = root;
|
||||
}
|
||||
// We didn't find anything to do in this root, so let's try the next one.
|
||||
root = root.nextScheduledRoot;
|
||||
}
|
||||
if (highestPriorityRoot) {
|
||||
nextPriorityLevel = highestPriorityLevel;
|
||||
return cloneFiber(
|
||||
highestPriorityRoot.current,
|
||||
highestPriorityLevel
|
||||
);
|
||||
}
|
||||
|
||||
nextPriorityLevel = NoWork;
|
||||
return null;
|
||||
}
|
||||
|
||||
function commitAllWork(finishedWork : Fiber) {
|
||||
// Commit all the side-effects within a tree.
|
||||
// TODO: Error handling.
|
||||
let effectfulFiber = finishedWork.firstEffect;
|
||||
while (effectfulFiber) {
|
||||
const current = effectfulFiber.alternate;
|
||||
commitWork(current, effectfulFiber);
|
||||
const next = effectfulFiber.nextEffect;
|
||||
// Ensure that we clean these up so that we don't accidentally keep them.
|
||||
// I'm not actually sure this matters because we can't reset firstEffect
|
||||
// and lastEffect since they're on every node, not just the effectful
|
||||
// ones. So we have to clean everything as we reuse nodes anyway.
|
||||
effectfulFiber.nextEffect = null;
|
||||
effectfulFiber = next;
|
||||
}
|
||||
}
|
||||
|
||||
function resetWorkPriority(workInProgress : Fiber) {
|
||||
let newPriority = NoWork;
|
||||
// progressedChild is going to be the child set with the highest priority.
|
||||
// Either it is the same as child, or it just bailed out because it choose
|
||||
// not to do the work.
|
||||
let child = workInProgress.progressedChild;
|
||||
while (child) {
|
||||
// Ensure that remaining work priority bubbles up.
|
||||
if (child.pendingWorkPriority !== NoWork &&
|
||||
(newPriority === NoWork ||
|
||||
newPriority > child.pendingWorkPriority)) {
|
||||
newPriority = child.pendingWorkPriority;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
workInProgress.pendingWorkPriority = newPriority;
|
||||
}
|
||||
|
||||
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
while (true) {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = completeWork(current, workInProgress);
|
||||
|
||||
resetWorkPriority(workInProgress);
|
||||
|
||||
// The work is now done. We don't need this anymore. This flags
|
||||
// to the system not to redo any work here.
|
||||
workInProgress.pendingProps = null;
|
||||
workInProgress.updateQueue = null;
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
|
||||
if (returnFiber) {
|
||||
// Ensure that the first and last effect of the parent corresponds
|
||||
// to the children's first and last effect. This probably relies on
|
||||
// children completing in order.
|
||||
if (!returnFiber.firstEffect) {
|
||||
returnFiber.firstEffect = workInProgress.firstEffect;
|
||||
}
|
||||
if (workInProgress.lastEffect) {
|
||||
if (returnFiber.lastEffect) {
|
||||
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
|
||||
}
|
||||
returnFiber.lastEffect = workInProgress.lastEffect;
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
// If completing this work spawned new work, do that next.
|
||||
return next;
|
||||
} else if (workInProgress.sibling) {
|
||||
// If there is more work to do in this returnFiber, do that next.
|
||||
return workInProgress.sibling;
|
||||
} else if (returnFiber) {
|
||||
// If there's no more work in this returnFiber. Complete the returnFiber.
|
||||
workInProgress = returnFiber;
|
||||
continue;
|
||||
} else {
|
||||
// If we're at the root, there's no more work to do. We can flush it.
|
||||
const root : FiberRoot = (workInProgress.stateNode : any);
|
||||
if (root.current === workInProgress) {
|
||||
throw new Error(
|
||||
'Cannot commit the same tree as before. This is probably a bug ' +
|
||||
'related to the return field.'
|
||||
);
|
||||
}
|
||||
root.current = workInProgress;
|
||||
// TODO: We can be smarter here and only look for more work in the
|
||||
// "next" scheduled work since we've already scanned passed. That
|
||||
// also ensures that work scheduled during reconciliation gets deferred.
|
||||
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
|
||||
commitAllWork(workInProgress);
|
||||
const nextWork = findNextUnitOfWork();
|
||||
// if (!nextWork && hasMoreWork) {
|
||||
// TODO: This can happen when some deep work completes and we don't
|
||||
// know if this was the last one. We should be able to keep track of
|
||||
// the highest priority still in the tree for one pass. But if we
|
||||
// terminate an update we don't know.
|
||||
// throw new Error('FiberRoots should not have flagged more work if there is none.');
|
||||
// }
|
||||
return nextWork;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
// progress.
|
||||
const current = workInProgress.alternate;
|
||||
const next = beginWork(current, workInProgress, nextPriorityLevel);
|
||||
|
||||
if (next) {
|
||||
// If this spawns new work, do that next.
|
||||
return next;
|
||||
} else {
|
||||
// Otherwise, complete the current work.
|
||||
return completeUnitOfWork(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
function performDeferredWork(deadline) {
|
||||
if (!nextUnitOfWork) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (nextUnitOfWork) {
|
||||
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
if (!nextUnitOfWork) {
|
||||
// Find more work. We might have time to complete some more.
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
} else {
|
||||
scheduleDeferredCallback(performDeferredWork);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleDeferredWork(root : FiberRoot, priority : PriorityLevel) {
|
||||
// We must reset the current unit of work pointer so that we restart the
|
||||
// search from the root during the next tick, in case there is now higher
|
||||
// priority work somewhere earlier than before.
|
||||
if (priority <= nextPriorityLevel) {
|
||||
nextUnitOfWork = null;
|
||||
}
|
||||
|
||||
// Set the priority on the root, without deprioritizing
|
||||
if (root.current.pendingWorkPriority === NoWork ||
|
||||
priority <= root.current.pendingWorkPriority) {
|
||||
root.current.pendingWorkPriority = priority;
|
||||
}
|
||||
|
||||
if (root.isScheduled) {
|
||||
// If we're already scheduled, we can bail out.
|
||||
return;
|
||||
}
|
||||
root.isScheduled = true;
|
||||
if (lastScheduledRoot) {
|
||||
// Schedule ourselves to the end.
|
||||
lastScheduledRoot.nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
} else {
|
||||
// We're the only work scheduled.
|
||||
nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
scheduleDeferredCallback(performDeferredWork);
|
||||
}
|
||||
}
|
||||
|
||||
function performAnimationWork() {
|
||||
// Always start from the root
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
while (nextUnitOfWork &&
|
||||
nextPriorityLevel !== NoWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
if (!nextUnitOfWork) {
|
||||
// Keep searching for animation work until there's no more left
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
// Stop if the next unit of work is low priority
|
||||
if (nextPriorityLevel > AnimationPriority) {
|
||||
scheduleDeferredCallback(performDeferredWork);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleAnimationWork(root: FiberRoot, priorityLevel : PriorityLevel) {
|
||||
// Set the priority on the root, without deprioritizing
|
||||
if (root.current.pendingWorkPriority === NoWork ||
|
||||
priorityLevel <= root.current.pendingWorkPriority) {
|
||||
root.current.pendingWorkPriority = priorityLevel;
|
||||
}
|
||||
|
||||
if (root.isScheduled) {
|
||||
// If we're already scheduled, we can bail out.
|
||||
return;
|
||||
}
|
||||
root.isScheduled = true;
|
||||
if (lastScheduledRoot) {
|
||||
// Schedule ourselves to the end.
|
||||
lastScheduledRoot.nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
} else {
|
||||
// We're the only work scheduled.
|
||||
nextScheduledRoot = root;
|
||||
lastScheduledRoot = root;
|
||||
scheduleAnimationCallback(performAnimationWork);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleWork(root : FiberRoot) {
|
||||
if (defaultPriority === SynchronousPriority) {
|
||||
throw new Error('Not implemented yet');
|
||||
}
|
||||
|
||||
if (defaultPriority === NoWork) {
|
||||
return;
|
||||
}
|
||||
if (defaultPriority > AnimationPriority) {
|
||||
scheduleDeferredWork(root, defaultPriority);
|
||||
return;
|
||||
}
|
||||
scheduleAnimationWork(root, defaultPriority);
|
||||
}
|
||||
|
||||
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
|
||||
const previousDefaultPriority = defaultPriority;
|
||||
defaultPriority = priorityLevel;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
defaultPriority = previousDefaultPriority;
|
||||
}
|
||||
}
|
||||
|
||||
scheduler = {
|
||||
scheduleWork: scheduleWork,
|
||||
scheduleDeferredWork: scheduleDeferredWork,
|
||||
performWithPriority: performWithPriority,
|
||||
};
|
||||
return scheduler;
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* 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 ReactFiberUpdateQueue
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
type UpdateQueueNode = {
|
||||
partialState: any,
|
||||
callback: ?Function,
|
||||
callbackWasCalled: boolean,
|
||||
next: ?UpdateQueueNode,
|
||||
};
|
||||
|
||||
export type UpdateQueue = UpdateQueueNode & {
|
||||
isReplace: boolean,
|
||||
isForced: boolean,
|
||||
tail: UpdateQueueNode
|
||||
};
|
||||
|
||||
exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue {
|
||||
const queue = {
|
||||
partialState,
|
||||
callback: null,
|
||||
callbackWasCalled: false,
|
||||
next: null,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
tail: (null : any),
|
||||
};
|
||||
queue.tail = queue;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.addToQueue = function(queue : UpdateQueue, partialState : mixed) : UpdateQueue {
|
||||
const node = {
|
||||
partialState,
|
||||
callback: null,
|
||||
callbackWasCalled: false,
|
||||
next: null,
|
||||
};
|
||||
queue.tail.next = node;
|
||||
queue.tail = node;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) : UpdateQueue {
|
||||
if (queue.tail.callback) {
|
||||
// If the tail already as a callback, add an empty node to queue
|
||||
exports.addToQueue(queue, null);
|
||||
}
|
||||
queue.tail.callback = callback;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.callCallbacks = function(queue : UpdateQueue, context : any) {
|
||||
let node : ?UpdateQueueNode = queue;
|
||||
while (node) {
|
||||
if (node.callback && !node.callbackWasCalled) {
|
||||
node.callbackWasCalled = true;
|
||||
node.callback.call(context);
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
};
|
||||
|
||||
exports.mergeUpdateQueue = function(queue : UpdateQueue, prevState : any, props : any) : any {
|
||||
let node : ?UpdateQueueNode = queue;
|
||||
let state = queue.isReplace ? null : Object.assign({}, prevState);
|
||||
while (node) {
|
||||
let partialState;
|
||||
if (typeof node.partialState === 'function') {
|
||||
const updateFn = node.partialState;
|
||||
partialState = updateFn(state, props);
|
||||
} else {
|
||||
partialState = node.partialState;
|
||||
}
|
||||
state = Object.assign(state || {}, partialState);
|
||||
node = node.next;
|
||||
}
|
||||
return state;
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 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 ReactPriorityLevel
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
module.exports = {
|
||||
NoWork: 0, // No work is pending.
|
||||
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
|
||||
AnimationPriority: 2, // Needs to complete before the next frame.
|
||||
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
|
||||
LowPriority: 4, // Data fetching, or result from updating stores.
|
||||
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 ReactReifiedYield
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactYield } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var { createFiberFromElementType } = require('ReactFiber');
|
||||
|
||||
export type ReifiedYield = { continuation: Fiber, props: Object };
|
||||
|
||||
exports.createReifiedYield = function(yieldNode : ReactYield) : ReifiedYield {
|
||||
var fiber = createFiberFromElementType(
|
||||
yieldNode.continuation,
|
||||
yieldNode.key
|
||||
);
|
||||
return {
|
||||
continuation: fiber,
|
||||
props: yieldNode.props,
|
||||
};
|
||||
};
|
||||
|
||||
exports.createUpdatedReifiedYield = function(previousYield : ReifiedYield, yieldNode : ReactYield) : ReifiedYield {
|
||||
return {
|
||||
continuation: previousYield.continuation,
|
||||
props: yieldNode.props,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* 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 ReactTypeOfWork
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
module.exports = {
|
||||
IndeterminateComponent: 0, // Before we know whether it is functional or class
|
||||
FunctionalComponent: 1,
|
||||
ClassComponent: 2,
|
||||
HostContainer: 3, // Root of a host tree. Could be nested inside another node.
|
||||
HostComponent: 4,
|
||||
CoroutineComponent: 5,
|
||||
CoroutineHandlerPhase: 6,
|
||||
YieldComponent: 7,
|
||||
};
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* Copyright 2014-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 ReactCoroutine
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactNodeList } from 'ReactTypes';
|
||||
|
||||
// The Symbol used to tag the special React types. If there is no native Symbol
|
||||
// nor polyfill, then a plain number is used for performance.
|
||||
var REACT_COROUTINE_TYPE =
|
||||
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.coroutine')) ||
|
||||
0xeac8;
|
||||
|
||||
var REACT_YIELD_TYPE =
|
||||
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.yield')) ||
|
||||
0xeac9;
|
||||
|
||||
type ReifiedYield = { continuation: Object, props: Object };
|
||||
type CoroutineHandler<T> = (props: T, yields: Array<ReifiedYield>) => ReactNodeList;
|
||||
|
||||
export type ReactCoroutine = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
children: any,
|
||||
// This should be a more specific CoroutineHandler
|
||||
handler: (props: any, yields: Array<ReifiedYield>) => ReactNodeList,
|
||||
/* $FlowFixMe(>=0.31.0): Which is it? mixed? Or Object? Must match
|
||||
* `ReactYield` type.
|
||||
*/
|
||||
props: mixed,
|
||||
};
|
||||
export type ReactYield = {
|
||||
$$typeof: Symbol | number,
|
||||
key: null | string,
|
||||
props: Object,
|
||||
continuation: mixed
|
||||
};
|
||||
|
||||
exports.createCoroutine = function<T>(
|
||||
children : mixed,
|
||||
handler : CoroutineHandler<T>,
|
||||
props : T,
|
||||
key : ?string = null
|
||||
) : ReactCoroutine {
|
||||
var coroutine = {
|
||||
// This tag allow us to uniquely identify this as a React Coroutine
|
||||
$$typeof: REACT_COROUTINE_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
children: children,
|
||||
handler: handler,
|
||||
props: props,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(coroutine.props);
|
||||
Object.freeze(coroutine);
|
||||
}
|
||||
}
|
||||
|
||||
return coroutine;
|
||||
};
|
||||
|
||||
exports.createYield = function(props : mixed, continuation : mixed, key : ?string = null) {
|
||||
var yieldNode = {
|
||||
// This tag allow us to uniquely identify this as a React Yield
|
||||
$$typeof: REACT_YIELD_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
props: props,
|
||||
continuation: continuation,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(yieldNode.props);
|
||||
Object.freeze(yieldNode);
|
||||
}
|
||||
}
|
||||
|
||||
return yieldNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the object is a coroutine object.
|
||||
*/
|
||||
exports.isCoroutine = function(object : mixed) : boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_COROUTINE_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies the object is a yield object.
|
||||
*/
|
||||
exports.isYield = function(object : mixed) : boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_YIELD_TYPE
|
||||
);
|
||||
};
|
||||
|
||||
exports.REACT_YIELD_TYPE = REACT_YIELD_TYPE;
|
||||
exports.REACT_COROUTINE_TYPE = REACT_COROUTINE_TYPE;
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright 2014-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 ReactTypes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
|
||||
export type ReactNode = ReactElement<any> | ReactCoroutine | ReactYield | ReactText | ReactFragment;
|
||||
|
||||
export type ReactFragment = ReactEmpty | Iterable<ReactNode>;
|
||||
|
||||
export type ReactNodeList = ReactEmpty | ReactNode;
|
||||
|
||||
export type ReactText = string | number;
|
||||
|
||||
export type ReactEmpty = null | void | boolean;
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright 2016-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 ReactHostOperationHistoryHook
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { DebugID } from 'ReactInstanceType';
|
||||
|
||||
export type Operation = {instanceID: DebugID} & (
|
||||
{type: 'mount', payload: string} |
|
||||
{type: 'insert child', payload: {toIndex: number, content: string}} |
|
||||
{type: 'move child', payload: {fromIndex: number, toIndex: number}} |
|
||||
{type: 'replace children', payload: string} |
|
||||
{type: 'replace text', payload: string} |
|
||||
{type: 'replace with', payload: string} |
|
||||
{type: 'update styles', payload: mixed /* Style Object */} |
|
||||
{type: 'update attribute', payload: {[name: string]: string}} |
|
||||
{type: 'remove attribute', payload: string}
|
||||
);
|
||||
|
||||
var history: Array<Operation> = [];
|
||||
|
||||
var ReactHostOperationHistoryHook = {
|
||||
onHostOperation(operation: Operation) {
|
||||
history.push(operation);
|
||||
},
|
||||
|
||||
clearHistory(): void {
|
||||
if (ReactHostOperationHistoryHook._preventClearing) {
|
||||
// Should only be used for tests.
|
||||
return;
|
||||
}
|
||||
|
||||
history = [];
|
||||
},
|
||||
|
||||
getHistory(): Array<Operation> {
|
||||
return history;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactHostOperationHistoryHook;
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright 2016-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 ReactInvalidSetStateWarningHook
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
if (__DEV__) {
|
||||
var processingChildContext = false;
|
||||
|
||||
var warnInvalidSetState = function() {
|
||||
warning(
|
||||
!processingChildContext,
|
||||
'setState(...): Cannot call setState() inside getChildContext()'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
var ReactInvalidSetStateWarningHook = {
|
||||
onBeginProcessingChildContext(): void {
|
||||
processingChildContext = true;
|
||||
},
|
||||
onEndProcessingChildContext(): void {
|
||||
processingChildContext = false;
|
||||
},
|
||||
onSetState(): void {
|
||||
warnInvalidSetState();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactInvalidSetStateWarningHook;
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 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 ReactInstanceMap
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* `ReactInstanceMap` maintains a mapping from a public facing stateful
|
||||
* instance (key) and the internal representation (value). This allows public
|
||||
* methods to accept the user facing instance as an argument and map them back
|
||||
* to internal methods.
|
||||
*/
|
||||
|
||||
// TODO: Replace this with ES6: var ReactInstanceMap = new Map();
|
||||
var ReactInstanceMap = {
|
||||
|
||||
/**
|
||||
* This API should be called `delete` but we'd have to make sure to always
|
||||
* transform these to strings for IE support. When this transform is fully
|
||||
* supported we can rename it.
|
||||
*/
|
||||
remove: function(key) {
|
||||
key._reactInternalInstance = undefined;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
return key._reactInternalInstance;
|
||||
},
|
||||
|
||||
has: function(key) {
|
||||
return key._reactInternalInstance !== undefined;
|
||||
},
|
||||
|
||||
set: function(key, value) {
|
||||
key._reactInternalInstance = value;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactInstanceMap;
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 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 shouldUpdateReactComponent
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Given a `prevElement` and `nextElement`, determines if the existing
|
||||
* instance should be updated as opposed to being destroyed or replaced by a new
|
||||
* instance. Both arguments are elements. This ensures that this logic can
|
||||
* operate on stateless trees without any backing instance.
|
||||
*
|
||||
* @param {?object} prevElement
|
||||
* @param {?object} nextElement
|
||||
* @return {boolean} True if the existing instance should be updated.
|
||||
* @protected
|
||||
*/
|
||||
function shouldUpdateReactComponent(prevElement, nextElement) {
|
||||
var prevEmpty = prevElement === null || prevElement === false;
|
||||
var nextEmpty = nextElement === null || nextElement === false;
|
||||
if (prevEmpty || nextEmpty) {
|
||||
return prevEmpty === nextEmpty;
|
||||
}
|
||||
|
||||
var prevType = typeof prevElement;
|
||||
var nextType = typeof nextElement;
|
||||
if (prevType === 'string' || prevType === 'number') {
|
||||
return (nextType === 'string' || nextType === 'number');
|
||||
} else {
|
||||
return (
|
||||
nextType === 'object' &&
|
||||
prevElement.type === nextElement.type &&
|
||||
prevElement.key === nextElement.key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = shouldUpdateReactComponent;
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* 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 EventConstants
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type PropagationPhases = 'bubbled' | 'captured';
|
||||
|
||||
/**
|
||||
* Types of raw signals from the browser caught at the top level.
|
||||
*/
|
||||
var topLevelTypes = {
|
||||
topAbort: null,
|
||||
topAnimationEnd: null,
|
||||
topAnimationIteration: null,
|
||||
topAnimationStart: null,
|
||||
topBlur: null,
|
||||
topCanPlay: null,
|
||||
topCanPlayThrough: null,
|
||||
topChange: null,
|
||||
topClick: null,
|
||||
topCompositionEnd: null,
|
||||
topCompositionStart: null,
|
||||
topCompositionUpdate: null,
|
||||
topContextMenu: null,
|
||||
topCopy: null,
|
||||
topCut: null,
|
||||
topDoubleClick: null,
|
||||
topDrag: null,
|
||||
topDragEnd: null,
|
||||
topDragEnter: null,
|
||||
topDragExit: null,
|
||||
topDragLeave: null,
|
||||
topDragOver: null,
|
||||
topDragStart: null,
|
||||
topDrop: null,
|
||||
topDurationChange: null,
|
||||
topEmptied: null,
|
||||
topEncrypted: null,
|
||||
topEnded: null,
|
||||
topError: null,
|
||||
topFocus: null,
|
||||
topInput: null,
|
||||
topInvalid: null,
|
||||
topKeyDown: null,
|
||||
topKeyPress: null,
|
||||
topKeyUp: null,
|
||||
topLoad: null,
|
||||
topLoadedData: null,
|
||||
topLoadedMetadata: null,
|
||||
topLoadStart: null,
|
||||
topMouseDown: null,
|
||||
topMouseMove: null,
|
||||
topMouseOut: null,
|
||||
topMouseOver: null,
|
||||
topMouseUp: null,
|
||||
topPaste: null,
|
||||
topPause: null,
|
||||
topPlay: null,
|
||||
topPlaying: null,
|
||||
topProgress: null,
|
||||
topRateChange: null,
|
||||
topReset: null,
|
||||
topScroll: null,
|
||||
topSeeked: null,
|
||||
topSeeking: null,
|
||||
topSelectionChange: null,
|
||||
topStalled: null,
|
||||
topSubmit: null,
|
||||
topSuspend: null,
|
||||
topTextInput: null,
|
||||
topTimeUpdate: null,
|
||||
topTouchCancel: null,
|
||||
topTouchEnd: null,
|
||||
topTouchMove: null,
|
||||
topTouchStart: null,
|
||||
topTransitionEnd: null,
|
||||
topVolumeChange: null,
|
||||
topWaiting: null,
|
||||
topWheel: null,
|
||||
};
|
||||
|
||||
export type TopLevelTypes = $Enum<typeof topLevelTypes>;
|
||||
|
||||
var EventConstants = {
|
||||
topLevelTypes,
|
||||
};
|
||||
|
||||
module.exports = EventConstants;
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* 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 EventPluginHub
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventPluginRegistry = require('EventPluginRegistry');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var ReactErrorUtils = require('ReactErrorUtils');
|
||||
|
||||
var accumulateInto = require('accumulateInto');
|
||||
var forEachAccumulated = require('forEachAccumulated');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* Internal store for event listeners
|
||||
*/
|
||||
var listenerBank = {};
|
||||
|
||||
/**
|
||||
* Internal queue of events that have accumulated their dispatches and are
|
||||
* waiting to have their dispatches executed.
|
||||
*/
|
||||
var eventQueue = null;
|
||||
|
||||
/**
|
||||
* Dispatches an event and releases it back into the pool, unless persistent.
|
||||
*
|
||||
* @param {?object} event Synthetic event to be dispatched.
|
||||
* @param {boolean} simulated If the event is simulated (changes exn behavior)
|
||||
* @private
|
||||
*/
|
||||
var executeDispatchesAndRelease = function(event, simulated) {
|
||||
if (event) {
|
||||
EventPluginUtils.executeDispatchesInOrder(event, simulated);
|
||||
|
||||
if (!event.isPersistent()) {
|
||||
event.constructor.release(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
var executeDispatchesAndReleaseSimulated = function(e) {
|
||||
return executeDispatchesAndRelease(e, true);
|
||||
};
|
||||
var executeDispatchesAndReleaseTopLevel = function(e) {
|
||||
return executeDispatchesAndRelease(e, false);
|
||||
};
|
||||
|
||||
var getDictionaryKey = function(inst) {
|
||||
// Prevents V8 performance issue:
|
||||
// https://github.com/facebook/react/pull/7232
|
||||
return '.' + inst._rootNodeID;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a unified interface for event plugins to be installed and configured.
|
||||
*
|
||||
* Event plugins can implement the following properties:
|
||||
*
|
||||
* `extractEvents` {function(string, DOMEventTarget, string, object): *}
|
||||
* Required. When a top-level event is fired, this method is expected to
|
||||
* extract synthetic events that will in turn be queued and dispatched.
|
||||
*
|
||||
* `eventTypes` {object}
|
||||
* Optional, plugins that fire events must publish a mapping of registration
|
||||
* names that are used to register listeners. Values of this mapping must
|
||||
* be objects that contain `registrationName` or `phasedRegistrationNames`.
|
||||
*
|
||||
* `executeDispatch` {function(object, function, string)}
|
||||
* Optional, allows plugins to override how an event gets dispatched. By
|
||||
* default, the listener is simply invoked.
|
||||
*
|
||||
* Each plugin that is injected into `EventsPluginHub` is immediately operable.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
var EventPluginHub = {
|
||||
|
||||
/**
|
||||
* Methods for injecting dependencies.
|
||||
*/
|
||||
injection: {
|
||||
|
||||
/**
|
||||
* @param {array} InjectedEventPluginOrder
|
||||
* @public
|
||||
*/
|
||||
injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
|
||||
|
||||
/**
|
||||
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
|
||||
*/
|
||||
injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName,
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores `listener` at `listenerBank[registrationName][key]`. Is idempotent.
|
||||
*
|
||||
* @param {object} inst The instance, which is the source of events.
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @param {function} listener The callback to store.
|
||||
*/
|
||||
putListener: function(inst, registrationName, listener) {
|
||||
invariant(
|
||||
typeof listener === 'function',
|
||||
'Expected %s listener to be a function, instead got type %s',
|
||||
registrationName, typeof listener
|
||||
);
|
||||
|
||||
var key = getDictionaryKey(inst);
|
||||
var bankForRegistrationName =
|
||||
listenerBank[registrationName] || (listenerBank[registrationName] = {});
|
||||
bankForRegistrationName[key] = listener;
|
||||
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.didPutListener) {
|
||||
PluginModule.didPutListener(inst, registrationName, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} inst The instance, which is the source of events.
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @return {?function} The stored callback.
|
||||
*/
|
||||
getListener: function(inst, registrationName) {
|
||||
var bankForRegistrationName = listenerBank[registrationName];
|
||||
var key = getDictionaryKey(inst);
|
||||
return bankForRegistrationName && bankForRegistrationName[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a listener from the registration bank.
|
||||
*
|
||||
* @param {object} inst The instance, which is the source of events.
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
*/
|
||||
deleteListener: function(inst, registrationName) {
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.willDeleteListener) {
|
||||
PluginModule.willDeleteListener(inst, registrationName);
|
||||
}
|
||||
|
||||
var bankForRegistrationName = listenerBank[registrationName];
|
||||
// TODO: This should never be null -- when is it?
|
||||
if (bankForRegistrationName) {
|
||||
var key = getDictionaryKey(inst);
|
||||
delete bankForRegistrationName[key];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes all listeners for the DOM element with the supplied ID.
|
||||
*
|
||||
* @param {object} inst The instance, which is the source of events.
|
||||
*/
|
||||
deleteAllListeners: function(inst) {
|
||||
var key = getDictionaryKey(inst);
|
||||
for (var registrationName in listenerBank) {
|
||||
if (!listenerBank.hasOwnProperty(registrationName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!listenerBank[registrationName][key]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var PluginModule =
|
||||
EventPluginRegistry.registrationNameModules[registrationName];
|
||||
if (PluginModule && PluginModule.willDeleteListener) {
|
||||
PluginModule.willDeleteListener(inst, registrationName);
|
||||
}
|
||||
|
||||
delete listenerBank[registrationName][key];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows registered plugins an opportunity to extract events from top-level
|
||||
* native browser events.
|
||||
*
|
||||
* @return {*} An accumulation of synthetic events.
|
||||
* @internal
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget) {
|
||||
var events;
|
||||
var plugins = EventPluginRegistry.plugins;
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
// Not every plugin in the ordering may be loaded at runtime.
|
||||
var possiblePlugin = plugins[i];
|
||||
if (possiblePlugin) {
|
||||
var extractedEvents = possiblePlugin.extractEvents(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
if (extractedEvents) {
|
||||
events = accumulateInto(events, extractedEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
return events;
|
||||
},
|
||||
|
||||
/**
|
||||
* Enqueues a synthetic event that should be dispatched when
|
||||
* `processEventQueue` is invoked.
|
||||
*
|
||||
* @param {*} events An accumulation of synthetic events.
|
||||
* @internal
|
||||
*/
|
||||
enqueueEvents: function(events) {
|
||||
if (events) {
|
||||
eventQueue = accumulateInto(eventQueue, events);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches all synthetic events on the event queue.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
processEventQueue: function(simulated) {
|
||||
// Set `eventQueue` to null before processing it so that we can tell if more
|
||||
// events get enqueued while processing.
|
||||
var processingEventQueue = eventQueue;
|
||||
eventQueue = null;
|
||||
if (simulated) {
|
||||
forEachAccumulated(
|
||||
processingEventQueue,
|
||||
executeDispatchesAndReleaseSimulated
|
||||
);
|
||||
} else {
|
||||
forEachAccumulated(
|
||||
processingEventQueue,
|
||||
executeDispatchesAndReleaseTopLevel
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
!eventQueue,
|
||||
'processEventQueue(): Additional events were enqueued while processing ' +
|
||||
'an event queue. Support for this has not yet been implemented.'
|
||||
);
|
||||
// This would be a good time to rethrow if any of the event handlers threw.
|
||||
ReactErrorUtils.rethrowCaughtError();
|
||||
},
|
||||
|
||||
/**
|
||||
* These are needed for tests only. Do not use!
|
||||
*/
|
||||
__purge: function() {
|
||||
listenerBank = {};
|
||||
},
|
||||
|
||||
__getListenerBank: function() {
|
||||
return listenerBank;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = EventPluginHub;
|
|
@ -0,0 +1,336 @@
|
|||
/**
|
||||
* 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 EventPluginRegistry
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
} from 'ReactSyntheticEventType';
|
||||
|
||||
import type {
|
||||
AnyNativeEvent,
|
||||
PluginName,
|
||||
PluginModule,
|
||||
} from 'PluginModuleType';
|
||||
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
|
||||
|
||||
type EventPluginOrder = null | Array<PluginName>;
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* Injectable ordering of event plugins.
|
||||
*/
|
||||
var eventPluginOrder: EventPluginOrder = null;
|
||||
|
||||
/**
|
||||
* Injectable mapping from names to event plugin modules.
|
||||
*/
|
||||
var namesToPlugins: NamesToPlugins = {};
|
||||
|
||||
/**
|
||||
* Recomputes the plugin list using the injected plugins and plugin ordering.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function recomputePluginOrdering(): void {
|
||||
if (!eventPluginOrder) {
|
||||
// Wait until an `eventPluginOrder` is injected.
|
||||
return;
|
||||
}
|
||||
for (var pluginName in namesToPlugins) {
|
||||
var pluginModule = namesToPlugins[pluginName];
|
||||
var pluginIndex = eventPluginOrder.indexOf(pluginName);
|
||||
invariant(
|
||||
pluginIndex > -1,
|
||||
'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
|
||||
'the plugin ordering, `%s`.',
|
||||
pluginName,
|
||||
);
|
||||
if (EventPluginRegistry.plugins[pluginIndex]) {
|
||||
continue;
|
||||
}
|
||||
invariant(
|
||||
pluginModule.extractEvents,
|
||||
'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
|
||||
'method, but `%s` does not.',
|
||||
pluginName,
|
||||
);
|
||||
EventPluginRegistry.plugins[pluginIndex] = pluginModule;
|
||||
var publishedEvents = pluginModule.eventTypes;
|
||||
for (var eventName in publishedEvents) {
|
||||
invariant(
|
||||
publishEventForPlugin(
|
||||
publishedEvents[eventName],
|
||||
pluginModule,
|
||||
eventName,
|
||||
),
|
||||
'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
|
||||
eventName,
|
||||
pluginName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes an event so that it can be dispatched by the supplied plugin.
|
||||
*
|
||||
* @param {object} dispatchConfig Dispatch configuration for the event.
|
||||
* @param {object} PluginModule Plugin publishing the event.
|
||||
* @return {boolean} True if the event was successfully published.
|
||||
* @private
|
||||
*/
|
||||
function publishEventForPlugin(
|
||||
dispatchConfig: DispatchConfig,
|
||||
pluginModule: PluginModule<AnyNativeEvent>,
|
||||
eventName: string,
|
||||
): boolean {
|
||||
invariant(
|
||||
!EventPluginRegistry.eventNameDispatchConfigs.hasOwnProperty(eventName),
|
||||
'EventPluginHub: More than one plugin attempted to publish the same ' +
|
||||
'event name, `%s`.',
|
||||
eventName,
|
||||
);
|
||||
EventPluginRegistry.eventNameDispatchConfigs[eventName] = dispatchConfig;
|
||||
|
||||
var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
|
||||
if (phasedRegistrationNames) {
|
||||
for (var phaseName in phasedRegistrationNames) {
|
||||
if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
|
||||
var phasedRegistrationName = phasedRegistrationNames[phaseName];
|
||||
publishRegistrationName(
|
||||
phasedRegistrationName,
|
||||
pluginModule,
|
||||
eventName,
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (dispatchConfig.registrationName) {
|
||||
publishRegistrationName(
|
||||
dispatchConfig.registrationName,
|
||||
pluginModule,
|
||||
eventName,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a registration name that is used to identify dispatched events and
|
||||
* can be used with `EventPluginHub.putListener` to register listeners.
|
||||
*
|
||||
* @param {string} registrationName Registration name to add.
|
||||
* @param {object} PluginModule Plugin publishing the event.
|
||||
* @private
|
||||
*/
|
||||
function publishRegistrationName(
|
||||
registrationName: string,
|
||||
pluginModule: PluginModule<AnyNativeEvent>,
|
||||
eventName: string,
|
||||
): void {
|
||||
invariant(
|
||||
!EventPluginRegistry.registrationNameModules[registrationName],
|
||||
'EventPluginHub: More than one plugin attempted to publish the same ' +
|
||||
'registration name, `%s`.',
|
||||
registrationName,
|
||||
);
|
||||
EventPluginRegistry.registrationNameModules[registrationName] = pluginModule;
|
||||
EventPluginRegistry.registrationNameDependencies[registrationName] =
|
||||
pluginModule.eventTypes[eventName].dependencies;
|
||||
|
||||
if (__DEV__) {
|
||||
var lowerCasedName = registrationName.toLowerCase();
|
||||
EventPluginRegistry.possibleRegistrationNames[lowerCasedName] =
|
||||
registrationName;
|
||||
|
||||
|
||||
if (registrationName === 'onDoubleClick') {
|
||||
EventPluginRegistry.possibleRegistrationNames.ondblclick = registrationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers plugins so that they can extract and dispatch events.
|
||||
*
|
||||
* @see {EventPluginHub}
|
||||
*/
|
||||
var EventPluginRegistry = {
|
||||
|
||||
/**
|
||||
* Ordered list of injected plugins.
|
||||
*/
|
||||
plugins: [],
|
||||
|
||||
/**
|
||||
* Mapping from event name to dispatch config
|
||||
*/
|
||||
eventNameDispatchConfigs: {},
|
||||
|
||||
/**
|
||||
* Mapping from registration name to plugin module
|
||||
*/
|
||||
registrationNameModules: {},
|
||||
|
||||
/**
|
||||
* Mapping from registration name to event name
|
||||
*/
|
||||
registrationNameDependencies: {},
|
||||
|
||||
/**
|
||||
* Mapping from lowercase registration names to the properly cased version,
|
||||
* used to warn in the case of missing event handlers. Available
|
||||
* only in __DEV__.
|
||||
* @type {Object}
|
||||
*/
|
||||
possibleRegistrationNames: __DEV__ ? {} : (null: any),
|
||||
// Trust the developer to only use possibleRegistrationNames in __DEV__
|
||||
|
||||
/**
|
||||
* Injects an ordering of plugins (by plugin name). This allows the ordering
|
||||
* to be decoupled from injection of the actual plugins so that ordering is
|
||||
* always deterministic regardless of packaging, on-the-fly injection, etc.
|
||||
*
|
||||
* @param {array} InjectedEventPluginOrder
|
||||
* @internal
|
||||
* @see {EventPluginHub.injection.injectEventPluginOrder}
|
||||
*/
|
||||
injectEventPluginOrder: function(
|
||||
injectedEventPluginOrder: EventPluginOrder,
|
||||
): void {
|
||||
invariant(
|
||||
!eventPluginOrder,
|
||||
'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
|
||||
'once. You are likely trying to load more than one copy of React.',
|
||||
);
|
||||
// Clone the ordering so it cannot be dynamically mutated.
|
||||
eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
|
||||
recomputePluginOrdering();
|
||||
},
|
||||
|
||||
/**
|
||||
* Injects plugins to be used by `EventPluginHub`. The plugin names must be
|
||||
* in the ordering injected by `injectEventPluginOrder`.
|
||||
*
|
||||
* Plugins can be injected as part of page initialization or on-the-fly.
|
||||
*
|
||||
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
|
||||
* @internal
|
||||
* @see {EventPluginHub.injection.injectEventPluginsByName}
|
||||
*/
|
||||
injectEventPluginsByName: function(
|
||||
injectedNamesToPlugins: NamesToPlugins
|
||||
): void {
|
||||
var isOrderingDirty = false;
|
||||
for (var pluginName in injectedNamesToPlugins) {
|
||||
if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
|
||||
continue;
|
||||
}
|
||||
var pluginModule = injectedNamesToPlugins[pluginName];
|
||||
if (!namesToPlugins.hasOwnProperty(pluginName) ||
|
||||
namesToPlugins[pluginName] !== pluginModule) {
|
||||
invariant(
|
||||
!namesToPlugins[pluginName],
|
||||
'EventPluginRegistry: Cannot inject two different event plugins ' +
|
||||
'using the same name, `%s`.',
|
||||
pluginName,
|
||||
);
|
||||
namesToPlugins[pluginName] = pluginModule;
|
||||
isOrderingDirty = true;
|
||||
}
|
||||
}
|
||||
if (isOrderingDirty) {
|
||||
recomputePluginOrdering();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up the plugin for the supplied event.
|
||||
*
|
||||
* @param {object} event A synthetic event.
|
||||
* @return {?object} The plugin that created the supplied event.
|
||||
* @internal
|
||||
*/
|
||||
getPluginModuleForEvent: function(
|
||||
event: ReactSyntheticEvent,
|
||||
): null | PluginModule<AnyNativeEvent> {
|
||||
var dispatchConfig = event.dispatchConfig;
|
||||
if (dispatchConfig.registrationName) {
|
||||
return EventPluginRegistry.registrationNameModules[
|
||||
dispatchConfig.registrationName
|
||||
] || null;
|
||||
}
|
||||
if (dispatchConfig.phasedRegistrationNames !== undefined) {
|
||||
// pulling phasedRegistrationNames out of dispatchConfig helps Flow see
|
||||
// that it is not undefined.
|
||||
var {phasedRegistrationNames} = dispatchConfig;
|
||||
for (var phase in phasedRegistrationNames) {
|
||||
if (!phasedRegistrationNames.hasOwnProperty(phase)) {
|
||||
continue;
|
||||
}
|
||||
var pluginModule = EventPluginRegistry.registrationNameModules[
|
||||
phasedRegistrationNames[phase]
|
||||
];
|
||||
if (pluginModule) {
|
||||
return pluginModule;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Exposed for unit testing.
|
||||
* @private
|
||||
*/
|
||||
_resetEventPlugins: function(): void {
|
||||
eventPluginOrder = null;
|
||||
for (var pluginName in namesToPlugins) {
|
||||
if (namesToPlugins.hasOwnProperty(pluginName)) {
|
||||
delete namesToPlugins[pluginName];
|
||||
}
|
||||
}
|
||||
EventPluginRegistry.plugins.length = 0;
|
||||
|
||||
var eventNameDispatchConfigs = EventPluginRegistry.eventNameDispatchConfigs;
|
||||
for (var eventName in eventNameDispatchConfigs) {
|
||||
if (eventNameDispatchConfigs.hasOwnProperty(eventName)) {
|
||||
delete eventNameDispatchConfigs[eventName];
|
||||
}
|
||||
}
|
||||
|
||||
var registrationNameModules = EventPluginRegistry.registrationNameModules;
|
||||
for (var registrationName in registrationNameModules) {
|
||||
if (registrationNameModules.hasOwnProperty(registrationName)) {
|
||||
delete registrationNameModules[registrationName];
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
var possibleRegistrationNames =
|
||||
EventPluginRegistry.possibleRegistrationNames;
|
||||
for (var lowerCasedName in possibleRegistrationNames) {
|
||||
if (possibleRegistrationNames.hasOwnProperty(lowerCasedName)) {
|
||||
delete possibleRegistrationNames[lowerCasedName];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = EventPluginRegistry;
|
|
@ -0,0 +1,258 @@
|
|||
/**
|
||||
* 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 EventPluginUtils
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactErrorUtils = require('ReactErrorUtils');
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
/**
|
||||
* Injected dependencies:
|
||||
*/
|
||||
|
||||
/**
|
||||
* - `ComponentTree`: [required] Module that can convert between React instances
|
||||
* and actual node references.
|
||||
*/
|
||||
var ComponentTree;
|
||||
var TreeTraversal;
|
||||
var injection = {
|
||||
injectComponentTree: function(Injected) {
|
||||
ComponentTree = Injected;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
Injected &&
|
||||
Injected.getNodeFromInstance &&
|
||||
Injected.getInstanceFromNode,
|
||||
'EventPluginUtils.injection.injectComponentTree(...): Injected ' +
|
||||
'module is missing getNodeFromInstance or getInstanceFromNode.'
|
||||
);
|
||||
}
|
||||
},
|
||||
injectTreeTraversal: function(Injected) {
|
||||
TreeTraversal = Injected;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
Injected && Injected.isAncestor && Injected.getLowestCommonAncestor,
|
||||
'EventPluginUtils.injection.injectTreeTraversal(...): Injected ' +
|
||||
'module is missing isAncestor or getLowestCommonAncestor.'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function isEndish(topLevelType) {
|
||||
return topLevelType === 'topMouseUp' ||
|
||||
topLevelType === 'topTouchEnd' ||
|
||||
topLevelType === 'topTouchCancel';
|
||||
}
|
||||
|
||||
function isMoveish(topLevelType) {
|
||||
return topLevelType === 'topMouseMove' ||
|
||||
topLevelType === 'topTouchMove';
|
||||
}
|
||||
function isStartish(topLevelType) {
|
||||
return topLevelType === 'topMouseDown' ||
|
||||
topLevelType === 'topTouchStart';
|
||||
}
|
||||
|
||||
|
||||
var validateEventDispatches;
|
||||
if (__DEV__) {
|
||||
validateEventDispatches = function(event) {
|
||||
var dispatchListeners = event._dispatchListeners;
|
||||
var dispatchInstances = event._dispatchInstances;
|
||||
|
||||
var listenersIsArr = Array.isArray(dispatchListeners);
|
||||
var listenersLen = listenersIsArr ?
|
||||
dispatchListeners.length :
|
||||
dispatchListeners ? 1 : 0;
|
||||
|
||||
var instancesIsArr = Array.isArray(dispatchInstances);
|
||||
var instancesLen = instancesIsArr ?
|
||||
dispatchInstances.length :
|
||||
dispatchInstances ? 1 : 0;
|
||||
|
||||
warning(
|
||||
instancesIsArr === listenersIsArr && instancesLen === listenersLen,
|
||||
'EventPluginUtils: Invalid `event`.'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the event to the listener.
|
||||
* @param {SyntheticEvent} event SyntheticEvent to handle
|
||||
* @param {boolean} simulated If the event is simulated (changes exn behavior)
|
||||
* @param {function} listener Application-level callback
|
||||
* @param {*} inst Internal component instance
|
||||
*/
|
||||
function executeDispatch(event, simulated, listener, inst) {
|
||||
var type = event.type || 'unknown-event';
|
||||
event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
|
||||
if (simulated) {
|
||||
ReactErrorUtils.invokeGuardedCallbackWithCatch(
|
||||
type,
|
||||
listener,
|
||||
event
|
||||
);
|
||||
} else {
|
||||
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
|
||||
}
|
||||
event.currentTarget = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard/simple iteration through an event's collected dispatches.
|
||||
*/
|
||||
function executeDispatchesInOrder(event, simulated) {
|
||||
var dispatchListeners = event._dispatchListeners;
|
||||
var dispatchInstances = event._dispatchInstances;
|
||||
if (__DEV__) {
|
||||
validateEventDispatches(event);
|
||||
}
|
||||
if (Array.isArray(dispatchListeners)) {
|
||||
for (var i = 0; i < dispatchListeners.length; i++) {
|
||||
if (event.isPropagationStopped()) {
|
||||
break;
|
||||
}
|
||||
// Listeners and Instances are two parallel arrays that are always in sync.
|
||||
executeDispatch(
|
||||
event,
|
||||
simulated,
|
||||
dispatchListeners[i],
|
||||
dispatchInstances[i]
|
||||
);
|
||||
}
|
||||
} else if (dispatchListeners) {
|
||||
executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
|
||||
}
|
||||
event._dispatchListeners = null;
|
||||
event._dispatchInstances = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard/simple iteration through an event's collected dispatches, but stops
|
||||
* at the first dispatch execution returning true, and returns that id.
|
||||
*
|
||||
* @return {?string} id of the first dispatch execution who's listener returns
|
||||
* true, or null if no listener returned true.
|
||||
*/
|
||||
function executeDispatchesInOrderStopAtTrueImpl(event) {
|
||||
var dispatchListeners = event._dispatchListeners;
|
||||
var dispatchInstances = event._dispatchInstances;
|
||||
if (__DEV__) {
|
||||
validateEventDispatches(event);
|
||||
}
|
||||
if (Array.isArray(dispatchListeners)) {
|
||||
for (var i = 0; i < dispatchListeners.length; i++) {
|
||||
if (event.isPropagationStopped()) {
|
||||
break;
|
||||
}
|
||||
// Listeners and Instances are two parallel arrays that are always in sync.
|
||||
if (dispatchListeners[i](event, dispatchInstances[i])) {
|
||||
return dispatchInstances[i];
|
||||
}
|
||||
}
|
||||
} else if (dispatchListeners) {
|
||||
if (dispatchListeners(event, dispatchInstances)) {
|
||||
return dispatchInstances;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see executeDispatchesInOrderStopAtTrueImpl
|
||||
*/
|
||||
function executeDispatchesInOrderStopAtTrue(event) {
|
||||
var ret = executeDispatchesInOrderStopAtTrueImpl(event);
|
||||
event._dispatchInstances = null;
|
||||
event._dispatchListeners = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution of a "direct" dispatch - there must be at most one dispatch
|
||||
* accumulated on the event or it is considered an error. It doesn't really make
|
||||
* sense for an event with multiple dispatches (bubbled) to keep track of the
|
||||
* return values at each dispatch execution, but it does tend to make sense when
|
||||
* dealing with "direct" dispatches.
|
||||
*
|
||||
* @return {*} The return value of executing the single dispatch.
|
||||
*/
|
||||
function executeDirectDispatch(event) {
|
||||
if (__DEV__) {
|
||||
validateEventDispatches(event);
|
||||
}
|
||||
var dispatchListener = event._dispatchListeners;
|
||||
var dispatchInstance = event._dispatchInstances;
|
||||
invariant(
|
||||
!Array.isArray(dispatchListener),
|
||||
'executeDirectDispatch(...): Invalid `event`.'
|
||||
);
|
||||
event.currentTarget = dispatchListener ? EventPluginUtils.getNodeFromInstance(dispatchInstance) : null;
|
||||
var res = dispatchListener ? dispatchListener(event) : null;
|
||||
event.currentTarget = null;
|
||||
event._dispatchListeners = null;
|
||||
event._dispatchInstances = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SyntheticEvent} event
|
||||
* @return {boolean} True iff number of dispatches accumulated is greater than 0.
|
||||
*/
|
||||
function hasDispatches(event) {
|
||||
return !!event._dispatchListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* General utilities that are useful in creating custom Event Plugins.
|
||||
*/
|
||||
var EventPluginUtils = {
|
||||
isEndish: isEndish,
|
||||
isMoveish: isMoveish,
|
||||
isStartish: isStartish,
|
||||
|
||||
executeDirectDispatch: executeDirectDispatch,
|
||||
executeDispatchesInOrder: executeDispatchesInOrder,
|
||||
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
|
||||
hasDispatches: hasDispatches,
|
||||
|
||||
getInstanceFromNode: function(node) {
|
||||
return ComponentTree.getInstanceFromNode(node);
|
||||
},
|
||||
getNodeFromInstance: function(node) {
|
||||
return ComponentTree.getNodeFromInstance(node);
|
||||
},
|
||||
isAncestor: function(a, b) {
|
||||
return TreeTraversal.isAncestor(a, b);
|
||||
},
|
||||
getLowestCommonAncestor: function(a, b) {
|
||||
return TreeTraversal.getLowestCommonAncestor(a, b);
|
||||
},
|
||||
getParentInstance: function(inst) {
|
||||
return TreeTraversal.getParentInstance(inst);
|
||||
},
|
||||
traverseTwoPhase: function(target, fn, arg) {
|
||||
return TreeTraversal.traverseTwoPhase(target, fn, arg);
|
||||
},
|
||||
traverseEnterLeave: function(from, to, fn, argFrom, argTo) {
|
||||
return TreeTraversal.traverseEnterLeave(from, to, fn, argFrom, argTo);
|
||||
},
|
||||
|
||||
injection: injection,
|
||||
};
|
||||
|
||||
module.exports = EventPluginUtils;
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* 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 EventPropagators
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
|
||||
var accumulateInto = require('accumulateInto');
|
||||
var forEachAccumulated = require('forEachAccumulated');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
import type { PropagationPhases } from 'EventConstants';
|
||||
|
||||
var getListener = EventPluginHub.getListener;
|
||||
|
||||
/**
|
||||
* Some event types have a notion of different registration names for different
|
||||
* "phases" of propagation. This finds listeners by a given phase.
|
||||
*/
|
||||
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
||||
var registrationName =
|
||||
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
|
||||
return getListener(inst, registrationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
|
||||
* here, allows us to not have to bind or create functions for each event.
|
||||
* Mutating the event's members allows us to not have to create a wrapping
|
||||
* "dispatch" object that pairs the event with the listener.
|
||||
*/
|
||||
function accumulateDirectionalDispatches(inst, phase, event) {
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
inst,
|
||||
'Dispatching inst must not be null'
|
||||
);
|
||||
}
|
||||
var listener = listenerAtPhase(inst, event, phase);
|
||||
if (listener) {
|
||||
event._dispatchListeners =
|
||||
accumulateInto(event._dispatchListeners, listener);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect dispatches (must be entirely collected before dispatching - see unit
|
||||
* tests). Lazily allocate the array to conserve memory. We must loop through
|
||||
* each event and perform the traversal for each one. We cannot perform a
|
||||
* single traversal for the entire collection of events because each event may
|
||||
* have a different target.
|
||||
*/
|
||||
function accumulateTwoPhaseDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
EventPluginUtils.traverseTwoPhase(
|
||||
event._targetInst,
|
||||
accumulateDirectionalDispatches,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
|
||||
*/
|
||||
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
var targetInst = event._targetInst;
|
||||
var parentInst =
|
||||
targetInst ? EventPluginUtils.getParentInstance(targetInst) : null;
|
||||
EventPluginUtils.traverseTwoPhase(
|
||||
parentInst,
|
||||
accumulateDirectionalDispatches,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `dispatchMarker` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(inst, ignoredDirection, event) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
var registrationName = event.dispatchConfig.registrationName;
|
||||
var listener = getListener(inst, registrationName);
|
||||
if (listener) {
|
||||
event._dispatchListeners =
|
||||
accumulateInto(event._dispatchListeners, listener);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `SyntheticEvent`, but only for the
|
||||
* `dispatchMarker`.
|
||||
* @param {SyntheticEvent} event
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
accumulateDispatches(event._targetInst, null, event);
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateTwoPhaseDispatches(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
|
||||
}
|
||||
|
||||
function accumulateTwoPhaseDispatchesSkipTarget(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
|
||||
}
|
||||
|
||||
function accumulateEnterLeaveDispatches(leave, enter, from, to) {
|
||||
EventPluginUtils.traverseEnterLeave(
|
||||
from,
|
||||
to,
|
||||
accumulateDispatches,
|
||||
leave,
|
||||
enter
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function accumulateDirectDispatches(events) {
|
||||
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A small set of propagation patterns, each of which will accept a small amount
|
||||
* of information, and generate a set of "dispatch ready event objects" - which
|
||||
* are sets of events that have already been annotated with a set of dispatched
|
||||
* listener functions/ids. The API is designed this way to discourage these
|
||||
* propagation strategies from actually executing the dispatches, since we
|
||||
* always want to collect the entire set of dispatches before executing event a
|
||||
* single one.
|
||||
*
|
||||
* @constructor EventPropagators
|
||||
*/
|
||||
var EventPropagators = {
|
||||
accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
|
||||
accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget,
|
||||
accumulateDirectDispatches: accumulateDirectDispatches,
|
||||
accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
|
||||
};
|
||||
|
||||
module.exports = EventPropagators;
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* 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 PluginModuleType
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactInstance} from 'ReactInstanceType';
|
||||
import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
} from 'ReactSyntheticEventType';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
|
||||
export type AnyNativeEvent =
|
||||
Event |
|
||||
KeyboardEvent |
|
||||
MouseEvent |
|
||||
Touch;
|
||||
|
||||
export type PluginName = string;
|
||||
|
||||
export type PluginModule<NativeEvent> = {
|
||||
eventTypes: EventTypes,
|
||||
extractEvents: (
|
||||
topLevelType: string,
|
||||
targetInst: ReactInstance,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
) => null | ReactSyntheticEvent,
|
||||
didPutListener?: (
|
||||
inst: ReactInstance,
|
||||
registrationName: string,
|
||||
listener: () => void,
|
||||
) => void,
|
||||
willDeleteListener?: (
|
||||
inst: ReactInstance,
|
||||
registrationName: string,
|
||||
) => void,
|
||||
tapMoveThreshold?: number,
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
* Flow type for SyntheticEvent class that includes private properties
|
||||
*
|
||||
* @providesModule ReactSyntheticEventType
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactInstance} from 'ReactInstanceType';
|
||||
|
||||
export type DispatchConfig = {
|
||||
dependencies: Array<string>,
|
||||
phasedRegistrationNames?: {
|
||||
bubbled: string,
|
||||
captured: string,
|
||||
},
|
||||
registrationName?: string,
|
||||
};
|
||||
|
||||
export type ReactSyntheticEvent = {
|
||||
dispatchConfig: DispatchConfig;
|
||||
getPooled: (
|
||||
dispatchConfig: DispatchConfig,
|
||||
targetInst: ReactInstance,
|
||||
nativeTarget: Event,
|
||||
nativeEventTarget: EventTarget,
|
||||
) => ReactSyntheticEvent;
|
||||
} & SyntheticEvent;
|
|
@ -0,0 +1,303 @@
|
|||
/**
|
||||
* 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 SyntheticEvent
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var PooledClass = require('PooledClass');
|
||||
|
||||
var emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var didWarnForAddedNewProperty = false;
|
||||
var isProxySupported = typeof Proxy === 'function';
|
||||
|
||||
var shouldBeReleasedProperties = [
|
||||
'dispatchConfig',
|
||||
'_targetInst',
|
||||
'nativeEvent',
|
||||
'isDefaultPrevented',
|
||||
'isPropagationStopped',
|
||||
'_dispatchListeners',
|
||||
'_dispatchInstances',
|
||||
];
|
||||
|
||||
/**
|
||||
* @interface Event
|
||||
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
||||
*/
|
||||
var EventInterface = {
|
||||
type: null,
|
||||
target: null,
|
||||
// currentTarget is set when dispatching; no use in copying it here
|
||||
currentTarget: emptyFunction.thatReturnsNull,
|
||||
eventPhase: null,
|
||||
bubbles: null,
|
||||
cancelable: null,
|
||||
timeStamp: function(event) {
|
||||
return event.timeStamp || Date.now();
|
||||
},
|
||||
defaultPrevented: null,
|
||||
isTrusted: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthetic events are dispatched by event plugins, typically in response to a
|
||||
* top-level event delegation handler.
|
||||
*
|
||||
* These systems should generally use pooling to reduce the frequency of garbage
|
||||
* collection. The system should check `isPersistent` to determine whether the
|
||||
* event should be released into the pool after being dispatched. Users that
|
||||
* need a persisted event should invoke `persist`.
|
||||
*
|
||||
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by
|
||||
* normalizing browser quirks. Subclasses do not necessarily have to implement a
|
||||
* DOM interface; custom application-specific events can also subclass this.
|
||||
*
|
||||
* @param {object} dispatchConfig Configuration used to dispatch this event.
|
||||
* @param {*} targetInst Marker identifying the event target.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @param {DOMEventTarget} nativeEventTarget Target node.
|
||||
*/
|
||||
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
|
||||
if (__DEV__) {
|
||||
// these have a getter/setter for warnings
|
||||
delete this.nativeEvent;
|
||||
delete this.preventDefault;
|
||||
delete this.stopPropagation;
|
||||
}
|
||||
|
||||
this.dispatchConfig = dispatchConfig;
|
||||
this._targetInst = targetInst;
|
||||
this.nativeEvent = nativeEvent;
|
||||
|
||||
var Interface = this.constructor.Interface;
|
||||
for (var propName in Interface) {
|
||||
if (!Interface.hasOwnProperty(propName)) {
|
||||
continue;
|
||||
}
|
||||
if (__DEV__) {
|
||||
delete this[propName]; // this has a getter/setter for warnings
|
||||
}
|
||||
var normalize = Interface[propName];
|
||||
if (normalize) {
|
||||
this[propName] = normalize(nativeEvent);
|
||||
} else {
|
||||
if (propName === 'target') {
|
||||
this.target = nativeEventTarget;
|
||||
} else {
|
||||
this[propName] = nativeEvent[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var defaultPrevented = nativeEvent.defaultPrevented != null ?
|
||||
nativeEvent.defaultPrevented :
|
||||
nativeEvent.returnValue === false;
|
||||
if (defaultPrevented) {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
} else {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
|
||||
}
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
|
||||
return this;
|
||||
}
|
||||
|
||||
Object.assign(SyntheticEvent.prototype, {
|
||||
|
||||
preventDefault: function() {
|
||||
this.defaultPrevented = true;
|
||||
var event = this.nativeEvent;
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
} else if (typeof event.returnValue !== 'unknown') { // eslint-disable-line valid-typeof
|
||||
event.returnValue = false;
|
||||
}
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
},
|
||||
|
||||
stopPropagation: function() {
|
||||
var event = this.nativeEvent;
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
} else if (typeof event.cancelBubble !== 'unknown') { // eslint-disable-line valid-typeof
|
||||
// The ChangeEventPlugin registers a "propertychange" event for
|
||||
// IE. This event does not support bubbling or cancelling, and
|
||||
// any references to cancelBubble throw "Member not found". A
|
||||
// typeof check of "unknown" circumvents this issue (and is also
|
||||
// IE specific).
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
* We release all dispatched `SyntheticEvent`s after each event loop, adding
|
||||
* them back into the pool. This allows a way to hold onto a reference that
|
||||
* won't be added back into the pool.
|
||||
*/
|
||||
persist: function() {
|
||||
this.isPersistent = emptyFunction.thatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if this event should be released back into the pool.
|
||||
*
|
||||
* @return {boolean} True if this should not be released, false otherwise.
|
||||
*/
|
||||
isPersistent: emptyFunction.thatReturnsFalse,
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for `destructor` on each instance it releases.
|
||||
*/
|
||||
destructor: function() {
|
||||
var Interface = this.constructor.Interface;
|
||||
for (var propName in Interface) {
|
||||
if (__DEV__) {
|
||||
Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
|
||||
} else {
|
||||
this[propName] = null;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < shouldBeReleasedProperties.length; i++) {
|
||||
this[shouldBeReleasedProperties[i]] = null;
|
||||
}
|
||||
if (__DEV__) {
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'nativeEvent',
|
||||
getPooledWarningPropertyDefinition('nativeEvent', null)
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'preventDefault',
|
||||
getPooledWarningPropertyDefinition('preventDefault', emptyFunction)
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'stopPropagation',
|
||||
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
SyntheticEvent.Interface = EventInterface;
|
||||
|
||||
if (__DEV__) {
|
||||
if (isProxySupported) {
|
||||
/*eslint-disable no-func-assign */
|
||||
SyntheticEvent = new Proxy(SyntheticEvent, {
|
||||
construct: function(target, args) {
|
||||
return this.apply(target, Object.create(target.prototype), args);
|
||||
},
|
||||
apply: function(constructor, that, args) {
|
||||
return new Proxy(constructor.apply(that, args), {
|
||||
set: function(target, prop, value) {
|
||||
if (prop !== 'isPersistent' &&
|
||||
!target.constructor.Interface.hasOwnProperty(prop) &&
|
||||
shouldBeReleasedProperties.indexOf(prop) === -1) {
|
||||
warning(
|
||||
didWarnForAddedNewProperty || target.isPersistent(),
|
||||
'This synthetic event is reused for performance reasons. If you\'re ' +
|
||||
'seeing this, you\'re adding a new property in the synthetic event object. ' +
|
||||
'The property is never released. See ' +
|
||||
'https://fb.me/react-event-pooling for more information.'
|
||||
);
|
||||
didWarnForAddedNewProperty = true;
|
||||
}
|
||||
target[prop] = value;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
/*eslint-enable no-func-assign */
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper to reduce boilerplate when creating subclasses.
|
||||
*
|
||||
* @param {function} Class
|
||||
* @param {?object} Interface
|
||||
*/
|
||||
SyntheticEvent.augmentClass = function(Class, Interface) {
|
||||
var Super = this;
|
||||
|
||||
var E = function() {};
|
||||
E.prototype = Super.prototype;
|
||||
var prototype = new E();
|
||||
|
||||
Object.assign(prototype, Class.prototype);
|
||||
Class.prototype = prototype;
|
||||
Class.prototype.constructor = Class;
|
||||
|
||||
Class.Interface = Object.assign({}, Super.Interface, Interface);
|
||||
Class.augmentClass = Super.augmentClass;
|
||||
|
||||
PooledClass.addPoolingTo(Class, PooledClass.fourArgumentPooler);
|
||||
};
|
||||
|
||||
PooledClass.addPoolingTo(SyntheticEvent, PooledClass.fourArgumentPooler);
|
||||
|
||||
module.exports = SyntheticEvent;
|
||||
|
||||
/**
|
||||
* Helper to nullify syntheticEvent instance properties when destructing
|
||||
*
|
||||
* @param {object} SyntheticEvent
|
||||
* @param {String} propName
|
||||
* @return {object} defineProperty object
|
||||
*/
|
||||
function getPooledWarningPropertyDefinition(propName, getVal) {
|
||||
var isFunction = typeof getVal === 'function';
|
||||
return {
|
||||
configurable: true,
|
||||
set: set,
|
||||
get: get,
|
||||
};
|
||||
|
||||
function set(val) {
|
||||
var action = isFunction ? 'setting the method' : 'setting the property';
|
||||
warn(action, 'This is effectively a no-op');
|
||||
return val;
|
||||
}
|
||||
|
||||
function get() {
|
||||
var action = isFunction ? 'accessing the method' : 'accessing the property';
|
||||
var result = isFunction ? 'This is a no-op function' : 'This is set to null';
|
||||
warn(action, result);
|
||||
return getVal;
|
||||
}
|
||||
|
||||
function warn(action, result) {
|
||||
var warningCondition = false;
|
||||
warning(
|
||||
warningCondition,
|
||||
'This synthetic event is reused for performance reasons. If you\'re seeing this, ' +
|
||||
'you\'re %s `%s` on a released/nullified synthetic event. %s. ' +
|
||||
'If you must keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
action,
|
||||
propName,
|
||||
result
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,598 @@
|
|||
/**
|
||||
* 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 ResponderEventPlugin
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var ResponderSyntheticEvent = require('ResponderSyntheticEvent');
|
||||
var ResponderTouchHistoryStore = require('ResponderTouchHistoryStore');
|
||||
|
||||
var accumulate = require('accumulate');
|
||||
|
||||
var isStartish = EventPluginUtils.isStartish;
|
||||
var isMoveish = EventPluginUtils.isMoveish;
|
||||
var isEndish = EventPluginUtils.isEndish;
|
||||
var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
|
||||
var hasDispatches = EventPluginUtils.hasDispatches;
|
||||
var executeDispatchesInOrderStopAtTrue =
|
||||
EventPluginUtils.executeDispatchesInOrderStopAtTrue;
|
||||
|
||||
/**
|
||||
* Instance of element that should respond to touch/move types of interactions,
|
||||
* as indicated explicitly by relevant callbacks.
|
||||
*/
|
||||
var responderInst = null;
|
||||
|
||||
/**
|
||||
* Count of current touches. A textInput should become responder iff the
|
||||
* selection changes while there is a touch on the screen.
|
||||
*/
|
||||
var trackedTouchCount = 0;
|
||||
|
||||
/**
|
||||
* Last reported number of active touches.
|
||||
*/
|
||||
var previousActiveTouches = 0;
|
||||
|
||||
var changeResponder = function(nextResponderInst, blockHostResponder) {
|
||||
var oldResponderInst = responderInst;
|
||||
responderInst = nextResponderInst;
|
||||
if (ResponderEventPlugin.GlobalResponderHandler !== null) {
|
||||
ResponderEventPlugin.GlobalResponderHandler.onChange(
|
||||
oldResponderInst,
|
||||
nextResponderInst,
|
||||
blockHostResponder
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var eventTypes = {
|
||||
/**
|
||||
* On a `touchStart`/`mouseDown`, is it desired that this element become the
|
||||
* responder?
|
||||
*/
|
||||
startShouldSetResponder: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: 'onStartShouldSetResponder',
|
||||
captured: 'onStartShouldSetResponderCapture',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* On a `scroll`, is it desired that this element become the responder? This
|
||||
* is usually not needed, but should be used to retroactively infer that a
|
||||
* `touchStart` had occurred during momentum scroll. During a momentum scroll,
|
||||
* a touch start will be immediately followed by a scroll event if the view is
|
||||
* currently scrolling.
|
||||
*
|
||||
* TODO: This shouldn't bubble.
|
||||
*/
|
||||
scrollShouldSetResponder: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: 'onScrollShouldSetResponder',
|
||||
captured: 'onScrollShouldSetResponderCapture',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* On text selection change, should this element become the responder? This
|
||||
* is needed for text inputs or other views with native selection, so the
|
||||
* JS view can claim the responder.
|
||||
*
|
||||
* TODO: This shouldn't bubble.
|
||||
*/
|
||||
selectionChangeShouldSetResponder: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: 'onSelectionChangeShouldSetResponder',
|
||||
captured: 'onSelectionChangeShouldSetResponderCapture',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* On a `touchMove`/`mouseMove`, is it desired that this element become the
|
||||
* responder?
|
||||
*/
|
||||
moveShouldSetResponder: {
|
||||
phasedRegistrationNames: {
|
||||
bubbled: 'onMoveShouldSetResponder',
|
||||
captured: 'onMoveShouldSetResponderCapture',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Direct responder events dispatched directly to responder. Do not bubble.
|
||||
*/
|
||||
responderStart: {registrationName: 'onResponderStart'},
|
||||
responderMove: {registrationName: 'onResponderMove'},
|
||||
responderEnd: {registrationName: 'onResponderEnd'},
|
||||
responderRelease: {registrationName: 'onResponderRelease'},
|
||||
responderTerminationRequest: {
|
||||
registrationName: 'onResponderTerminationRequest',
|
||||
},
|
||||
responderGrant: {registrationName: 'onResponderGrant'},
|
||||
responderReject: {registrationName: 'onResponderReject'},
|
||||
responderTerminate: {registrationName: 'onResponderTerminate'},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Responder System:
|
||||
* ----------------
|
||||
*
|
||||
* - A global, solitary "interaction lock" on a view.
|
||||
* - If a node becomes the responder, it should convey visual feedback
|
||||
* immediately to indicate so, either by highlighting or moving accordingly.
|
||||
* - To be the responder means, that touches are exclusively important to that
|
||||
* responder view, and no other view.
|
||||
* - While touches are still occurring, the responder lock can be transferred to
|
||||
* a new view, but only to increasingly "higher" views (meaning ancestors of
|
||||
* the current responder).
|
||||
*
|
||||
* Responder being granted:
|
||||
* ------------------------
|
||||
*
|
||||
* - Touch starts, moves, and scrolls can cause an ID to become the responder.
|
||||
* - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to
|
||||
* the "appropriate place".
|
||||
* - If nothing is currently the responder, the "appropriate place" is the
|
||||
* initiating event's `targetID`.
|
||||
* - If something *is* already the responder, the "appropriate place" is the
|
||||
* first common ancestor of the event target and the current `responderInst`.
|
||||
* - Some negotiation happens: See the timing diagram below.
|
||||
* - Scrolled views automatically become responder. The reasoning is that a
|
||||
* platform scroll view that isn't built on top of the responder system has
|
||||
* began scrolling, and the active responder must now be notified that the
|
||||
* interaction is no longer locked to it - the system has taken over.
|
||||
*
|
||||
* - Responder being released:
|
||||
* As soon as no more touches that *started* inside of descendants of the
|
||||
* *current* responderInst, an `onResponderRelease` event is dispatched to the
|
||||
* current responder, and the responder lock is released.
|
||||
*
|
||||
* TODO:
|
||||
* - on "end", a callback hook for `onResponderEndShouldRemainResponder` that
|
||||
* determines if the responder lock should remain.
|
||||
* - If a view shouldn't "remain" the responder, any active touches should by
|
||||
* default be considered "dead" and do not influence future negotiations or
|
||||
* bubble paths. It should be as if those touches do not exist.
|
||||
* -- For multitouch: Usually a translate-z will choose to "remain" responder
|
||||
* after one out of many touches ended. For translate-y, usually the view
|
||||
* doesn't wish to "remain" responder after one of many touches end.
|
||||
* - Consider building this on top of a `stopPropagation` model similar to
|
||||
* `W3C` events.
|
||||
* - Ensure that `onResponderTerminate` is called on touch cancels, whether or
|
||||
* not `onResponderTerminationRequest` returns `true` or `false`.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Negotiation Performed
|
||||
+-----------------------+
|
||||
/ \
|
||||
Process low level events to + Current Responder + wantsResponderID
|
||||
determine who to perform negot-| (if any exists at all) |
|
||||
iation/transition | Otherwise just pass through|
|
||||
-------------------------------+----------------------------+------------------+
|
||||
Bubble to find first ID | |
|
||||
to return true:wantsResponderID| |
|
||||
| |
|
||||
+-------------+ | |
|
||||
| onTouchStart| | |
|
||||
+------+------+ none | |
|
||||
| return| |
|
||||
+-----------v-------------+true| +------------------------+ |
|
||||
|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+
|
||||
+-----------+-------------+ | +------------------------+ | |
|
||||
| | | +--------+-------+
|
||||
| returned true for| false:REJECT +-------->|onResponderReject
|
||||
| wantsResponderID | | | +----------------+
|
||||
| (now attempt | +------------------+-----+ |
|
||||
| handoff) | | onResponder | |
|
||||
+------------------->| TerminationRequest| |
|
||||
| +------------------+-----+ |
|
||||
| | | +----------------+
|
||||
| true:GRANT +-------->|onResponderGrant|
|
||||
| | +--------+-------+
|
||||
| +------------------------+ | |
|
||||
| | onResponderTerminate |<-----------+
|
||||
| +------------------+-----+ |
|
||||
| | | +----------------+
|
||||
| +-------->|onResponderStart|
|
||||
| | +----------------+
|
||||
Bubble to find first ID | |
|
||||
to return true:wantsResponderID| |
|
||||
| |
|
||||
+-------------+ | |
|
||||
| onTouchMove | | |
|
||||
+------+------+ none | |
|
||||
| return| |
|
||||
+-----------v-------------+true| +------------------------+ |
|
||||
|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+
|
||||
+-----------+-------------+ | +------------------------+ | |
|
||||
| | | +--------+-------+
|
||||
| returned true for| false:REJECT +-------->|onResponderRejec|
|
||||
| wantsResponderID | | | +----------------+
|
||||
| (now attempt | +------------------+-----+ |
|
||||
| handoff) | | onResponder | |
|
||||
+------------------->| TerminationRequest| |
|
||||
| +------------------+-----+ |
|
||||
| | | +----------------+
|
||||
| true:GRANT +-------->|onResponderGrant|
|
||||
| | +--------+-------+
|
||||
| +------------------------+ | |
|
||||
| | onResponderTerminate |<-----------+
|
||||
| +------------------+-----+ |
|
||||
| | | +----------------+
|
||||
| +-------->|onResponderMove |
|
||||
| | +----------------+
|
||||
| |
|
||||
| |
|
||||
Some active touch started| |
|
||||
inside current responder | +------------------------+ |
|
||||
+------------------------->| onResponderEnd | |
|
||||
| | +------------------------+ |
|
||||
+---+---------+ | |
|
||||
| onTouchEnd | | |
|
||||
+---+---------+ | |
|
||||
| | +------------------------+ |
|
||||
+------------------------->| onResponderEnd | |
|
||||
No active touches started| +-----------+------------+ |
|
||||
inside current responder | | |
|
||||
| v |
|
||||
| +------------------------+ |
|
||||
| | onResponderRelease | |
|
||||
| +------------------------+ |
|
||||
| |
|
||||
+ + */
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A note about event ordering in the `EventPluginHub`.
|
||||
*
|
||||
* Suppose plugins are injected in the following order:
|
||||
*
|
||||
* `[R, S, C]`
|
||||
*
|
||||
* To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
|
||||
* `onClick` etc) and `R` is `ResponderEventPlugin`.
|
||||
*
|
||||
* "Deferred-Dispatched Events":
|
||||
*
|
||||
* - The current event plugin system will traverse the list of injected plugins,
|
||||
* in order, and extract events by collecting the plugin's return value of
|
||||
* `extractEvents()`.
|
||||
* - These events that are returned from `extractEvents` are "deferred
|
||||
* dispatched events".
|
||||
* - When returned from `extractEvents`, deferred-dispatched events contain an
|
||||
* "accumulation" of deferred dispatches.
|
||||
* - These deferred dispatches are accumulated/collected before they are
|
||||
* returned, but processed at a later time by the `EventPluginHub` (hence the
|
||||
* name deferred).
|
||||
*
|
||||
* In the process of returning their deferred-dispatched events, event plugins
|
||||
* themselves can dispatch events on-demand without returning them from
|
||||
* `extractEvents`. Plugins might want to do this, so that they can use event
|
||||
* dispatching as a tool that helps them decide which events should be extracted
|
||||
* in the first place.
|
||||
*
|
||||
* "On-Demand-Dispatched Events":
|
||||
*
|
||||
* - On-demand-dispatched events are not returned from `extractEvents`.
|
||||
* - On-demand-dispatched events are dispatched during the process of returning
|
||||
* the deferred-dispatched events.
|
||||
* - They should not have side effects.
|
||||
* - They should be avoided, and/or eventually be replaced with another
|
||||
* abstraction that allows event plugins to perform multiple "rounds" of event
|
||||
* extraction.
|
||||
*
|
||||
* Therefore, the sequence of event dispatches becomes:
|
||||
*
|
||||
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
|
||||
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
|
||||
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
|
||||
* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
|
||||
* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
|
||||
* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
|
||||
*
|
||||
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
|
||||
* on-demand dispatch returns `true` (and some other details are satisfied) the
|
||||
* `onResponderGrant` deferred dispatched event is returned from
|
||||
* `extractEvents`. The sequence of dispatch executions in this case
|
||||
* will appear as follows:
|
||||
*
|
||||
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
|
||||
* - `touchStartCapture` (`EventPluginHub` dispatches as usual)
|
||||
* - `touchStart` (`EventPluginHub` dispatches as usual)
|
||||
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
|
||||
*/
|
||||
|
||||
function setResponderAndExtractTransfer(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
) {
|
||||
var shouldSetEventType =
|
||||
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
|
||||
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
|
||||
topLevelType === 'topSelectionChange' ?
|
||||
eventTypes.selectionChangeShouldSetResponder :
|
||||
eventTypes.scrollShouldSetResponder;
|
||||
|
||||
// TODO: stop one short of the current responder.
|
||||
var bubbleShouldSetFrom = !responderInst ?
|
||||
targetInst :
|
||||
EventPluginUtils.getLowestCommonAncestor(responderInst, targetInst);
|
||||
|
||||
// When capturing/bubbling the "shouldSet" event, we want to skip the target
|
||||
// (deepest ID) if it happens to be the current responder. The reasoning:
|
||||
// It's strange to get an `onMoveShouldSetResponder` when you're *already*
|
||||
// the responder.
|
||||
var skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst;
|
||||
var shouldSetEvent = ResponderSyntheticEvent.getPooled(
|
||||
shouldSetEventType,
|
||||
bubbleShouldSetFrom,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
if (skipOverBubbleShouldSetFrom) {
|
||||
EventPropagators.accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);
|
||||
} else {
|
||||
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
|
||||
}
|
||||
var wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
|
||||
if (!shouldSetEvent.isPersistent()) {
|
||||
shouldSetEvent.constructor.release(shouldSetEvent);
|
||||
}
|
||||
|
||||
if (!wantsResponderInst || wantsResponderInst === responderInst) {
|
||||
return null;
|
||||
}
|
||||
var extracted;
|
||||
var grantEvent = ResponderSyntheticEvent.getPooled(
|
||||
eventTypes.responderGrant,
|
||||
wantsResponderInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
|
||||
EventPropagators.accumulateDirectDispatches(grantEvent);
|
||||
var blockHostResponder = executeDirectDispatch(grantEvent) === true;
|
||||
if (responderInst) {
|
||||
|
||||
var terminationRequestEvent = ResponderSyntheticEvent.getPooled(
|
||||
eventTypes.responderTerminationRequest,
|
||||
responderInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
terminationRequestEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
|
||||
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
|
||||
executeDirectDispatch(terminationRequestEvent);
|
||||
if (!terminationRequestEvent.isPersistent()) {
|
||||
terminationRequestEvent.constructor.release(terminationRequestEvent);
|
||||
}
|
||||
|
||||
if (shouldSwitch) {
|
||||
var terminateEvent = ResponderSyntheticEvent.getPooled(
|
||||
eventTypes.responderTerminate,
|
||||
responderInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
EventPropagators.accumulateDirectDispatches(terminateEvent);
|
||||
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
|
||||
changeResponder(wantsResponderInst, blockHostResponder);
|
||||
} else {
|
||||
var rejectEvent = ResponderSyntheticEvent.getPooled(
|
||||
eventTypes.responderReject,
|
||||
wantsResponderInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
EventPropagators.accumulateDirectDispatches(rejectEvent);
|
||||
extracted = accumulate(extracted, rejectEvent);
|
||||
}
|
||||
} else {
|
||||
extracted = accumulate(extracted, grantEvent);
|
||||
changeResponder(wantsResponderInst, blockHostResponder);
|
||||
}
|
||||
return extracted;
|
||||
}
|
||||
|
||||
/**
|
||||
* A transfer is a negotiation between a currently set responder and the next
|
||||
* element to claim responder status. Any start event could trigger a transfer
|
||||
* of responderInst. Any move event could trigger a transfer.
|
||||
*
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @return {boolean} True if a transfer of responder could possibly occur.
|
||||
*/
|
||||
function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
|
||||
return topLevelInst && (
|
||||
// responderIgnoreScroll: We are trying to migrate away from specifically
|
||||
// tracking native scroll events here and responderIgnoreScroll indicates we
|
||||
// will send topTouchCancel to handle canceling touch events instead
|
||||
(topLevelType === 'topScroll' &&
|
||||
!nativeEvent.responderIgnoreScroll) ||
|
||||
(trackedTouchCount > 0 &&
|
||||
topLevelType === 'topSelectionChange') ||
|
||||
isStartish(topLevelType) ||
|
||||
isMoveish(topLevelType)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this touch end event makes it such that there are no
|
||||
* longer any touches that started inside of the current `responderInst`.
|
||||
*
|
||||
* @param {NativeEvent} nativeEvent Native touch end event.
|
||||
* @return {boolean} Whether or not this touch end event ends the responder.
|
||||
*/
|
||||
function noResponderTouches(nativeEvent) {
|
||||
var touches = nativeEvent.touches;
|
||||
if (!touches || touches.length === 0) {
|
||||
return true;
|
||||
}
|
||||
for (var i = 0; i < touches.length; i++) {
|
||||
var activeTouch = touches[i];
|
||||
var target = activeTouch.target;
|
||||
if (target !== null && target !== undefined && target !== 0) {
|
||||
// Is the original touch location inside of the current responder?
|
||||
var targetInst = EventPluginUtils.getInstanceFromNode(target);
|
||||
if (EventPluginUtils.isAncestor(responderInst, targetInst)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var ResponderEventPlugin = {
|
||||
|
||||
/* For unit testing only */
|
||||
_getResponderID: function() {
|
||||
return responderInst ? responderInst._rootNodeID : null;
|
||||
},
|
||||
|
||||
eventTypes: eventTypes,
|
||||
|
||||
/**
|
||||
* We must be resilient to `targetInst` being `null` on `touchMove` or
|
||||
* `touchEnd`. On certain platforms, this means that a native scroll has
|
||||
* assumed control and the original touch targets are destroyed.
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
) {
|
||||
if (isStartish(topLevelType)) {
|
||||
trackedTouchCount += 1;
|
||||
} else if (isEndish(topLevelType)) {
|
||||
if (trackedTouchCount >= 0) {
|
||||
trackedTouchCount -= 1;
|
||||
} else {
|
||||
console.error(
|
||||
'Ended a touch event which was not counted in `trackedTouchCount`.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
|
||||
|
||||
var extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent) ?
|
||||
setResponderAndExtractTransfer(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget) :
|
||||
null;
|
||||
// Responder may or may not have transferred on a new touch start/move.
|
||||
// Regardless, whoever is the responder after any potential transfer, we
|
||||
// direct all touch start/move/ends to them in the form of
|
||||
// `onResponderMove/Start/End`. These will be called for *every* additional
|
||||
// finger that move/start/end, dispatched directly to whoever is the
|
||||
// current responder at that moment, until the responder is "released".
|
||||
//
|
||||
// These multiple individual change touch events are are always bookended
|
||||
// by `onResponderGrant`, and one of
|
||||
// (`onResponderRelease/onResponderTerminate`).
|
||||
var isResponderTouchStart = responderInst && isStartish(topLevelType);
|
||||
var isResponderTouchMove = responderInst && isMoveish(topLevelType);
|
||||
var isResponderTouchEnd = responderInst && isEndish(topLevelType);
|
||||
var incrementalTouch =
|
||||
isResponderTouchStart ? eventTypes.responderStart :
|
||||
isResponderTouchMove ? eventTypes.responderMove :
|
||||
isResponderTouchEnd ? eventTypes.responderEnd :
|
||||
null;
|
||||
|
||||
if (incrementalTouch) {
|
||||
var gesture =
|
||||
ResponderSyntheticEvent.getPooled(
|
||||
incrementalTouch,
|
||||
responderInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
EventPropagators.accumulateDirectDispatches(gesture);
|
||||
extracted = accumulate(extracted, gesture);
|
||||
}
|
||||
|
||||
var isResponderTerminate =
|
||||
responderInst &&
|
||||
topLevelType === 'topTouchCancel';
|
||||
var isResponderRelease =
|
||||
responderInst &&
|
||||
!isResponderTerminate &&
|
||||
isEndish(topLevelType) &&
|
||||
noResponderTouches(nativeEvent);
|
||||
var finalTouch =
|
||||
isResponderTerminate ? eventTypes.responderTerminate :
|
||||
isResponderRelease ? eventTypes.responderRelease :
|
||||
null;
|
||||
if (finalTouch) {
|
||||
var finalEvent = ResponderSyntheticEvent.getPooled(
|
||||
finalTouch, responderInst, nativeEvent, nativeEventTarget
|
||||
);
|
||||
finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
|
||||
EventPropagators.accumulateDirectDispatches(finalEvent);
|
||||
extracted = accumulate(extracted, finalEvent);
|
||||
changeResponder(null);
|
||||
}
|
||||
|
||||
var numberActiveTouches =
|
||||
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
|
||||
if (ResponderEventPlugin.GlobalInteractionHandler &&
|
||||
numberActiveTouches !== previousActiveTouches) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler.onChange(
|
||||
numberActiveTouches
|
||||
);
|
||||
}
|
||||
previousActiveTouches = numberActiveTouches;
|
||||
|
||||
return extracted;
|
||||
},
|
||||
|
||||
GlobalResponderHandler: null,
|
||||
GlobalInteractionHandler: null,
|
||||
|
||||
injection: {
|
||||
/**
|
||||
* @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler
|
||||
* Object that handles any change in responder. Use this to inject
|
||||
* integration with an existing touch handling system etc.
|
||||
*/
|
||||
injectGlobalResponderHandler: function(GlobalResponderHandler) {
|
||||
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
|
||||
* Object that handles any change in the number of active touches.
|
||||
*/
|
||||
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ResponderEventPlugin;
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 ResponderSyntheticEvent
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
|
||||
/**
|
||||
* `touchHistory` isn't actually on the native event, but putting it in the
|
||||
* interface will ensure that it is cleaned up when pooled/destroyed. The
|
||||
* `ResponderEventPlugin` will populate it appropriately.
|
||||
*/
|
||||
var ResponderEventInterface = {
|
||||
touchHistory: function(nativeEvent) {
|
||||
return null; // Actually doesn't even look at the native event.
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} dispatchConfig Configuration used to dispatch this event.
|
||||
* @param {string} dispatchMarker Marker identifying the event target.
|
||||
* @param {object} nativeEvent Native event.
|
||||
* @extends {SyntheticEvent}
|
||||
*/
|
||||
function ResponderSyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
|
||||
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
|
||||
}
|
||||
|
||||
SyntheticEvent.augmentClass(ResponderSyntheticEvent, ResponderEventInterface);
|
||||
|
||||
module.exports = ResponderSyntheticEvent;
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* 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 ResponderTouchHistoryStore
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventPluginUtils = require('EventPluginUtils');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
const {
|
||||
isEndish,
|
||||
isMoveish,
|
||||
isStartish,
|
||||
} = EventPluginUtils;
|
||||
|
||||
/**
|
||||
* Tracks the position and time of each active touch by `touch.identifier`. We
|
||||
* should typically only see IDs in the range of 1-20 because IDs get recycled
|
||||
* when touches end and start again.
|
||||
*/
|
||||
type TouchRecord = {
|
||||
touchActive: boolean,
|
||||
startPageX: number,
|
||||
startPageY: number,
|
||||
startTimeStamp: number,
|
||||
currentPageX: number,
|
||||
currentPageY: number,
|
||||
currentTimeStamp: number,
|
||||
previousPageX: number,
|
||||
previousPageY: number,
|
||||
previousTimeStamp: number,
|
||||
};
|
||||
|
||||
const MAX_TOUCH_BANK = 20;
|
||||
const touchBank: Array<TouchRecord> = [];
|
||||
const touchHistory = {
|
||||
touchBank,
|
||||
numberActiveTouches: 0,
|
||||
// If there is only one active touch, we remember its location. This prevents
|
||||
// us having to loop through all of the touches all the time in the most
|
||||
// common case.
|
||||
indexOfSingleActiveTouch: -1,
|
||||
mostRecentTimeStamp: 0,
|
||||
};
|
||||
|
||||
type Touch = {
|
||||
identifier: ?number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
timestamp: number,
|
||||
};
|
||||
type TouchEvent = {
|
||||
changedTouches: Array<Touch>,
|
||||
touches: Array<Touch>,
|
||||
};
|
||||
|
||||
function timestampForTouch(touch: Touch): number {
|
||||
// The legacy internal implementation provides "timeStamp", which has been
|
||||
// renamed to "timestamp". Let both work for now while we iron it out
|
||||
// TODO (evv): rename timeStamp to timestamp in internal code
|
||||
return (touch: any).timeStamp || touch.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Instead of making gestures recompute filtered velocity, we could
|
||||
* include a built in velocity computation that can be reused globally.
|
||||
*/
|
||||
function createTouchRecord(touch: Touch): TouchRecord {
|
||||
return {
|
||||
touchActive: true,
|
||||
startPageX: touch.pageX,
|
||||
startPageY: touch.pageY,
|
||||
startTimeStamp: timestampForTouch(touch),
|
||||
currentPageX: touch.pageX,
|
||||
currentPageY: touch.pageY,
|
||||
currentTimeStamp: timestampForTouch(touch),
|
||||
previousPageX: touch.pageX,
|
||||
previousPageY: touch.pageY,
|
||||
previousTimeStamp: timestampForTouch(touch),
|
||||
};
|
||||
}
|
||||
|
||||
function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
|
||||
touchRecord.touchActive = true;
|
||||
touchRecord.startPageX = touch.pageX;
|
||||
touchRecord.startPageY = touch.pageY;
|
||||
touchRecord.startTimeStamp = timestampForTouch(touch);
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchRecord.previousPageX = touch.pageX;
|
||||
touchRecord.previousPageY = touch.pageY;
|
||||
touchRecord.previousTimeStamp = timestampForTouch(touch);
|
||||
}
|
||||
|
||||
function getTouchIdentifier({identifier}: Touch): number {
|
||||
invariant(identifier != null, 'Touch object is missing identifier.');
|
||||
warning(
|
||||
identifier <= MAX_TOUCH_BANK,
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
identifier,
|
||||
MAX_TOUCH_BANK
|
||||
);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function recordTouchStart(touch: Touch): void {
|
||||
const identifier = getTouchIdentifier(touch);
|
||||
const touchRecord = touchBank[identifier];
|
||||
if (touchRecord) {
|
||||
resetTouchRecord(touchRecord, touch);
|
||||
} else {
|
||||
touchBank[identifier] = createTouchRecord(touch);
|
||||
}
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
}
|
||||
|
||||
function recordTouchMove(touch: Touch): void {
|
||||
const touchRecord = touchBank[getTouchIdentifier(touch)];
|
||||
if (touchRecord) {
|
||||
touchRecord.touchActive = true;
|
||||
touchRecord.previousPageX = touchRecord.currentPageX;
|
||||
touchRecord.previousPageY = touchRecord.currentPageY;
|
||||
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.error(
|
||||
'Cannot record touch move without a touch start.\n' +
|
||||
'Touch Move: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function recordTouchEnd(touch: Touch): void {
|
||||
const touchRecord = touchBank[getTouchIdentifier(touch)];
|
||||
if (touchRecord) {
|
||||
touchRecord.touchActive = false;
|
||||
touchRecord.previousPageX = touchRecord.currentPageX;
|
||||
touchRecord.previousPageY = touchRecord.currentPageY;
|
||||
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
|
||||
touchRecord.currentPageX = touch.pageX;
|
||||
touchRecord.currentPageY = touch.pageY;
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.error(
|
||||
'Cannot record touch end without a touch start.\n' +
|
||||
'Touch End: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
printTouchBank()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function printTouch(touch: Touch): string {
|
||||
return JSON.stringify({
|
||||
identifier: touch.identifier,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
timestamp: timestampForTouch(touch),
|
||||
});
|
||||
}
|
||||
|
||||
function printTouchBank(): string {
|
||||
let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK));
|
||||
if (touchBank.length > MAX_TOUCH_BANK) {
|
||||
printed += ' (original size: ' + touchBank.length + ')';
|
||||
}
|
||||
return printed;
|
||||
}
|
||||
|
||||
const ResponderTouchHistoryStore = {
|
||||
recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void {
|
||||
if (isMoveish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordTouchMove);
|
||||
} else if (isStartish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordTouchStart);
|
||||
touchHistory.numberActiveTouches = nativeEvent.touches.length;
|
||||
if (touchHistory.numberActiveTouches === 1) {
|
||||
touchHistory.indexOfSingleActiveTouch =
|
||||
nativeEvent.touches[0].identifier;
|
||||
}
|
||||
} else if (isEndish(topLevelType)) {
|
||||
nativeEvent.changedTouches.forEach(recordTouchEnd);
|
||||
touchHistory.numberActiveTouches = nativeEvent.touches.length;
|
||||
if (touchHistory.numberActiveTouches === 1) {
|
||||
for (let i = 0; i < touchBank.length; i++) {
|
||||
const touchTrackToCheck = touchBank[i];
|
||||
if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
|
||||
touchHistory.indexOfSingleActiveTouch = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
|
||||
warning(
|
||||
activeRecord != null &&
|
||||
activeRecord.touchActive,
|
||||
'Cannot find single active touch.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
touchHistory,
|
||||
};
|
||||
|
||||
|
||||
module.exports = ResponderTouchHistoryStore;
|
|
@ -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;
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Copyright 2014-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 ReactChildReconciler
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var KeyEscapeUtils = require('KeyEscapeUtils');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
var traverseAllChildren = require('traverseAllChildren');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var ReactComponentTreeHook;
|
||||
|
||||
if (
|
||||
typeof process !== 'undefined' &&
|
||||
process.env &&
|
||||
process.env.NODE_ENV === 'test'
|
||||
) {
|
||||
// Temporary hack.
|
||||
// Inline requires don't work well with Jest:
|
||||
// https://github.com/facebook/react/issues/7240
|
||||
// Remove the inline requires when we don't need them anymore:
|
||||
// https://github.com/facebook/react/pull/7178
|
||||
ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
|
||||
}
|
||||
|
||||
function instantiateChild(childInstances, child, name, selfDebugID) {
|
||||
// We found a component instance.
|
||||
var keyUnique = (childInstances[name] === undefined);
|
||||
if (__DEV__) {
|
||||
if (!ReactComponentTreeHook) {
|
||||
ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
|
||||
}
|
||||
if (!keyUnique) {
|
||||
warning(
|
||||
false,
|
||||
'flattenChildren(...): Encountered two children with the same key, ' +
|
||||
'`%s`. Child keys must be unique; when two children share a key, only ' +
|
||||
'the first child will be used.%s',
|
||||
KeyEscapeUtils.unescape(name),
|
||||
ReactComponentTreeHook.getStackAddendumByID(selfDebugID)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (child != null && keyUnique) {
|
||||
childInstances[name] = instantiateReactComponent(child, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ReactChildReconciler provides helpers for initializing or updating a set of
|
||||
* children. Its output is suitable for passing it onto ReactMultiChild which
|
||||
* does diffed reordering and insertion.
|
||||
*/
|
||||
var ReactChildReconciler = {
|
||||
/**
|
||||
* Generates a "mount image" for each of the supplied children. In the case
|
||||
* of `ReactDOMComponent`, a mount image is a string of markup.
|
||||
*
|
||||
* @param {?object} nestedChildNodes Nested child maps.
|
||||
* @return {?object} A set of child instances.
|
||||
* @internal
|
||||
*/
|
||||
instantiateChildren: function(
|
||||
nestedChildNodes,
|
||||
transaction,
|
||||
context,
|
||||
selfDebugID // 0 in production and for roots
|
||||
) {
|
||||
if (nestedChildNodes == null) {
|
||||
return null;
|
||||
}
|
||||
var childInstances = {};
|
||||
|
||||
if (__DEV__) {
|
||||
traverseAllChildren(
|
||||
nestedChildNodes,
|
||||
(childInsts, child, name) => instantiateChild(
|
||||
childInsts,
|
||||
child,
|
||||
name,
|
||||
selfDebugID
|
||||
),
|
||||
childInstances
|
||||
);
|
||||
} else {
|
||||
traverseAllChildren(nestedChildNodes, instantiateChild, childInstances);
|
||||
}
|
||||
return childInstances;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the rendered children and returns a new set of children.
|
||||
*
|
||||
* @param {?object} prevChildren Previously initialized set of children.
|
||||
* @param {?object} nextChildren Flat child element maps.
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @param {object} context
|
||||
* @return {?object} A new set of child instances.
|
||||
* @internal
|
||||
*/
|
||||
updateChildren: function(
|
||||
prevChildren,
|
||||
nextChildren,
|
||||
mountImages,
|
||||
removedNodes,
|
||||
transaction,
|
||||
hostParent,
|
||||
hostContainerInfo,
|
||||
context,
|
||||
selfDebugID // 0 in production and for roots
|
||||
) {
|
||||
// We currently don't have a way to track moves here but if we use iterators
|
||||
// instead of for..in we can zip the iterators and check if an item has
|
||||
// moved.
|
||||
// TODO: If nothing has changed, return the prevChildren object so that we
|
||||
// can quickly bailout if nothing has changed.
|
||||
if (!nextChildren && !prevChildren) {
|
||||
return;
|
||||
}
|
||||
var name;
|
||||
var prevChild;
|
||||
for (name in nextChildren) {
|
||||
if (!nextChildren.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
prevChild = prevChildren && prevChildren[name];
|
||||
var prevElement = prevChild && prevChild._currentElement;
|
||||
var nextElement = nextChildren[name];
|
||||
if (prevChild != null &&
|
||||
shouldUpdateReactComponent(prevElement, nextElement)) {
|
||||
ReactReconciler.receiveComponent(
|
||||
prevChild, nextElement, transaction, context
|
||||
);
|
||||
nextChildren[name] = prevChild;
|
||||
} else {
|
||||
if (prevChild) {
|
||||
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
|
||||
ReactReconciler.unmountComponent(prevChild, false);
|
||||
}
|
||||
// The child must be instantiated before it's mounted.
|
||||
var nextChildInstance = instantiateReactComponent(nextElement, true);
|
||||
nextChildren[name] = nextChildInstance;
|
||||
// Creating mount image now ensures refs are resolved in right order
|
||||
// (see https://github.com/facebook/react/pull/7101 for explanation).
|
||||
var nextChildMountImage = ReactReconciler.mountComponent(
|
||||
nextChildInstance,
|
||||
transaction,
|
||||
hostParent,
|
||||
hostContainerInfo,
|
||||
context,
|
||||
selfDebugID
|
||||
);
|
||||
mountImages.push(nextChildMountImage);
|
||||
}
|
||||
}
|
||||
// Unmount children that are no longer present.
|
||||
for (name in prevChildren) {
|
||||
if (prevChildren.hasOwnProperty(name) &&
|
||||
!(nextChildren && nextChildren.hasOwnProperty(name))) {
|
||||
prevChild = prevChildren[name];
|
||||
removedNodes[name] = ReactReconciler.getHostNode(prevChild);
|
||||
ReactReconciler.unmountComponent(prevChild, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts all rendered children. This should be used to clean up children
|
||||
* when this component is unmounted.
|
||||
*
|
||||
* @param {?object} renderedChildren Previously initialized set of children.
|
||||
* @internal
|
||||
*/
|
||||
unmountChildren: function(renderedChildren, safely) {
|
||||
for (var name in renderedChildren) {
|
||||
if (renderedChildren.hasOwnProperty(name)) {
|
||||
var renderedChild = renderedChildren[name];
|
||||
ReactReconciler.unmountComponent(renderedChild, safely);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactChildReconciler;
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче