Flat ReactNative renderer bundle [WIP]

Reviewed By: trueadm

Differential Revision: D5013497

fbshipit-source-id: 1e23b08751b8b6e2dd570ff584c815c8a9b8f35f
This commit is contained in:
Brian Vaughn 2017-05-26 10:51:06 -07:00 committed by Facebook Github Bot
parent 21e3d4db20
commit 94c565a2c4
173 changed files with 14373 additions and 31629 deletions

View File

@ -18,7 +18,6 @@
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
.*/Libraries/react-native/ReactNative.js
[include]

View File

@ -1,5 +0,0 @@
# 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).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
/**
* 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 NativeMethodsMixin
* @flow
*/
'use strict';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
import type {NativeMethodsMixinType} from 'ReactNativeTypes';
const {NativeMethodsMixin} = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
module.exports = ((NativeMethodsMixin: any): $Exact<NativeMethodsMixinType>);

View File

@ -95,8 +95,8 @@ var addPoolingTo = function<T>(
CopyConstructor: Class<T>,
pooler: Pooler,
): Class<T> & {
getPooled(...args: $ReadOnlyArray<mixed>): /* arguments of the constructor */ T,
release(instance: mixed): void,
getPooled(): /* arguments of the constructor */ T,
release(): void,
} {
// Casting as any so that flow ignores the actual implementation and trusts
// it to match the type we declared

View File

@ -6,12 +6,14 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @providesModule ReactPropTypesSecret
* @providesModule ReactDebugTool
*/
'use strict';
const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports = ReactPropTypesSecret;
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactDebugTool;

View File

@ -0,0 +1,19 @@
/**
* 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 ReactGlobalSharedState
*/
'use strict';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactGlobalSharedState;

View File

@ -13,6 +13,18 @@
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
module.exports = ReactNativeFeatureFlags.useFiber
? require('ReactNativeFiber')
: require('ReactNativeStack');
import type {ReactNativeType} from 'ReactNativeTypes';
let ReactNative;
if (__DEV__) {
ReactNative = ReactNativeFeatureFlags.useFiber
? require('ReactNativeFiber-dev')
: require('ReactNativeStack-dev');
} else {
ReactNative = ReactNativeFeatureFlags.useFiber
? require('ReactNativeFiber-prod')
: require('ReactNativeStack-prod');
}
module.exports = (ReactNative: ReactNativeType);

View File

@ -0,0 +1,20 @@
/**
* 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
* @flow
*/
'use strict';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactNativeComponentTree;

View File

@ -0,0 +1,20 @@
/**
* 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 ReactNativePropRegistry
* @flow
*/
'use strict';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactNativePropRegistry;

View File

@ -0,0 +1,89 @@
/**
* 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 ReactNativeTypes
* @flow
*/
'use strict';
import type React from 'react';
export type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number,
) => void;
export type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void;
export type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number,
) => void;
/**
* This type keeps ReactNativeFiberHostComponent and NativeMethodsMixin in sync.
* It can also provide types for ReactNative applications that use NMM or refs.
*/
export type NativeMethodsMixinType = {
blur(): void,
focus(): void,
measure(callback: MeasureOnSuccessCallback): void,
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void,
measureLayout(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void,
): void,
setNativeProps(nativeProps: Object): void,
};
type ReactNativeBaseComponentViewConfig = {
validAttributes: Object,
uiViewClassName: string,
propTypes?: Object,
};
type SecretInternalsType = {
NativeMethodsMixin: NativeMethodsMixinType,
createReactNativeComponentClass(
viewConfig: ReactNativeBaseComponentViewConfig,
): any,
ReactNativeComponentTree: any,
ReactNativePropRegistry: any,
// TODO (bvaughn) Decide which additional types to expose here?
// And how much information to fill in for the above types.
};
/**
* Flat ReactNative renderer bundles are too big for Flow to parse effeciently.
* Provide minimal Flow typing for the high-level RN API and call it a day.
*/
export type ReactNativeType = {
findNodeHandle(componentOrHandle: any): ?number,
render(
element: React.Element<any>,
containerTag: any,
callback: ?Function,
): any,
unmountComponentAtNode(containerTag: number): any,
unmountComponentAtNodeAndRemoveContainer(containerTag: number): any,
unstable_batchedUpdates: any, // TODO (bvaughn) Add types
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType,
};

View File

@ -6,16 +6,13 @@
* 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 getNextDebugID
* @flow
* @providesModule ReactPerf
*/
'use strict';
var nextDebugID = 1;
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
function getNextDebugID(): number {
return nextDebugID++;
}
module.exports = getNextDebugID;
module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf;

View File

@ -12,9 +12,6 @@
'use strict';
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
import type {ReactPortal} from 'ReactPortal';
export type ReactNode =
| ReactElement<any>
| ReactCoroutine
@ -30,3 +27,26 @@ export type ReactNodeList = ReactEmpty | ReactNode;
export type ReactText = string | number;
export type ReactEmpty = null | void | boolean;
export type ReactCoroutine = {
$$typeof: Symbol | number,
key: null | string,
children: any,
// This should be a more specific CoroutineHandler
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
props: any,
};
export type ReactYield = {
$$typeof: Symbol | number,
value: mixed,
};
export type ReactPortal = {
$$typeof: Symbol | number,
key: null | string,
containerInfo: any,
children: ReactNodeList,
// TODO: figure out the API for cross-renderer implementation.
implementation: any,
};

View File

@ -6,9 +6,14 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactVersion
* @providesModule TouchHistoryMath
*/
'use strict';
module.exports = '16.0.0-alpha.12';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.TouchHistoryMath;

View File

@ -0,0 +1,20 @@
/**
* 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 createReactNativeComponentClass
* @flow
*/
'use strict';
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.createReactNativeComponentClass;

View File

@ -6,15 +6,14 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactTypeOfInternalContext
* @flow
* @providesModule takeSnapshot
*/
'use strict';
export type TypeOfInternalContext = number;
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
} = require('ReactNative');
module.exports = {
NoContext: 0,
AsyncUpdates: 1,
};
module.exports =
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.takeSnapshot;

View File

@ -1,282 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule NativeMethodsMixin
* @flow
*/
'use strict';
var ReactNative = require('ReactNative');
var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var TextInputState = require('TextInputState');
var UIManager = require('UIManager');
var invariant = require('fbjs/lib/invariant');
var findNodeHandle = require('findNodeHandle');
var {
mountSafeCallback,
throwOnStylesProp,
warnForStyleProps,
} = require('NativeMethodsMixinUtils');
import type {
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
} from 'NativeMethodsMixinUtils';
import type {
ReactNativeBaseComponentViewConfig,
} from 'ReactNativeViewConfigRegistry';
/**
* `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).
*/
// TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to-
// ensure that these mixins and ReactNativeFiberHostComponent stay in sync.
// Unfortunately, using it causes Flow to complain WRT createClass mixins:
// "call of method `createClass`. Expected an exact object instead of ..."
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(
ReactNative.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(
ReactNative.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
* `ReactNative.findNodeHandle(component)`.
*/
measureLayout: function(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) {
UIManager.measureLayout(
ReactNative.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) {
// Ensure ReactNative factory function has configured findNodeHandle.
// Requiring it won't execute the factory function until first referenced.
// It's possible for tests that use ReactTestRenderer to reach this point,
// Without having executed ReactNative.
// Defer the factory function until now to avoid a cycle with UIManager.
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
require('ReactNative');
injectedSetNativeProps(this, nativeProps);
},
/**
* 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(ReactNative.findNodeHandle(this));
},
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
*/
blur: function() {
TextInputState.blurTextInput(ReactNative.findNodeHandle(this));
},
};
// TODO (bvaughn) Inline this once ReactNativeStack is dropped.
function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) {
// Class components don't have viewConfig -> validateAttributes.
// Nor does it make sense to set native props on a non-native component.
// Instead, find the nearest host component and set props on it.
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
// We want the instance/wrapper (not the native tag).
let maybeInstance;
// Fiber errors if findNodeHandle is called for an umounted component.
// Tests using ReactTestRenderer will trigger this case indirectly.
// Mimicking stack behavior, we should silently ignore this case.
// TODO Fix ReactTestRenderer so we can remove this try/catch.
try {
maybeInstance = findNodeHandle(componentOrHandle);
} catch (error) {}
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
const viewConfig: ReactNativeBaseComponentViewConfig =
maybeInstance.viewConfig;
if (__DEV__) {
warnForStyleProps(nativeProps, viewConfig.validAttributes);
}
var updatePayload = ReactNativeAttributePayload.create(
nativeProps,
viewConfig.validAttributes,
);
UIManager.updateView(
maybeInstance._nativeTag,
viewConfig.uiViewClassName,
updatePayload,
);
}
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
function setNativePropsStack(componentOrHandle: any, nativeProps: Object) {
// Class components don't have viewConfig -> validateAttributes.
// Nor does it make sense to set native props on a non-native component.
// Instead, find the nearest host component and set props on it.
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
// We want the instance/wrapper (not the native tag).
let maybeInstance = findNodeHandle(componentOrHandle);
// If there is no host component beneath this we should fail silently.
// This is not an error; it could mean a class component rendered null.
if (maybeInstance == null) {
return;
}
let viewConfig: ReactNativeBaseComponentViewConfig;
if (maybeInstance.viewConfig !== undefined) {
// ReactNativeBaseComponent
viewConfig = maybeInstance.viewConfig;
} else if (
maybeInstance._instance !== undefined &&
maybeInstance._instance.viewConfig !== undefined
) {
// ReactCompositeComponentWrapper
// Some instances (eg Text) define their own viewConfig
viewConfig = maybeInstance._instance.viewConfig;
} else {
// ReactCompositeComponentWrapper
// Other instances (eg TextInput) defer to their children's viewConfig
while (maybeInstance._renderedComponent !== undefined) {
maybeInstance = maybeInstance._renderedComponent;
}
viewConfig = maybeInstance.viewConfig;
}
const tag: number = typeof maybeInstance.getHostNode === 'function'
? maybeInstance.getHostNode()
: maybeInstance._rootNodeID;
if (__DEV__) {
warnForStyleProps(nativeProps, viewConfig.validAttributes);
}
var updatePayload = ReactNativeAttributePayload.create(
nativeProps,
viewConfig.validAttributes,
);
UIManager.updateView(tag, viewConfig.uiViewClassName, updatePayload);
}
// Switching based on fiber vs stack to avoid a lot of inline checks at runtime.
// HACK Normally this injection would be done by the renderer, but in this case
// that would result in a cycle between ReactNative and NativeMethodsMixin.
// We avoid requiring additional code for this injection so it's probably ok?
// TODO (bvaughn) Remove this once ReactNativeStack is gone.
let injectedSetNativeProps: (
componentOrHandle: any,
nativeProps: Object,
) => void;
if (ReactNativeFeatureFlags.useFiber) {
injectedSetNativeProps = setNativePropsFiber;
} else {
injectedSetNativeProps = setNativePropsStack;
}
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);
};
}
module.exports = NativeMethodsMixin;

View File

@ -1,122 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule NativeMethodsMixinUtils
* @flow
*/
'use strict';
export type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number,
) => void;
export type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void;
export type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number,
) => void;
/**
* Shared between ReactNativeFiberHostComponent and NativeMethodsMixin to keep
* API in sync.
*/
export interface NativeMethodsInterface {
blur(): void,
focus(): void,
measure(callback: MeasureOnSuccessCallback): void,
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void,
measureLayout(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void,
): void,
setNativeProps(nativeProps: Object): void,
}
/**
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
function mountSafeCallback(context: any, callback: ?Function): any {
return function() {
if (!callback) {
return undefined;
}
if (typeof context.__isMounted === 'boolean') {
// TODO(gaearon): this is gross and should be removed.
// It is currently necessary because View uses createClass,
// and so any measure() calls on View (which are done by React
// DevTools) trigger the isMounted() deprecation warning.
if (!context.__isMounted) {
return undefined;
}
// The else branch is important so that we don't
// trigger the deprecation warning by calling isMounted.
} else if (typeof context.isMounted === 'function') {
if (!context.isMounted()) {
return undefined;
}
}
return callback.apply(context, arguments);
};
}
function throwOnStylesProp(component: any, props: any) {
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);
}
}
function warnForStyleProps(props: any, validAttributes: any) {
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 +
': ... } }`',
);
}
}
}
module.exports = {
mountSafeCallback,
throwOnStylesProp,
warnForStyleProps,
};

View File

@ -1,516 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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 preceding 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.
updatePayload = diffNestedProperty(
updatePayload,
prevProp,
nextProp,
((attributeConfig: any): AttributeConfiguration),
);
if (removedKeyCount > 0 && updatePayload) {
restoreDeletedValuesInNestedArray(
updatePayload,
nextProp,
((attributeConfig: any): AttributeConfiguration),
);
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: any): AttributeConfiguration),
);
}
}
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;

View File

@ -1,194 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeBaseComponent
* @flow
*/
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactMultiChild = require('ReactMultiChild');
var UIManager = require('UIManager');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
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(safely, skipLifecycle) {
ReactNativeComponentTree.uncacheNode(this);
this.unmountChildren(safely, skipLifecycle);
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.updateChildren(nextElement.props.children, transaction, context);
},
/**
* 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.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;

View File

@ -1,70 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,30 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,92 +0,0 @@
/**
* 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 = {};
var instanceProps = {};
/**
* 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 precacheFiberNode(hostInst, tag) {
instanceCache[tag] = hostInst;
}
function uncacheNode(inst) {
var tag = inst._rootNodeID;
if (tag) {
delete instanceCache[tag];
}
}
function uncacheFiberNode(tag) {
delete instanceCache[tag];
delete instanceProps[tag];
}
function getInstanceFromTag(tag) {
return instanceCache[tag] || null;
}
function getTagFromInstance(inst) {
// TODO (bvaughn) Clean up once Stack is deprecated
var tag = typeof inst.tag !== 'number'
? inst._rootNodeID
: inst.stateNode._nativeTag;
invariant(tag, 'All native instances should have a tag.');
return tag;
}
function getFiberCurrentPropsFromNode(stateNode) {
return instanceProps[stateNode._nativeTag] || null;
}
function updateFiberProps(tag, props) {
instanceProps[tag] = props;
}
var ReactNativeComponentTree = {
getClosestInstanceFromNode: getInstanceFromTag,
getInstanceFromNode: getInstanceFromTag,
getNodeFromInstance: getTagFromInstance,
precacheFiberNode,
precacheNode,
uncacheFiberNode,
uncacheNode,
getFiberCurrentPropsFromNode,
updateFiberProps,
};
module.exports = ReactNativeComponentTree;

View File

@ -1,21 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeContainerInfo
* @flow
*/
'use strict';
function ReactNativeContainerInfo(tag: number) {
var info = {
_tag: tag,
};
return info;
}
module.exports = ReactNativeContainerInfo;

View File

@ -1,85 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,201 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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 ReactGenericBatching = require('ReactGenericBatching');
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;
};
var ReactNativeEventEmitter = {
...ReactEventEmitterMixin,
registrationNames: EventPluginRegistry.registrationNameModules,
getListener: EventPluginHub.getListener,
/**
* 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);
ReactGenericBatching.batchedUpdates(function() {
ReactNativeEventEmitter.handleTopLevel(
topLevelType,
inst,
nativeEvent,
nativeEvent.target,
);
});
// React Native doesn't use ReactControlledComponent but if it did, here's
// where it would do it.
},
/**
* 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 occurred on tag zero.',
);
}
} else {
rootNodeID = target;
}
}
ReactNativeEventEmitter._receiveRootNodeIDEvent(
rootNodeID,
eventTopLevelType,
nativeEvent,
);
}
},
};
module.exports = ReactNativeEventEmitter;

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeEventPluginOrder
* @flow
*/
'use strict';
var ReactNativeEventPluginOrder = [
'ResponderEventPlugin',
'ReactNativeBridgeEventPlugin',
];
module.exports = ReactNativeEventPluginOrder;

View File

@ -1,474 +0,0 @@
/**
* 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 ReactNativeFiber
* @flow
*/
'use strict';
const ReactFiberErrorLogger = require('ReactFiberErrorLogger');
const ReactFiberReconciler = require('ReactFiberReconciler');
const ReactGenericBatching = require('ReactGenericBatching');
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
const ReactNativeComponentTree = require('ReactNativeComponentTree');
const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog');
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
const ReactNativeInjection = require('ReactNativeInjection');
const ReactNativeTagHandles = require('ReactNativeTagHandles');
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactPortal = require('ReactPortal');
const ReactVersion = require('ReactVersion');
const UIManager = require('UIManager');
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
const emptyObject = require('fbjs/lib/emptyObject');
const findNodeHandle = require('findNodeHandle');
const invariant = require('fbjs/lib/invariant');
const {injectInternals} = require('ReactFiberDevToolsHook');
import type {Element} from 'React';
import type {Fiber} from 'ReactFiber';
import type {
ReactNativeBaseComponentViewConfig,
} from 'ReactNativeViewConfigRegistry';
import type {ReactNodeList} from 'ReactTypes';
const {
precacheFiberNode,
uncacheFiberNode,
updateFiberProps,
} = ReactNativeComponentTree;
ReactNativeInjection.inject();
type Container = number;
export type Instance = {
_children: Array<Instance | number>,
_nativeTag: number,
viewConfig: ReactNativeBaseComponentViewConfig,
};
type Props = Object;
type TextInstance = number;
function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
if (typeof node === 'number') {
// Leaf node (eg text)
uncacheFiberNode(node);
} else {
uncacheFiberNode((node: any)._nativeTag);
(node: any)._children.forEach(recursivelyUncacheFiberNode);
}
}
const NativeRenderer = ReactFiberReconciler({
appendChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
if (typeof parentInstance === 'number') {
// Root container
UIManager.setChildren(
parentInstance, // containerTag
[childTag], // reactTags
);
} else {
const children = parentInstance._children;
const index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
}
},
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance._children.push(child);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayloadTODO: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload, // props
);
},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: {},
internalInstanceHandle: Object,
): Instance {
const tag = ReactNativeTagHandles.allocateTag();
const viewConfig = ReactNativeViewConfigRegistry.get(type);
if (__DEV__) {
for (const key in viewConfig.validAttributes) {
if (props.hasOwnProperty(key)) {
deepFreezeAndThrowOnMutationInDev(props[key]);
}
}
}
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
);
UIManager.createView(
tag, // reactTag
viewConfig.uiViewClassName, // viewName
rootContainerInstance, // rootTag
updatePayload, // props
);
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
// Not sure how to avoid this cast. Flow is okay if the component is defined
// in the same file but if it's external it can't see the types.
return ((component: any): Instance);
},
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: {},
internalInstanceHandle: Object,
): TextInstance {
const tag = ReactNativeTagHandles.allocateTag();
UIManager.createView(
tag, // reactTag
'RCTRawText', // viewName
rootContainerInstance, // rootTag
{text: text}, // props
);
precacheFiberNode(internalInstanceHandle, tag);
return tag;
},
finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
// Don't send a no-op message over the bridge.
if (parentInstance._children.length === 0) {
return false;
}
// Map from child objects to native tags.
// Either way we need to pass a copy of the Array to prevent it from being frozen.
const nativeTags = parentInstance._children.map(
child =>
(typeof child === 'number'
? child // Leaf node (eg text)
: child._nativeTag),
);
UIManager.setChildren(
parentInstance._nativeTag, // containerTag
nativeTags, // reactTags
);
return false;
},
getRootHostContext(): {} {
return emptyObject;
},
getChildHostContext(): {} {
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
insertBefore(
parentInstance: Instance | Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
const children = (parentInstance: any)._children;
const index = children.indexOf(child);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
},
prepareForCommit(): void {
// Noop
},
prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: {},
): null | Object {
return emptyObject;
},
removeChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
if (typeof parentInstance === 'number') {
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
} else {
const children = parentInstance._children;
const index = children.indexOf(child);
children.splice(index, 1);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
}
},
resetAfterCommit(): void {
// Noop
},
resetTextContent(instance: Instance): void {
// Noop
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
scheduleAnimationCallback: global.requestAnimationFrame,
scheduleDeferredCallback: global.requestIdleCallback,
shouldSetTextContent(props: Props): boolean {
// TODO (bvaughn) Revisit this decision.
// Always returning false simplifies the createInstance() implementation,
// But creates an additional child Fiber for raw text children.
// No additional native views are created though.
// It's not clear to me which is better so I'm deferring for now.
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
return false;
},
useSyncScheduling: true,
});
ReactGenericBatching.injection.injectFiberBatchedUpdates(
NativeRenderer.batchedUpdates,
);
const roots = new Map();
findNodeHandle.injection.injectFindNode((fiber: Fiber) =>
NativeRenderer.findHostInstance(fiber),
);
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
// Intercept lifecycle errors and ensure they are shown with the correct stack
// trace within the native redbox component.
ReactFiberErrorLogger.injection.injectDialog(
ReactNativeFiberErrorDialog.showDialog,
);
const ReactNative = {
// External users of findNodeHandle() expect the host tag number return type.
// The injected findNodeHandle() strategy returns the instance wrapper though.
// See NativeMethodsMixin#setNativeProps for more info on why this is done.
findNodeHandle(componentOrHandle: any): ?number {
const instance: any = findNodeHandle(componentOrHandle);
if (instance == null || typeof instance === 'number') {
return instance;
}
return instance._nativeTag;
},
render(element: Element<any>, containerTag: any, callback: ?Function) {
let root = roots.get(containerTag);
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = NativeRenderer.createContainer(containerTag);
roots.set(containerTag, root);
}
NativeRenderer.updateContainer(element, root, null, callback);
return NativeRenderer.getPublicRootInstance(root);
},
unmountComponentAtNode(containerTag: number) {
const root = roots.get(containerTag);
if (root) {
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
NativeRenderer.updateContainer(null, root, null, () => {
roots.delete(containerTag);
});
}
},
unmountComponentAtNodeAndRemoveContainer(containerTag: number) {
ReactNative.unmountComponentAtNode(containerTag);
// Call back into native to remove all of the subviews from this container
UIManager.removeRootView(containerTag);
},
unstable_createPortal(
children: ReactNodeList,
containerTag: number,
key: ?string = null,
) {
return ReactPortal.createPortal(children, containerTag, null, key);
},
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
};
if (typeof injectInternals === 'function') {
injectInternals({
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
findHostInstanceByFiber: NativeRenderer.findHostInstance,
// This is an enum because we may add more (e.g. profiler build)
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
});
}
module.exports = ReactNative;

View File

@ -1,57 +0,0 @@
/**
* 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 ReactNativeFiberErrorDialog
* @flow
*/
'use strict';
const ExceptionsManager = require('ExceptionsManager');
import type {CapturedError} from 'ReactFiberScheduler';
/**
* Intercept lifecycle errors and ensure they are shown with the correct stack
* trace within the native redbox component.
*/
function ReactNativeFiberErrorDialog(capturedError: CapturedError): boolean {
const {componentStack, error} = capturedError;
let errorMessage: string;
let errorStack: string;
let errorType: Class<Error>;
// Typically Errors are thrown but eg strings or null can be thrown as well.
if (error && typeof error === 'object') {
const {message, name} = error;
const summary = message ? `${name}: ${message}` : name;
errorMessage = `${summary}\n\nThis error is located at:${componentStack}`;
errorStack = error.stack;
errorType = error.constructor;
} else {
errorMessage = `Unspecified error at:${componentStack}`;
errorStack = '';
errorType = Error;
}
const newError = new errorType(errorMessage);
newError.stack = errorStack;
ExceptionsManager.handleException(newError, false);
// Return false here to prevent ReactFiberErrorLogger default behavior of
// logging error details to console.error. Calls to console.error are
// automatically routed to the native redbox controller, which we've already
// done above by calling ExceptionsManager.
return false;
}
module.exports.showDialog = ReactNativeFiberErrorDialog;

View File

@ -1,101 +0,0 @@
/**
* 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 ReactNativeFiberHostComponent
* @flow
* @preventMunge
*/
'use strict';
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var TextInputState = require('TextInputState');
var UIManager = require('UIManager');
var {mountSafeCallback, warnForStyleProps} = require('NativeMethodsMixinUtils');
import type {
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
NativeMethodsInterface,
} from 'NativeMethodsMixinUtils';
import type {Instance} from 'ReactNativeFiber';
import type {
ReactNativeBaseComponentViewConfig,
} from 'ReactNativeViewConfigRegistry';
/**
* This component defines the same methods as NativeMethodsMixin but without the
* findNodeHandle wrapper. This wrapper is unnecessary for HostComponent views
* and would also result in a circular require.js dependency (since
* ReactNativeFiber depends on this component and NativeMethodsMixin depends on
* ReactNativeFiber).
*/
class ReactNativeFiberHostComponent implements NativeMethodsInterface {
_children: Array<Instance | number>;
_nativeTag: number;
viewConfig: ReactNativeBaseComponentViewConfig;
constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig) {
this._nativeTag = tag;
this._children = [];
this.viewConfig = viewConfig;
}
blur() {
TextInputState.blurTextInput(this._nativeTag);
}
focus() {
TextInputState.focusTextInput(this._nativeTag);
}
measure(callback: MeasureOnSuccessCallback) {
UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
}
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
UIManager.measureInWindow(
this._nativeTag,
mountSafeCallback(this, callback),
);
}
measureLayout(
relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */,
) {
UIManager.measureLayout(
this._nativeTag,
relativeToNativeNode,
mountSafeCallback(this, onFail),
mountSafeCallback(this, onSuccess),
);
}
setNativeProps(nativeProps: Object) {
if (__DEV__) {
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
}
var updatePayload = ReactNativeAttributePayload.create(
nativeProps,
this.viewConfig.validAttributes,
);
UIManager.updateView(
this._nativeTag,
this.viewConfig.uiViewClassName,
updatePayload,
);
}
}
module.exports = ReactNativeFiberHostComponent;

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeGlobalResponderHandler
*/
'use strict';
var UIManager = require('UIManager');
var ReactNativeGlobalResponderHandler = {
onChange: function(from, to, blockNativeResponder) {
if (to !== null) {
// TODO (bvaughn) Clean up once Stack is deprecated
var tag = typeof to.tag !== 'number'
? to._rootNodeID
: to.stateNode._nativeTag;
UIManager.setJSResponder(tag, blockNativeResponder);
} else {
UIManager.clearJSResponder();
}
},
};
module.exports = ReactNativeGlobalResponderHandler;

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeInjection
* @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 ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder');
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
var ResponderEventPlugin = require('ResponderEventPlugin');
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);
ResponderEventPlugin.injection.injectGlobalResponderHandler(
ReactNativeGlobalResponderHandler,
);
/**
* Some important event plugins included by default (without having to require
* them).
*/
EventPluginHub.injection.injectEventPluginsByName({
ResponderEventPlugin: ResponderEventPlugin,
ReactNativeBridgeEventPlugin: ReactNativeBridgeEventPlugin,
});
}
module.exports = {
inject: inject,
};

