Put perf integration behind a feature flag (#11455)

* Enable User Timing API integration with a feature flag

* Expose a way to toggle user timing flag in www

* Update ReactNativeCSFeatureFlags.js

* Update ReactFeatureFlags.js
This commit is contained in:
Dan Abramov
2017-11-06 16:07:08 +00:00
committed by GitHub
parent 0c6630d1c4
commit db77e97a91
10 changed files with 497 additions and 494 deletions
@@ -7,6 +7,8 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
import typeof * as CSFeatureFlagsType from './ReactNativeCSFeatureFlags';
@@ -14,12 +16,18 @@ export const enableAsyncSubtreeAPI = true;
export const enableAsyncSchedulingByDefaultInReactDOM = false;
export const enableReactFragment = false;
export const enableCreateRoot = false;
export const enableUserTimingAPI = __DEV__;
// React Native CS uses persistent reconciler.
export const enableMutatingReconciler = false;
export const enableNoopReconciler = false;
export const enablePersistentReconciler = true;
// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');
}
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y=_X> = null;
+3
View File
@@ -13,6 +13,7 @@ import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import * as ReactFiberErrorLogger
from 'react-reconciler/src/ReactFiberErrorLogger';
import ReactErrorUtils from 'shared/ReactErrorUtils';
import {addUserTimingListener} from 'shared/ReactFeatureFlags';
import ReactDOM from './ReactDOM';
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
@@ -31,6 +32,8 @@ Object.assign(
ReactInstanceMap,
// Used by www msite:
TapEventPlugin,
// Perf experiment
addUserTimingListener,
},
);
+384 -362
View File
@@ -9,6 +9,7 @@
import type {Fiber} from './ReactFiber';
import {enableUserTimingAPI} from 'shared/ReactFeatureFlags';
import getComponentName from 'shared/getComponentName';
import {
HostRoot,
@@ -19,9 +20,6 @@ import {
Fragment,
} from 'shared/ReactTypeOfWork';
// Trust the developer to only use this with a __DEV__ check
let ReactDebugFiberPerf = (({}: any): typeof ReactDebugFiberPerf);
type MeasurementPhase =
| 'componentWillMount'
| 'componentWillUnmount'
@@ -32,382 +30,406 @@ type MeasurementPhase =
| 'componentDidMount'
| 'getChildContext';
if (__DEV__) {
// 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';
// 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();
// 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 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 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 beginMark = (markName: string) => {
performance.mark(formatMarkName(markName));
};
const clearMark = (markName: string) => {
performance.clearMarks(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 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 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 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);
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.
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 ReturnComponent:
case Fragment:
return true;
default:
return false;
}
labelsInCurrentCommit.add(label);
}
};
const markName = getFiberMarkName(label, debugID);
beginMark(markName);
return true;
};
const clearPendingPhaseMeasurement = () => {
if (currentPhase !== null && currentPhaseFiber !== null) {
clearFiberMark(currentPhaseFiber, currentPhase);
}
currentPhaseFiber = null;
currentPhase = null;
hasScheduledUpdateInCurrentPhase = false;
};
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 ReturnComponent:
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);
}
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) {
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);
},
}
fiber = fiber.return;
}
};
stopFailedWorkTimer(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;
const warning = 'An error was thrown inside this error boundary';
endFiberMark(fiber, null, warning);
},
const resumeTimersRecursively = (fiber: Fiber) => {
if (fiber.return !== null) {
resumeTimersRecursively(fiber.return);
}
if (fiber._debugIsCurrentlyTiming) {
beginFiberMark(fiber, null);
}
};
startPhaseTimer(fiber: Fiber, phase: MeasurementPhase): void {
if (!supportsUserTiming) {
return;
}
clearPendingPhaseMeasurement();
if (!beginFiberMark(fiber, phase)) {
return;
}
currentPhaseFiber = fiber;
currentPhase = phase;
},
const resumeTimers = () => {
// Resumes all measurements that were active during the last deferred loop.
if (currentFiber !== null) {
resumeTimersRecursively(currentFiber);
}
};
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,
);
},
};
export function recordEffect(): void {
if (enableUserTimingAPI) {
effectCountInCurrentCommit++;
}
}
// TODO: convert to named exports
// if this doesn't inflate the bundle.
export default ReactDebugFiberPerf;
export function recordScheduleUpdate(): void {
if (enableUserTimingAPI) {
if (isCommitting) {
hasScheduledUpdateInCurrentCommit = true;
}
if (
currentPhase !== null &&
currentPhase !== 'componentWillMount' &&
currentPhase !== 'componentWillReceiveProps'
) {
hasScheduledUpdateInCurrentPhase = true;
}
}
}
export function startWorkTimer(fiber: Fiber): void {
if (enableUserTimingAPI) {
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;
}
}
export function cancelWorkTimer(fiber: Fiber): void {
if (enableUserTimingAPI) {
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);
}
}
export function stopWorkTimer(fiber: Fiber): void {
if (enableUserTimingAPI) {
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);
}
}
export function stopFailedWorkTimer(fiber: Fiber): void {
if (enableUserTimingAPI) {
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;
const warning = 'An error was thrown inside this error boundary';
endFiberMark(fiber, null, warning);
}
}
export function startPhaseTimer(fiber: Fiber, phase: MeasurementPhase): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
clearPendingPhaseMeasurement();
if (!beginFiberMark(fiber, phase)) {
return;
}
currentPhaseFiber = fiber;
currentPhase = phase;
}
}
export function stopPhaseTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
if (currentPhase !== null && currentPhaseFiber !== null) {
const warning = hasScheduledUpdateInCurrentPhase
? 'Scheduled a cascading update'
: null;
endFiberMark(currentPhaseFiber, currentPhase, warning);
}
currentPhase = null;
currentPhaseFiber = null;
}
}
export function startWorkLoopTimer(): void {
if (enableUserTimingAPI) {
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();
}
}
export function stopWorkLoopTimer(): void {
if (enableUserTimingAPI) {
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,
);
}
}
export function startCommitTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
isCommitting = true;
hasScheduledUpdateInCurrentCommit = false;
labelsInCurrentCommit.clear();
beginMark('(Committing Changes)');
}
}
export function stopCommitTimer(): void {
if (enableUserTimingAPI) {
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);
}
}
export function startCommitHostEffectsTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
effectCountInCurrentCommit = 0;
beginMark('(Committing Host Effects)');
}
}
export function stopCommitHostEffectsTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
const count = effectCountInCurrentCommit;
effectCountInCurrentCommit = 0;
endMark(
`(Committing Host Effects: ${count} Total)`,
'(Committing Host Effects)',
null,
);
}
}
export function startCommitLifeCyclesTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
effectCountInCurrentCommit = 0;
beginMark('(Calling Lifecycle Methods)');
}
}
export function stopCommitLifeCyclesTimer(): void {
if (enableUserTimingAPI) {
if (!supportsUserTiming) {
return;
}
const count = effectCountInCurrentCommit;
effectCountInCurrentCommit = 0;
endMark(
`(Calling Lifecycle Methods: ${count} Total)`,
'(Calling Lifecycle Methods)',
null,
);
}
}
+3 -8
View File
@@ -40,7 +40,7 @@ import invariant from 'fbjs/lib/invariant';
import getComponentName from 'shared/getComponentName';
import warning from 'fbjs/lib/warning';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import ReactDebugFiberPerf from './ReactDebugFiberPerf';
import {cancelWorkTimer} from './ReactDebugFiberPerf';
import ReactFiberClassComponent from './ReactFiberClassComponent';
import {
@@ -60,7 +60,6 @@ import {
} from './ReactFiberContext';
import {NoWork, Never} from './ReactFiberExpirationTime';
var {cancelWorkTimer} = ReactDebugFiberPerf;
if (__DEV__) {
var warnedAboutStatelessRefs = {};
}
@@ -666,9 +665,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
current,
workInProgress: Fiber,
): Fiber | null {
if (__DEV__) {
cancelWorkTimer(workInProgress);
}
cancelWorkTimer(workInProgress);
// 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
@@ -689,9 +686,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
}
function bailoutOnLowPriority(current, workInProgress) {
if (__DEV__) {
cancelWorkTimer(workInProgress);
}
cancelWorkTimer(workInProgress);
// TODO: Handle HostComponent tags here as well and call pushHostContext()?
// See PR 8590 discussion for context
+10 -26
View File
@@ -20,7 +20,7 @@ import shallowEqual from 'fbjs/lib/shallowEqual';
import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import ReactDebugFiberPerf from './ReactDebugFiberPerf';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {AsyncUpdates} from './ReactTypeOfInternalContext';
import {
cacheContext,
@@ -34,7 +34,6 @@ import {
} from './ReactFiberUpdateQueue';
import {hasContextChanged} from './ReactFiberContext';
var {startPhaseTimer, stopPhaseTimer} = ReactDebugFiberPerf;
const fakeInternalInstance = {};
const isArray = Array.isArray;
@@ -161,17 +160,13 @@ export default function(
const instance = workInProgress.stateNode;
const type = workInProgress.type;
if (typeof instance.shouldComponentUpdate === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
}
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
newContext,
);
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
if (__DEV__) {
warning(
@@ -365,14 +360,11 @@ export default function(
}
function callComponentWillMount(workInProgress, instance) {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillMount');
}
startPhaseTimer(workInProgress, 'componentWillMount');
const oldState = instance.state;
instance.componentWillMount();
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
if (oldState !== instance.state) {
if (__DEV__) {
@@ -394,14 +386,10 @@ export default function(
newProps,
newContext,
) {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
}
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
if (instance.state !== oldState) {
if (__DEV__) {
@@ -673,13 +661,9 @@ export default function(
if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
if (__DEV__) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
}
startPhaseTimer(workInProgress, 'componentWillUpdate');
instance.componentWillUpdate(newProps, newState, newContext);
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
+14 -27
View File
@@ -29,10 +29,9 @@ import invariant from 'fbjs/lib/invariant';
import {commitCallbacks} from './ReactFiberUpdateQueue';
import {onCommitUnmount} from './ReactFiberDevToolsHook';
import ReactDebugFiberPerf from './ReactDebugFiberPerf';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
var {invokeGuardedCallback, hasCaughtError, clearCaughtError} = ReactErrorUtils;
var {startPhaseTimer, stopPhaseTimer} = ReactDebugFiberPerf;
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
@@ -40,22 +39,20 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
) {
const {getPublicInstance, mutation, persistence} = config;
if (__DEV__) {
var callComponentWillUnmountWithTimerInDev = function(current, instance) {
startPhaseTimer(current, 'componentWillUnmount');
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
instance.componentWillUnmount();
stopPhaseTimer();
};
}
var callComponentWillUnmountWithTimer = function(current, instance) {
startPhaseTimer(current, 'componentWillUnmount');
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
instance.componentWillUnmount();
stopPhaseTimer();
};
// Capture errors so they don't interrupt unmounting.
function safelyCallComponentWillUnmount(current, instance) {
if (__DEV__) {
invokeGuardedCallback(
null,
callComponentWillUnmountWithTimerInDev,
callComponentWillUnmountWithTimer,
null,
current,
instance,
@@ -66,9 +63,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
}
} else {
try {
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
instance.componentWillUnmount();
callComponentWillUnmountWithTimer(current, instance);
} catch (unmountError) {
captureError(current, unmountError);
}
@@ -100,27 +95,19 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidMount');
}
startPhaseTimer(finishedWork, 'componentDidMount');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
} else {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidUpdate');
}
startPhaseTimer(finishedWork, 'componentDidUpdate');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
if (__DEV__) {
stopPhaseTimer();
}
stopPhaseTimer();
}
}
const updateQueue = finishedWork.updateQueue;
+6 -8
View File
@@ -20,9 +20,7 @@ import checkPropTypes from 'prop-types/checkPropTypes';
import {createCursor, pop, push} from './ReactFiberStack';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import ReactDebugFiberPerf from './ReactDebugFiberPerf';
const {startPhaseTimer, stopPhaseTimer} = ReactDebugFiberPerf;
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
if (__DEV__) {
var warnedAboutMissingGetChildContext = {};
@@ -177,12 +175,12 @@ export function processChildContext(
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
}
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase(null);
} else {
childContext = instance.getChildContext();
}
for (let contextKey in childContext) {
invariant(
+34 -63
View File
@@ -46,7 +46,21 @@ import ReactFiberHostContext from './ReactFiberHostContext';
import ReactFiberHydrationContext from './ReactFiberHydrationContext';
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import ReactDebugFiberPerf from './ReactDebugFiberPerf';
import {
recordEffect,
recordScheduleUpdate,
startWorkTimer,
stopWorkTimer,
stopFailedWorkTimer,
startWorkLoopTimer,
stopWorkLoopTimer,
startCommitTimer,
stopCommitTimer,
startCommitHostEffectsTimer,
stopCommitHostEffectsTimer,
startCommitLifeCyclesTimer,
stopCommitLifeCyclesTimer,
} from './ReactDebugFiberPerf';
import {popContextProvider} from './ReactFiberContext';
import {reset} from './ReactFiberStack';
import {logCapturedError} from './ReactFiberErrorLogger';
@@ -64,21 +78,6 @@ import {getUpdateExpirationTime} from './ReactFiberUpdateQueue';
import {resetContext} from './ReactFiberContext';
var {invokeGuardedCallback, hasCaughtError, clearCaughtError} = ReactErrorUtils;
var {
recordEffect,
recordScheduleUpdate,
startWorkTimer,
stopWorkTimer,
stopFailedWorkTimer,
startWorkLoopTimer,
stopWorkLoopTimer,
startCommitTimer,
stopCommitTimer,
startCommitHostEffectsTimer,
stopCommitHostEffectsTimer,
startCommitLifeCyclesTimer,
stopCommitLifeCyclesTimer,
} = ReactDebugFiberPerf;
export type CapturedError = {
componentName: ?string,
@@ -229,8 +228,8 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
while (nextEffect !== null) {
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
recordEffect();
}
recordEffect();
const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
@@ -298,24 +297,18 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
if (__DEV__) {
recordEffect();
}
recordEffect();
const current = nextEffect.alternate;
commitLifeCycles(current, nextEffect);
}
if (effectTag & Ref) {
if (__DEV__) {
recordEffect();
}
recordEffect();
commitAttachRef(nextEffect);
}
if (effectTag & Err) {
if (__DEV__) {
recordEffect();
}
recordEffect();
commitErrorHandling(nextEffect);
}
@@ -338,9 +331,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// captured elsewhere, to prevent the unmount from being interrupted.
isWorking = true;
isCommitting = true;
if (__DEV__) {
startCommitTimer();
}
startCommitTimer();
const root: FiberRoot = finishedWork.stateNode;
invariant(
@@ -377,9 +368,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
if (__DEV__) {
startCommitHostEffectsTimer();
}
startCommitHostEffectsTimer();
while (nextEffect !== null) {
let didError = false;
let error;
@@ -410,9 +399,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
}
}
}
if (__DEV__) {
stopCommitHostEffectsTimer();
}
stopCommitHostEffectsTimer();
resetAfterCommit();
@@ -427,9 +414,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
if (__DEV__) {
startCommitLifeCyclesTimer();
}
startCommitLifeCyclesTimer();
while (nextEffect !== null) {
let didError = false;
let error;
@@ -462,10 +447,8 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
isCommitting = false;
isWorking = false;
if (__DEV__) {
stopCommitLifeCyclesTimer();
stopCommitTimer();
}
stopCommitLifeCyclesTimer();
stopCommitTimer();
if (typeof onCommitRoot === 'function') {
onCommitRoot(finishedWork.stateNode);
}
@@ -551,9 +534,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
resetExpirationTime(workInProgress, nextRenderExpirationTime);
if (next !== null) {
if (__DEV__) {
stopWorkTimer(workInProgress);
}
stopWorkTimer(workInProgress);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
}
@@ -595,9 +576,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
}
}
if (__DEV__) {
stopWorkTimer(workInProgress);
}
stopWorkTimer(workInProgress);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
}
@@ -631,8 +610,8 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
if (__DEV__) {
startWorkTimer(workInProgress);
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
}
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
@@ -661,8 +640,8 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
if (__DEV__) {
startWorkTimer(workInProgress);
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
}
let next = beginFailedWork(
@@ -773,9 +752,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
root: FiberRoot,
expirationTime: ExpirationTime,
): Fiber | null {
if (__DEV__) {
startWorkLoopTimer();
}
startWorkLoopTimer();
invariant(
!isWorking,
@@ -892,9 +869,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
didFatal = false;
firstUncaughtError = null;
if (__DEV__) {
stopWorkLoopTimer();
}
stopWorkLoopTimer();
if (uncaughtError !== null) {
onUncaughtError(uncaughtError);
@@ -1131,11 +1106,9 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
break;
}
if (node === to || node.alternate === to) {
if (__DEV__) {
stopFailedWorkTimer(node);
}
stopFailedWorkTimer(node);
break;
} else if (__DEV__) {
} else {
stopWorkTimer(node);
}
node = node.return;
@@ -1190,9 +1163,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
expirationTime: ExpirationTime,
isErrorRecovery: boolean,
) {
if (__DEV__) {
recordScheduleUpdate();
}
recordScheduleUpdate();
if (__DEV__) {
if (!isErrorRecovery && fiber.tag === ClassComponent) {
+8
View File
@@ -7,12 +7,15 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant';
export const enableAsyncSubtreeAPI = true;
export const enableAsyncSchedulingByDefaultInReactDOM = false;
// Exports React.Fragment
export const enableReactFragment = false;
// Exports ReactDOM.createRoot
export const enableCreateRoot = false;
export const enableUserTimingAPI = __DEV__;
// Mutating mode (React DOM, React ART, React Native):
export const enableMutatingReconciler = true;
@@ -20,3 +23,8 @@ export const enableMutatingReconciler = true;
export const enableNoopReconciler = false;
// Experimental persistent mode (CS):
export const enablePersistentReconciler = false;
// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');
}
@@ -16,11 +16,38 @@ export const {
enableAsyncSchedulingByDefaultInReactDOM,
enableReactFragment,
enableCreateRoot,
// Reconciler flags
enableMutatingReconciler,
enableNoopReconciler,
enablePersistentReconciler,
} = require('ReactFeatureFlags');
export let enableUserTimingAPI = __DEV__;
let refCount = 0;
export function addUserTimingListener() {
if (__DEV__) {
// Noop.
return () => {};
}
refCount++;
updateFlagOutsideOfReactCallStack();
return () => {
refCount--;
updateFlagOutsideOfReactCallStack();
};
}
let timeout = null;
function updateFlagOutsideOfReactCallStack() {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
enableUserTimingAPI = refCount > 0;
});
}
}
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y=_X> = null;