View File

@ -1,227 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;
if (callback) {
var nonNullCallback = callback;
instance._pendingCallbacks = [
function() {
nonNullCallback.call(instance._renderedComponent.getPublicInstance());
},
];
}
// 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._renderedComponent.getPublicInstance();
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;

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,132 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,87 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeStack
* @flow
*/
'use strict';
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeInjection = require('ReactNativeInjection');
var ReactNativeMount = require('ReactNativeMount');
var ReactNativeStackInjection = require('ReactNativeStackInjection');
var ReactUpdates = require('ReactUpdates');
var findNodeHandle = require('findNodeHandle');
ReactNativeInjection.inject();
ReactNativeStackInjection.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,
// External users of findNodeHandle() expect the host tag number return type.
// The injected findNodeHandle() strategy returns the instance wrapper though.
// See NativeMethodsMixin#setNativeProps for more info on why this is done.
findNodeHandle(componentOrHandle: any): ?number {
const nodeHandle = findNodeHandle(componentOrHandle);
if (nodeHandle == null || typeof nodeHandle === 'number') {
return nodeHandle;
}
return nodeHandle.getHostNode();
},
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;

View File

@ -1,83 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeStackInjection
* @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 React = require('react');
var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactGenericBatching = require('ReactGenericBatching');
var ReactHostComponent = require('ReactHostComponent');
var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
var ReactNativeTextComponent = require('ReactNativeTextComponent');
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
var ReactUpdates = require('ReactUpdates');
var findNodeHandle = require('findNodeHandle');
var invariant = require('fbjs/lib/invariant');
function inject() {
ReactGenericBatching.injection.injectStackBatchedUpdates(
ReactUpdates.batchedUpdates,
);
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,
);
};
findNodeHandle.injection.injectFindNode(instance => instance);
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
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,
};

View File

@ -1,58 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule 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;

View File

@ -1,79 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @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;

View File

@ -1,46 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeViewConfigRegistry
* @flow
*/
'use strict';
const invariant = require('fbjs/lib/invariant');
export type ReactNativeBaseComponentViewConfig = {
validAttributes: Object,
uiViewClassName: string,
propTypes?: Object,
};
const viewConfigs = new Map();
const prefix = 'topsecret-';
const ReactNativeViewConfigRegistry = {
register(viewConfig: ReactNativeBaseComponentViewConfig) {
const name = viewConfig.uiViewClassName;
invariant(
!viewConfigs.has(name),
'Tried to register two views with the same name %s',
name,
);
const secretName = prefix + name;
viewConfigs.set(secretName, viewConfig);
return secretName;
},
get(secretName: string) {
const config = viewConfigs.get(secretName);
invariant(config, 'View config not found for name %s', secretName);
return config;
},
};
module.exports = ReactNativeViewConfigRegistry;

View File

@ -1,16 +0,0 @@
/**
* 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';
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
module.exports = ReactNativeFeatureFlags.useFiber
? require('ReactNativeFiber')
: require('ReactNativeStack');

View File

@ -1,236 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var ReactNativePropRegistry = require('ReactNativePropRegistry');
var diff = ReactNativeAttributePayload.diff;
describe('ReactNativeAttributePayload', () => {
it('should work with simple example', () => {
expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
a: null,
b: 2,
});
});
it('should skip fields that are equal', () => {
expect(
diff(
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
{a: true, b: true, c: true, d: true, e: true, f: true},
),
).toEqual(null);
});
it('should remove fields', () => {
expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
});
it('should remove fields that are set to undefined', () => {
expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
});
it('should ignore invalid fields', () => {
expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
});
it('should use the diff attribute', () => {
var diffA = jest.fn((a, b) => true);
var diffB = jest.fn((a, b) => false);
expect(
diff(
{a: [1], b: [3]},
{a: [2], b: [4]},
{a: {diff: diffA}, b: {diff: diffB}},
),
).toEqual({a: [2]});
expect(diffA).toBeCalledWith([1], [2]);
expect(diffB).toBeCalledWith([3], [4]);
});
it('should not use the diff attribute on addition/removal', () => {
var diffA = jest.fn();
var diffB = jest.fn();
expect(
diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
).toEqual({a: null, b: [2]});
expect(diffA).not.toBeCalled();
expect(diffB).not.toBeCalled();
});
it('should do deep diffs of Objects by default', () => {
expect(
diff(
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
{a: true, b: true, c: true},
),
).toEqual({a: [2], c: {k: [4, 5]}});
});
it('should work with undefined styles', () => {
expect(
diff(
{style: {a: '#ffffff', b: 1}},
{style: undefined},
{style: {b: true}},
),
).toEqual({b: null});
expect(
diff(
{style: undefined},
{style: {a: '#ffffff', b: 1}},
{style: {b: true}},
),
).toEqual({b: 1});
expect(
diff({style: undefined}, {style: undefined}, {style: {b: true}}),
).toEqual(null);
});
it('should work with empty styles', () => {
expect(diff({a: 1, c: 3}, {}, {a: true, b: true})).toEqual({a: null});
expect(diff({}, {a: 1, c: 3}, {a: true, b: true})).toEqual({a: 1});
expect(diff({}, {}, {a: true, b: true})).toEqual(null);
});
it('should flatten nested styles and predefined styles', () => {
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
expect(
diff({}, {someStyle: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
).toEqual({foo: 1, bar: 2});
expect(
diff({someStyle: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
).toEqual({foo: null, bar: null});
var barStyle = ReactNativePropRegistry.register({
bar: 3,
});
expect(
diff(
{},
{someStyle: [[{foo: 1}, {foo: 2}], barStyle]},
validStyleAttribute,
),
).toEqual({foo: 2, bar: 3});
});
it('should reset a value to a previous if it is removed', () => {
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
expect(
diff(
{someStyle: [{foo: 1}, {foo: 3}]},
{someStyle: [{foo: 1}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: 1, bar: 2});
});
it('should not clear removed props if they are still in another slot', () => {
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
expect(
diff(
{someStyle: [{}, {foo: 3, bar: 2}]},
{someStyle: [{foo: 3}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
expect(
diff(
{someStyle: [{}, {foo: 3, bar: 2}]},
{someStyle: [{foo: 1, bar: 1}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({bar: 2, foo: 1});
});
it('should clear a prop if a later style is explicit null/undefined', () => {
var validStyleAttribute = {someStyle: {foo: true, bar: true}};
expect(
diff(
{someStyle: [{}, {foo: 3, bar: 2}]},
{someStyle: [{foo: 1}, {bar: 2, foo: null}]},
validStyleAttribute,
),
).toEqual({foo: null});
expect(
diff(
{someStyle: [{foo: 3}, {foo: null, bar: 2}]},
{someStyle: [{foo: null}, {bar: 2}]},
validStyleAttribute,
),
).toEqual({foo: null});
expect(
diff(
{someStyle: [{foo: 1}, {foo: null}]},
{someStyle: [{foo: 2}, {foo: null}]},
validStyleAttribute,
),
).toEqual({foo: null}); // this should ideally be null. heuristic.
// Test the same case with object equality because an early bailout doesn't
// work in this case.
var fooObj = {foo: 3};
expect(
diff(
{someStyle: [{foo: 1}, fooObj]},
{someStyle: [{foo: 2}, fooObj]},
validStyleAttribute,
),
).toEqual({foo: 3}); // this should ideally be null. heuristic.
expect(
diff(
{someStyle: [{foo: 1}, {foo: 3}]},
{someStyle: [{foo: 2}, {foo: undefined}]},
validStyleAttribute,
),
).toEqual({foo: null}); // this should ideally be null. heuristic.
});
// Function properties are just markers to native that events should be sent.
it('should convert functions to booleans', () => {
// Note that if the property changes from one function to another, we don't
// need to send an update.
expect(
diff(
{
a: function() {
return 1;
},
b: function() {
return 2;
},
c: 3,
},
{
b: function() {
return 9;
},
c: function() {
return 3;
},
},
{a: true, b: true, c: true},
),
).toEqual({a: null, c: true});
});
});

View File

@ -1,366 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var PropTypes;
var RCTEventEmitter;
var React;
var ReactNative;
var ResponderEventPlugin;
var UIManager;
var createReactNativeComponentClass;
beforeEach(() => {
jest.resetModules();
PropTypes = require('prop-types');
RCTEventEmitter = require('RCTEventEmitter');
React = require('react');
ReactNative = require('ReactNative');
ResponderEventPlugin = require('ResponderEventPlugin');
UIManager = require('UIManager');
createReactNativeComponentClass = require('createReactNativeComponentClass');
});
it('handles events', () => {
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
var View = createReactNativeComponentClass({
validAttributes: {foo: true},
uiViewClassName: 'View',
});
var log = [];
ReactNative.render(
<View
foo="outer"
onTouchEnd={() => log.push('outer touchend')}
onTouchEndCapture={() => log.push('outer touchend capture')}
onTouchStart={() => log.push('outer touchstart')}
onTouchStartCapture={() => log.push('outer touchstart capture')}>
<View
foo="inner"
onTouchEndCapture={() => log.push('inner touchend capture')}
onTouchEnd={() => log.push('inner touchend')}
onTouchStartCapture={() => log.push('inner touchstart capture')}
onTouchStart={() => log.push('inner touchstart')}
/>
</View>,
1,
);
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
expect(UIManager.createView.mock.calls.length).toBe(2);
// Don't depend on the order of createView() calls.
// Stack creates views outside-in; fiber creates them inside-out.
var innerTag = UIManager.createView.mock.calls.find(
args => args[3].foo === 'inner',
)[0];
EventEmitter.receiveTouches(
'topTouchStart',
[{target: innerTag, identifier: 17}],
[0],
);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: innerTag, identifier: 17}],
[0],
);
expect(log).toEqual([
'outer touchstart capture',
'inner touchstart capture',
'inner touchstart',
'outer touchstart',
'outer touchend capture',
'inner touchend capture',
'inner touchend',
'outer touchend',
]);
});
it('handles events on text nodes', () => {
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
var Text = createReactNativeComponentClass({
validAttributes: {foo: true},
uiViewClassName: 'Text',
});
class ContextHack extends React.Component {
static childContextTypes = {isInAParentText: PropTypes.bool};
getChildContext() {
return {isInAParentText: true};
}
render() {
return this.props.children;
}
}
var log = [];
ReactNative.render(
<ContextHack>
<Text>
<Text
onTouchEnd={() => log.push('string touchend')}
onTouchEndCapture={() => log.push('string touchend capture')}
onTouchStart={() => log.push('string touchstart')}
onTouchStartCapture={() => log.push('string touchstart capture')}>
Text Content
</Text>
<Text
onTouchEnd={() => log.push('number touchend')}
onTouchEndCapture={() => log.push('number touchend capture')}
onTouchStart={() => log.push('number touchstart')}
onTouchStartCapture={() => log.push('number touchstart capture')}>
{123}
</Text>
</Text>
</ContextHack>,
1,
);
expect(UIManager.createView.mock.calls.length).toBe(5);
// Don't depend on the order of createView() calls.
// Stack creates views outside-in; fiber creates them inside-out.
var innerTagString = UIManager.createView.mock.calls.find(
args => args[3] && args[3].text === 'Text Content',
)[0];
var innerTagNumber = UIManager.createView.mock.calls.find(
args => args[3] && args[3].text === '123',
)[0];
EventEmitter.receiveTouches(
'topTouchStart',
[{target: innerTagString, identifier: 17}],
[0],
);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: innerTagString, identifier: 17}],
[0],
);
EventEmitter.receiveTouches(
'topTouchStart',
[{target: innerTagNumber, identifier: 18}],
[0],
);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: innerTagNumber, identifier: 18}],
[0],
);
expect(log).toEqual([
'string touchstart capture',
'string touchstart',
'string touchend capture',
'string touchend',
'number touchstart capture',
'number touchstart',
'number touchend capture',
'number touchend',
]);
});
it('handles when a responder is unmounted while a touch sequence is in progress', () => {
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
var View = createReactNativeComponentClass({
validAttributes: {id: true},
uiViewClassName: 'View',
});
function getViewById(id) {
return UIManager.createView.mock.calls.find(
args => args[3] && args[3].id === id,
)[0];
}
function getResponderId() {
const responder = ResponderEventPlugin._getResponder();
if (responder === null) {
return null;
}
const props = typeof responder.tag === 'number'
? responder.memoizedProps
: responder._currentElement.props;
return props ? props.id : null;
}
var log = [];
ReactNative.render(
<View id="parent">
<View key={1}>
<View
id="one"
onResponderEnd={() => log.push('one responder end')}
onResponderStart={() => log.push('one responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
1,
);
EventEmitter.receiveTouches(
'topTouchStart',
[{target: getViewById('one'), identifier: 17}],
[0],
);
expect(getResponderId()).toBe('one');
expect(log).toEqual(['one responder start']);
log.splice(0);
ReactNative.render(
<View id="parent">
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
1,
);
// TODO Verify the onResponderEnd listener has been called (before the unmount)
// expect(log).toEqual(['one responder end']);
// log.splice(0);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: getViewById('two'), identifier: 17}],
[0],
);
expect(getResponderId()).toBeNull();
expect(log).toEqual([]);
EventEmitter.receiveTouches(
'topTouchStart',
[{target: getViewById('two'), identifier: 17}],
[0],
);
expect(getResponderId()).toBe('two');
expect(log).toEqual(['two responder start']);
});
it('handles events without target', () => {
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
var View = createReactNativeComponentClass({
validAttributes: {id: true},
uiViewClassName: 'View',
});
function getViewById(id) {
return UIManager.createView.mock.calls.find(
args => args[3] && args[3].id === id,
)[0];
}
function getResponderId() {
const responder = ResponderEventPlugin._getResponder();
if (responder === null) {
return null;
}
const props = typeof responder.tag === 'number'
? responder.memoizedProps
: responder._currentElement.props;
return props ? props.id : null;
}
var log = [];
function render(renderFirstComponent) {
ReactNative.render(
<View id="parent">
<View key={1}>
{renderFirstComponent
? <View
id="one"
onResponderEnd={() => log.push('one responder end')}
onResponderStart={() => log.push('one responder start')}
onStartShouldSetResponder={() => true}
/>
: null}
</View>
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
1,
);
}
render(true);
EventEmitter.receiveTouches(
'topTouchStart',
[{target: getViewById('one'), identifier: 17}],
[0],
);
// Unmounting component 'one'.
render(false);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: getViewById('one'), identifier: 17}],
[0],
);
expect(getResponderId()).toBe(null);
EventEmitter.receiveTouches(
'topTouchStart',
[{target: getViewById('two'), identifier: 18}],
[0],
);
expect(getResponderId()).toBe('two');
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: getViewById('two'), identifier: 18}],
[0],
);
expect(getResponderId()).toBe(null);
expect(log).toEqual([
'one responder start',
'two responder start',
'two responder end',
]);
});

View File

@ -1,111 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNative;
var createReactNativeComponentClass;
var UIManager;
describe('ReactNative', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNative = require('ReactNative');
UIManager = require('UIManager');
createReactNativeComponentClass = require('createReactNativeComponentClass');
});
it('should be able to create and render a native component', () => {
var View = createReactNativeComponentClass({
validAttributes: {foo: true},
uiViewClassName: 'View',
});
ReactNative.render(<View foo="test" />, 1);
expect(UIManager.createView).toBeCalled();
expect(UIManager.setChildren).toBeCalled();
expect(UIManager.manageChildren).not.toBeCalled();
expect(UIManager.updateView).not.toBeCalled();
});
it('should be able to create and update a native component', () => {
var View = createReactNativeComponentClass({
validAttributes: {foo: true},
uiViewClassName: 'View',
});
ReactNative.render(<View foo="foo" />, 11);
expect(UIManager.createView.mock.calls.length).toBe(1);
expect(UIManager.setChildren.mock.calls.length).toBe(1);
expect(UIManager.manageChildren).not.toBeCalled();
expect(UIManager.updateView).not.toBeCalled();
ReactNative.render(<View foo="bar" />, 11);
expect(UIManager.createView.mock.calls.length).toBe(1);
expect(UIManager.setChildren.mock.calls.length).toBe(1);
expect(UIManager.manageChildren).not.toBeCalled();
expect(UIManager.updateView).toBeCalledWith(2, 'View', {foo: 'bar'});
});
it('returns the correct instance and calls it in the callback', () => {
var View = createReactNativeComponentClass({
validAttributes: {foo: true},
uiViewClassName: 'View',
});
var a;
var b;
var c = ReactNative.render(
<View foo="foo" ref={v => (a = v)} />,
11,
function() {
b = this;
},
);
expect(a).toBeTruthy();
expect(a).toBe(b);
expect(a).toBe(c);
});
it('renders and reorders children', () => {
var View = createReactNativeComponentClass({
validAttributes: {title: true},
uiViewClassName: 'View',
});
class Component extends React.Component {
render() {
var chars = this.props.chars.split('');
return (
<View>
{chars.map(text => <View key={text} title={text} />)}
</View>
);
}
}
// Mini multi-child stress test: lots of reorders, some adds, some removes.
var before = 'abcdefghijklmnopqrst';
var after = 'mxhpgwfralkeoivcstzy';
ReactNative.render(<Component chars={before} />, 11);
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
ReactNative.render(<Component chars={after} />, 11);
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
});
});

View File

@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`handles events 1`] = `
"<native root> {}
View {\\"foo\\":\\"outer\\"}
View {\\"foo\\":\\"inner\\"}"
`;

View File

@ -1,51 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactNative renders and reorders children 1`] = `
"<native root> {}
View null
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"b\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"d\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"j\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"n\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"q\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}"
`;
exports[`ReactNative renders and reorders children 2`] = `
"<native root> {}
View null
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"x\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"w\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"v\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}
View {\\"title\\":\\"z\\"}
View {\\"title\\":\\"y\\"}"
`;

View File

@ -1,62 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule createReactNativeComponentClass
* @flow
*/
'use strict';
const ReactNativeBaseComponent = require('ReactNativeBaseComponent');
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
// See also ReactNativeBaseComponent
type ReactNativeBaseComponentViewConfig = {
validAttributes: Object,
uiViewClassName: string,
propTypes?: Object,
};
/**
* @param {string} config iOS View configuration.
* @private
*/
const createReactNativeFiberComponentClass = function(
viewConfig: ReactNativeBaseComponentViewConfig,
): string {
return ReactNativeViewConfigRegistry.register(viewConfig);
};
/**
* @param {string} config iOS View configuration.
* @private
*/
const createReactNativeComponentClass = function(
viewConfig: ReactNativeBaseComponentViewConfig,
): ReactClass<any> {
const 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 = ReactNativeFeatureFlags.useFiber
? createReactNativeFiberComponentClass
: createReactNativeComponentClass;

View File

@ -1,139 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule findNodeHandle
* @flow
*/
'use strict';
var ReactInstanceMap = require('ReactInstanceMap');
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
var getComponentName = require('getComponentName');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
import type {Fiber} from 'ReactFiber';
import type {ReactInstance} from 'ReactInstanceType';
/**
* 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
*/
let injectedFindNode;
let injectedFindRootNodeID;
// TODO (bvaughn) Rename the findNodeHandle module to something more descriptive
// eg findInternalHostInstance. This will reduce the likelihood of someone
// accidentally deep-requiring this version.
function findNodeHandle(componentOrHandle: any): any {
if (__DEV__) {
var owner =
((ReactCurrentOwner.current: any): ReactInstance | Fiber | null);
if (owner !== null) {
const isFiber = typeof (owner: any).tag === 'number';
const warnedAboutRefsInRender = isFiber
? ((owner: any): Fiber).stateNode._warnedAboutRefsInRender
: ((owner: any): ReactInstance)._warnedAboutRefsInRender;
warning(
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.',
getComponentName(owner) || 'A component',
);
if (isFiber) {
((owner: any): Fiber).stateNode._warnedAboutRefsInRender = true;
} else {
((owner: any): ReactInstance)._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 injectedFindNode(internalInstance);
} else {
var rootNodeID = injectedFindRootNodeID(component);
if (rootNodeID) {
return rootNodeID;
} else {
invariant(
// Native
(typeof component === 'object' &&
('_rootNodeID' in component || // TODO (bvaughn) Clean up once Stack is deprecated
'_nativeTag' 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.',
);
}
}
}
// Fiber and stack implementations differ; each must inject a strategy
findNodeHandle.injection = {
injectFindNode(findNode) {
injectedFindNode = findNode;
},
injectFindRootNodeID(findRootNodeID) {
injectedFindRootNodeID = findRootNodeID;
},
};
module.exports = findNodeHandle;

View File

@ -1,54 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule takeSnapshot
* @flow
*/
'use strict';
var ReactNative = require('ReactNative');
var UIManager = require('UIManager');
import type {Element} from 'React';
/**
* Capture an image of the screen, window or an individual view. The image
* will be stored in a temporary file that will only exist for as long as the
* app is running.
*
* The `view` argument can be the literal string `window` if you want to
* capture the entire window, or it can be a reference to a specific
* React Native component.
*
* The `options` argument may include:
* - width/height (number) - the width and height of the image to capture.
* - format (string) - either 'png' or 'jpeg'. Defaults to 'png'.
* - quality (number) - the quality when using jpeg. 0.0 - 1.0 (default).
*
* Returns a Promise.
* @platform ios
*/
function takeSnapshot(
view?: 'window' | Element<any> | number,
options?: {
width?: number,
height?: number,
format?: 'png' | 'jpeg',
quality?: number,
},
): Promise<any> {
if (typeof view !== 'number' && view !== 'window') {
view = ReactNative.findNodeHandle(view) || 'window';
}
// Call the hidden '__takeSnapshot' method; the main one throws an error to
// prevent accidental backwards-incompatible usage.
return UIManager.__takeSnapshot(view, options);
}
module.exports = takeSnapshot;

View File

@ -1,391 +0,0 @@
/**
* 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 ReactNoop
* @flow
*/
/**
* This is a renderer of React that doesn't have a render target output.
* It is useful to demonstrate the internals of the reconciler in isolation
* and for testing semantics of reconciliation separate from the host
* environment.
*/
'use strict';
import type { Fiber } from 'ReactFiber';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactInstanceMap = require('ReactInstanceMap');
var {
AnimationPriority,
} = require('ReactPriorityLevel');
var emptyObject = require('fbjs/lib/emptyObject');
const UPDATE_SIGNAL = {};
var scheduledAnimationCallback = null;
var scheduledDeferredCallback = null;
type Container = { rootID: string, children: Array<Instance | TextInstance> };
type Props = { prop: any };
type Instance = {| type: string, id: number, children: Array<Instance | TextInstance>, prop: any |};
type TextInstance = {| text: string, id: number |};
var instanceCounter = 0;
var failInBeginPhase = false;
var NoopRenderer = ReactFiberReconciler({
getRootHostContext() {
if (failInBeginPhase) {
throw new Error('Error in host config.');
}
return emptyObject;
},
getChildHostContext() {
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
createInstance(type : string, props : Props) : Instance {
const inst = {
id: instanceCounter++,
type: type,
children: [],
prop: props.prop,
};
// Hide from unit tests
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
return inst;
},
appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
parentInstance.children.push(child);
},
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : boolean {
return false;
},
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : null | {} {
return UPDATE_SIGNAL;
},
commitMount(instance : Instance, type : string, newProps : Props) : void {
// Noop
},
commitUpdate(instance : Instance, updatePayload : Object, type : string, oldProps : Props, newProps : Props) : void {
instance.prop = newProps.prop;
},
shouldSetTextContent(props : Props) : boolean {
return (
typeof props.children === 'string' ||
typeof props.children === 'number'
);
},
resetTextContent(instance : Instance) : void {},
createTextInstance(
text : string,
rootContainerInstance : Container,
hostContext : Object,
internalInstanceHandle : Object
) : TextInstance {
var inst = { text : text, id: instanceCounter++ };
// Hide from unit tests
Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false });
return inst;
},
commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
textInstance.text = newText;
},
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},
insertBefore(
parentInstance : Instance | Container,
child : Instance | TextInstance,
beforeChild : Instance | TextInstance
) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
const beforeIndex = parentInstance.children.indexOf(beforeChild);
if (beforeIndex === -1) {
throw new Error('This child does not exist.');
}
parentInstance.children.splice(beforeIndex, 0, child);
},
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index === -1) {
throw new Error('This child does not exist.');
}
parentInstance.children.splice(index, 1);
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
scheduleAnimationCallback(callback) {
if (scheduledAnimationCallback) {
throw new Error(
'Scheduling an animation callback twice is excessive. ' +
'Instead, keep track of whether the callback has already been scheduled.'
);
}
scheduledAnimationCallback = callback;
},
scheduleDeferredCallback(callback) {
if (scheduledDeferredCallback) {
throw new Error(
'Scheduling deferred callback twice is excessive. ' +
'Instead, keep track of whether the callback has already been scheduled.'
);
}
scheduledDeferredCallback = callback;
},
prepareForCommit() : void {
},
resetAfterCommit() : void {
},
});
var rootContainers = new Map();
var roots = new Map();
var DEFAULT_ROOT_ID = '<default>';
var ReactNoop = {
getChildren(rootID : string = DEFAULT_ROOT_ID) {
const container = rootContainers.get(rootID);
if (container) {
return container.children;
} else {
return null;
}
},
// Shortcut for testing a single root
render(element : ReactElement<any>, callback: ?Function) {
ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback);
},
renderToRootWithID(element : ReactElement<any>, rootID : string, callback : ?Function) {
let root = roots.get(rootID);
if (!root) {
const container = { rootID: rootID, children: [] };
rootContainers.set(rootID, container);
root = NoopRenderer.createContainer(container);
roots.set(rootID, root);
}
NoopRenderer.updateContainer(element, root, null, callback);
},
unmountRootWithID(rootID : string) {
const root = roots.get(rootID);
if (root) {
NoopRenderer.updateContainer(null, root, null, () => {
roots.delete(rootID);
rootContainers.delete(rootID);
});
}
},
findInstance(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Instance | TextInstance {
if (componentOrElement == null) {
return null;
}
// Unsound duck typing.
const component = (componentOrElement : any);
if (typeof component.id === 'number') {
return component;
}
const inst = ReactInstanceMap.get(component);
return inst ? NoopRenderer.findHostInstance(inst) : null;
},
flushAnimationPri() {
var cb = scheduledAnimationCallback;
if (cb === null) {
return;
}
scheduledAnimationCallback = null;
cb();
},
flushDeferredPri(timeout : number = Infinity) {
var cb = scheduledDeferredCallback;
if (cb === null) {
return;
}
scheduledDeferredCallback = null;
var timeRemaining = timeout;
cb({
timeRemaining() {
// Simulate a fix amount of time progressing between each call.
timeRemaining -= 5;
if (timeRemaining < 0) {
timeRemaining = 0;
}
return timeRemaining;
},
});
},
flush() {
ReactNoop.flushAnimationPri();
ReactNoop.flushDeferredPri();
},
performAnimationWork(fn: Function) {
NoopRenderer.performWithPriority(AnimationPriority, fn);
},
batchedUpdates: NoopRenderer.batchedUpdates,
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
syncUpdates: NoopRenderer.syncUpdates,
// Logs the current state of the tree.
dumpTree(rootID : string = DEFAULT_ROOT_ID) {
const root = roots.get(rootID);
const rootContainer = rootContainers.get(rootID);
if (!root || !rootContainer) {
console.log('Nothing rendered yet.');
return;
}
var bufferedLog = [];
function log(...args) {
bufferedLog.push(...args, '\n');
}
function logHostInstances(children: Array<Instance | TextInstance>, depth) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
var indent = ' '.repeat(depth);
if (typeof child.text === 'string') {
log(indent + '- ' + child.text);
} else {
// $FlowFixMe - The child should've been refined now.
log(indent + '- ' + child.type + '#' + child.id);
// $FlowFixMe - The child should've been refined now.
logHostInstances(child.children, depth + 1);
}
}
}
function logContainer(container : Container, depth) {
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
logHostInstances(container.children, depth + 1);
}
function logUpdateQueue(updateQueue : UpdateQueue, depth) {
log(
' '.repeat(depth + 1) + 'QUEUED UPDATES'
);
const firstUpdate = updateQueue.first;
if (!firstUpdate) {
return;
}
log(
' '.repeat(depth + 1) + '~',
firstUpdate && firstUpdate.partialState,
firstUpdate.callback ? 'with callback' : '',
'[' + firstUpdate.priorityLevel + ']'
);
var next;
while (next = firstUpdate.next) {
log(
' '.repeat(depth + 1) + '~',
next.partialState,
next.callback ? 'with callback' : '',
'[' + firstUpdate.priorityLevel + ']'
);
}
}
function logFiber(fiber : Fiber, depth) {
log(
' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
'[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
);
if (fiber.updateQueue) {
logUpdateQueue(fiber.updateQueue, depth);
}
const childInProgress = fiber.progressedChild;
if (childInProgress && childInProgress !== fiber.child) {
log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
logFiber(childInProgress, depth + 1);
if (fiber.child) {
log(' '.repeat(depth + 1) + 'CURRENT');
}
} else if (fiber.child && fiber.updateQueue) {
log(' '.repeat(depth + 1) + 'CHILDREN');
}
if (fiber.child) {
logFiber(fiber.child, depth + 1);
}
if (fiber.sibling) {
logFiber(fiber.sibling, depth);
}
}
log('HOST INSTANCES:');
logContainer(rootContainer, 0);
log('FIBERS:');
logFiber(root.current, 0);
console.log(...bufferedLog);
},
simulateErrorInHostConfig(fn : () => void) {
failInBeginPhase = true;
try {
fn();
} finally {
failInBeginPhase = false;
}
},
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// Private. Used only by fixtures/fiber-debugger.
// (To be fair, it's the only place where `react-noop-renderer` package is used at all.)
ReactFiberInstrumentation,
},
};
module.exports = ReactNoop;

View File

@ -1,170 +0,0 @@
/**
* 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 ReactDOMFrameScheduling
* @flow
*/
'use strict';
// This a built-in polyfill for requestIdleCallback. It works by scheduling
// a requestAnimationFrame, store the time for the start of the frame, then
// schedule a postMessage which gets scheduled after paint. Within the
// postMessage handler do as much work as possible until time + frame rate.
// By separating the idle call into a separate event tick we ensure that
// layout, paint and other browser work is counted against the available time.
// The frame rate is dynamically adjusted.
import type {Deadline} from 'ReactFiberReconciler';
var invariant = require('fbjs/lib/invariant');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
// TODO: There's no way to cancel these, because Fiber doesn't atm.
let rAF: (callback: (time: number) => void) => number;
let rIC: (callback: (deadline: Deadline) => void) => number;
if (!ExecutionEnvironment.canUseDOM) {
rAF = function(frameCallback: (time: number) => void): number {
setTimeout(frameCallback, 16);
return 0;
};
rIC = function(frameCallback: (deadline: Deadline) => void): number {
setTimeout(() => {
frameCallback({
timeRemaining() {
return Infinity;
},
});
});
return 0;
};
} else if (typeof requestAnimationFrame !== 'function') {
invariant(
false,
'React depends on requestAnimationFrame. Make sure that you load a ' +
'polyfill in older browsers.',
);
} else if (typeof requestIdleCallback !== 'function') {
// Wrap requestAnimationFrame and polyfill requestIdleCallback.
var scheduledRAFCallback = null;
var scheduledRICCallback = null;
var isIdleScheduled = false;
var isAnimationFrameScheduled = false;
var frameDeadline = 0;
// We start out assuming that we run at 30fps but then the heuristic tracking
// will adjust this value to a faster fps if we get more frequent animation
// frames.
var previousFrameTime = 33;
var activeFrameTime = 33;
var frameDeadlineObject = {
timeRemaining: typeof performance === 'object' &&
typeof performance.now === 'function'
? function() {
// We assume that if we have a performance timer that the rAF callback
// gets a performance timer value. Not sure if this is always true.
return frameDeadline - performance.now();
}
: function() {
// As a fallback we use Date.now.
return frameDeadline - Date.now();
},
};
// We use the postMessage trick to defer idle work until after the repaint.
var messageKey = '__reactIdleCallback$' + Math.random().toString(36).slice(2);
var idleTick = function(event) {
if (event.source !== window || event.data !== messageKey) {
return;
}
isIdleScheduled = false;
var callback = scheduledRICCallback;
scheduledRICCallback = null;
if (callback) {
callback(frameDeadlineObject);
}
};
// Assumes that we have addEventListener in this environment. Might need
// something better for old IE.
window.addEventListener('message', idleTick, false);
var animationTick = function(rafTime) {
isAnimationFrameScheduled = false;
var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
if (
nextFrameTime < activeFrameTime &&
previousFrameTime < activeFrameTime
) {
if (nextFrameTime < 8) {
// Defensive coding. We don't support higher frame rates than 120hz.
// If we get lower than that, it is probably a bug.
nextFrameTime = 8;
}
// If one frame goes long, then the next one can be short to catch up.
// If two frames are short in a row, then that's an indication that we
// actually have a higher frame rate than what we're currently optimizing.
// We adjust our heuristic dynamically accordingly. For example, if we're
// running on 120hz display or 90hz VR display.
// Take the max of the two in case one of them was an anomaly due to
// missed frame deadlines.
activeFrameTime = nextFrameTime < previousFrameTime
? previousFrameTime
: nextFrameTime;
} else {
previousFrameTime = nextFrameTime;
}
frameDeadline = rafTime + activeFrameTime;
if (!isIdleScheduled) {
isIdleScheduled = true;
window.postMessage(messageKey, '*');
}
var callback = scheduledRAFCallback;
scheduledRAFCallback = null;
if (callback) {
callback(rafTime);
}
};
rAF = function(callback: (time: number) => void): number {
// This assumes that we only schedule one callback at a time because that's
// how Fiber uses it.
scheduledRAFCallback = callback;
if (!isAnimationFrameScheduled) {
// If rIC didn't already schedule one, we need to schedule a frame.
isAnimationFrameScheduled = true;
requestAnimationFrame(animationTick);
}
return 0;
};
rIC = function(callback: (deadline: Deadline) => void): number {
// This assumes that we only schedule one callback at a time because that's
// how Fiber uses it.
scheduledRICCallback = callback;
if (!isAnimationFrameScheduled) {
// If rAF didn't already schedule one, we need to schedule a frame.
// TODO: If this rAF doesn't materialize because the browser throttles, we
// might want to still have setTimeout trigger rIC as a backup to ensure
// that we keep performing work.
isAnimationFrameScheduled = true;
requestAnimationFrame(animationTick);
}
return 0;
};
} else {
rAF = requestAnimationFrame;
rIC = requestIdleCallback;
}
exports.rAF = rAF;
exports.rIC = rIC;

View File

@ -1,438 +0,0 @@
/**
* 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
* @flow
*/
'use strict';
var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
var {ReactComponentTreeHook} = require('ReactGlobalSharedState');
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>;
// Trust the developer to only use this with a __DEV__ check
var ReactDebugTool = ((null: any): typeof ReactDebugTool);
if (__DEV__) {
var hooks = [];
var didHookThrowForEvent = {};
const callHook = function(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;
}
};
const emitEvent = function(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;
const clearHistory = function() {
ReactComponentTreeHook.purgeUnmountedComponents();
ReactHostOperationHistoryHook.clearHistory();
};
const getTreeSnapshot = function(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;
}, {});
};
const resetMeasurements = function() {
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 = [];
};
const checkDebugID = function(debugID, allowRoot = false) {
if (allowRoot && debugID === 0) {
return;
}
if (!debugID) {
warning(false, 'ReactDebugTool: debugID may not be empty.');
}
};
const beginLifeCycleTimer = function(debugID, timerType) {
if (currentFlushNesting === 0) {
return;
}
if (currentTimerType && !lifeCycleTimerHasWarned) {
warning(
false,
'There is an internal error in the React performance measurement code.' +
'\n\nDid 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;
};
const endLifeCycleTimer = function(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;
};
const pauseCurrentLifeCycleTimer = function() {
var currentTimer = {
startTime: currentTimerStartTime,
nestedFlushStartTime: performanceNow(),
debugID: currentTimerDebugID,
timerType: currentTimerType,
};
lifeCycleTimerStack.push(currentTimer);
currentTimerStartTime = 0;
currentTimerNestedFlushDuration = 0;
currentTimerDebugID = null;
currentTimerType = null;
};
const resumeCurrentLifeCycleTimer = function() {
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 =
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function' &&
typeof performance.measure === 'function' &&
typeof performance.clearMeasures === 'function';
const shouldMark = function(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;
};
const markBegin = function(debugID, markType) {
if (!shouldMark(debugID)) {
return;
}
var markName = `${debugID}::${markType}`;
lastMarkTimeStamp = performanceNow();
performance.mark(markName);
};
const markEnd = function(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);
if (measurementName) {
performance.clearMeasures(measurementName);
}
};
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');
},
};
ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
ReactDebugTool.addHook(ReactComponentTreeHook);
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if (/[?&]react_perf\b/.test(url)) {
ReactDebugTool.beginProfiling();
}
}
module.exports = ReactDebugTool;

View File

@ -1,28 +0,0 @@
/**
* 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 ReactGlobalSharedState
*/
'use strict';
var ReactInternals = require('react')
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
var ReactGlobalSharedState = {
ReactCurrentOwner: ReactInternals.ReactCurrentOwner,
};
if (__DEV__) {
Object.assign(ReactGlobalSharedState, {
ReactComponentTreeHook: ReactInternals.ReactComponentTreeHook,
ReactDebugCurrentFrame: ReactInternals.ReactDebugCurrentFrame,
});
}
module.exports = ReactGlobalSharedState;

View File

@ -1,23 +0,0 @@
/**
* 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};

View File

@ -1,458 +0,0 @@
/**
* 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;

View File

@ -1,55 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
const describeFiber = ReactDOMFeatureFlags.useFiber ? describe : xdescribe;
describeFiber('ReactDOMFrameScheduling', () => {
it('throws when requestAnimationFrame is not polyfilled in the browser', () => {
const previousRAF = global.requestAnimationFrame;
try {
global.requestAnimationFrame = undefined;
jest.resetModules();
expect(() => {
require('ReactDOM');
}).toThrow(
'React depends on requestAnimationFrame. Make sure that you load a ' +
'polyfill in older browsers.',
);
} finally {
global.requestAnimationFrame = previousRAF;
}
});
// We're just testing importing, not using it.
// It is important because even isomorphic components may import it.
it('can import findDOMNode in Node environment', () => {
const previousRAF = global.requestAnimationFrame;
const previousRIC = global.requestIdleCallback;
const prevWindow = global.window;
try {
global.requestAnimationFrame = undefined;
global.requestIdleCallback = undefined;
// Simulate the Node environment:
delete global.window;
jest.resetModules();
expect(() => {
require('ReactDOM');
}).not.toThrow();
} finally {
global.requestAnimationFrame = previousRAF;
global.requestIdleCallback = previousRIC;
global.window = prevWindow;
}
});
});

View File

@ -1,84 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
describe('ReactDebugTool', () => {
var ReactDebugTool;
beforeEach(() => {
jest.resetModules();
ReactDebugTool = require('ReactDebugTool');
});
it('should add and remove hooks', () => {
var handler1 = jasmine.createSpy('spy');
var handler2 = jasmine.createSpy('spy');
var hook1 = {onTestEvent: handler1};
var hook2 = {onTestEvent: handler2};
ReactDebugTool.addHook(hook1);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(1);
expect(handler2.calls.count()).toBe(0);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(2);
expect(handler2.calls.count()).toBe(0);
ReactDebugTool.addHook(hook2);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(3);
expect(handler2.calls.count()).toBe(1);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(4);
expect(handler2.calls.count()).toBe(2);
ReactDebugTool.removeHook(hook1);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(4);
expect(handler2.calls.count()).toBe(3);
ReactDebugTool.removeHook(hook2);
ReactDebugTool.onTestEvent();
expect(handler1.calls.count()).toBe(4);
expect(handler2.calls.count()).toBe(3);
});
it('warns once when an error is thrown in hook', () => {
spyOn(console, 'error');
ReactDebugTool.addHook({
onTestEvent() {
throw new Error('Hi.');
},
});
ReactDebugTool.onTestEvent();
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Exception thrown by hook while handling ' + 'onTestEvent: Error: Hi.',
);
ReactDebugTool.onTestEvent();
expectDev(console.error.calls.count()).toBe(1);
});
it('returns isProfiling state', () => {
expect(ReactDebugTool.isProfiling()).toBe(false);
ReactDebugTool.beginProfiling();
expect(ReactDebugTool.isProfiling()).toBe(true);
ReactDebugTool.endProfiling();
expect(ReactDebugTool.isProfiling()).toBe(false);
});
});

View File

@ -1,60 +0,0 @@
/**
* 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 ReactDebugCurrentFiber
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
type LifeCyclePhase = 'render' | 'getChildContext';
if (__DEV__) {
var getComponentName = require('getComponentName');
var {
getStackAddendumByWorkInProgressFiber,
} = require('ReactFiberComponentTreeHook');
}
function getCurrentFiberOwnerName(): string | null {
if (__DEV__) {
const fiber = ReactDebugCurrentFiber.current;
if (fiber === null) {
return null;
}
if (fiber._debugOwner != null) {
return getComponentName(fiber._debugOwner);
}
}
return null;
}
function getCurrentFiberStackAddendum(): string | null {
if (__DEV__) {
const fiber = ReactDebugCurrentFiber.current;
if (fiber === null) {
return null;
}
// Safe because if current fiber exists, we are reconciling,
// and it is guaranteed to be the work-in-progress version.
return getStackAddendumByWorkInProgressFiber(fiber);
}
return null;
}
var ReactDebugCurrentFiber = {
current: (null: Fiber | null),
phase: (null: LifeCyclePhase | null),
getCurrentFiberOwnerName,
getCurrentFiberStackAddendum,
};
module.exports = ReactDebugCurrentFiber;

View File

@ -1,401 +0,0 @@
/**
* 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 ReactDebugFiberPerf
* @flow
*/
import type {Fiber} from 'ReactFiber';
type MeasurementPhase =
| 'componentWillMount'
| 'componentWillUnmount'
| 'componentWillReceiveProps'
| 'shouldComponentUpdate'
| 'componentWillUpdate'
| 'componentDidUpdate'
| 'componentDidMount'
| 'getChildContext';
// Trust the developer to only use this with a __DEV__ check
let ReactDebugFiberPerf = ((null: any): typeof ReactDebugFiberPerf);
if (__DEV__) {
const {
HostRoot,
HostComponent,
HostText,
HostPortal,
YieldComponent,
Fragment,
} = require('ReactTypeOfWork');
const getComponentName = require('getComponentName');
// Prefix measurements so that it's possible to filter them.
// Longer prefixes are hard to read in DevTools.
const reactEmoji = '\u269B';
const warningEmoji = '\u26D4';
const supportsUserTiming =
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function' &&
typeof performance.measure === 'function' &&
typeof performance.clearMeasures === 'function';
// Keep track of current fiber so that we know the path to unwind on pause.
// TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them?
let currentFiber: Fiber | null = null;
// If we're in the middle of user code, which fiber and method is it?
// Reusing `currentFiber` would be confusing for this because user code fiber
// can change during commit phase too, but we don't need to unwind it (since
// lifecycles in the commit phase don't resemble a tree).
let currentPhase: MeasurementPhase | null = null;
let currentPhaseFiber: Fiber | null = null;
// Did lifecycle hook schedule an update? This is often a performance problem,
// so we will keep track of it, and include it in the report.
// Track commits caused by cascading updates.
let isCommitting: boolean = false;
let hasScheduledUpdateInCurrentCommit: boolean = false;
let hasScheduledUpdateInCurrentPhase: boolean = false;
let commitCountInCurrentWorkLoop: number = 0;
let effectCountInCurrentCommit: number = 0;
// During commits, we only show a measurement once per method name
// to avoid stretch the commit phase with measurement overhead.
const labelsInCurrentCommit: Set<string> = new Set();
const formatMarkName = (markName: string) => {
return `${reactEmoji} ${markName}`;
};
const formatLabel = (label: string, warning: string | null) => {
const prefix = warning ? `${warningEmoji} ` : `${reactEmoji} `;
const suffix = warning ? ` Warning: ${warning}` : '';
return `${prefix}${label}${suffix}`;
};
const beginMark = (markName: string) => {
performance.mark(formatMarkName(markName));
};
const clearMark = (markName: string) => {
performance.clearMarks(formatMarkName(markName));
};
const endMark = (label: string, markName: string, warning: string | null) => {
const formattedMarkName = formatMarkName(markName);
const formattedLabel = formatLabel(label, warning);
try {
performance.measure(formattedLabel, formattedMarkName);
} catch (err) {
// If previous mark was missing for some reason, this will throw.
// This could only happen if React crashed in an unexpected place earlier.
// Don't pile on with more errors.
}
// Clear marks immediately to avoid growing buffer.
performance.clearMarks(formattedMarkName);
performance.clearMeasures(formattedLabel);
};
const getFiberMarkName = (label: string, debugID: number) => {
return `${label} (#${debugID})`;
};
const getFiberLabel = (
componentName: string,
isMounted: boolean,
phase: MeasurementPhase | null,
) => {
if (phase === null) {
// These are composite component total time measurements.
return `${componentName} [${isMounted ? 'update' : 'mount'}]`;
} else {
// Composite component methods.
return `${componentName}.${phase}`;
}
};
const beginFiberMark = (
fiber: Fiber,
phase: MeasurementPhase | null,
): boolean => {
const componentName = getComponentName(fiber) || 'Unknown';
const debugID = ((fiber._debugID: any): number);
const isMounted = fiber.alternate !== null;
const label = getFiberLabel(componentName, isMounted, phase);
if (isCommitting && labelsInCurrentCommit.has(label)) {
// During the commit phase, we don't show duplicate labels because
// there is a fixed overhead for every measurement, and we don't
// want to stretch the commit phase beyond necessary.
return false;
}
labelsInCurrentCommit.add(label);
const markName = getFiberMarkName(label, debugID);
beginMark(markName);
return true;
};
const clearFiberMark = (fiber: Fiber, phase: MeasurementPhase | null) => {
const componentName = getComponentName(fiber) || 'Unknown';
const debugID = ((fiber._debugID: any): number);
const isMounted = fiber.alternate !== null;
const label = getFiberLabel(componentName, isMounted, phase);
const markName = getFiberMarkName(label, debugID);
clearMark(markName);
};
const endFiberMark = (
fiber: Fiber,
phase: MeasurementPhase | null,
warning: string | null,
) => {
const componentName = getComponentName(fiber) || 'Unknown';
const debugID = ((fiber._debugID: any): number);
const isMounted = fiber.alternate !== null;
const label = getFiberLabel(componentName, isMounted, phase);
const markName = getFiberMarkName(label, debugID);
endMark(label, markName, warning);
};
const shouldIgnoreFiber = (fiber: Fiber): boolean => {
// Host components should be skipped in the timeline.
// We could check typeof fiber.type, but does this work with RN?
switch (fiber.tag) {
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case YieldComponent:
case Fragment:
return true;
default:
return false;
}
};
const clearPendingPhaseMeasurement = () => {
if (currentPhase !== null && currentPhaseFiber !== null) {
clearFiberMark(currentPhaseFiber, currentPhase);
}
currentPhaseFiber = null;
currentPhase = null;
hasScheduledUpdateInCurrentPhase = false;
};
const pauseTimers = () => {
// Stops all currently active measurements so that they can be resumed
// if we continue in a later deferred loop from the same unit of work.
let fiber = currentFiber;
while (fiber) {
if (fiber._debugIsCurrentlyTiming) {
endFiberMark(fiber, null, null);
}
fiber = fiber.return;
}
};
const resumeTimersRecursively = (fiber: Fiber) => {
if (fiber.return !== null) {
resumeTimersRecursively(fiber.return);
}
if (fiber._debugIsCurrentlyTiming) {
beginFiberMark(fiber, null);
}
};
const resumeTimers = () => {
// Resumes all measurements that were active during the last deferred loop.
if (currentFiber !== null) {
resumeTimersRecursively(currentFiber);
}
};
ReactDebugFiberPerf = {
recordEffect(): void {
effectCountInCurrentCommit++;
},
recordScheduleUpdate(): void {
if (isCommitting) {
hasScheduledUpdateInCurrentCommit = true;
}
if (
currentPhase !== null &&
currentPhase !== 'componentWillMount' &&
currentPhase !== 'componentWillReceiveProps'
) {
hasScheduledUpdateInCurrentPhase = true;
}
},
startWorkTimer(fiber: Fiber): void {
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
return;
}
// If we pause, this is the fiber to unwind from.
currentFiber = fiber;
if (!beginFiberMark(fiber, null)) {
return;
}
fiber._debugIsCurrentlyTiming = true;
},
cancelWorkTimer(fiber: Fiber): void {
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
return;
}
// Remember we shouldn't complete measurement for this fiber.
// Otherwise flamechart will be deep even for small updates.
fiber._debugIsCurrentlyTiming = false;
clearFiberMark(fiber, null);
},
stopWorkTimer(fiber: Fiber): void {
if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
return;
}
// If we pause, its parent is the fiber to unwind from.
currentFiber = fiber.return;
if (!fiber._debugIsCurrentlyTiming) {
return;
}
fiber._debugIsCurrentlyTiming = false;
endFiberMark(fiber, null, null);
},
startPhaseTimer(fiber: Fiber, phase: MeasurementPhase): void {
if (!supportsUserTiming) {
return;
}
clearPendingPhaseMeasurement();
if (!beginFiberMark(fiber, phase)) {
return;
}
currentPhaseFiber = fiber;
currentPhase = phase;
},
stopPhaseTimer(): void {
if (!supportsUserTiming) {
return;
}
if (currentPhase !== null && currentPhaseFiber !== null) {
const warning = hasScheduledUpdateInCurrentPhase
? 'Scheduled a cascading update'
: null;
endFiberMark(currentPhaseFiber, currentPhase, warning);
}
currentPhase = null;
currentPhaseFiber = null;
},
startWorkLoopTimer(): void {
if (!supportsUserTiming) {
return;
}
commitCountInCurrentWorkLoop = 0;
// This is top level call.
// Any other measurements are performed within.
beginMark('(React Tree Reconciliation)');
// Resume any measurements that were in progress during the last loop.
resumeTimers();
},
stopWorkLoopTimer(): void {
if (!supportsUserTiming) {
return;
}
const warning = commitCountInCurrentWorkLoop > 1
? 'There were cascading updates'
: null;
commitCountInCurrentWorkLoop = 0;
// Pause any measurements until the next loop.
pauseTimers();
endMark(
'(React Tree Reconciliation)',
'(React Tree Reconciliation)',
warning,
);
},
startCommitTimer(): void {
if (!supportsUserTiming) {
return;
}
isCommitting = true;
hasScheduledUpdateInCurrentCommit = false;
labelsInCurrentCommit.clear();
beginMark('(Committing Changes)');
},
stopCommitTimer(): void {
if (!supportsUserTiming) {
return;
}
let warning = null;
if (hasScheduledUpdateInCurrentCommit) {
warning = 'Lifecycle hook scheduled a cascading update';
} else if (commitCountInCurrentWorkLoop > 0) {
warning = 'Caused by a cascading update in earlier commit';
}
hasScheduledUpdateInCurrentCommit = false;
commitCountInCurrentWorkLoop++;
isCommitting = false;
labelsInCurrentCommit.clear();
endMark('(Committing Changes)', '(Committing Changes)', warning);
},
startCommitHostEffectsTimer(): void {
if (!supportsUserTiming) {
return;
}
effectCountInCurrentCommit = 0;
beginMark('(Committing Host Effects)');
},
stopCommitHostEffectsTimer(): void {
if (!supportsUserTiming) {
return;
}
const count = effectCountInCurrentCommit;
effectCountInCurrentCommit = 0;
endMark(
`(Committing Host Effects: ${count} Total)`,
'(Committing Host Effects)',
null,
);
},
startCommitLifeCyclesTimer(): void {
if (!supportsUserTiming) {
return;
}
effectCountInCurrentCommit = 0;
beginMark('(Calling Lifecycle Methods)');
},
stopCommitLifeCyclesTimer(): void {
if (!supportsUserTiming) {
return;
}
const count = effectCountInCurrentCommit;
effectCountInCurrentCommit = 0;
endMark(
`(Calling Lifecycle Methods: ${count} Total)`,
'(Calling Lifecycle Methods)',
null,
);
},
};
}
module.exports = ReactDebugFiberPerf;

View File

@ -1,474 +0,0 @@
/**
* 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 {ReactElement, Source} from 'ReactElementType';
import type {ReactInstance, DebugID} from 'ReactInstanceType';
import type {ReactFragment} from 'ReactTypes';
import type {ReactCoroutine, ReactYield} from 'ReactCoroutine';
import type {ReactPortal} from 'ReactPortal';
import type {TypeOfWork} from 'ReactTypeOfWork';
import type {TypeOfInternalContext} from 'ReactTypeOfInternalContext';
import type {TypeOfSideEffect} from 'ReactTypeOfSideEffect';
import type {PriorityLevel} from 'ReactPriorityLevel';
import type {UpdateQueue} from 'ReactFiberUpdateQueue';
var {
IndeterminateComponent,
ClassComponent,
HostRoot,
HostComponent,
HostText,
HostPortal,
CoroutineComponent,
YieldComponent,
Fragment,
} = require('ReactTypeOfWork');
var {NoWork} = require('ReactPriorityLevel');
var {NoContext} = require('ReactTypeOfInternalContext');
var {NoEffect} = require('ReactTypeOfSideEffect');
var {cloneUpdateQueue} = require('ReactFiberUpdateQueue');
var invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var getComponentName = require('getComponentName');
var hasBadMapPolyfill = false;
try {
const nonExtensibleObject = Object.preventExtensions({});
/* eslint-disable no-new */
new Map([[nonExtensibleObject, null]]);
new Set([nonExtensibleObject]);
/* eslint-enable no-new */
} catch (e) {
// TODO: Consider warning about bad polyfills
hasBadMapPolyfill = true;
}
}
// 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 = {
// __DEV__ only
_debugID?: DebugID,
_debugSource?: Source | null,
_debugOwner?: Fiber | ReactInstance | null, // Stack compatible
_debugIsCurrentlyTiming?: boolean,
// These first fields are conceptually members of an Instance. This used to
// be split into a separate type and intersected with the other Fiber fields,
// but until Flow fixes its intersection bugs, we've merged them into a
// single type.
// 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.
// 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.
// Remaining fields belong to Fiber
// 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 | null,
// Singly Linked List Tree Structure.
child: Fiber | null,
sibling: Fiber | null,
index: number,
// 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: mixed) => void) & {_stringRef: ?string}),
// 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 state updates and callbacks.
updateQueue: UpdateQueue | null,
// The state used to create the output
memoizedState: any,
// Bitfield that describes properties about the fiber and its subtree. E.g.
// the AsyncUpdates flag indicates whether the subtree should be async-by-
// default. When a fiber is created, it inherits the internalContextTag of its
// parent. Additional flags can be set at creation time, but after than the
// value should remain unchanged throughout the fiber's lifetime, particularly
// before its child fibers are created.
internalContextTag: TypeOfInternalContext,
// Effect
effectTag: TypeOfSideEffect,
// Singly linked list fast path to the next fiber with side-effects.
nextEffect: Fiber | null,
// 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 | null,
lastEffect: Fiber | null,
// 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 | null,
// When we reconcile children onto progressedChild it is possible that we have
// to delete some child fibers. We need to keep track of this side-effects so
// that if we continue later on, we have to include those effects. Deletions
// are added in the reverse order from sibling pointers.
progressedFirstDeletion: Fiber | null,
progressedLastDeletion: Fiber | null,
// 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 | null,
// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
};
if (__DEV__) {
var debugCounter = 1;
}
// 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,
internalContextTag: TypeOfInternalContext,
): Fiber {
var fiber: Fiber = {
// Instance
tag: tag,
key: key,
type: null,
stateNode: null,
// Fiber
return: null,
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps: null,
memoizedProps: null,
updateQueue: null,
memoizedState: null,
internalContextTag,
effectTag: NoEffect,
nextEffect: null,
firstEffect: null,
lastEffect: null,
pendingWorkPriority: NoWork,
progressedPriority: NoWork,
progressedChild: null,
progressedFirstDeletion: null,
progressedLastDeletion: null,
alternate: null,
};
if (__DEV__) {
fiber._debugID = debugCounter++;
fiber._debugSource = null;
fiber._debugOwner = null;
fiber._debugIsCurrentlyTiming = false;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(fiber);
}
}
return fiber;
};
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 !== null) {
// If we clone, then we do so from the "current" state. The current state
// can't have any side-effects that are still valid so we reset just to be
// sure.
alt.effectTag = NoEffect;
alt.nextEffect = null;
alt.firstEffect = null;
alt.lastEffect = null;
} else {
// This should not have an alternate already
alt = createFiber(fiber.tag, fiber.key, fiber.internalContextTag);
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.index = fiber.index; // This should always be overridden.
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;
cloneUpdateQueue(fiber, alt);
alt.pendingWorkPriority = priorityLevel;
alt.memoizedProps = fiber.memoizedProps;
alt.memoizedState = fiber.memoizedState;
if (__DEV__) {
alt._debugID = fiber._debugID;
alt._debugSource = fiber._debugSource;
alt._debugOwner = fiber._debugOwner;
}
return alt;
};
exports.createHostRootFiber = function(): Fiber {
const fiber = createFiber(HostRoot, null, NoContext);
return fiber;
};
exports.createFiberFromElement = function(
element: ReactElement,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
const fiber = createFiberFromElementType(
element.type,
element.key,
internalContextTag,
owner,
);
fiber.pendingProps = element.props;
fiber.pendingWorkPriority = priorityLevel;
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
};
exports.createFiberFromFragment = function(
elements: ReactFragment,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
// TODO: Consider supporting keyed fragments. Technically, we accidentally
// support that in the existing React.
const fiber = createFiber(Fragment, null, internalContextTag);
fiber.pendingProps = elements;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
exports.createFiberFromText = function(
content: string,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
const fiber = createFiber(HostText, null, internalContextTag);
fiber.pendingProps = content;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
function createFiberFromElementType(
type: mixed,
key: null | string,
internalContextTag: TypeOfInternalContext,
debugOwner: null | Fiber | ReactInstance,
): Fiber {
let fiber;
if (typeof type === 'function') {
fiber = shouldConstruct(type)
? createFiber(ClassComponent, key, internalContextTag)
: createFiber(IndeterminateComponent, key, internalContextTag);
fiber.type = type;
} else if (typeof type === 'string') {
fiber = createFiber(HostComponent, key, internalContextTag);
fiber.type = type;
} else if (
typeof type === 'object' &&
type !== null &&
typeof type.tag === 'number'
) {
// Currently assumed to be a continuation and therefore is a fiber already.
// TODO: The yield system is currently broken for updates in some cases.
// The reified yield stores a fiber, but we don't know which fiber that is;
// the current or a workInProgress? When the continuation gets rendered here
// we don't know if we can reuse that fiber or if we need to clone it.
// There is probably a clever way to restructure this.
fiber = ((type: any): Fiber);
} else {
let info = '';
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in.";
}
const ownerName = debugOwner ? getComponentName(debugOwner) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
invariant(
false,
'Element type is invalid: expected a string (for built-in components) ' +
'or a class/function (for composite components) but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
return fiber;
}
exports.createFiberFromElementType = createFiberFromElementType;
exports.createFiberFromCoroutine = function(
coroutine: ReactCoroutine,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
const fiber = createFiber(
CoroutineComponent,
coroutine.key,
internalContextTag,
);
fiber.type = coroutine.handler;
fiber.pendingProps = coroutine;
fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
exports.createFiberFromYield = function(
yieldNode: ReactYield,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
const fiber = createFiber(YieldComponent, null, internalContextTag);
return fiber;
};
exports.createFiberFromPortal = function(
portal: ReactPortal,
internalContextTag: TypeOfInternalContext,
priorityLevel: PriorityLevel,
): Fiber {
const fiber = createFiber(HostPortal, portal.key, internalContextTag);
fiber.pendingProps = portal.children || [];
fiber.pendingWorkPriority = priorityLevel;
fiber.stateNode = {
containerInfo: portal.containerInfo,
implementation: portal.implementation,
};
return fiber;
};

View File

@ -1,871 +0,0 @@
/**
* 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
* @flow
*/
'use strict';
import type {ReactCoroutine} from 'ReactCoroutine';
import type {Fiber} from 'ReactFiber';
import type {HostContext} from 'ReactFiberHostContext';
import type {FiberRoot} from 'ReactFiberRoot';
import type {HostConfig} from 'ReactFiberReconciler';
import type {PriorityLevel} from 'ReactPriorityLevel';
var {
mountChildFibersInPlace,
reconcileChildFibers,
reconcileChildFibersInPlace,
cloneChildFibers,
} = require('ReactChildFiber');
var {beginUpdateQueue} = require('ReactFiberUpdateQueue');
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
getMaskedContext,
getUnmaskedContext,
hasContextChanged,
pushContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
} = require('ReactFiberContext');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostRoot,
HostComponent,
HostText,
HostPortal,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
Fragment,
} = ReactTypeOfWork;
var {NoWork, OffscreenPriority} = require('ReactPriorityLevel');
var {Placement, ContentReset, Err, Ref} = require('ReactTypeOfSideEffect');
var ReactFiberClassComponent = require('ReactFiberClassComponent');
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
var invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var {cancelWorkTimer} = require('ReactDebugFiberPerf');
var warning = require('fbjs/lib/warning');
var warnedAboutStatelessRefs = {};
}
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
hostContext: HostContext<C, CX>,
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
) {
const {
shouldSetTextContent,
useSyncScheduling,
shouldDeprioritizeSubtree,
} = config;
const {pushHostContext, pushHostContainer} = hostContext;
const {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
updateClassInstance,
} = ReactFiberClassComponent(
scheduleUpdate,
getPriorityContext,
memoizeProps,
memoizeState,
);
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 !== null) {
// 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 clearDeletions(workInProgress) {
workInProgress.progressedFirstDeletion = workInProgress.progressedLastDeletion = null;
}
function transferDeletions(workInProgress) {
// Any deletions get added first into the effect list.
workInProgress.firstEffect = workInProgress.progressedFirstDeletion;
workInProgress.lastEffect = workInProgress.progressedLastDeletion;
}
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 === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibersInPlace(
workInProgress,
workInProgress.child,
nextChildren,
priorityLevel,
);
} else if (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.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
clearDeletions(workInProgress);
workInProgress.child = reconcileChildFibers(
workInProgress,
workInProgress.child,
nextChildren,
priorityLevel,
);
transferDeletions(workInProgress);
} else {
// If, on the other hand, 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,
);
transferDeletions(workInProgress);
}
markChildAsProgressed(current, workInProgress, priorityLevel);
}
function updateFragment(current, workInProgress) {
var nextChildren = workInProgress.pendingProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextChildren === null) {
nextChildren = workInProgress.memoizedProps;
}
} else if (
nextChildren === null ||
workInProgress.memoizedProps === nextChildren
) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
}
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (ref !== null && (!current || current.ref !== ref)) {
// Schedule a Ref effect
workInProgress.effectTag |= Ref;
}
}
function updateFunctionalComponent(current, workInProgress) {
var fn = workInProgress.type;
var nextProps = workInProgress.pendingProps;
const memoizedProps = workInProgress.memoizedProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextProps === null) {
nextProps = memoizedProps;
}
} else {
if (nextProps === null || memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
// 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' &&
!fn.shouldComponentUpdate(memoizedProps, nextProps)
) {
// Memoize props even if shouldComponentUpdate returns false
memoizeProps(workInProgress, nextProps);
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
var unmaskedContext = getUnmaskedContext(workInProgress);
var context = getMaskedContext(workInProgress, unmaskedContext);
var nextChildren;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactDebugCurrentFiber.phase = 'render';
nextChildren = fn(nextProps, context);
ReactDebugCurrentFiber.phase = null;
} else {
nextChildren = fn(nextProps, context);
}
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
priorityLevel: PriorityLevel,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
const hasContext = pushContextProvider(workInProgress);
let shouldUpdate;
if (current === null) {
if (!workInProgress.stateNode) {
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, workInProgress.pendingProps);
mountClassInstance(workInProgress, priorityLevel);
shouldUpdate = true;
} else {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(workInProgress, priorityLevel);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
priorityLevel,
);
}
return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
);
}
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
shouldUpdate: boolean,
hasContext: boolean,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
if (!shouldUpdate) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (__DEV__) {
ReactDebugCurrentFiber.phase = 'render';
nextChildren = instance.render();
ReactDebugCurrentFiber.phase = null;
} else {
nextChildren = instance.render();
}
reconcileChildren(current, workInProgress, nextChildren);
// Memoize props and state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
memoizeState(workInProgress, instance.state);
memoizeProps(workInProgress, instance.props);
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress);
}
return workInProgress.child;
}
function updateHostRoot(current, workInProgress, priorityLevel) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
// Should always be set
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
const prevState = workInProgress.memoizedState;
const state = beginUpdateQueue(
workInProgress,
updateQueue,
null,
prevState,
null,
priorityLevel,
);
if (prevState === state) {
// If the state is the same as before, that's a bailout because we had
// no work matching this priority.
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const element = state.element;
reconcileChildren(current, workInProgress, element);
memoizeState(workInProgress, state);
return workInProgress.child;
}
// If there is no update queue, that's a bailout because the root has no props.
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
function updateHostComponent(current, workInProgress) {
pushHostContext(workInProgress);
let nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
const memoizedProps = workInProgress.memoizedProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextProps === null) {
nextProps = memoizedProps;
invariant(
nextProps !== null,
'We should always have pending or current props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
} else if (nextProps === null || memoizedProps === nextProps) {
if (
!useSyncScheduling &&
shouldDeprioritizeSubtree(workInProgress.type, memoizedProps) &&
workInProgress.pendingWorkPriority !== OffscreenPriority
) {
// This subtree still has work, but it should be deprioritized so we need
// to bail out and not do any work yet.
// TODO: It would be better if this tree got its correct priority set
// during scheduleUpdate instead because otherwise we'll start a higher
// priority reconciliation first before we can get down here. However,
// that is a bit tricky since workInProgress and current can have
// different "hidden" settings.
let child = workInProgress.progressedChild;
while (child !== null) {
// To ensure that this subtree gets its priority reset, the children
// need to be reset.
child.pendingWorkPriority = OffscreenPriority;
child = child.sibling;
}
return null;
}
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also have access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps && shouldSetTextContent(prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}
markRef(current, workInProgress);
if (
!useSyncScheduling &&
shouldDeprioritizeSubtree(workInProgress.type, nextProps) &&
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,
);
memoizeProps(workInProgress, nextProps);
workInProgress.child = current !== null ? current.child : null;
if (current === null) {
// If this doesn't have a current we won't track it for placement
// effects. However, when we come back around to this we have already
// inserted the parent which means that we'll infact need to make this a
// placement.
// TODO: There has to be a better solution to this problem.
let child = workInProgress.progressedChild;
while (child !== null) {
child.effectTag = Placement;
child = child.sibling;
}
}
// Abort and don't process children yet.
return null;
} else {
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
}
function updateHostText(current, workInProgress) {
let nextProps = workInProgress.pendingProps;
if (nextProps === null) {
nextProps = workInProgress.memoizedProps;
}
memoizeProps(workInProgress, nextProps);
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
return null;
}
function mountIndeterminateComponent(current, workInProgress, priorityLevel) {
invariant(
current === null,
'An indeterminate component should never have mounted. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
var fn = workInProgress.type;
var props = workInProgress.pendingProps;
var unmaskedContext = getUnmaskedContext(workInProgress);
var context = getMaskedContext(workInProgress, unmaskedContext);
var value;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
value = fn(props, context);
} else {
value = fn(props, context);
}
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function'
) {
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
const hasContext = pushContextProvider(workInProgress);
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, priorityLevel);
return finishClassComponent(current, workInProgress, true, hasContext);
} else {
// Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent;
if (__DEV__) {
const Component = workInProgress.type;
if (Component) {
warning(
!Component.childContextTypes,
'%s(...): childContextTypes cannot be defined on a functional component.',
Component.displayName || Component.name || 'Component',
);
}
if (workInProgress.ref !== null) {
let info = '';
const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
let warningKey = ownerName || workInProgress._debugID || '';
const debugSource = workInProgress._debugSource;
if (debugSource) {
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
}
if (!warnedAboutStatelessRefs[warningKey]) {
warnedAboutStatelessRefs[warningKey] = true;
warning(
false,
'Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.%s%s',
info,
ReactDebugCurrentFiber.getCurrentFiberStackAddendum(),
);
}
}
}
reconcileChildren(current, workInProgress, value);
memoizeProps(workInProgress, props);
return workInProgress.child;
}
}
function updateCoroutineComponent(current, workInProgress) {
var nextCoroutine = (workInProgress.pendingProps: null | ReactCoroutine);
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextCoroutine === null) {
nextCoroutine = current && current.memoizedProps;
invariant(
nextCoroutine !== null,
'We should always have pending or current props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
} else if (
nextCoroutine === null ||
workInProgress.memoizedProps === nextCoroutine
) {
nextCoroutine = workInProgress.memoizedProps;
// TODO: When bailing out, we might need to return the stateNode instead
// of the child. To check it for work.
// return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const nextChildren = nextCoroutine.children;
const priorityLevel = workInProgress.pendingWorkPriority;
// The following is a fork of reconcileChildrenAtPriority but using
// stateNode to store the child.
// At this point any memoization is no longer valid since we'll have changed
// the children.
workInProgress.memoizedProps = null;
if (current === null) {
workInProgress.stateNode = mountChildFibersInPlace(
workInProgress,
workInProgress.stateNode,
nextChildren,
priorityLevel,
);
} else if (current.child === workInProgress.child) {
clearDeletions(workInProgress);
workInProgress.stateNode = reconcileChildFibers(
workInProgress,
workInProgress.stateNode,
nextChildren,
priorityLevel,
);
transferDeletions(workInProgress);
} else {
workInProgress.stateNode = reconcileChildFibersInPlace(
workInProgress,
workInProgress.stateNode,
nextChildren,
priorityLevel,
);
transferDeletions(workInProgress);
}
memoizeProps(workInProgress, nextCoroutine);
// This doesn't take arbitrary time so we could synchronously just begin
// eagerly do the work of workInProgress.child as an optimization.
return workInProgress.stateNode;
}
function updatePortalComponent(current, workInProgress) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const priorityLevel = workInProgress.pendingWorkPriority;
let nextChildren = workInProgress.pendingProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextChildren === null) {
nextChildren = current && current.memoizedProps;
invariant(
nextChildren != null,
'We should always have pending or current props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
} else if (
nextChildren === null ||
workInProgress.memoizedProps === nextChildren
) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
if (current === null) {
// Portals are special because we don't append the children during mount
// but at commit. Therefore we need to track insertions which the normal
// flow doesn't do during mount. This doesn't happen at the root because
// the root always starts with a "current" with a null child.
// TODO: Consider unifying this with how the root works.
workInProgress.child = reconcileChildFibersInPlace(
workInProgress,
workInProgress.child,
nextChildren,
priorityLevel,
);
memoizeProps(workInProgress, nextChildren);
markChildAsProgressed(current, workInProgress, priorityLevel);
} else {
reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextChildren);
}
return workInProgress.child;
}
/*
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 | null {
if (__DEV__) {
cancelWorkTimer(workInProgress);
}
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;
// }
if (current && workInProgress.child === current.child) {
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
clearDeletions(workInProgress);
}
cloneChildFibers(current, workInProgress);
markChildAsProgressed(current, workInProgress, priorityLevel);
return workInProgress.child;
}
function bailoutOnLowPriority(current, workInProgress) {
if (__DEV__) {
cancelWorkTimer(workInProgress);
}
// TODO: Handle HostComponent tags here as well and call pushHostContext()?
// See PR 8590 discussion for context
switch (workInProgress.tag) {
case ClassComponent:
pushContextProvider(workInProgress);
break;
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
}
// TODO: What if this is currently in progress?
// How can that happen? How is this not being cloned?
return null;
}
function memoizeProps(workInProgress: Fiber, nextProps: any) {
workInProgress.memoizedProps = nextProps;
// Reset the pending props
workInProgress.pendingProps = null;
}
function memoizeState(workInProgress: Fiber, nextState: any) {
workInProgress.memoizedState = nextState;
// Don't reset the updateQueue, in case there are pending updates. Resetting
// is handled by beginUpdateQueue.
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
priorityLevel: PriorityLevel,
): Fiber | null {
if (
workInProgress.pendingWorkPriority === NoWork ||
workInProgress.pendingWorkPriority > priorityLevel
) {
return bailoutOnLowPriority(current, workInProgress);
}
if (__DEV__) {
ReactDebugCurrentFiber.current = workInProgress;
}
// If we don't bail out, we're going be recomputing our children so we need
// to drop our effect list.
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
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;
}
switch (workInProgress.tag) {
case IndeterminateComponent:
return mountIndeterminateComponent(
current,
workInProgress,
priorityLevel,
);
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(current, workInProgress, priorityLevel);
case HostRoot:
return updateHostRoot(current, workInProgress, priorityLevel);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
return updateHostText(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:
return updateCoroutineComponent(current, workInProgress);
case YieldComponent:
// A yield component is just a placeholder, we can just run through the
// next one immediately.
return null;
case HostPortal:
return updatePortalComponent(current, workInProgress);
case Fragment:
return updateFragment(current, workInProgress);
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
function beginFailedWork(
current: Fiber | null,
workInProgress: Fiber,
priorityLevel: PriorityLevel,
) {
invariant(
workInProgress.tag === ClassComponent || workInProgress.tag === HostRoot,
'Invalid type of work. This error is likely caused by a bug in React. ' +
'Please file an issue.',
);
// Add an error effect so we can handle the error during the commit phase
workInProgress.effectTag |= Err;
if (
workInProgress.pendingWorkPriority === NoWork ||
workInProgress.pendingWorkPriority > priorityLevel
) {
return bailoutOnLowPriority(current, workInProgress);
}
// If we don't bail out, we're going be recomputing our children so we need
// to drop our effect list.
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
// Unmount the current children as if the component rendered null
const nextChildren = null;
reconcileChildren(current, workInProgress, nextChildren);
if (workInProgress.tag === ClassComponent) {
const instance = workInProgress.stateNode;
workInProgress.memoizedProps = instance.props;
workInProgress.memoizedState = instance.state;
workInProgress.pendingProps = null;
}
return workInProgress.child;
}
return {
beginWork,
beginFailedWork,
};
};

View File

@ -1,597 +0,0 @@
/**
* 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 ReactFiberClassComponent
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
import type {PriorityLevel} from 'ReactPriorityLevel';
var {Update} = require('ReactTypeOfSideEffect');
var ReactFeatureFlags = require('ReactFeatureFlags');
var {AsyncUpdates} = require('ReactTypeOfInternalContext');
var {
cacheContext,
getMaskedContext,
getUnmaskedContext,
isContextConsumer,
} = require('ReactFiberContext');
var {
addUpdate,
addReplaceUpdate,
addForceUpdate,
beginUpdateQueue,
} = require('ReactFiberUpdateQueue');
var {hasContextChanged} = require('ReactFiberContext');
var {isMounted} = require('ReactFiberTreeReflection');
var ReactInstanceMap = require('ReactInstanceMap');
var emptyObject = require('fbjs/lib/emptyObject');
var getComponentName = require('getComponentName');
var shallowEqual = require('fbjs/lib/shallowEqual');
var invariant = require('fbjs/lib/invariant');
const isArray = Array.isArray;
if (__DEV__) {
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
var warning = require('fbjs/lib/warning');
var warnOnInvalidCallback = function(callback: mixed, callerName: string) {
warning(
callback === null || typeof callback === 'function',
'%s(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callerName,
callback,
);
};
}
module.exports = function(
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
memoizeProps: (workInProgress: Fiber, props: any) => void,
memoizeState: (workInProgress: Fiber, state: any) => void,
) {
// Class component state updater
const updater = {
isMounted,
enqueueSetState(instance, partialState, callback) {
const fiber = ReactInstanceMap.get(instance);
const priorityLevel = getPriorityContext(fiber, false);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
addUpdate(fiber, partialState, callback, priorityLevel);
scheduleUpdate(fiber, priorityLevel);
},
enqueueReplaceState(instance, state, callback) {
const fiber = ReactInstanceMap.get(instance);
const priorityLevel = getPriorityContext(fiber, false);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'replaceState');
}
addReplaceUpdate(fiber, state, callback, priorityLevel);
scheduleUpdate(fiber, priorityLevel);
},
enqueueForceUpdate(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
const priorityLevel = getPriorityContext(fiber, false);
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'forceUpdate');
}
addForceUpdate(fiber, callback, priorityLevel);
scheduleUpdate(fiber, priorityLevel);
},
};
function checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
) {
if (
oldProps === null ||
(workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate)
) {
// If the workInProgress already has an Update effect, return true
return true;
}
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (typeof instance.shouldComponentUpdate === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
}
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
);
if (__DEV__) {
stopPhaseTimer();
}
if (__DEV__) {
warning(
shouldUpdate !== undefined,
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
'boolean value. Make sure to return true or false.',
getComponentName(workInProgress) || 'Unknown',
);
}
return shouldUpdate;
}
if (type.prototype && type.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
function checkClassInstance(workInProgress: Fiber) {
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (__DEV__) {
const name = getComponentName(workInProgress);
const renderPresent = instance.render;
warning(
renderPresent,
'%s(...): No `render` method found on the returned component ' +
'instance: you may have forgotten to define `render`.',
name,
);
const noGetInitialStateOnES6 =
!instance.getInitialState ||
instance.getInitialState.isReactClassApproved ||
instance.state;
warning(
noGetInitialStateOnES6,
'getInitialState was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Did you mean to define a state property instead?',
name,
);
const noGetDefaultPropsOnES6 =
!instance.getDefaultProps ||
instance.getDefaultProps.isReactClassApproved;
warning(
noGetDefaultPropsOnES6,
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
name,
);
const noInstancePropTypes = !instance.propTypes;
warning(
noInstancePropTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
'property to define propTypes instead.',
name,
);
const noInstanceContextTypes = !instance.contextTypes;
warning(
noInstanceContextTypes,
'contextTypes was defined as an instance property on %s. Use a static ' +
'property to define contextTypes instead.',
name,
);
const noComponentShouldUpdate =
typeof instance.componentShouldUpdate !== 'function';
warning(
noComponentShouldUpdate,
'%s has a method called ' +
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
'The name is phrased as a question because the function is ' +
'expected to return a value.',
name,
);
if (
type.prototype &&
type.prototype.isPureReactComponent &&
typeof instance.shouldComponentUpdate !== 'undefined'
) {
warning(
false,
'%s has a method called shouldComponentUpdate(). ' +
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
'Please extend React.Component if shouldComponentUpdate is used.',
getComponentName(workInProgress) || 'A pure component',
);
}
const noComponentDidUnmount =
typeof instance.componentDidUnmount !== 'function';
warning(
noComponentDidUnmount,
'%s has a method called ' +
'componentDidUnmount(). But there is no such lifecycle method. ' +
'Did you mean componentWillUnmount()?',
name,
);
const noComponentWillRecieveProps =
typeof instance.componentWillRecieveProps !== 'function';
warning(
noComponentWillRecieveProps,
'%s has a method called ' +
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
name,
);
const hasMutatedProps = instance.props !== workInProgress.pendingProps;
warning(
instance.props === undefined || !hasMutatedProps,
'%s(...): When calling super() in `%s`, make sure to pass ' +
"up the same props that your component's constructor was passed.",
name,
name,
);
const noInstanceDefaultProps = !instance.defaultProps;
warning(
noInstanceDefaultProps,
'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
' Instead, define defaultProps as a static property on %s.',
name,
name,
);
}
const state = instance.state;
if (state && (typeof state !== 'object' || isArray(state))) {
invariant(
false,
'%s.state: must be set to an object or null',
getComponentName(workInProgress),
);
}
if (typeof instance.getChildContext === 'function') {
invariant(
typeof workInProgress.type.childContextTypes === 'object',
'%s.getChildContext(): childContextTypes must be defined in order to ' +
'use getChildContext().',
getComponentName(workInProgress),
);
}
}
function resetInputPointers(workInProgress: Fiber, instance: any) {
instance.props = workInProgress.memoizedProps;
instance.state = workInProgress.memoizedState;
}
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = updater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
ReactInstanceMap.set(instance, workInProgress);
}
function constructClassInstance(workInProgress: Fiber, props: any): any {
const ctor = workInProgress.type;
const unmaskedContext = getUnmaskedContext(workInProgress);
const needsContext = isContextConsumer(workInProgress);
const context = needsContext
? getMaskedContext(workInProgress, unmaskedContext)
: emptyObject;
const instance = new ctor(props, context);
adoptClassInstance(workInProgress, instance);
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
if (needsContext) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
// Invokes the mount life-cycles on a previously never rendered instance.
function mountClassInstance(
workInProgress: Fiber,
priorityLevel: PriorityLevel,
): void {
if (__DEV__) {
checkClassInstance(workInProgress);
}
const instance = workInProgress.stateNode;
const state = instance.state || null;
let props = workInProgress.pendingProps;
invariant(
props,
'There must be pending props for an initial mount. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
const unmaskedContext = getUnmaskedContext(workInProgress);
instance.props = props;
instance.state = state;
instance.refs = emptyObject;
instance.context = getMaskedContext(workInProgress, unmaskedContext);
if (
ReactFeatureFlags.enableAsyncSubtreeAPI &&
workInProgress.type != null &&
workInProgress.type.unstable_asyncUpdates === true
) {
workInProgress.internalContextTag |= AsyncUpdates;
}
if (typeof instance.componentWillMount === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillMount');
}
instance.componentWillMount();
if (__DEV__) {
stopPhaseTimer();
}
// If we had additional state updates during this life-cycle, let's
// process them now.
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
instance.state = beginUpdateQueue(
workInProgress,
updateQueue,
instance,
state,
props,
priorityLevel,
);
}
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
// Called on a preexisting class instance. Returns false if a resumed render
// could be reused.
function resumeMountClassInstance(
workInProgress: Fiber,
priorityLevel: PriorityLevel,
): boolean {
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
let newState = workInProgress.memoizedState;
let newProps = workInProgress.pendingProps;
if (!newProps) {
// If there isn't any new props, then we'll reuse the memoized props.
// This could be from already completed work.
newProps = workInProgress.memoizedProps;
invariant(
newProps != null,
'There should always be pending or memoized props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
// TODO: Should we deal with a setState that happened after the last
// componentWillMount and before this componentWillMount? Probably
// unsupported anyway.
if (
!checkShouldComponentUpdate(
workInProgress,
workInProgress.memoizedProps,
newProps,
workInProgress.memoizedState,
newState,
newContext,
)
) {
// Update the existing instance's state, props, and context pointers even
// though we're bailing out.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;
return false;
}
// If we didn't bail out we need to construct a new instance. We don't
// want to reuse one that failed to fully mount.
const newInstance = constructClassInstance(workInProgress, newProps);
newInstance.props = newProps;
newInstance.state = newState = newInstance.state || null;
newInstance.context = newContext;
if (typeof newInstance.componentWillMount === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillMount');
}
newInstance.componentWillMount();
if (__DEV__) {
stopPhaseTimer();
}
}
// If we had additional state updates, process them now.
// They may be from componentWillMount() or from error boundary's setState()
// during initial mounting.
const newUpdateQueue = workInProgress.updateQueue;
if (newUpdateQueue !== null) {
newInstance.state = beginUpdateQueue(
workInProgress,
newUpdateQueue,
newInstance,
newState,
newProps,
priorityLevel,
);
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
return true;
}
// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
priorityLevel: PriorityLevel,
): boolean {
const instance = workInProgress.stateNode;
resetInputPointers(workInProgress, instance);
const oldProps = workInProgress.memoizedProps;
let newProps = workInProgress.pendingProps;
if (!newProps) {
// If there aren't any new props, then we'll reuse the memoized props.
// This could be from already completed work.
newProps = oldProps;
invariant(
newProps != null,
'There should always be pending or memoized props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
const oldContext = instance.context;
const newUnmaskedContext = getUnmaskedContext(workInProgress);
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
if (oldProps !== newProps || oldContext !== newContext) {
if (typeof instance.componentWillReceiveProps === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
}
instance.componentWillReceiveProps(newProps, newContext);
if (__DEV__) {
stopPhaseTimer();
}
if (instance.state !== workInProgress.memoizedState) {
if (__DEV__) {
warning(
false,
'%s.componentWillReceiveProps(): Assigning directly to ' +
"this.state is deprecated (except inside a component's " +
'constructor). Use setState instead.',
getComponentName(workInProgress),
);
}
updater.enqueueReplaceState(instance, instance.state, null);
}
}
}
// Compute the next state using the memoized state and the update queue.
const updateQueue = workInProgress.updateQueue;
const oldState = workInProgress.memoizedState;
// TODO: Previous state can be null.
let newState;
if (updateQueue !== null) {
newState = beginUpdateQueue(
workInProgress,
updateQueue,
instance,
oldState,
newProps,
priorityLevel,
);
} else {
newState = oldState;
}
if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(updateQueue !== null && updateQueue.hasForceUpdate)
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
return false;
}
const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);
if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
}
instance.componentWillUpdate(newProps, newState, newContext);
if (__DEV__) {
stopPhaseTimer();
}
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
memoizeProps(workInProgress, newProps);
memoizeState(workInProgress, newState);
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = newContext;
return shouldUpdate;
}
return {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
updateClassInstance,
};
};

View File

@ -1,541 +0,0 @@
/**
* 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 {HostConfig} from 'ReactFiberReconciler';
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
ClassComponent,
HostRoot,
HostComponent,
HostText,
HostPortal,
CoroutineComponent,
} = ReactTypeOfWork;
var {commitCallbacks} = require('ReactFiberUpdateQueue');
var {onCommitUnmount} = require('ReactFiberDevToolsHook');
var {invokeGuardedCallback} = require('ReactErrorUtils');
var {
Placement,
Update,
Callback,
ContentReset,
} = require('ReactTypeOfSideEffect');
var invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
}
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
captureError: (failedFiber: Fiber, error: Error) => Fiber | null,
) {
const {
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
insertBefore,
removeChild,
getPublicInstance,
} = config;
if (__DEV__) {
var callComponentWillUnmountWithTimerInDev = function(current, instance) {
startPhaseTimer(current, 'componentWillUnmount');
instance.componentWillUnmount();
stopPhaseTimer();
};
}
// Capture errors so they don't interrupt unmounting.
function safelyCallComponentWillUnmount(current, instance) {
if (__DEV__) {
const unmountError = invokeGuardedCallback(
null,
callComponentWillUnmountWithTimerInDev,
null,
current,
instance,
);
if (unmountError) {
captureError(current, unmountError);
}
} else {
try {
instance.componentWillUnmount();
} catch (unmountError) {
captureError(current, unmountError);
}
}
}
function safelyDetachRef(current: Fiber) {
const ref = current.ref;
if (ref !== null) {
if (__DEV__) {
const refError = invokeGuardedCallback(null, ref, null, null);
if (refError !== null) {
captureError(current, refError);
}
} else {
try {
ref(null);
} catch (refError) {
captureError(current, refError);
}
}
}
}
function getHostParent(fiber: Fiber): I | C {
let parent = fiber.return;
while (parent !== null) {
switch (parent.tag) {
case HostComponent:
return parent.stateNode;
case HostRoot:
return parent.stateNode.containerInfo;
case HostPortal:
return parent.stateNode.containerInfo;
}
parent = parent.return;
}
invariant(
false,
'Expected to find a host parent. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
invariant(
false,
'Expected to find a host parent. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
fiber.tag === HostPortal
);
}
function getHostSibling(fiber: Fiber): ?I {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
let node: Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
while (node.tag !== HostComponent && node.tag !== HostText) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
if (node.effectTag & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
node.child.return = node;
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
if (!(node.effectTag & Placement)) {
// Found it!
return node.stateNode;
}
}
}
function commitPlacement(finishedWork: Fiber): void {
// Recursively insert all host nodes into the parent.
const parentFiber = getHostParentFiber(finishedWork);
let parent;
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
break;
case HostPortal:
parent = parentFiber.stateNode.containerInfo;
break;
default:
invariant(
false,
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
if (parentFiber.effectTag & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.effectTag &= ~ContentReset;
}
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = finishedWork;
while (true) {
if (node.tag === HostComponent || node.tag === HostText) {
if (before) {
insertBefore(parent, node.stateNode, before);
} else {
appendChild(parent, node.stateNode);
}
} else if (node.tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
while (true) {
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (node.child !== null && node.tag !== HostPortal) {
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function unmountHostComponents(parent, current): void {
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = current;
while (true) {
if (node.tag === HostComponent || node.tag === HostText) {
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
removeChild(parent, node.stateNode);
// Don't visit children because we already visited them.
} else if (node.tag === HostPortal) {
// When we go into a portal, it becomes the parent to remove from.
// We will reassign it back when we pop the portal on the way up.
parent = node.stateNode.containerInfo;
// Visit children because portals might contain host components.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
} else {
commitUnmount(node);
// Visit children because we may find more host components below.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
}
if (node === current) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return;
if (node.tag === HostPortal) {
// When we go out of the portal, we need to restore the parent.
// Since we don't keep a stack of them, we will search for it.
parent = getHostParent(node);
}
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function commitDeletion(current: Fiber): void {
// Recursively delete all host nodes from the parent.
const parent = getHostParent(current);
// Detach refs and call componentWillUnmount() on the whole subtree.
unmountHostComponents(parent, current);
// Cut off the return pointers to disconnect it from the tree. Ideally, we
// should clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child. This child
// itself will be GC:ed when the parent updates the next time.
current.return = null;
current.child = null;
if (current.alternate) {
current.alternate.child = null;
current.alternate.return = null;
}
}
// User-originating errors (lifecycles and refs) should not interrupt
// deletion, so don't let them throw. Host-originating errors should
// interrupt deletion, so it's okay
function commitUnmount(current: Fiber): void {
if (typeof onCommitUnmount === 'function') {
onCommitUnmount(current);
}
switch (current.tag) {
case ClassComponent: {
safelyDetachRef(current);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
safelyDetachRef(current);
return;
}
case CoroutineComponent: {
commitNestedUnmounts(current.stateNode);
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
const parent = getHostParent(current);
unmountHostComponents(parent, current);
return;
}
}
}
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
if (instance != null && current !== null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | PL = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
invariant(
finishedWork.stateNode !== null && current !== null,
'This should only be done during updates. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
const textInstance: TI = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
const oldText: string = current.memoizedProps;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
case HostPortal: {
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
function commitLifeCycles(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidMount');
}
instance.componentDidMount();
if (__DEV__) {
stopPhaseTimer();
}
} else {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidUpdate');
}
instance.componentDidUpdate(prevProps, prevState);
if (__DEV__) {
stopPhaseTimer();
}
}
}
if (
finishedWork.effectTag & Callback &&
finishedWork.updateQueue !== null
) {
commitCallbacks(finishedWork, finishedWork.updateQueue, instance);
}
return;
}
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
const instance = finishedWork.child && finishedWork.child.stateNode;
commitCallbacks(finishedWork, updateQueue, instance);
}
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = getPublicInstance(finishedWork.stateNode);
ref(instance);
}
}
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
currentRef(null);
}
}
return {
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
commitAttachRef,
commitDetachRef,
};
};

View File

@ -1,361 +0,0 @@
/**
* 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 {HostContext} from 'ReactFiberHostContext';
import type {FiberRoot} from 'ReactFiberRoot';
import type {HostConfig} from 'ReactFiberReconciler';
var {reconcileChildFibers} = require('ReactChildFiber');
var {popContextProvider} = require('ReactFiberContext');
var ReactTypeOfWork = require('ReactTypeOfWork');
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostRoot,
HostComponent,
HostText,
HostPortal,
CoroutineComponent,
CoroutineHandlerPhase,
YieldComponent,
Fragment,
} = ReactTypeOfWork;
var {Ref, Update} = ReactTypeOfSideEffect;
if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
}
var invariant = require('fbjs/lib/invariant');
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
hostContext: HostContext<C, CX>,
) {
const {
createInstance,
createTextInstance,
appendInitialChild,
finalizeInitialChildren,
prepareUpdate,
} = config;
const {
getRootHostContainer,
popHostContext,
getHostContext,
popHostContainer,
} = hostContext;
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 !== null) {
// 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 markUpdate(workInProgress: Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// an UpdateAndPlacement.
workInProgress.effectTag |= Update;
}
function markRef(workInProgress: Fiber) {
workInProgress.effectTag |= Ref;
}
function appendAllYields(yields: Array<mixed>, workInProgress: Fiber) {
let node = workInProgress.stateNode;
if (node) {
node.return = workInProgress;
}
while (node !== null) {
if (
node.tag === HostComponent ||
node.tag === HostText ||
node.tag === HostPortal
) {
invariant(false, 'A coroutine cannot have host component children.');
} else if (node.tag === YieldComponent) {
yields.push(node.type);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function moveCoroutineToHandlerPhase(
current: Fiber | null,
workInProgress: Fiber,
) {
var coroutine = (workInProgress.memoizedProps: ?ReactCoroutine);
invariant(
coroutine,
'Should be resolved by now. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
// 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<mixed> = [];
appendAllYields(yields, workInProgress);
var fn = coroutine.handler;
var props = coroutine.props;
var nextChildren = fn(props, yields);
var currentFirstChild = current !== null ? current.child : null;
// Inherit the priority of the returnFiber.
const priority = workInProgress.pendingWorkPriority;
workInProgress.child = reconcileChildFibers(
workInProgress,
currentFirstChild,
nextChildren,
priority,
);
markChildAsProgressed(current, workInProgress, priority);
return workInProgress.child;
}
function appendAllChildren(parent: I, workInProgress: Fiber) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node = node.sibling;
}
}
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
): Fiber | null {
if (__DEV__) {
ReactDebugCurrentFiber.current = workInProgress;
}
switch (workInProgress.tag) {
case FunctionalComponent:
return null;
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
popContextProvider(workInProgress);
return null;
}
case HostRoot: {
// TODO: Pop the host container after #8607 lands.
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
const newProps = workInProgress.memoizedProps;
if (current !== null && 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.
const instance: I = workInProgress.stateNode;
const currentHostContext = getHostContext();
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
if (updatePayload) {
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
case HostText: {
let newText = workInProgress.memoizedProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const textInstance = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
workInProgress.stateNode = textInstance;
}
return null;
}
case CoroutineComponent:
return moveCoroutineToHandlerPhase(current, workInProgress);
case CoroutineHandlerPhase:
// Reset the tag to now be a first phase coroutine.
workInProgress.tag = CoroutineComponent;
return null;
case YieldComponent:
// Does nothing.
return null;
case Fragment:
return null;
case HostPortal:
// TODO: Only mark this as an update if we have any pending callbacks.
markUpdate(workInProgress);
popHostContainer(workInProgress);
return null;
// Error cases
case IndeterminateComponent:
invariant(
false,
'An indeterminate component should have become determinate before ' +
'completing. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// eslint-disable-next-line no-fallthrough
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
return {
completeWork,
};
};

View File

@ -1,289 +0,0 @@
/**
* 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 ReactFiberContext
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
import type {StackCursor} from 'ReactFiberStack';
var checkPropTypes = require('prop-types/checkPropTypes');
var emptyObject = require('fbjs/lib/emptyObject');
var getComponentName = require('getComponentName');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
var {isFiberMounted} = require('ReactFiberTreeReflection');
var {ClassComponent, HostRoot} = require('ReactTypeOfWork');
const {createCursor, pop, push} = require('ReactFiberStack');
if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState');
var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf');
var warnedAboutMissingGetChildContext = {};
}
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.
// We use this to get access to the parent context after we have already
// pushed the next context provider, and now need to merge their contexts.
let previousContext: Object = emptyObject;
function getUnmaskedContext(workInProgress: Fiber): Object {
const hasOwnContext = isContextProvider(workInProgress);
if (hasOwnContext) {
// If the fiber is a context provider itself, when we read its context
// we have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
return contextStackCursor.current;
}
exports.getUnmaskedContext = getUnmaskedContext;
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
) {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
exports.cacheContext = cacheContext;
exports.getMaskedContext = function(
workInProgress: Fiber,
unmaskedContext: Object,
) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentName(workInProgress) || 'Unknown';
ReactDebugCurrentFrame.current = workInProgress;
checkPropTypes(
contextTypes,
context,
'context',
name,
ReactDebugCurrentFrame.getStackAddendum,
);
ReactDebugCurrentFrame.current = null;
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
};
exports.hasContextChanged = function(): boolean {
return didPerformWorkStackCursor.current;
};
function isContextConsumer(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
}
exports.isContextConsumer = isContextConsumer;
function isContextProvider(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
}
exports.isContextProvider = isContextProvider;
function popContextProvider(fiber: Fiber): void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
exports.popContextProvider = popContextProvider;
exports.pushTopLevelContextObject = function(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
invariant(
contextStackCursor.cursor == null,
'Unexpected context found on stack',
);
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
};
function processChildContext(
fiber: Fiber,
parentContext: Object,
isReconciling: boolean,
): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentName(fiber) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
warning(
false,
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.phase = 'getChildContext';
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
ReactDebugCurrentFiber.phase = null;
} else {
childContext = instance.getChildContext();
}
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentName(fiber) || 'Unknown';
// We can only provide accurate element stacks if we pass work-in-progress tree
// during the begin or complete phase. However currently this function is also
// called from unstable_renderSubtree legacy implementation. In this case it unsafe to
// assume anything about the given fiber. We won't pass it down if we aren't sure.
// TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
const workInProgress = isReconciling ? fiber : null;
ReactDebugCurrentFrame.current = workInProgress;
checkPropTypes(
childContextTypes,
childContext,
'child context',
name,
ReactDebugCurrentFrame.getStackAddendum,
);
ReactDebugCurrentFrame.current = null;
}
return {...parentContext, ...childContext};
}
exports.processChildContext = processChildContext;
exports.pushContextProvider = function(workInProgress: Fiber): boolean {
if (!isContextProvider(workInProgress)) {
return false;
}
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyObject;
// Remember the parent context so we can merge with it later.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(didPerformWorkStackCursor, false, workInProgress);
return true;
};
exports.invalidateContextProvider = function(workInProgress: Fiber): void {
const instance = workInProgress.stateNode;
invariant(instance, 'Expected to have an instance by this point.');
// Merge parent and own context.
const mergedContext = processChildContext(
workInProgress,
previousContext,
true,
);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, true, workInProgress);
};
exports.resetContext = function(): void {
previousContext = emptyObject;
contextStackCursor.current = emptyObject;
didPerformWorkStackCursor.current = false;
};
exports.findCurrentUnmaskedContext = function(fiber: Fiber): Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component',
);
let node: Fiber = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
const parent = node.return;
invariant(parent, 'Found unexpected detached subtree parent');
node = parent;
}
return node.stateNode.context;
};

View File

@ -1,72 +0,0 @@
/**
* 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 ReactFiberDevToolsHook
* @flow
*/
'use strict';
var warning = require('fbjs/lib/warning');
import type {Fiber} from 'ReactFiber';
import type {FiberRoot} from 'ReactFiberRoot';
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
let rendererID = null;
let injectInternals = null;
let onCommitRoot = null;
let onCommitUnmount = null;
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
__REACT_DEVTOOLS_GLOBAL_HOOK__.supportsFiber
) {
let {
inject,
onCommitFiberRoot,
onCommitFiberUnmount,
} = __REACT_DEVTOOLS_GLOBAL_HOOK__;
injectInternals = function(internals: Object) {
warning(rendererID == null, 'Cannot inject into DevTools twice.');
rendererID = inject(internals);
};
onCommitRoot = function(root: FiberRoot) {
if (rendererID == null) {
return;
}
try {
onCommitFiberRoot(rendererID, root);
} catch (err) {
// Catch all errors because it is unsafe to throw in the commit phase.
if (__DEV__) {
warning(false, 'React DevTools encountered an error: %s', err);
}
}
};
onCommitUnmount = function(fiber: Fiber) {
if (rendererID == null) {
return;
}
try {
onCommitFiberUnmount(rendererID, fiber);
} catch (err) {
// Catch all errors because it is unsafe to throw in the commit phase.
if (__DEV__) {
warning(false, 'React DevTools encountered an error: %s', err);
}
}
};
}
exports.injectInternals = injectInternals;
exports.onCommitRoot = onCommitRoot;
exports.onCommitUnmount = onCommitUnmount;

View File

@ -1,116 +0,0 @@
/**
* 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 ReactFiberErrorLogger
* @flow
*/
'use strict';
const invariant = require('fbjs/lib/invariant');
import type {CapturedError} from 'ReactFiberScheduler';
const defaultShowDialog = (capturedError: CapturedError) => true;
let showDialog = defaultShowDialog;
function logCapturedError(capturedError: CapturedError): void {
const logError = showDialog(capturedError);
// Allow injected showDialog() to prevent default console.error logging.
// This enables renderers like ReactNative to better manage redbox behavior.
if (logError === false) {
return;
}
if (__DEV__) {
const {
componentName,
componentStack,
error,
errorBoundaryName,
errorBoundaryFound,
willRetry,
} = capturedError;
const {message, name, stack} = error;
const errorSummary = message ? `${name}: ${message}` : name;
const componentNameMessage = componentName
? `React caught an error thrown by ${componentName}.`
: 'React caught an error thrown by one of your components.';
// Error stack varies by browser, eg:
// Chrome prepends the Error name and type.
// Firefox, Safari, and IE don't indent the stack lines.
// Format it in a consistent way for error logging.
let formattedCallStack = stack.slice(0, errorSummary.length) ===
errorSummary
? stack.slice(errorSummary.length)
: stack;
formattedCallStack = formattedCallStack
.trim()
.split('\n')
.map(line => `\n ${line.trim()}`)
.join();
let errorBoundaryMessage;
// errorBoundaryFound check is sufficient; errorBoundaryName check is to satisfy Flow.
if (errorBoundaryFound && errorBoundaryName) {
if (willRetry) {
errorBoundaryMessage =
`React will try to recreate this component tree from scratch ` +
`using the error boundary you provided, ${errorBoundaryName}.`;
} else {
errorBoundaryMessage =
`This error was initially handled by the error boundary ${errorBoundaryName}. ` +
`Recreating the tree from scratch failed so React will unmount the tree.`;
}
} else {
// TODO Link to unstable_handleError() documentation once it exists.
errorBoundaryMessage =
'Consider adding an error boundary to your tree to customize error handling behavior.';
}
console.error(
`${componentNameMessage} You should fix this error in your code. ${errorBoundaryMessage}\n\n` +
`${errorSummary}\n\n` +
`The error is located at: ${componentStack}\n\n` +
`The error was thrown at: ${formattedCallStack}`,
);
}
if (!__DEV__) {
const {error} = capturedError;
console.error(
`React caught an error thrown by one of your components.\n\n${error.stack}`,
);
}
}
exports.injection = {
/**
* Display custom dialog for lifecycle errors.
* Return false to prevent default behavior of logging to console.error.
*/
injectDialog(fn: (e: CapturedError) => boolean) {
invariant(
showDialog === defaultShowDialog,
'The custom dialog was already injected.',
);
invariant(
typeof fn === 'function',
'Injected showDialog() must be a function.',
);
showDialog = fn;
},
};
exports.logCapturedError = logCapturedError;

View File

@ -1,130 +0,0 @@
/**
* 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 ReactFiberHostContext
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
import type {HostConfig} from 'ReactFiberReconciler';
import type {StackCursor} from 'ReactFiberStack';
const {createCursor, pop, push} = require('ReactFiberStack');
const invariant = require('fbjs/lib/invariant');
declare class NoContextT {}
const NO_CONTEXT: NoContextT = ({}: any);
export type HostContext<C, CX> = {
getHostContext(): CX,
getRootHostContainer(): C,
popHostContainer(fiber: Fiber): void,
popHostContext(fiber: Fiber): void,
pushHostContainer(fiber: Fiber, container: C): void,
pushHostContext(fiber: Fiber): void,
resetHostContainer(): void,
};
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
): HostContext<C, CX> {
const {getChildHostContext, getRootHostContext} = config;
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
NO_CONTEXT,
);
let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
NO_CONTEXT,
);
let rootInstanceStackCursor: StackCursor<C | NoContextT> = createCursor(
NO_CONTEXT,
);
function requiredContext<Value>(c: Value | NoContextT): Value {
invariant(
c !== NO_CONTEXT,
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
return (c: any);
}
function getRootHostContainer(): C {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
function pushHostContainer(fiber: Fiber, nextRootInstance: C) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
function popHostContainer(fiber: Fiber) {
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
pop(rootInstanceStackCursor, fiber);
}
function getHostContext(): CX {
const context = requiredContext(contextStackCursor.current);
return context;
}
function pushHostContext(fiber: Fiber): void {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
const context = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type, rootInstance);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function popHostContext(fiber: Fiber): void {
// Do not pop unless this Fiber provided the current context.
// pushHostContext() only pushes Fibers that provide unique contexts.
if (contextFiberStackCursor.current !== fiber) {
return;
}
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
}
function resetHostContainer() {
contextStackCursor.current = NO_CONTEXT;
rootInstanceStackCursor.current = NO_CONTEXT;
}
return {
getHostContext,
getRootHostContainer,
popHostContainer,
popHostContext,
pushHostContainer,
pushHostContext,
resetHostContainer,
};
};

View File

@ -1,23 +0,0 @@
/**
* 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 ReactFiberInstrumentation
* @flow
*/
'use strict';
// This lets us hook into Fiber to debug what it's doing.
// See https://github.com/facebook/react/pull/8033.
// This is not part of the public API, not even for React DevTools.
// You may only inject a debugTool if you work on React Fiber itself.
var ReactFiberInstrumentation = {
debugTool: null,
};
module.exports = ReactFiberInstrumentation;

View File

@ -1,271 +0,0 @@
/**
* 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 {PriorityLevel} from 'ReactPriorityLevel';
import type {ReactNodeList} from 'ReactTypes';
var ReactFeatureFlags = require('ReactFeatureFlags');
var {addTopLevelUpdate} = require('ReactFiberUpdateQueue');
var {
findCurrentUnmaskedContext,
isContextProvider,
processChildContext,
} = require('ReactFiberContext');
var {createFiberRoot} = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');
if (__DEV__) {
var warning = require('fbjs/lib/warning');
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var getComponentName = require('getComponentName');
}
var {findCurrentHostFiber} = require('ReactFiberTreeReflection');
var getContextForSubtree = require('getContextForSubtree');
export type Deadline = {
timeRemaining: () => number,
};
type OpaqueHandle = Fiber;
type OpaqueRoot = FiberRoot;
export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
getRootHostContext(rootContainerInstance: C): CX,
getChildHostContext(parentHostContext: CX, type: T, instance: C): CX,
getPublicInstance(instance: I | TI): PI,
createInstance(
type: T,
props: P,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): I,
appendInitialChild(parentInstance: I, child: I | TI): void,
finalizeInitialChildren(
parentInstance: I,
type: T,
props: P,
rootContainerInstance: C,
): boolean,
prepareUpdate(
instance: I,
type: T,
oldProps: P,
newProps: P,
rootContainerInstance: C,
hostContext: CX,
): null | PL,
commitUpdate(
instance: I,
updatePayload: PL,
type: T,
oldProps: P,
newProps: P,
internalInstanceHandle: OpaqueHandle,
): void,
commitMount(
instance: I,
type: T,
newProps: P,
internalInstanceHandle: OpaqueHandle,
): void,
shouldSetTextContent(props: P): boolean,
resetTextContent(instance: I): void,
shouldDeprioritizeSubtree(type: T, props: P): boolean,
createTextInstance(
text: string,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): TI,
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
appendChild(parentInstance: I | C, child: I | TI): void,
insertBefore(parentInstance: I | C, child: I | TI, beforeChild: I | TI): void,
removeChild(parentInstance: I | C, child: I | TI): void,
scheduleAnimationCallback(callback: () => void): number | void,
scheduleDeferredCallback(
callback: (deadline: Deadline) => void,
): number | void,
prepareForCommit(): void,
resetAfterCommit(): void,
useSyncScheduling?: boolean,
};
export type Reconciler<C, I, TI> = {
createContainer(containerInfo: C): OpaqueRoot,
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?ReactComponent<any, any, any>,
callback: ?Function,
): void,
performWithPriority(priorityLevel: PriorityLevel, fn: Function): void,
batchedUpdates<A>(fn: () => A): A,
unbatchedUpdates<A>(fn: () => A): A,
syncUpdates<A>(fn: () => A): A,
deferredUpdates<A>(fn: () => A): A,
// Used to extract the return value from the initial render. Legacy API.
getPublicRootInstance(
container: OpaqueRoot,
): ReactComponent<any, any, any> | TI | I | null,
// Use for findDOMNode/findHostNode. Legacy API.
findHostInstance(component: Fiber): I | TI | null,
};
getContextForSubtree._injectFiber(function(fiber: Fiber) {
const parentContext = findCurrentUnmaskedContext(fiber);
return isContextProvider(fiber)
? processChildContext(fiber, parentContext, false)
: parentContext;
});
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
): Reconciler<C, I, TI> {
var {
scheduleUpdate,
getPriorityContext,
performWithPriority,
batchedUpdates,
unbatchedUpdates,
syncUpdates,
deferredUpdates,
} = ReactFiberScheduler(config);
function scheduleTopLevelUpdate(
current: Fiber,
element: ReactNodeList,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactDebugCurrentFiber.phase === 'render' &&
ReactDebugCurrentFiber.current !== null
) {
warning(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
);
}
}
// Check if the top-level element is an async wrapper component. If so, treat
// updates to the root as async. This is a bit weird but lets us avoid a separate
// `renderAsync` API.
const forceAsync =
(ReactFeatureFlags : any).enableAsyncSubtreeAPI &&
element != null &&
element.type != null &&
(element.type: any).unstable_asyncUpdates === true;
const priorityLevel = getPriorityContext(current, forceAsync);
const nextState = {element};
callback = callback === undefined ? null : callback;
if (__DEV__) {
warning(
callback === null || typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
addTopLevelUpdate(current, nextState, callback, priorityLevel);
scheduleUpdate(current, priorityLevel);
}
return {
createContainer(containerInfo: C): OpaqueRoot {
return createFiberRoot(containerInfo);
},
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?ReactComponent<any, any, any>,
callback: ?Function,
): void {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
if (__DEV__) {
if (ReactFiberInstrumentation.debugTool) {
if (current.alternate === null) {
ReactFiberInstrumentation.debugTool.onMountContainer(container);
} else if (element === null) {
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
} else {
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
}
}
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
scheduleTopLevelUpdate(current, element, callback);
},
performWithPriority,
batchedUpdates,
unbatchedUpdates,
syncUpdates,
deferredUpdates,
getPublicRootInstance(
container: OpaqueRoot,
): ReactComponent<any, any, any> | I | TI | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
return containerFiber.child.stateNode;
},
findHostInstance(fiber: Fiber): I | TI | null {
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
},
};
};

View File

@ -1,47 +0,0 @@
/**
* 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 {createHostRootFiber} = 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 | null,
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
};
exports.createFiberRoot = function(containerInfo: any): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber();
const root = {
current: uninitializedFiber,
containerInfo: containerInfo,
isScheduled: false,
nextScheduledRoot: null,
context: null,
pendingContext: null,
};
uninitializedFiber.stateNode = root;
return root;
};

View File

@ -1,92 +0,0 @@
/**
* 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 ReactFiberStack
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
export type StackCursor<T> = {
current: T,
};
const warning = require('fbjs/lib/warning');
const valueStack: Array<any> = [];
if (__DEV__) {
var fiberStack: Array<Fiber | null> = [];
}
let index = -1;
exports.createCursor = function<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
};
exports.isEmpty = function(): boolean {
return index === -1;
};
exports.pop = function<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
};
exports.push = function<T>(
cursor: StackCursor<T>,
value: T,
fiber: Fiber,
): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
};
exports.reset = function(): void {
while (index > -1) {
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
};

View File

@ -1,265 +0,0 @@
/**
* 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 ReactFiberTreeReflection
* @flow
*/
'use strict';
import type {Fiber} from 'ReactFiber';
var ReactInstanceMap = require('ReactInstanceMap');
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
var getComponentName = require('getComponentName');
var invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var warning = require('fbjs/lib/warning');
}
var {
HostRoot,
HostComponent,
HostText,
ClassComponent,
} = require('ReactTypeOfWork');
var {NoEffect, Placement} = require('ReactTypeOfSideEffect');
var MOUNTING = 1;
var MOUNTED = 2;
var UNMOUNTED = 3;
function isFiberMountedImpl(fiber: Fiber): number {
let node = fiber;
if (!fiber.alternate) {
// If there is no alternate, this might be a new tree that isn't inserted
// yet. If it is, then it will have a pending insertion effect on it.
if ((node.effectTag & Placement) !== NoEffect) {
return MOUNTING;
}
while (node.return) {
node = node.return;
if ((node.effectTag & Placement) !== NoEffect) {
return MOUNTING;
}
}
} else {
while (node.return) {
node = node.return;
}
}
if (node.tag === HostRoot) {
// TODO: Check if this was a nested HostRoot when used with
// renderContainerIntoSubtree.
return MOUNTED;
}
// If we didn't hit the root, that means that we're in an disconnected tree
// that has been unmounted.
return UNMOUNTED;
}
exports.isFiberMounted = function(fiber: Fiber): boolean {
return isFiberMountedImpl(fiber) === MOUNTED;
};
exports.isMounted = function(
component: ReactComponent<any, any, any>,
): boolean {
if (__DEV__) {
const owner = (ReactCurrentOwner.current: any);
if (owner !== null && owner.tag === ClassComponent) {
const ownerFiber: Fiber = owner;
const instance = ownerFiber.stateNode;
warning(
instance._warnedAboutRefsInRender,
'%s is accessing isMounted inside its render() function. ' +
'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.',
getComponentName(ownerFiber) || 'A component',
);
instance._warnedAboutRefsInRender = true;
}
}
var fiber: ?Fiber = ReactInstanceMap.get(component);
if (!fiber) {
return false;
}
return isFiberMountedImpl(fiber) === MOUNTED;
};
function assertIsMounted(fiber) {
invariant(
isFiberMountedImpl(fiber) === MOUNTED,
'Unable to find node on an unmounted component.',
);
}
function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
let alternate = fiber.alternate;
if (!alternate) {
// If there is no alternate, then we only need to check if it is mounted.
const state = isFiberMountedImpl(fiber);
invariant(
state !== UNMOUNTED,
'Unable to find node on an unmounted component.',
);
if (state === MOUNTING) {
return null;
}
return fiber;
}
// If we have two possible branches, we'll walk backwards up to the root
// to see what path the root points to. On the way we may hit one of the
// special cases and we'll deal with them.
let a = fiber;
let b = alternate;
while (true) {
let parentA = a.return;
let parentB = parentA ? parentA.alternate : null;
if (!parentA || !parentB) {
// We're at the root.
break;
}
// If both copies of the parent fiber point to the same child, we can
// assume that the child is current. This happens when we bailout on low
// priority: the bailed out fiber's child reuses the current child.
if (parentA.child === parentB.child) {
let child = parentA.child;
while (child) {
if (child === a) {
// We've determined that A is the current branch.
assertIsMounted(parentA);
return fiber;
}
if (child === b) {
// We've determined that B is the current branch.
assertIsMounted(parentA);
return alternate;
}
child = child.sibling;
}
// We should never have an alternate for any mounting node. So the only
// way this could possibly happen is if this was unmounted, if at all.
invariant(false, 'Unable to find node on an unmounted component.');
}
if (a.return !== b.return) {
// The return pointer of A and the return pointer of B point to different
// fibers. We assume that return pointers never criss-cross, so A must
// belong to the child set of A.return, and B must belong to the child
// set of B.return.
a = parentA;
b = parentB;
} else {
// The return pointers pointer to the same fiber. We'll have to use the
// default, slow path: scan the child sets of each parent alternate to see
// which child belongs to which set.
//
// Search parent A's child set
let didFindChild = false;
let child = parentA.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentA;
b = parentB;
break;
}
if (child === b) {
didFindChild = true;
b = parentA;
a = parentB;
break;
}
child = child.sibling;
}
if (!didFindChild) {
// Search parent B's child set
child = parentB.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentB;
b = parentA;
break;
}
if (child === b) {
didFindChild = true;
b = parentB;
a = parentA;
break;
}
child = child.sibling;
}
invariant(
didFindChild,
'Child was not found in either parent set. This indicates a bug ' +
'related to the return pointer.',
);
}
}
invariant(
a.alternate === b,
"Return fibers should always be each others' alternates.",
);
}
// If the root is not a host container, we're in a disconnected tree. I.e.
// unmounted.
invariant(
a.tag === HostRoot,
'Unable to find node on an unmounted component.',
);
if (a.stateNode.current === a) {
// We've determined that A is the current branch.
return fiber;
}
// Otherwise B has to be current branch.
return alternate;
}
exports.findCurrentFiberUsingSlowPath = findCurrentFiberUsingSlowPath;
exports.findCurrentHostFiber = function(parent: Fiber): Fiber | null {
const currentParent = findCurrentFiberUsingSlowPath(parent);
if (!currentParent) {
return null;
}
// Next we'll drill down this component to find the first HostComponent/Text.
let node: Fiber = currentParent;
while (true) {
if (node.tag === HostComponent || node.tag === HostText) {
return node;
} else if (node.child) {
// TODO: If we hit a Portal, we're supposed to skip it.
node.child.return = node;
node = node.child;
continue;
}
if (node === currentParent) {
return null;
}
while (!node.sibling) {
if (!node.return || node.return === currentParent) {
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
// Flow needs the return null here, but ESLint complains about it.
// eslint-disable-next-line no-unreachable
return null;
};

View File

@ -1,509 +0,0 @@
/**
* 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';
import type {Fiber} from 'ReactFiber';
import type {PriorityLevel} from 'ReactPriorityLevel';
const {Callback: CallbackEffect} = require('ReactTypeOfSideEffect');
const {
NoWork,
SynchronousPriority,
TaskPriority,
} = require('ReactPriorityLevel');
const invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var warning = require('fbjs/lib/warning');
}
type PartialState<State, Props> =
| $Subtype<State>
| ((prevState: State, props: Props) => $Subtype<State>);
// Callbacks are not validated until invocation
type Callback = mixed;
type Update = {
priorityLevel: PriorityLevel,
partialState: PartialState<any, any>,
callback: Callback | null,
isReplace: boolean,
isForced: boolean,
isTopLevelUnmount: boolean,
next: Update | null,
};
// Singly linked-list of updates. When an update is scheduled, it is added to
// the queue of the current fiber and the work-in-progress fiber. The two queues
// are separate but they share a persistent structure.
//
// During reconciliation, updates are removed from the work-in-progress fiber,
// but they remain on the current fiber. That ensures that if a work-in-progress
// is aborted, the aborted updates are recovered by cloning from current.
//
// The work-in-progress queue is always a subset of the current queue.
//
// When the tree is committed, the work-in-progress becomes the current.
export type UpdateQueue = {
first: Update | null,
last: Update | null,
hasForceUpdate: boolean,
callbackList: null | Array<Callback>,
// Dev only
isProcessing?: boolean,
};
function comparePriority(a: PriorityLevel, b: PriorityLevel): number {
// When comparing update priorities, treat sync and Task work as equal.
// TODO: Could we avoid the need for this by always coercing sync priority
// to Task when scheduling an update?
if (
(a === TaskPriority || a === SynchronousPriority) &&
(b === TaskPriority || b === SynchronousPriority)
) {
return 0;
}
if (a === NoWork && b !== NoWork) {
return -255;
}
if (a !== NoWork && b === NoWork) {
return 255;
}
return a - b;
}
// Ensures that a fiber has an update queue, creating a new one if needed.
// Returns the new or existing queue.
function ensureUpdateQueue(fiber: Fiber): UpdateQueue {
if (fiber.updateQueue !== null) {
// We already have an update queue.
return fiber.updateQueue;
}
let queue;
if (__DEV__) {
queue = {
first: null,
last: null,
hasForceUpdate: false,
callbackList: null,
isProcessing: false,
};
} else {
queue = {
first: null,
last: null,
hasForceUpdate: false,
callbackList: null,
};
}
fiber.updateQueue = queue;
return queue;
}
// Clones an update queue from a source fiber onto its alternate.
function cloneUpdateQueue(
current: Fiber,
workInProgress: Fiber,
): UpdateQueue | null {
const currentQueue = current.updateQueue;
if (currentQueue === null) {
// The source fiber does not have an update queue.
workInProgress.updateQueue = null;
return null;
}
// If the alternate already has a queue, reuse the previous object.
const altQueue = workInProgress.updateQueue !== null
? workInProgress.updateQueue
: {};
altQueue.first = currentQueue.first;
altQueue.last = currentQueue.last;
// These fields are invalid by the time we clone from current. Reset them.
altQueue.hasForceUpdate = false;
altQueue.callbackList = null;
altQueue.isProcessing = false;
workInProgress.updateQueue = altQueue;
return altQueue;
}
exports.cloneUpdateQueue = cloneUpdateQueue;
function cloneUpdate(update: Update): Update {
return {
priorityLevel: update.priorityLevel,
partialState: update.partialState,
callback: update.callback,
isReplace: update.isReplace,
isForced: update.isForced,
isTopLevelUnmount: update.isTopLevelUnmount,
next: null,
};
}
function insertUpdateIntoQueue(
queue: UpdateQueue,
update: Update,
insertAfter: Update | null,
insertBefore: Update | null,
) {
if (insertAfter !== null) {
insertAfter.next = update;
} else {
// This is the first item in the queue.
update.next = queue.first;
queue.first = update;
}
if (insertBefore !== null) {
update.next = insertBefore;
} else {
// This is the last item in the queue.
queue.last = update;
}
}
// Returns the update after which the incoming update should be inserted into
// the queue, or null if it should be inserted at beginning.
function findInsertionPosition(queue, update): Update | null {
const priorityLevel = update.priorityLevel;
let insertAfter = null;
let insertBefore = null;
if (
queue.last !== null &&
comparePriority(queue.last.priorityLevel, priorityLevel) <= 0
) {
// Fast path for the common case where the update should be inserted at
// the end of the queue.
insertAfter = queue.last;
} else {
insertBefore = queue.first;
while (
insertBefore !== null &&
comparePriority(insertBefore.priorityLevel, priorityLevel) <= 0
) {
insertAfter = insertBefore;
insertBefore = insertBefore.next;
}
}
return insertAfter;
}
// The work-in-progress queue is a subset of the current queue (if it exists).
// We need to insert the incoming update into both lists. However, it's possible
// that the correct position in one list will be different from the position in
// the other. Consider the following case:
//
// Current: 3-5-6
// Work-in-progress: 6
//
// Then we receive an update with priority 4 and insert it into each list:
//
// Current: 3-4-5-6
// Work-in-progress: 4-6
//
// In the current queue, the new update's `next` pointer points to the update
// with priority 5. But in the work-in-progress queue, the pointer points to the
// update with priority 6. Because these two queues share the same persistent
// data structure, this won't do. (This can only happen when the incoming update
// has higher priority than all the updates in the work-in-progress queue.)
//
// To solve this, in the case where the incoming update needs to be inserted
// into two different positions, we'll make a clone of the update and insert
// each copy into a separate queue. This forks the list while maintaining a
// persistent structure, because the update that is added to the work-in-progress
// is always added to the front of the list.
//
// However, if incoming update is inserted into the same position of both lists,
// we shouldn't make a copy.
//
// If the update is cloned, it returns the cloned update.
function insertUpdate(fiber: Fiber, update: Update): Update | null {
const queue1 = ensureUpdateQueue(fiber);
const queue2 = fiber.alternate !== null
? ensureUpdateQueue(fiber.alternate)
: null;
// Warn if an update is scheduled from inside an updater function.
if (__DEV__) {
if (queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) {
warning(
false,
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
}
}
// Find the insertion position in the first queue.
const insertAfter1 = findInsertionPosition(queue1, update);
const insertBefore1 = insertAfter1 !== null
? insertAfter1.next
: queue1.first;
if (queue2 === null) {
// If there's no alternate queue, there's nothing else to do but insert.
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
return null;
}
// If there is an alternate queue, find the insertion position.
const insertAfter2 = findInsertionPosition(queue2, update);
const insertBefore2 = insertAfter2 !== null
? insertAfter2.next
: queue2.first;
// Now we can insert into the first queue. This must come after finding both
// insertion positions because it mutates the list.
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
if (insertBefore1 !== insertBefore2) {
// The insertion positions are different, so we need to clone the update and
// insert the clone into the alternate queue.
const update2 = cloneUpdate(update);
insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
return update2;
} else {
// The insertion positions are the same, so when we inserted into the first
// queue, it also inserted into the alternate. All we need to do is update
// the alternate queue's `first` and `last` pointers, in case they
// have changed.
if (insertAfter2 === null) {
queue2.first = update;
}
if (insertBefore2 === null) {
queue2.last = null;
}
}
return null;
}
function addUpdate(
fiber: Fiber,
partialState: PartialState<any, any> | null,
callback: mixed,
priorityLevel: PriorityLevel,
): void {
const update = {
priorityLevel,
partialState,
callback,
isReplace: false,
isForced: false,
isTopLevelUnmount: false,
next: null,
};
insertUpdate(fiber, update);
}
exports.addUpdate = addUpdate;
function addReplaceUpdate(
fiber: Fiber,
state: any | null,
callback: Callback | null,
priorityLevel: PriorityLevel,
): void {
const update = {
priorityLevel,
partialState: state,
callback,
isReplace: true,
isForced: false,
isTopLevelUnmount: false,
next: null,
};
insertUpdate(fiber, update);
}
exports.addReplaceUpdate = addReplaceUpdate;
function addForceUpdate(
fiber: Fiber,
callback: Callback | null,
priorityLevel: PriorityLevel,
): void {
const update = {
priorityLevel,
partialState: null,
callback,
isReplace: false,
isForced: true,
isTopLevelUnmount: false,
next: null,
};
insertUpdate(fiber, update);
}
exports.addForceUpdate = addForceUpdate;
function getPendingPriority(queue: UpdateQueue): PriorityLevel {
return queue.first !== null ? queue.first.priorityLevel : NoWork;
}
exports.getPendingPriority = getPendingPriority;
function addTopLevelUpdate(
fiber: Fiber,
partialState: PartialState<any, any>,
callback: Callback | null,
priorityLevel: PriorityLevel,
): void {
const isTopLevelUnmount = partialState.element === null;
const update = {
priorityLevel,
partialState,
callback,
isReplace: false,
isForced: false,
isTopLevelUnmount,
next: null,
};
const update2 = insertUpdate(fiber, update);
if (isTopLevelUnmount) {
// Drop all updates that are lower-priority, so that the tree is not
// remounted. We need to do this for both queues.
const queue1 = fiber.updateQueue;
const queue2 = fiber.alternate !== null
? fiber.alternate.updateQueue
: null;
if (queue1 !== null && update.next !== null) {
update.next = null;
queue1.last = update;
}
if (queue2 !== null && update2 !== null && update2.next !== null) {
update2.next = null;
queue2.last = update;
}
}
}
exports.addTopLevelUpdate = addTopLevelUpdate;
function getStateFromUpdate(update, instance, prevState, props) {
const partialState = update.partialState;
if (typeof partialState === 'function') {
const updateFn = partialState;
return updateFn.call(instance, prevState, props);
} else {
return partialState;
}
}
function beginUpdateQueue(
workInProgress: Fiber,
queue: UpdateQueue,
instance: any,
prevState: any,
props: any,
priorityLevel: PriorityLevel,
): any {
if (__DEV__) {
// Set this flag so we can warn if setState is called inside the update
// function of another setState.
queue.isProcessing = true;
}
queue.hasForceUpdate = false;
// Applies updates with matching priority to the previous state to create
// a new state object.
let state = prevState;
let dontMutatePrevState = true;
let callbackList = null;
let update = queue.first;
while (
update !== null &&
comparePriority(update.priorityLevel, priorityLevel) <= 0
) {
// Remove each update from the queue right before it is processed. That way
// if setState is called from inside an updater function, the new update
// will be inserted in the correct position.
queue.first = update.next;
if (queue.first === null) {
queue.last = null;
}
let partialState;
if (update.isReplace) {
state = getStateFromUpdate(update, instance, state, props);
dontMutatePrevState = true;
} else {
partialState = getStateFromUpdate(update, instance, state, props);
if (partialState) {
if (dontMutatePrevState) {
state = Object.assign({}, state, partialState);
} else {
state = Object.assign(state, partialState);
}
dontMutatePrevState = false;
}
}
if (update.isForced) {
queue.hasForceUpdate = true;
}
// Second condition ignores top-level unmount callbacks if they are not the
// last update in the queue, since a subsequent update will cause a remount.
if (
update.callback !== null &&
!(update.isTopLevelUnmount && update.next !== null)
) {
callbackList = callbackList || [];
callbackList.push(update.callback);
workInProgress.effectTag |= CallbackEffect;
}
update = update.next;
}
queue.callbackList = callbackList;
if (queue.first === null && callbackList === null && !queue.hasForceUpdate) {
// The queue is empty and there are no callbacks. We can reset it.
workInProgress.updateQueue = null;
}
if (__DEV__) {
queue.isProcessing = false;
}
return state;
}
exports.beginUpdateQueue = beginUpdateQueue;
function commitCallbacks(
finishedWork: Fiber,
queue: UpdateQueue,
context: mixed,
) {
const callbackList = queue.callbackList;
if (callbackList === null) {
return;
}
for (let i = 0; i < callbackList.length; i++) {
const callback = callbackList[i];
invariant(
typeof callback === 'function',
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: %s',
callback,
);
callback.call(context);
}
}
exports.commitCallbacks = commitCallbacks;

View File

@ -1,25 +0,0 @@
/**
* 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 | 6;
module.exports = {
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
TaskPriority: 2, // Completes at the end of the current tick.
AnimationPriority: 3, // Needs to complete before the next frame.
HighPriority: 4, // Interaction that needs to complete pretty soon to feel responsive.
LowPriority: 5, // Data fetching, or result from updating stores.
OffscreenPriority: 6, // Won't be visible but do the work in case it becomes visible.
};

View File

@ -1,27 +0,0 @@
/**
* 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 ReactTypeOfSideEffect
* @flow
*/
'use strict';
export type TypeOfSideEffect = number;
module.exports = {
NoEffect: 0, // 0b0000000
Placement: 1, // 0b0000001
Update: 2, // 0b0000010
PlacementAndUpdate: 3, // 0b0000011
Deletion: 4, // 0b0000100
ContentReset: 8, // 0b0001000
Callback: 16, // 0b0010000
Err: 32, // 0b0100000
Ref: 64, // 0b1000000
};

View File

@ -1,247 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactCoroutine;
var ReactFeatureFlags;
describe('ReactCoroutine', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('ReactNoop');
ReactCoroutine = require('ReactCoroutine');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
function div(...children) {
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
return {type: 'div', children, prop: undefined};
}
function span(prop) {
return {type: 'span', children: [], prop};
}
it('should render a coroutine', () => {
var ops = [];
function Continuation({isSame}) {
ops.push(['Continuation', isSame]);
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}
// An alternative API could mark Continuation as something that needs
// yielding. E.g. Continuation.yieldType = 123;
function Child({bar}) {
ops.push(['Child', bar]);
return ReactCoroutine.createYield({
props: {
bar: bar,
},
continuation: Continuation,
});
}
function Indirection() {
ops.push('Indirection');
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}
function HandleYields(props, yields) {
ops.push('HandleYields');
return yields.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}
// An alternative API could mark Parent as something that needs
// yielding. E.g. Parent.handler = HandleYields;
function Parent(props) {
ops.push('Parent');
return ReactCoroutine.createCoroutine(
props.children,
HandleYields,
props,
);
}
function App() {
return <div><Parent foo={true}><Indirection /></Parent></div>;
}
ReactNoop.render(<App />);
ReactNoop.flush();
expect(ops).toEqual([
'Parent',
'Indirection',
['Child', true],
// Yield
['Child', false],
// Yield
'HandleYields',
// Continue yields
['Continuation', true],
['Continuation', false],
]);
expect(ReactNoop.getChildren()).toEqual([
div(span('foo==bar'), span('foo!=bar')),
]);
});
it('should update a coroutine', () => {
function Continuation({isSame}) {
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}
function Child({bar}) {
return ReactCoroutine.createYield({
props: {
bar: bar,
},
continuation: Continuation,
});
}
function Indirection() {
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
}
function HandleYields(props, yields) {
return yields.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}
function Parent(props) {
return ReactCoroutine.createCoroutine(
props.children,
HandleYields,
props,
);
}
function App(props) {
return <div><Parent foo={props.foo}><Indirection /></Parent></div>;
}
ReactNoop.render(<App foo={true} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([
div(span('foo==bar'), span('foo!=bar')),
]);
ReactNoop.render(<App foo={false} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([
div(span('foo!=bar'), span('foo==bar')),
]);
});
it('should unmount a composite in a coroutine', () => {
var ops = [];
class Continuation extends React.Component {
render() {
ops.push('Continuation');
return <div />;
}
componentWillUnmount() {
ops.push('Unmount Continuation');
}
}
class Child extends React.Component {
render() {
ops.push('Child');
return ReactCoroutine.createYield(Continuation);
}
componentWillUnmount() {
ops.push('Unmount Child');
}
}
function HandleYields(props, yields) {
ops.push('HandleYields');
return yields.map((ContinuationComponent, i) => (
<ContinuationComponent key={i} />
));
}
class Parent extends React.Component {
render() {
ops.push('Parent');
return ReactCoroutine.createCoroutine(
this.props.children,
HandleYields,
this.props,
);
}
componentWillUnmount() {
ops.push('Unmount Parent');
}
}
ReactNoop.render(<Parent><Child /></Parent>);
ReactNoop.flush();
expect(ops).toEqual(['Parent', 'Child', 'HandleYields', 'Continuation']);
ops = [];
ReactNoop.render(<div />);
ReactNoop.flush();
expect(ops).toEqual([
'Unmount Parent',
'Unmount Child',
'Unmount Continuation',
]);
});
it('should handle deep updates in coroutine', () => {
let instances = {};
class Counter extends React.Component {
state = {value: 5};
render() {
instances[this.props.id] = this;
return ReactCoroutine.createYield(this.state.value);
}
}
function App(props) {
return ReactCoroutine.createCoroutine(
[
<Counter key="a" id="a" />,
<Counter key="b" id="b" />,
<Counter key="c" id="c" />,
],
(p, yields) => yields.map((y, i) => <span key={i} prop={y * 100} />),
{},
);
}
ReactNoop.render(<App />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(500), span(500), span(500)]);
instances.a.setState({value: 1});
instances.b.setState({value: 2});
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(100), span(200), span(500)]);
});
});

View File

@ -1,62 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactFiberReconciler;
describe('ReactFiberHostContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('React');
ReactFiberReconciler = require('ReactFiberReconciler');
});
it('works with null host context', () => {
var creates = 0;
var Renderer = ReactFiberReconciler({
prepareForCommit: function() {},
resetAfterCommit: function() {},
getRootHostContext: function() {
return null;
},
getChildHostContext: function() {
return null;
},
shouldSetTextContent: function() {
return false;
},
createInstance: function() {
creates++;
},
finalizeInitialChildren: function() {
return null;
},
appendInitialChild: function() {
return null;
},
appendChild: function() {
return null;
},
useSyncScheduling: true,
});
const container = Renderer.createContainer(/* root: */ null);
Renderer.updateContainer(
<a><b /></a>,
container,
/* parentComponent: */ null,
/* callback: */ null,
);
expect(creates).toBe(2);
});
});

View File

@ -1,490 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
describe('ReactDebugFiberPerf', () => {
let React;
let ReactCoroutine;
let ReactFeatureFlags;
let ReactNoop;
let ReactPortal;
let PropTypes;
let root;
let activeMeasure;
let knownMarks;
let knownMeasures;
function resetFlamechart() {
root = {
children: [],
indent: -1,
markName: null,
label: null,
parent: null,
toString() {
return this.children.map(c => c.toString()).join('\n');
},
};
activeMeasure = root;
knownMarks = new Set();
knownMeasures = new Set();
}
function addComment(comment) {
activeMeasure.children.push(
`${' '.repeat(activeMeasure.indent + 1)}// ${comment}`,
);
}
function getFlameChart() {
// Make sure we unwind the measurement stack every time.
expect(activeMeasure.indent).toBe(-1);
expect(activeMeasure).toBe(root);
// We should always clean them up because browsers
// buffer user timing measurements forever.
expect(knownMarks.size).toBe(0);
expect(knownMeasures.size).toBe(0);
return root.toString();
}
function createUserTimingPolyfill() {
// This is not a true polyfill, but it gives us enough
// to capture measurements in a readable tree-like output.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
return {
mark(markName) {
const measure = {
children: [],
indent: activeMeasure.indent + 1,
markName: markName,
// Will be assigned on measure() call:
label: null,
parent: activeMeasure,
toString() {
return (
[
' '.repeat(this.indent) + this.label,
...this.children.map(c => c.toString()),
].join('\n') +
// Extra newline after each root reconciliation
(this.indent === 0 ? '\n' : '')
);
},
};
// Step one level deeper
activeMeasure.children.push(measure);
activeMeasure = measure;
knownMarks.add(markName);
},
// We don't use the overload with three arguments.
measure(label, markName) {
if (markName !== activeMeasure.markName) {
throw new Error('Unexpected measure() call.');
}
// Step one level up
activeMeasure.label = label;
activeMeasure = activeMeasure.parent;
knownMeasures.add(label);
},
clearMarks(markName) {
if (markName === activeMeasure.markName) {
// Step one level up if we're in this measure
activeMeasure = activeMeasure.parent;
activeMeasure.children.length--;
}
knownMarks.delete(markName);
},
clearMeasures(label) {
knownMeasures.delete(label);
},
};
}
beforeEach(() => {
jest.resetModules();
resetFlamechart();
global.performance = createUserTimingPolyfill();
// Import after the polyfill is set up:
React = require('React');
ReactCoroutine = require('ReactCoroutine');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactNoop = require('ReactNoop');
ReactPortal = require('ReactPortal');
ReactFeatureFlags.disableNewFiberFeatures = false;
PropTypes = require('prop-types');
});
afterEach(() => {
delete global.performance;
});
function Parent(props) {
return <div>{props.children}</div>;
}
function Child(props) {
return <div>{props.children}</div>;
}
it('measures a simple reconciliation', () => {
ReactNoop.render(<Parent><Child /></Parent>);
addComment('Mount');
ReactNoop.flush();
ReactNoop.render(<Parent><Child /></Parent>);
addComment('Update');
ReactNoop.flush();
ReactNoop.render(null);
addComment('Unmount');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('skips parents during setState', () => {
class A extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class B extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
let a;
let b;
ReactNoop.render(
<Parent>
<Parent>
<Parent>
<A ref={inst => (a = inst)} />
</Parent>
</Parent>
<Parent>
<B ref={inst => (b = inst)} />
</Parent>
</Parent>,
);
ReactNoop.flush();
resetFlamechart();
a.setState({});
b.setState({});
addComment('Should include just A and B, no Parents');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('warns on cascading renders from setState', () => {
class Cascading extends React.Component {
componentDidMount() {
this.setState({});
}
render() {
return <div>{this.props.children}</div>;
}
}
ReactNoop.render(<Parent><Cascading /></Parent>);
addComment('Should print a warning');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('warns on cascading renders from top-level render', () => {
class Cascading extends React.Component {
componentDidMount() {
ReactNoop.renderToRootWithID(<Child />, 'b');
addComment('Scheduling another root from componentDidMount');
ReactNoop.flush();
}
render() {
return <div>{this.props.children}</div>;
}
}
ReactNoop.renderToRootWithID(<Cascading />, 'a');
addComment('Rendering the first root');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('does not treat setState from cWM or cWRP as cascading', () => {
class NotCascading extends React.Component {
componentWillMount() {
this.setState({});
}
componentWillReceiveProps() {
this.setState({});
}
render() {
return <div>{this.props.children}</div>;
}
}
ReactNoop.render(<Parent><NotCascading /></Parent>);
addComment('Should not print a warning');
ReactNoop.flush();
ReactNoop.render(<Parent><NotCascading /></Parent>);
addComment('Should not print a warning');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('captures all lifecycles', () => {
class AllLifecycles extends React.Component {
static childContextTypes = {
foo: PropTypes.any,
};
shouldComponentUpdate() {
return true;
}
getChildContext() {
return {foo: 42};
}
componentWillMount() {}
componentDidMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
componentDidUpdate() {}
componentWillUnmount() {}
render() {
return <div />;
}
}
ReactNoop.render(<AllLifecycles />);
addComment('Mount');
ReactNoop.flush();
ReactNoop.render(<AllLifecycles />);
addComment('Update');
ReactNoop.flush();
ReactNoop.render(null);
addComment('Unmount');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('measures deprioritized work', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.render(
<Parent>
<div hidden={true}>
<Child />
</div>
</Parent>,
);
});
addComment('Flush the parent');
ReactNoop.flushAnimationPri();
addComment('Flush the child');
ReactNoop.flushDeferredPri();
expect(getFlameChart()).toMatchSnapshot();
});
it('measures deferred work in chunks', () => {
class A extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class B extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
ReactNoop.render(
<Parent>
<A>
<Child />
</A>
<B>
<Child />
</B>
</Parent>,
);
addComment('Start mounting Parent and A');
ReactNoop.flushDeferredPri(40);
addComment('Mount B just a little (but not enough to memoize)');
ReactNoop.flushDeferredPri(10);
addComment('Complete B and Parent');
ReactNoop.flushDeferredPri();
expect(getFlameChart()).toMatchSnapshot();
});
it('recovers from fatal errors', () => {
function Baddie() {
throw new Error('Game over');
}
ReactNoop.render(<Parent><Baddie /></Parent>);
try {
addComment('Will fatal');
ReactNoop.flush();
} catch (err) {
expect(err.message).toBe('Game over');
}
ReactNoop.render(<Parent><Child /></Parent>);
addComment('Will reconcile from a clean state');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('recovers from caught errors', () => {
function Baddie() {
throw new Error('Game over');
}
function ErrorReport() {
return <div />;
}
class Boundary extends React.Component {
state = {error: null};
unstable_handleError(error) {
this.setState({error});
}
render() {
if (this.state.error) {
return <ErrorReport />;
}
return this.props.children;
}
}
ReactNoop.render(
<Parent>
<Boundary>
<Parent>
<Baddie />
</Parent>
</Boundary>
</Parent>,
);
addComment('Stop on Baddie and restart from Boundary');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('deduplicates lifecycle names during commit to reduce overhead', () => {
class A extends React.Component {
componentDidUpdate() {}
render() {
return <div />;
}
}
class B extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.cascade && !prevProps.cascade) {
this.setState({});
}
}
render() {
return <div />;
}
}
ReactNoop.render(
<Parent>
<A />
<B />
<A />
<B />
</Parent>,
);
ReactNoop.flush();
resetFlamechart();
ReactNoop.render(
<Parent>
<A />
<B />
<A />
<B />
</Parent>,
);
addComment('The commit phase should mention A and B just once');
ReactNoop.flush();
ReactNoop.render(
<Parent>
<A />
<B />
<A />
<B cascade={true} />
</Parent>,
);
addComment("Because of deduplication, we don't know B was cascading,");
addComment('but we should still see the warning for the commit phase.');
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('supports coroutines', () => {
function Continuation({isSame}) {
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
}
function CoChild({bar}) {
return ReactCoroutine.createYield({
props: {
bar: bar,
},
continuation: Continuation,
});
}
function Indirection() {
return [<CoChild key="a" bar={true} />, <CoChild key="b" bar={false} />];
}
function HandleYields(props, yields) {
return yields.map((y, i) => (
<y.continuation key={i} isSame={props.foo === y.props.bar} />
));
}
function CoParent(props) {
return ReactCoroutine.createCoroutine(
props.children,
HandleYields,
props,
);
}
function App() {
return <div><CoParent foo={true}><Indirection /></CoParent></div>;
}
ReactNoop.render(<App />);
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('supports portals', () => {
const noopContainer = {children: []};
ReactNoop.render(
<Parent>
{ReactPortal.createPortal(<Child />, noopContainer, null)}
</Parent>,
);
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
});

View File

@ -1,288 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactFeatureFlags;
describe('ReactIncrementalReflection', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
it('handles isMounted even when the initial render is deferred', () => {
let ops = [];
const instances = [];
class Component extends React.Component {
_isMounted() {
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
return this.updater.isMounted(this);
}
componentWillMount() {
instances.push(this);
ops.push('componentWillMount', this._isMounted());
}
componentDidMount() {
ops.push('componentDidMount', this._isMounted());
}
render() {
return <span />;
}
}
function Foo() {
return <Component />;
}
ReactNoop.render(<Foo />);
// Render part way through but don't yet commit the updates.
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['componentWillMount', false]);
expect(instances[0]._isMounted()).toBe(false);
ops = [];
// Render the rest and commit the updates.
ReactNoop.flush();
expect(ops).toEqual(['componentDidMount', true]);
expect(instances[0]._isMounted()).toBe(true);
});
it('handles isMounted when an unmount is deferred', () => {
let ops = [];
const instances = [];
class Component extends React.Component {
_isMounted() {
return this.updater.isMounted(this);
}
componentWillMount() {
instances.push(this);
}
componentWillUnmount() {
ops.push('componentWillUnmount', this._isMounted());
}
render() {
ops.push('Component');
return <span />;
}
}
function Other() {
ops.push('Other');
return <span />;
}
function Foo(props) {
return props.mount ? <Component /> : <Other />;
}
ReactNoop.render(<Foo mount={true} />);
ReactNoop.flush();
expect(ops).toEqual(['Component']);
ops = [];
expect(instances[0]._isMounted()).toBe(true);
ReactNoop.render(<Foo mount={false} />);
// Render part way through but don't yet commit the updates so it is not
// fully unmounted yet.
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['Other']);
ops = [];
expect(instances[0]._isMounted()).toBe(true);
// Finish flushing the unmount.
ReactNoop.flush();
expect(ops).toEqual(['componentWillUnmount', true]);
expect(instances[0]._isMounted()).toBe(false);
});
it('finds no node before insertion and correct node before deletion', () => {
let ops = [];
let classInstance = null;
class Component extends React.Component {
componentWillMount() {
classInstance = this;
ops.push('componentWillMount', ReactNoop.findInstance(this));
}
componentDidMount() {
ops.push('componentDidMount', ReactNoop.findInstance(this));
}
componentWillUpdate() {
ops.push('componentWillUpdate', ReactNoop.findInstance(this));
}
componentDidUpdate() {
ops.push('componentDidUpdate', ReactNoop.findInstance(this));
}
componentWillUnmount() {
ops.push('componentWillUnmount', ReactNoop.findInstance(this));
}
render() {
ops.push('render');
return this.props.step < 2
? <span ref={ref => (this.span = ref)} />
: this.props.step === 2
? <div ref={ref => (this.div = ref)} />
: this.props.step === 3
? null
: this.props.step === 4
? <div ref={ref => (this.span = ref)} />
: null;
}
}
function Sibling() {
// Sibling is used to assert that we've rendered past the first component.
ops.push('render sibling');
return <span />;
}
function Foo(props) {
return [<Component key="a" step={props.step} />, <Sibling key="b" />];
}
ReactNoop.render(<Foo step={0} />);
// Flush past Component but don't complete rendering everything yet.
ReactNoop.flushDeferredPri(30);
expect(ops).toEqual([
'componentWillMount',
null,
'render',
'render sibling',
]);
ops = [];
expect(classInstance).toBeDefined();
// The instance has been complete but is still not committed so it should
// not find any host nodes in it.
expect(ReactNoop.findInstance(classInstance)).toBe(null);
ReactNoop.flush();
const hostSpan = classInstance.span;
expect(hostSpan).toBeDefined();
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
expect(ops).toEqual(['componentDidMount', hostSpan]);
ops = [];
// Flush next step which will cause an update but not yet render a new host
// node.
ReactNoop.render(<Foo step={1} />);
ReactNoop.flush();
expect(ops).toEqual([
'componentWillUpdate',
hostSpan,
'render',
'render sibling',
'componentDidUpdate',
hostSpan,
]);
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
ops = [];
// The next step will render a new host node but won't get committed yet.
// We expect this to mutate the original Fiber.
ReactNoop.render(<Foo step={2} />);
ReactNoop.flushDeferredPri(30);
expect(ops).toEqual([
'componentWillUpdate',
hostSpan,
'render',
'render sibling',
]);
ops = [];
// This should still be the host span.
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
// When we finally flush the tree it will get committed.
ReactNoop.flush();
const hostDiv = classInstance.div;
expect(hostDiv).toBeDefined();
expect(hostSpan).not.toBe(hostDiv);
expect(ops).toEqual(['componentDidUpdate', hostDiv]);
ops = [];
// We should now find the new host node.
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
// Render to null but don't commit it yet.
ReactNoop.render(<Foo step={3} />);
ReactNoop.flushDeferredPri(25);
expect(ops).toEqual([
'componentWillUpdate',
hostDiv,
'render',
'render sibling',
]);
ops = [];
// This should still be the host div since the deletion is not committed.
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
ReactNoop.flush();
expect(ops).toEqual(['componentDidUpdate', null]);
// This should still be the host div since the deletion is not committed.
expect(ReactNoop.findInstance(classInstance)).toBe(null);
// Render a div again
ReactNoop.render(<Foo step={4} />);
ReactNoop.flush();
ops = [];
// Unmount the component.
ReactNoop.render([]);
ReactNoop.flush();
expect(ops).toEqual(['componentWillUnmount', hostDiv]);
});
});

View File

@ -1,409 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactFeatureFlags;
describe('ReactIncrementalScheduling', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
function span(prop) {
return {type: 'span', children: [], prop};
}
it('schedules and flushes deferred work', () => {
ReactNoop.render(<span prop="1" />);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
it('schedules and flushes animation work', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="1" />);
});
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
it('searches for work on other roots once the current root completes', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.flush();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
});
it('schedules an animation callback when there`\s leftover animation work', () => {
class Foo extends React.Component {
state = {step: 0};
componentDidMount() {
ReactNoop.performAnimationWork(() => {
this.setState({step: 2});
});
this.setState({step: 1});
}
render() {
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
// Flush just enough work to mount the component, but not enough to flush
// the animation update.
ReactNoop.flushDeferredPri(25);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
// There's more animation work. A callback should have been scheduled.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
it('schedules top-level updates in order of priority', () => {
// Initial render.
ReactNoop.render(<span prop={1} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.render(<span prop={5} />);
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop={2} />);
ReactNoop.render(<span prop={3} />);
ReactNoop.render(<span prop={4} />);
});
// The low pri update should be flushed last, even though it was scheduled
// before the animation updates.
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
it('schedules top-level updates with same priority in order of insertion', () => {
// Initial render.
ReactNoop.render(<span prop={1} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.render(<span prop={2} />);
ReactNoop.render(<span prop={3} />);
ReactNoop.render(<span prop={4} />);
ReactNoop.render(<span prop={5} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
it('works on deferred roots in the order they were scheduled', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.flush();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
// Schedule deferred work in the reverse order
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
// Ensure it starts in the order it was scheduled
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
// Schedule last bit of work, it will get processed the last
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
// Keep performing work in the order it was scheduled
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('schedules sync updates when inside componentDidMount/Update', () => {
var instance;
var ops = [];
class Foo extends React.Component {
state = {tick: 0};
componentDidMount() {
ops.push('componentDidMount (before setState): ' + this.state.tick);
this.setState({tick: 1});
// We're in a batch. Update hasn't flushed yet.
ops.push('componentDidMount (after setState): ' + this.state.tick);
}
componentDidUpdate() {
ops.push('componentDidUpdate: ' + this.state.tick);
if (this.state.tick === 2) {
ops.push('componentDidUpdate (before setState): ' + this.state.tick);
this.setState({tick: 3});
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
// We're in a batch. Update hasn't flushed yet.
}
}
render() {
ops.push('render: ' + this.state.tick);
instance = this;
return <span prop={this.state.tick} />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 0',
'componentDidMount (before setState): 0',
'componentDidMount (after setState): 0',
// If the setState inside componentDidMount were deferred, there would be
// no more ops. Because it has Task priority, we get these ops, too:
'render: 1',
'componentDidUpdate: 1',
]);
ops = [];
instance.setState({tick: 2});
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 2',
'componentDidUpdate: 2',
'componentDidUpdate (before setState): 2',
'componentDidUpdate (after setState): 2',
// If the setState inside componentDidUpdate were deferred, there would be
// no more ops. Because it has Task priority, we get these ops, too:
'render: 3',
'componentDidUpdate: 3',
]);
});
it('can opt-in to deferred/animation scheduling inside componentDidMount/Update', () => {
var instance;
var ops = [];
class Foo extends React.Component {
state = {tick: 0};
componentDidMount() {
ReactNoop.performAnimationWork(() => {
ops.push('componentDidMount (before setState): ' + this.state.tick);
this.setState({tick: 1});
ops.push('componentDidMount (after setState): ' + this.state.tick);
});
}
componentDidUpdate() {
ReactNoop.performAnimationWork(() => {
ops.push('componentDidUpdate: ' + this.state.tick);
if (this.state.tick === 2) {
ops.push(
'componentDidUpdate (before setState): ' + this.state.tick,
);
this.setState({tick: 3});
ops.push('componentDidUpdate (after setState): ' + this.state.tick);
}
});
}
render() {
ops.push('render: ' + this.state.tick);
instance = this;
return <span prop={this.state.tick} />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 0',
'componentDidMount (before setState): 0',
'componentDidMount (after setState): 0',
// Following items shouldn't appear because they are the result of an
// update scheduled with animation priority
// 'render: 1',
// 'componentDidUpdate: 1',
]);
ops = [];
ReactNoop.flushAnimationPri();
expect(ops).toEqual(['render: 1', 'componentDidUpdate: 1']);
ops = [];
instance.setState({tick: 2});
ReactNoop.flushDeferredPri(20 + 5);
expect(ops).toEqual([
'render: 2',
'componentDidUpdate: 2',
'componentDidUpdate (before setState): 2',
'componentDidUpdate (after setState): 2',
// Following items shouldn't appear because they are the result of an
// update scheduled with animation priority
// 'render: 3',
// 'componentDidUpdate: 3',
]);
ops = [];
ReactNoop.flushAnimationPri();
expect(ops).toEqual(['render: 3', 'componentDidUpdate: 3']);
});
it('performs Task work even after time runs out', () => {
class Foo extends React.Component {
state = {step: 1};
componentDidMount() {
this.setState({step: 2}, () => {
this.setState({step: 3}, () => {
this.setState({step: 4}, () => {
this.setState({step: 5});
});
});
});
}
render() {
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
// This should be just enough to complete all the work, but not enough to
// commit it.
ReactNoop.flushDeferredPri(20);
expect(ReactNoop.getChildren()).toEqual([]);
// Do one more unit of work.
ReactNoop.flushDeferredPri(10);
// The updates should all be flushed with Task priority
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
it('does not perform animation work after time runs out', () => {
class Foo extends React.Component {
state = {step: 1};
componentDidMount() {
ReactNoop.performAnimationWork(() => {
this.setState({step: 2}, () => {
this.setState({step: 3}, () => {
this.setState({step: 4}, () => {
this.setState({step: 5});
});
});
});
});
}
render() {
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
// This should be just enough to complete all the work, but not enough to
// commit it.
ReactNoop.flushDeferredPri(20);
expect(ReactNoop.getChildren()).toEqual([]);
// Do one more unit of work.
ReactNoop.flushDeferredPri(10);
// None of the updates should be flushed because they only have
// animation priority.
expect(ReactNoop.getChildren()).toEqual([span(1)]);
});
it('can opt-out of batching using unbatchedUpdates', () => {
// syncUpdates gives synchronous priority to updates
ReactNoop.syncUpdates(() => {
// batchedUpdates downgrades sync updates to task priority
ReactNoop.batchedUpdates(() => {
ReactNoop.render(<span prop={0} />);
expect(ReactNoop.getChildren()).toEqual([]);
// Should not have flushed yet because we're still batching
// unbatchedUpdates reverses the effect of batchedUpdates, so sync
// updates are not batched
ReactNoop.unbatchedUpdates(() => {
ReactNoop.render(<span prop={1} />);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.render(<span prop={2} />);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
ReactNoop.render(<span prop={3} />);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
// Remaining update is now flushed
expect(ReactNoop.getChildren()).toEqual([span(3)]);
});
});
it('nested updates are always deferred, even inside unbatchedUpdates', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {step: 0};
componentDidUpdate() {
ops.push('componentDidUpdate: ' + this.state.step);
if (this.state.step === 1) {
ReactNoop.unbatchedUpdates(() => {
// This is a nested state update, so it should not be
// flushed synchronously, even though we wrapped it
// in unbatchedUpdates.
this.setState({step: 2});
});
expect(ReactNoop.getChildren()).toEqual([span(1)]);
}
}
render() {
ops.push('render: ' + this.state.step);
instance = this;
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(0)]);
ReactNoop.syncUpdates(() => {
instance.setState({step: 1});
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
expect(ops).toEqual([
'render: 0',
'render: 1',
'componentDidUpdate: 1',
'render: 2',
'componentDidUpdate: 2',
]);
});
});

View File

@ -1,376 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactFeatureFlags;
describe('ReactIncrementalUpdates', () => {
beforeEach(() => {
jest.resetModuleRegistry();
React = require('react');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
it('applies updates in order of priority', () => {
let state;
class Foo extends React.Component {
state = {};
componentDidMount() {
ReactNoop.performAnimationWork(() => {
// Has Animation priority
this.setState({b: 'b'});
this.setState({c: 'c'});
});
// Has Task priority
this.setState({a: 'a'});
}
render() {
state = this.state;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flushDeferredPri(25);
expect(state).toEqual({a: 'a'});
ReactNoop.flush();
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
});
it('applies updates with equal priority in insertion order', () => {
let state;
class Foo extends React.Component {
state = {};
componentDidMount() {
// All have Task priority
this.setState({a: 'a'});
this.setState({b: 'b'});
this.setState({c: 'c'});
}
render() {
state = this.state;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
});
it('only drops updates with equal or lesser priority when replaceState is called', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentDidMount() {
ops.push('componentDidMount');
}
componentDidUpdate() {
ops.push('componentDidUpdate');
}
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState({x: 'x'});
instance.setState({y: 'y'});
ReactNoop.performAnimationWork(() => {
instance.setState({a: 'a'});
instance.setState({b: 'b'});
});
instance.updater.enqueueReplaceState(instance, {c: 'c'});
instance.setState({d: 'd'});
ReactNoop.flushAnimationPri();
// Even though a replaceState has been already scheduled, it hasn't been
// flushed yet because it has low priority.
expect(instance.state).toEqual({a: 'a', b: 'b'});
expect(ops).toEqual([
'render',
'componentDidMount',
'render',
'componentDidUpdate',
]);
ops = [];
ReactNoop.flush();
// Now the rest of the updates are flushed.
expect(instance.state).toEqual({c: 'c', d: 'd'});
expect(ops).toEqual(['render', 'componentDidUpdate']);
});
it('can abort an update, schedule additional updates, and resume', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentDidUpdate() {
ops.push('componentDidUpdate');
}
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
let progressedUpdates = [];
function createUpdate(letter) {
return () => {
progressedUpdates.push(letter);
return {
[letter]: letter,
};
};
}
instance.setState(createUpdate('a'));
instance.setState(createUpdate('b'));
instance.setState(createUpdate('c'));
// Do just enough work to begin the update but not enough to flush it
ReactNoop.flushDeferredPri(15);
// expect(ReactNoop.getChildren()).toEqual([span('')]);
expect(ops).toEqual(['render']);
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c'});
ops = [];
progressedUpdates = [];
instance.setState(createUpdate('f'));
ReactNoop.performAnimationWork(() => {
instance.setState(createUpdate('d'));
instance.setState(createUpdate('e'));
});
instance.setState(createUpdate('g'));
ReactNoop.flushAnimationPri();
expect(ops).toEqual([
// Flushes animation work (d and e)
'render',
'componentDidUpdate',
]);
ops = [];
ReactNoop.flush();
expect(ops).toEqual([
// Flushes deferred work (f and g)
'render',
'componentDidUpdate',
]);
expect(progressedUpdates).toEqual(['d', 'e', 'a', 'b', 'c', 'f', 'g']);
expect(instance.state).toEqual({
a: 'a',
b: 'b',
c: 'c',
d: 'd',
e: 'e',
f: 'f',
g: 'g',
});
});
it('can abort an update, schedule a replaceState, and resume', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentDidUpdate() {
ops.push('componentDidUpdate');
}
render() {
ops.push('render');
instance = this;
return <span />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
let progressedUpdates = [];
function createUpdate(letter) {
return () => {
progressedUpdates.push(letter);
return {
[letter]: letter,
};
};
}
instance.setState(createUpdate('a'));
instance.setState(createUpdate('b'));
instance.setState(createUpdate('c'));
// Do just enough work to begin the update but not enough to flush it
ReactNoop.flushDeferredPri(20);
expect(ops).toEqual(['render']);
expect(progressedUpdates).toEqual(['a', 'b', 'c']);
expect(instance.state).toEqual({a: 'a', b: 'b', c: 'c'});
ops = [];
progressedUpdates = [];
instance.setState(createUpdate('f'));
ReactNoop.performAnimationWork(() => {
instance.setState(createUpdate('d'));
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
instance.updater.enqueueReplaceState(instance, createUpdate('e'));
});
instance.setState(createUpdate('g'));
ReactNoop.flush();
expect(progressedUpdates).toEqual(['d', 'e', 'f', 'g']);
expect(instance.state).toEqual({e: 'e', f: 'f', g: 'g'});
});
it('passes accumulation of previous updates to replaceState updater function', () => {
let instance;
class Foo extends React.Component {
state = {};
render() {
instance = this;
return <span />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState({a: 'a'});
instance.setState({b: 'b'});
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
instance.updater.enqueueReplaceState(instance, previousState => ({
previousState,
}));
ReactNoop.flush();
expect(instance.state).toEqual({previousState: {a: 'a', b: 'b'}});
});
it('does not call callbacks that are scheduled by another callback until a later commit', () => {
let ops = [];
class Foo extends React.Component {
state = {};
componentDidMount() {
ops.push('did mount');
this.setState({a: 'a'}, () => {
ops.push('callback a');
this.setState({b: 'b'}, () => {
ops.push('callback b');
});
});
}
render() {
ops.push('render');
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ops).toEqual([
'render',
'did mount',
'render',
'callback a',
'render',
'callback b',
]);
});
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
componentWillReceiveProps() {
ops.push('componentWillReceiveProps');
this.setState({b: 'b'});
}
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
ops = [];
ReactNoop.performAnimationWork(() => {
instance.setState({a: 'a'});
ReactNoop.render(<Foo />); // Trigger componentWillReceiveProps
});
ReactNoop.flush();
expect(instance.state).toEqual({a: 'a', b: 'b'});
expect(ops).toEqual(['componentWillReceiveProps', 'render']);
});
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
spyOn(console, 'error');
let instance;
let ops = [];
class Foo extends React.Component {
state = {};
render() {
ops.push('render');
instance = this;
return <div />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
instance.setState(function a() {
ops.push('setState updater');
this.setState({b: 'b'});
return {a: 'a'};
});
ReactNoop.flush();
expect(ops).toEqual([
// Initial render
'render',
'setState updater',
// Update b is enqueued with the same priority as update a, so it should
// be flushed in the same commit.
'render',
]);
expect(instance.state).toEqual({a: 'a', b: 'b'});
expectDev(console.error.calls.count()).toBe(1);
console.error.calls.reset();
});
});

View File

@ -1,163 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactFeatureFlags;
// This is a new feature in Fiber so I put it in its own test file. It could
// probably move to one of the other test files once it is official.
describe('ReactTopLevelFragment', function() {
beforeEach(function() {
jest.resetModules();
React = require('react');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
it('should render a simple fragment at the top of a component', function() {
function Fragment() {
return [<div key="a">Hello</div>, <div key="b">World</div>];
}
ReactNoop.render(<Fragment />);
ReactNoop.flush();
});
it('should preserve state when switching from a single child', function() {
var instance = null;
class Stateful extends React.Component {
render() {
instance = this;
return <div>Hello</div>;
}
}
function Fragment({condition}) {
return condition
? <Stateful key="a" />
: [<Stateful key="a" />, <div key="b">World</div>];
}
ReactNoop.render(<Fragment />);
ReactNoop.flush();
var instanceA = instance;
expect(instanceA).not.toBe(null);
ReactNoop.render(<Fragment condition={true} />);
ReactNoop.flush();
var instanceB = instance;
expect(instanceB).toBe(instanceA);
});
it('should not preserve state when switching to a nested array', function() {
var instance = null;
class Stateful extends React.Component {
render() {
instance = this;
return <div>Hello</div>;
}
}
function Fragment({condition}) {
return condition
? <Stateful key="a" />
: [[<Stateful key="a" />, <div key="b">World</div>], <div key="c" />];
}
ReactNoop.render(<Fragment />);
ReactNoop.flush();
var instanceA = instance;
expect(instanceA).not.toBe(null);
ReactNoop.render(<Fragment condition={true} />);
ReactNoop.flush();
var instanceB = instance;
expect(instanceB).not.toBe(instanceA);
});
it('preserves state if an implicit key slot switches from/to null', function() {
var instance = null;
class Stateful extends React.Component {
render() {
instance = this;
return <div>World</div>;
}
}
function Fragment({condition}) {
return condition
? [null, <Stateful key="a" />]
: [<div key="b">Hello</div>, <Stateful key="a" />];
}
ReactNoop.render(<Fragment />);
ReactNoop.flush();
var instanceA = instance;
expect(instanceA).not.toBe(null);
ReactNoop.render(<Fragment condition={true} />);
ReactNoop.flush();
var instanceB = instance;
expect(instanceB).toBe(instanceA);
ReactNoop.render(<Fragment condition={false} />);
ReactNoop.flush();
var instanceC = instance;
expect(instanceC === instanceA).toBe(true);
});
it('should preserve state in a reorder', function() {
var instance = null;
class Stateful extends React.Component {
render() {
instance = this;
return <div>Hello</div>;
}
}
function Fragment({condition}) {
return condition
? [[<div key="b">World</div>, <Stateful key="a" />]]
: [[<Stateful key="a" />, <div key="b">World</div>], <div key="c" />];
}
ReactNoop.render(<Fragment />);
ReactNoop.flush();
var instanceA = instance;
expect(instanceA).not.toBe(null);
ReactNoop.render(<Fragment condition={true} />);
ReactNoop.flush();
var instanceB = instance;
expect(instanceB).toBe(instanceA);
});
});

View File

@ -1,42 +0,0 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
var React;
var ReactNoop;
var ReactFeatureFlags;
// This is a new feature in Fiber so I put it in its own test file. It could
// probably move to one of the other test files once it is official.
describe('ReactTopLevelText', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('ReactNoop');
ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false;
});
it('should render a component returning strings directly from render', () => {
const Text = ({value}) => value;
ReactNoop.render(<Text value="foo" />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([{text: 'foo'}]);
});
it('should render a component returning numbers directly from render', () => {
const Text = ({value}) => value;
ReactNoop.render(<Text value={10} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([{text: '10'}]);
});
});

View File

@ -1,260 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
"// Mount
⚛ (React Tree Reconciliation)
⚛ AllLifecycles [mount]
⚛ AllLifecycles.componentWillMount
⚛ AllLifecycles.getChildContext
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ AllLifecycles.componentDidMount
// Update
⚛ (React Tree Reconciliation)
⚛ AllLifecycles [update]
⚛ AllLifecycles.componentWillReceiveProps
⚛ AllLifecycles.shouldComponentUpdate
⚛ AllLifecycles.componentWillUpdate
⚛ AllLifecycles.getChildContext
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
⚛ AllLifecycles.componentDidUpdate
// Unmount
⚛ (React Tree Reconciliation)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ AllLifecycles.componentWillUnmount
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
"// The commit phase should mention A and B just once
⚛ (React Tree Reconciliation)
⚛ Parent [update]
⚛ A [update]
⚛ B [update]
⚛ A [update]
⚛ B [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
⚛ A.componentDidUpdate
⚛ B.componentDidUpdate
// Because of deduplication, we don't know B was cascading,
// but we should still see the warning for the commit phase.
⛔ (React Tree Reconciliation) Warning: There were cascading updates
⚛ Parent [update]
⚛ A [update]
⚛ B [update]
⚛ A [update]
⚛ B [update]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 9 Total)
⚛ (Calling Lifecycle Methods: 9 Total)
⚛ A.componentDidUpdate
⚛ B.componentDidUpdate
⚛ B [update]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 3 Total)
⚛ B.componentDidUpdate
"
`;
exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
"// Should not print a warning
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ NotCascading [mount]
⚛ NotCascading.componentWillMount
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Should not print a warning
⚛ (React Tree Reconciliation)
⚛ Parent [update]
⚛ NotCascading [update]
⚛ NotCascading.componentWillReceiveProps
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
"// Mount
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Update
⚛ (React Tree Reconciliation)
⚛ Parent [update]
⚛ Child [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
// Unmount
⚛ (React Tree Reconciliation)
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
"// Start mounting Parent and A
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ A [mount]
⚛ Child [mount]
// Mount B just a little (but not enough to memoize)
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ B [mount]
// Complete B and Parent
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ B [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
"// Flush the parent
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
// Flush the child
⚛ (React Tree Reconciliation)
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
"// Stop on Baddie and restart from Boundary
⛔ (React Tree Reconciliation) Warning: There were cascading updates
⚛ Parent [mount]
⚛ Boundary [mount]
⚛ Parent [mount]
⚛ Baddie [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⚛ Boundary [update]
⚛ ErrorReport [mount]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
"
`;
exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
"// Will fatal
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Baddie [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
// Will reconcile from a clean state
⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
"// Should include just A and B, no Parents
⚛ (React Tree Reconciliation)
⚛ A [update]
⚛ B [update]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 6 Total)
⚛ (Calling Lifecycle Methods: 6 Total)
"
`;
exports[`ReactDebugFiberPerf supports coroutines 1`] = `
"⚛ (React Tree Reconciliation)
⚛ App [mount]
⚛ CoParent [mount]
⚛ HandleYields [mount]
⚛ Indirection [mount]
⚛ CoChild [mount]
⚛ CoChild [mount]
⚛ Continuation [mount]
⚛ Continuation [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (React Tree Reconciliation)
⚛ Parent [mount]
⚛ Child [mount]
⚛ (Committing Changes)
⚛ (Committing Host Effects: 3 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
"
`;
exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
"// Should print a warning
⛔ (React Tree Reconciliation) Warning: There were cascading updates
⚛ Parent [mount]
⚛ Cascading [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
⚛ Cascading [update]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 2 Total)
⚛ (Calling Lifecycle Methods: 2 Total)
"
`;
exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
"// Rendering the first root
⛔ (React Tree Reconciliation) Warning: There were cascading updates
⚛ Cascading [mount]
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 1 Total)
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
// Scheduling another root from componentDidMount
⚛ Child [mount]
⛔ (Committing Changes) Warning: Caused by a cascading update in earlier commit
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;

View File

@ -1,110 +0,0 @@
/**
* 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;
var REACT_YIELD_TYPE;
if (typeof Symbol === 'function' && Symbol.for) {
REACT_COROUTINE_TYPE = Symbol.for('react.coroutine');
REACT_YIELD_TYPE = Symbol.for('react.yield');
} else {
REACT_COROUTINE_TYPE = 0xeac8;
REACT_YIELD_TYPE = 0xeac9;
}
type CoroutineHandler<T> = (props: T, yields: Array<mixed>) => ReactNodeList;
export type ReactCoroutine = {
$$typeof: Symbol | number,
key: null | string,
children: any,
// This should be a more specific CoroutineHandler
handler: (props: any, yields: Array<mixed>) => ReactNodeList,
props: any,
};
export type ReactYield = {
$$typeof: Symbol | number,
value: 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(value: mixed): ReactYield {
var yieldNode = {
// This tag allow us to uniquely identify this as a React Yield
$$typeof: REACT_YIELD_TYPE,
value: value,
};
if (__DEV__) {
// TODO: Add _store property for marking this as validated.
if (Object.freeze) {
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;

View File

@ -1,60 +0,0 @@
/**
* 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 ReactPortal
* @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_PORTAL_TYPE =
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.portal')) ||
0xeaca;
export type ReactPortal = {
$$typeof: Symbol | number,
key: null | string,
containerInfo: any,
children: ReactNodeList,
// TODO: figure out the API for cross-renderer implementation.
implementation: any,
};
exports.createPortal = function(
children: ReactNodeList,
containerInfo: any,
// TODO: figure out the API for cross-renderer implementation.
implementation: any,
key: ?string = null,
): ReactPortal {
return {
// This tag allow us to uniquely identify this as a React Portal
$$typeof: REACT_PORTAL_TYPE,
key: key == null ? null : '' + key,
children,
containerInfo,
implementation,
};
};
/**
* Verifies the object is a portal object.
*/
exports.isPortal = function(object: mixed): boolean {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_PORTAL_TYPE
);
};
exports.REACT_PORTAL_TYPE = REACT_PORTAL_TYPE;

View File

@ -1,54 +0,0 @@
/**
* 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});
// Trust the developer to only use this with a __DEV__ check
var ReactHostOperationHistoryHook = ((null: any): typeof ReactHostOperationHistoryHook);
if (__DEV__) {
var history: Array<Operation> = [];
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;

View File

@ -1,42 +0,0 @@
/**
* 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');
var ReactInvalidSetStateWarningHook = {};
if (__DEV__) {
var processingChildContext = false;
var warnInvalidSetState = function() {
warning(
!processingChildContext,
'setState(...): Cannot call setState() inside getChildContext()',
);
};
ReactInvalidSetStateWarningHook = {
onBeginProcessingChildContext(): void {
processingChildContext = true;
},
onEndProcessingChildContext(): void {
processingChildContext = false;
},
onSetState(): void {
warnInvalidSetState();
},
};
}
module.exports = ReactInvalidSetStateWarningHook;

View File

@ -1,45 +0,0 @@
/**
* 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;

View File

@ -1,146 +0,0 @@
/**
* 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 ReactTreeTraversal
*/
'use strict';
var {HostComponent} = require('ReactTypeOfWork');
function getParent(inst) {
if (inst._hostParent !== undefined) {
return inst._hostParent;
}
if (typeof inst.tag === 'number') {
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
} while (inst && inst.tag !== HostComponent);
if (inst) {
return inst;
}
}
return null;
}
/**
* 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 = getParent(tempA)) {
depthA++;
}
var depthB = 0;
for (var tempB = instB; tempB; tempB = getParent(tempB)) {
depthB++;
}
// If A is deeper, crawl up.
while (depthA - depthB > 0) {
instA = getParent(instA);
depthA--;
}
// If B is deeper, crawl up.
while (depthB - depthA > 0) {
instB = getParent(instB);
depthB--;
}
// Walk in lockstep until we find a match.
var depth = depthA;
while (depth--) {
if (instA === instB || instA === instB.alternate) {
return instA;
}
instA = getParent(instA);
instB = getParent(instB);
}
return null;
}
/**
* Return if A is an ancestor of B.
*/
function isAncestor(instA, instB) {
while (instB) {
if (instA === instB || instA === instB.alternate) {
return true;
}
instB = getParent(instB);
}
return false;
}
/**
* Return the parent instance of the passed-in instance.
*/
function getParentInstance(inst) {
return getParent(inst);
}
/**
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
*/
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = getParent(inst);
}
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 = getParent(from);
}
var pathTo = [];
while (to && to !== common) {
pathTo.push(to);
to = getParent(to);
}
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,
};

Some files were not shown because too many files have changed in this diff Show More