Refactored how React/DevTools log Timeline performance data (#23102)

Until now, DEV and PROFILING builds of React recorded Timeline profiling data using the User Timing API. This commit changes things so that React records this data by calling methods on the DevTools hook. (For now, DevTools still records that data using the User Timing API, to match previous behavior.)

This commit is large but most of it is just moving things around:

* New methods have been added to the DevTools hook (in "backend/profilingHooks") for recording the Timeline performance events.
* Reconciler's "ReactFiberDevToolsHook" has been updated to call these new methods (when they're present).
* User Timing method calls in "SchedulingProfiler" have been moved to DevTools "backend/profilingHooks" (to match previous behavior, for now).
* The old reconciler tests, "SchedulingProfiler-test" and "SchedulingProfilerLabels-test", have been moved into DevTools "TimelineProfiler-test" to ensure behavior didn't change unexpectedly.
* Two new methods have been added to the injected renderer interface: injectProfilingHooks() and getLaneLabelMap().

Relates to #22529.
This commit is contained in:
Brian Vaughn 2022-01-13 14:55:54 -05:00 committed by GitHub
parent c09596cc60
commit 51947a14bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 4208 additions and 3593 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -162,3 +162,7 @@ env.afterEach(() => {
// so that we don't disconnect the ReactCurrentDispatcher ref.
jest.resetModules();
});
expect.extend({
...require('../../../../scripts/jest/matchers/schedulerTestMatchers'),
});

View File

@ -163,6 +163,10 @@ export function getRendererID(): number {
}
export function legacyRender(elements, container) {
if (container == null) {
container = document.createElement('div');
}
const ReactDOM = require('react-dom');
withErrorsOrWarningsIgnored(
['ReactDOM.render is no longer supported in React 18'],
@ -170,6 +174,10 @@ export function legacyRender(elements, container) {
ReactDOM.render(elements, container);
},
);
return () => {
ReactDOM.unmountComponentAtNode(container);
};
}
export function requireTestRenderer(): ReactTestRenderer {

View File

@ -0,0 +1,364 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Lane,
Lanes,
DevToolsProfilingHooks,
} from 'react-devtools-shared/src/backend/types';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {Wakeable} from 'shared/ReactTypes';
import isArray from 'shared/isArray';
import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-timeline/src/constants';
let performanceTarget: Performance | null = null;
// If performance exists and supports the subset of the User Timing API that we require.
let supportsUserTiming =
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function';
let supportsUserTimingV3 = false;
if (supportsUserTiming) {
const CHECK_V3_MARK = '__v3';
const markOptions = {};
// $FlowFixMe: Ignore Flow complaining about needing a value
Object.defineProperty(markOptions, 'startTime', {
get: function() {
supportsUserTimingV3 = true;
return 0;
},
set: function() {},
});
try {
// $FlowFixMe: Flow expects the User Timing level 2 API.
performance.mark(CHECK_V3_MARK, markOptions);
} catch (error) {
// Ignore
} finally {
performance.clearMarks(CHECK_V3_MARK);
}
}
if (supportsUserTimingV3) {
performanceTarget = performance;
}
// Mocking the Performance Object (and User Timing APIs) for testing is fragile.
// This API allows tests to directly override the User Timing APIs.
export function setPerformanceMock_ONLY_FOR_TESTING(
performanceMock: Performance | null,
) {
performanceTarget = performanceMock;
supportsUserTiming = performanceMock !== null;
supportsUserTimingV3 = performanceMock !== null;
}
function markAndClear(markName) {
// This method won't be called unless these functions are defined, so we can skip the extra typeof check.
((performanceTarget: any): Performance).mark(markName);
((performanceTarget: any): Performance).clearMarks(markName);
}
export function createProfilingHooks({
getDisplayNameForFiber,
getLaneLabelMap,
reactVersion,
}: {|
getDisplayNameForFiber: (fiber: Fiber) => string | null,
getLaneLabelMap?: () => Map<Lane, string>,
reactVersion: string,
|}): DevToolsProfilingHooks {
function markMetadata() {
markAndClear(`--react-version-${reactVersion}`);
markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
'function'
) {
// Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
// This check would not be required,
// except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
if (isArray(ranges)) {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (isArray(range) && range.length === 2) {
const [startStackFrame, stopStackFrame] = ranges[i];
markAndClear(`--react-internal-module-start-${startStackFrame}`);
markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
}
}
}
}
if (typeof getLaneLabelMap === 'function') {
const map = getLaneLabelMap();
const labels = Array.from(map.values()).join(',');
markAndClear(`--react-lane-labels-${labels}`);
}
}
function markCommitStarted(lanes: Lanes): void {
if (supportsUserTimingV3) {
markAndClear(`--commit-start-${lanes}`);
// Certain types of metadata should be logged infrequently.
// Normally we would log this during module init,
// but there's no guarantee a user is profiling at that time.
// Commits happen infrequently (less than renders or state updates)
// so we log this extra information along with a commit.
// It will likely be logged more than once but that's okay.
//
// TODO (timeline) Only log this once, when profiling starts.
// For the first phase refactoring we'll match the previous behavior.
markMetadata();
}
}
function markCommitStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--commit-stop');
}
}
function markComponentRenderStarted(fiber: Fiber): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--component-render-start-${componentName}`);
}
}
function markComponentRenderStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--component-render-stop');
}
}
function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--component-passive-effect-mount-start-${componentName}`);
}
}
function markComponentPassiveEffectMountStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-mount-stop');
}
}
function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
}
}
function markComponentPassiveEffectUnmountStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-unmount-stop');
}
}
function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--component-layout-effect-mount-start-${componentName}`);
}
}
function markComponentLayoutEffectMountStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-mount-stop');
}
}
function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
}
}
function markComponentLayoutEffectUnmountStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-unmount-stop');
}
}
function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';
let message = '';
if (
thrownValue !== null &&
typeof thrownValue === 'object' &&
typeof thrownValue.message === 'string'
) {
message = thrownValue.message;
} else if (typeof thrownValue === 'string') {
message = thrownValue;
}
// TODO (timeline) Record and cache component stack
markAndClear(`--error-${componentName}-${phase}-${message}`);
}
}
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
let wakeableID: number = 0;
function getWakeableID(wakeable: Wakeable): number {
if (!wakeableIDs.has(wakeable)) {
wakeableIDs.set(wakeable, wakeableID++);
}
return ((wakeableIDs.get(wakeable): any): number);
}
function markComponentSuspended(
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
): void {
if (supportsUserTimingV3) {
const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
const id = getWakeableID(wakeable);
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';
// Following the non-standard fn.displayName convention,
// frameworks like Relay may also annotate Promises with a displayName,
// describing what operation/data the thrown Promise is related to.
// When this is available we should pass it along to the Timeline.
const displayName = (wakeable: any).displayName || '';
// TODO (timeline) Record and cache component stack
markAndClear(
`--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
);
wakeable.then(
() => markAndClear(`--suspense-resolved-${id}-${componentName}`),
() => markAndClear(`--suspense-rejected-${id}-${componentName}`),
);
}
}
function markLayoutEffectsStarted(lanes: Lanes): void {
if (supportsUserTimingV3) {
markAndClear(`--layout-effects-start-${lanes}`);
}
}
function markLayoutEffectsStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--layout-effects-stop');
}
}
function markPassiveEffectsStarted(lanes: Lanes): void {
if (supportsUserTimingV3) {
markAndClear(`--passive-effects-start-${lanes}`);
}
}
function markPassiveEffectsStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--passive-effects-stop');
}
}
function markRenderStarted(lanes: Lanes): void {
if (supportsUserTimingV3) {
markAndClear(`--render-start-${lanes}`);
}
}
function markRenderYielded(): void {
if (supportsUserTimingV3) {
markAndClear('--render-yield');
}
}
function markRenderStopped(): void {
if (supportsUserTimingV3) {
markAndClear('--render-stop');
}
}
function markRenderScheduled(lane: Lane): void {
if (supportsUserTimingV3) {
markAndClear(`--schedule-render-${lane}`);
}
}
function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
}
}
function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
// TODO (timeline) Record and cache component stack
markAndClear(`--schedule-state-update-${lane}-${componentName}`);
}
}
return {
markCommitStarted,
markCommitStopped,
markComponentRenderStarted,
markComponentRenderStopped,
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
markComponentPassiveEffectUnmountStopped,
markComponentLayoutEffectMountStarted,
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
markComponentErrored,
markComponentSuspended,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
markRenderScheduled,
markForceUpdateScheduled,
markStateUpdateScheduled,
};
}

View File

@ -92,6 +92,7 @@ import is from 'shared/objectIs';
import isArray from 'shared/isArray';
import hasOwnProperty from 'shared/hasOwnProperty';
import {getStyleXData} from './StyleX/utils';
import {createProfilingHooks} from './profilingHooks';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
@ -590,6 +591,8 @@ export function attach(
} = ReactPriorityLevels;
const {
getLaneLabelMap,
injectProfilingHooks,
overrideHookState,
overrideHookStateDeletePath,
overrideHookStateRenamePath,
@ -624,6 +627,16 @@ export function attach(
};
}
if (typeof injectProfilingHooks === 'function') {
injectProfilingHooks(
createProfilingHooks({
getDisplayNameForFiber,
getLaneLabelMap,
reactVersion: version,
}),
);
}
// Tracks Fibers with recently changed number of error/warning messages.
// These collections store the Fiber rather than the ID,
// in order to avoid generating an ID for Fibers that never get mounted

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {ReactContext} from 'shared/ReactTypes';
import type {ReactContext, Wakeable} from 'shared/ReactTypes';
import type {Source} from 'shared/ReactElementType';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
@ -87,6 +87,9 @@ export type ReactProviderType<T> = {
...
};
export type Lane = number;
export type Lanes = number;
export type ReactRenderer = {
findFiberByHostInstance: (hostInstance: NativeType) => ?Fiber,
version: string,
@ -147,6 +150,9 @@ export type ReactRenderer = {
setErrorHandler?: ?(shouldError: (fiber: Object) => ?boolean) => void,
// Intentionally opaque type to avoid coupling DevTools to different Fast Refresh versions.
scheduleRefresh?: Function,
// 18.0+
injectProfilingHooks?: (profilingHooks: DevToolsProfilingHooks) => void,
getLaneLabelMap?: () => Map<Lane, string>,
...
};
@ -390,11 +396,56 @@ export type RendererInterface = {
) => void,
unpatchConsoleForStrictMode: () => void,
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
// Timeline profiler interface
...
};
export type Handler = (data: any) => void;
// Renderers use these APIs to report profiling data to DevTools at runtime.
// They get passed from the DevTools backend to the reconciler during injection.
export type DevToolsProfilingHooks = {|
// Scheduling methods:
markRenderScheduled: (lane: Lane) => void,
markStateUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
markForceUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
// Work loop level methods:
markRenderStarted: (lanes: Lanes) => void,
markRenderYielded: () => void,
markRenderStopped: () => void,
markCommitStarted: (lanes: Lanes) => void,
markCommitStopped: () => void,
markLayoutEffectsStarted: (lanes: Lanes) => void,
markLayoutEffectsStopped: () => void,
markPassiveEffectsStarted: (lanes: Lanes) => void,
markPassiveEffectsStopped: () => void,
// Fiber level methods:
markComponentRenderStarted: (fiber: Fiber) => void,
markComponentRenderStopped: () => void,
markComponentErrored: (
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
) => void,
markComponentSuspended: (
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
) => void,
markComponentLayoutEffectMountStarted: (fiber: Fiber) => void,
markComponentLayoutEffectMountStopped: () => void,
markComponentLayoutEffectUnmountStarted: (fiber: Fiber) => void,
markComponentLayoutEffectUnmountStopped: () => void,
markComponentPassiveEffectMountStarted: (fiber: Fiber) => void,
markComponentPassiveEffectMountStopped: () => void,
markComponentPassiveEffectUnmountStarted: (fiber: Fiber) => void,
markComponentPassiveEffectUnmountStopped: () => void,
|};
export type DevToolsHook = {
listeners: {[key: string]: Array<Handler>, ...},
rendererInterfaces: Map<RendererID, RendererInterface>,
@ -431,5 +482,6 @@ export type DevToolsHook = {
// Testing
dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
...
};

View File

@ -9,14 +9,13 @@
*/
import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools';
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
import {
patch as patchConsole,
registerRenderer as registerRendererWithConsole,
} from './backend/console';
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
declare var window: any;
export function installHook(target: any): DevToolsHook | null {

View File

@ -35,7 +35,8 @@ import checkPropTypes from 'shared/checkPropTypes';
import {
markComponentRenderStarted,
markComponentRenderStopped,
} from './SchedulingProfiler';
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook.new';
import {
IndeterminateComponent,
FunctionComponent,
@ -240,7 +241,6 @@ import {createCapturedValue} from './ReactCapturedValue';
import {createClassErrorUpdate} from './ReactFiberThrow.new';
import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.new';
import is from 'shared/objectIs';
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
import {
getForksAtLevel,
isForkedChild,

View File

@ -35,7 +35,8 @@ import checkPropTypes from 'shared/checkPropTypes';
import {
markComponentRenderStarted,
markComponentRenderStopped,
} from './SchedulingProfiler';
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook.old';
import {
IndeterminateComponent,
FunctionComponent,
@ -240,7 +241,6 @@ import {createCapturedValue} from './ReactCapturedValue';
import {createClassErrorUpdate} from './ReactFiberThrow.old';
import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.old';
import is from 'shared/objectIs';
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
import {
getForksAtLevel,
isForkedChild,

View File

@ -37,7 +37,6 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
import getComponentNameFromType from 'shared/getComponentNameFromType';
import isArray from 'shared/isArray';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
import {
@ -74,11 +73,11 @@ import {
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.new';
import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
import {
markForceUpdateScheduled,
markStateUpdateScheduled,
} from './SchedulingProfiler';
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook.new';
const fakeInternalInstance = {};

View File

@ -37,7 +37,6 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
import getComponentNameFromType from 'shared/getComponentNameFromType';
import isArray from 'shared/isArray';
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
import {
@ -74,11 +73,11 @@ import {
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.old';
import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
import {
markForceUpdateScheduled,
markStateUpdateScheduled,
} from './SchedulingProfiler';
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook.old';
const fakeInternalInstance = {};

View File

@ -86,8 +86,6 @@ import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
import {
isCurrentUpdateNested,
@ -147,6 +145,7 @@ import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
import {doesFiberContain} from './ReactFiberTreeReflection';
import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
import {
isDevToolsPresent,
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
@ -155,7 +154,8 @@ import {
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
} from './SchedulingProfiler';
onCommitUnmount,
} from './ReactFiberDevToolsHook.new';
import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;

View File

@ -86,8 +86,6 @@ import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {onCommitUnmount} from './ReactFiberDevToolsHook.old';
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
import {
isCurrentUpdateNested,
@ -147,6 +145,7 @@ import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
import {doesFiberContain} from './ReactFiberTreeReflection';
import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
import {
isDevToolsPresent,
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
@ -155,7 +154,8 @@ import {
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
} from './SchedulingProfiler';
onCommitUnmount,
} from './ReactFiberDevToolsHook.old';
import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;

View File

@ -7,16 +7,22 @@
* @flow
*/
import type {Lane, Lanes} from './ReactFiberLane.new';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.new';
import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
import {
getLabelForLane,
TotalLanes,
} from 'react-reconciler/src/ReactFiberLane.new';
import {DidCapture} from './ReactFiberFlags';
import {
consoleManagedByDevToolsDuringStrictMode,
enableProfilerTimer,
enableSchedulingProfiler,
} from 'shared/ReactFeatureFlags';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.new';
import {DidCapture} from './ReactFiberFlags';
import {
DiscreteEventPriority,
ContinuousEventPriority,
@ -38,6 +44,7 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
let rendererID = null;
let injectedHook = null;
let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
let hasLoggedError = false;
export const isDevToolsPresent =
@ -67,7 +74,11 @@ export function injectInternals(internals: Object): boolean {
return true;
}
try {
rendererID = hook.inject(internals);
rendererID = hook.inject({
...internals,
getLaneLabelMap,
injectProfilingHooks,
});
// We have successfully injected, so now it is safe to set up hooks.
injectedHook = hook;
} catch (err) {
@ -212,3 +223,302 @@ export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
}
}
}
// Profiler API hooks
function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
injectedProfilingHooks = profilingHooks;
}
function getLaneLabelMap(): Map<Lane, string> {
const map: Map<Lane, string> = new Map();
let lane = 1;
for (let index = 0; index < TotalLanes; index++) {
const label = ((getLabelForLane(lane): any): string);
map.set(lane, label);
lane *= 2;
}
return map;
}
export function markCommitStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStarted === 'function'
) {
injectedProfilingHooks.markCommitStarted(lanes);
}
}
}
export function markCommitStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStopped === 'function'
) {
injectedProfilingHooks.markCommitStopped();
}
}
}
export function markComponentRenderStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
) {
injectedProfilingHooks.markComponentRenderStarted(fiber);
}
}
}
export function markComponentRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
) {
injectedProfilingHooks.markComponentRenderStopped();
}
}
}
export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
}
}
}
export function markComponentPassiveEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStopped();
}
}
}
export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
}
}
}
export function markComponentPassiveEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
}
}
}
export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
}
}
}
export function markComponentLayoutEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStopped();
}
}
}
export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
}
}
}
export function markComponentLayoutEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
}
}
}
export function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentErrored === 'function'
) {
injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
}
}
}
export function markComponentSuspended(
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentSuspended === 'function'
) {
injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
}
}
}
export function markLayoutEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStarted(lanes);
}
}
}
export function markLayoutEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStopped();
}
}
}
export function markPassiveEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStarted(lanes);
}
}
}
export function markPassiveEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStopped();
}
}
}
export function markRenderStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStarted === 'function'
) {
injectedProfilingHooks.markRenderStarted(lanes);
}
}
}
export function markRenderYielded(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderYielded === 'function'
) {
injectedProfilingHooks.markRenderYielded();
}
}
}
export function markRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStopped === 'function'
) {
injectedProfilingHooks.markRenderStopped();
}
}
}
export function markRenderScheduled(lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderScheduled === 'function'
) {
injectedProfilingHooks.markRenderScheduled(lane);
}
}
}
export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
) {
injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
}
}
}
export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
) {
injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
}
}
}

View File

@ -7,16 +7,22 @@
* @flow
*/
import type {Lane, Lanes} from './ReactFiberLane.old';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.old';
import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
import {
getLabelForLane,
TotalLanes,
} from 'react-reconciler/src/ReactFiberLane.old';
import {DidCapture} from './ReactFiberFlags';
import {
consoleManagedByDevToolsDuringStrictMode,
enableProfilerTimer,
enableSchedulingProfiler,
} from 'shared/ReactFeatureFlags';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.old';
import {DidCapture} from './ReactFiberFlags';
import {
DiscreteEventPriority,
ContinuousEventPriority,
@ -38,6 +44,7 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
let rendererID = null;
let injectedHook = null;
let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
let hasLoggedError = false;
export const isDevToolsPresent =
@ -67,7 +74,11 @@ export function injectInternals(internals: Object): boolean {
return true;
}
try {
rendererID = hook.inject(internals);
rendererID = hook.inject({
...internals,
getLaneLabelMap,
injectProfilingHooks,
});
// We have successfully injected, so now it is safe to set up hooks.
injectedHook = hook;
} catch (err) {
@ -212,3 +223,302 @@ export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
}
}
}
// Profiler API hooks
function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
injectedProfilingHooks = profilingHooks;
}
function getLaneLabelMap(): Map<Lane, string> {
const map: Map<Lane, string> = new Map();
let lane = 1;
for (let index = 0; index < TotalLanes; index++) {
const label = ((getLabelForLane(lane): any): string);
map.set(lane, label);
lane *= 2;
}
return map;
}
export function markCommitStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStarted === 'function'
) {
injectedProfilingHooks.markCommitStarted(lanes);
}
}
}
export function markCommitStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStopped === 'function'
) {
injectedProfilingHooks.markCommitStopped();
}
}
}
export function markComponentRenderStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
) {
injectedProfilingHooks.markComponentRenderStarted(fiber);
}
}
}
export function markComponentRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
) {
injectedProfilingHooks.markComponentRenderStopped();
}
}
}
export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
}
}
}
export function markComponentPassiveEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStopped();
}
}
}
export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
}
}
}
export function markComponentPassiveEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
}
}
}
export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
}
}
}
export function markComponentLayoutEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStopped();
}
}
}
export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
}
}
}
export function markComponentLayoutEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
}
}
}
export function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentErrored === 'function'
) {
injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
}
}
}
export function markComponentSuspended(
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentSuspended === 'function'
) {
injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
}
}
}
export function markLayoutEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStarted(lanes);
}
}
}
export function markLayoutEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStopped();
}
}
}
export function markPassiveEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStarted(lanes);
}
}
}
export function markPassiveEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStopped();
}
}
}
export function markRenderStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStarted === 'function'
) {
injectedProfilingHooks.markRenderStarted(lanes);
}
}
}
export function markRenderYielded(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderYielded === 'function'
) {
injectedProfilingHooks.markRenderYielded();
}
}
}
export function markRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStopped === 'function'
) {
injectedProfilingHooks.markRenderStopped();
}
}
}
export function markRenderScheduled(lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderScheduled === 'function'
) {
injectedProfilingHooks.markRenderScheduled(lane);
}
}
}
export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
) {
injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
}
}
}
export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
) {
injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
}
}
}

View File

@ -101,7 +101,7 @@ import {
warnAboutMultipleRenderersDEV,
} from './ReactMutableSource.new';
import {logStateUpdateScheduled} from './DebugTracing';
import {markStateUpdateScheduled} from './SchedulingProfiler';
import {markStateUpdateScheduled} from './ReactFiberDevToolsHook.new';
import {createCache, CacheContext} from './ReactFiberCacheComponent.new';
import {
createUpdate as createLegacyQueueUpdate,

View File

@ -101,7 +101,7 @@ import {
warnAboutMultipleRenderersDEV,
} from './ReactMutableSource.old';
import {logStateUpdateScheduled} from './DebugTracing';
import {markStateUpdateScheduled} from './SchedulingProfiler';
import {markStateUpdateScheduled} from './ReactFiberDevToolsHook.old';
import {createCache, CacheContext} from './ReactFiberCacheComponent.old';
import {
createUpdate as createLegacyQueueUpdate,

View File

@ -44,7 +44,11 @@ import {
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext.new';
import {createFiberRoot} from './ReactFiberRoot.new';
import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.new';
import {
injectInternals,
markRenderScheduled,
onScheduleRoot,
} from './ReactFiberDevToolsHook.new';
import {
requestEventTime,
requestUpdateLane,
@ -87,7 +91,6 @@ import {
setRefreshHandler,
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.new';
import {markRenderScheduled} from './SchedulingProfiler';
import ReactVersion from 'shared/ReactVersion';
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
export {createPortal} from './ReactPortal';

View File

@ -44,7 +44,11 @@ import {
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext.old';
import {createFiberRoot} from './ReactFiberRoot.old';
import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.old';
import {
injectInternals,
markRenderScheduled,
onScheduleRoot,
} from './ReactFiberDevToolsHook.old';
import {
requestEventTime,
requestUpdateLane,
@ -87,7 +91,6 @@ import {
setRefreshHandler,
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.old';
import {markRenderScheduled} from './SchedulingProfiler';
import ReactVersion from 'shared/ReactVersion';
export {registerMutableSourceForHydration} from './ReactMutableSource.old';
export {createPortal} from './ReactPortal';

View File

@ -65,20 +65,6 @@ import {
logRenderStarted,
logRenderStopped,
} from './DebugTracing';
import {
markCommitStarted,
markCommitStopped,
markComponentRenderStopped,
markComponentSuspended,
markComponentErrored,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
} from './SchedulingProfiler';
import {
resetAfterCommit,
@ -224,9 +210,21 @@ import {
clearCaughtError,
} from 'shared/ReactErrorUtils';
import {
isDevToolsPresent,
markCommitStarted,
markCommitStopped,
markComponentRenderStopped,
markComponentSuspended,
markComponentErrored,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
onCommitRoot as onCommitRootDevTools,
onPostCommitRoot as onPostCommitRootDevTools,
isDevToolsPresent,
} from './ReactFiberDevToolsHook.new';
import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
import {releaseCache} from './ReactFiberCacheComponent.new';

View File

@ -65,20 +65,6 @@ import {
logRenderStarted,
logRenderStopped,
} from './DebugTracing';
import {
markCommitStarted,
markCommitStopped,
markComponentRenderStopped,
markComponentSuspended,
markComponentErrored,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
} from './SchedulingProfiler';
import {
resetAfterCommit,
@ -224,9 +210,21 @@ import {
clearCaughtError,
} from 'shared/ReactErrorUtils';
import {
isDevToolsPresent,
markCommitStarted,
markCommitStopped,
markComponentRenderStopped,
markComponentSuspended,
markComponentErrored,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
onCommitRoot as onCommitRootDevTools,
onPostCommitRoot as onPostCommitRootDevTools,
isDevToolsPresent,
} from './ReactFiberDevToolsHook.old';
import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
import {releaseCache} from './ReactFiberCacheComponent.old';

View File

@ -1,395 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Lane, Lanes} from './ReactFiberLane.old';
import type {Fiber} from './ReactInternalTypes';
import type {Wakeable} from 'shared/ReactTypes';
import {
enableNewReconciler,
enableSchedulingProfiler,
} from 'shared/ReactFeatureFlags';
import ReactVersion from 'shared/ReactVersion';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-timeline/src/constants';
import isArray from 'shared/isArray';
import {
getLabelForLane as getLabelForLane_old,
TotalLanes as TotalLanes_old,
} from 'react-reconciler/src/ReactFiberLane.old';
import {
getLabelForLane as getLabelForLane_new,
TotalLanes as TotalLanes_new,
} from 'react-reconciler/src/ReactFiberLane.new';
const getLabelForLane = enableNewReconciler
? getLabelForLane_new
: getLabelForLane_old;
const TotalLanes = enableNewReconciler ? TotalLanes_new : TotalLanes_old;
/**
* If performance exists and supports the subset of the User Timing API that we
* require.
*/
const supportsUserTiming =
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function';
let supportsUserTimingV3 = false;
if (enableSchedulingProfiler) {
if (supportsUserTiming) {
const CHECK_V3_MARK = '__v3';
const markOptions = {};
// $FlowFixMe: Ignore Flow complaining about needing a value
Object.defineProperty(markOptions, 'startTime', {
get: function() {
supportsUserTimingV3 = true;
return 0;
},
set: function() {},
});
try {
// $FlowFixMe: Flow expects the User Timing level 2 API.
performance.mark(CHECK_V3_MARK, markOptions);
} catch (error) {
// Ignore
} finally {
performance.clearMarks(CHECK_V3_MARK);
}
}
}
const laneLabels: Array<string> = [];
export function getLaneLabels(): Array<string> {
if (laneLabels.length === 0) {
let lane = 1;
for (let index = 0; index < TotalLanes; index++) {
laneLabels.push(((getLabelForLane(lane): any): string));
lane *= 2;
}
}
return laneLabels;
}
function markLaneToLabelMetadata() {
getLaneLabels();
markAndClear(`--react-lane-labels-${laneLabels.join(',')}`);
}
function markAndClear(name) {
performance.mark(name);
performance.clearMarks(name);
}
function markVersionMetadata() {
markAndClear(`--react-version-${ReactVersion}`);
markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
}
function markInternalModuleRanges() {
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges === 'function'
) {
const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
// This check would not be required,
// except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
if (isArray(ranges)) {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (isArray(range) && range.length === 2) {
const [startStackFrame, stopStackFrame] = ranges[i];
markAndClear(`--react-internal-module-start-${startStackFrame}`);
markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
}
}
}
}
}
export function markCommitStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear(`--commit-start-${lanes}`);
// Certain types of metadata should be logged infrequently.
// Normally we would log this during module init,
// but there's no guarantee a user is profiling at that time.
// Commits happen infrequently (less than renders or state updates)
// so we log this extra information along with a commit.
// It will likely be logged more than once but that's okay.
//
// TODO Once DevTools supports starting/stopping the profiler,
// we can log this data only once (when started) and remove the per-commit logging.
markVersionMetadata();
markLaneToLabelMetadata();
markInternalModuleRanges();
}
}
}
export function markCommitStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--commit-stop');
}
}
}
export function markComponentRenderStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--component-render-start-${componentName}`);
}
}
}
export function markComponentRenderStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--component-render-stop');
}
}
}
export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--component-passive-effect-mount-start-${componentName}`);
}
}
}
export function markComponentPassiveEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-mount-stop');
}
}
}
export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
}
}
}
export function markComponentPassiveEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-unmount-stop');
}
}
}
export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--component-layout-effect-mount-start-${componentName}`);
}
}
}
export function markComponentLayoutEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-mount-stop');
}
}
}
export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
}
}
}
export function markComponentLayoutEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-unmount-stop');
}
}
}
export function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';
let message = '';
if (
thrownValue !== null &&
typeof thrownValue === 'object' &&
typeof thrownValue.message === 'string'
) {
message = thrownValue.message;
} else if (typeof thrownValue === 'string') {
message = thrownValue;
}
// TODO (timeline) Add component stack id
markAndClear(`--error-${componentName}-${phase}-${message}`);
}
}
}
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
let wakeableID: number = 0;
function getWakeableID(wakeable: Wakeable): number {
if (!wakeableIDs.has(wakeable)) {
wakeableIDs.set(wakeable, wakeableID++);
}
return ((wakeableIDs.get(wakeable): any): number);
}
export function markComponentSuspended(
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
const id = getWakeableID(wakeable);
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';
// Following the non-standard fn.displayName convention,
// frameworks like Relay may also annotate Promises with a displayName,
// describing what operation/data the thrown Promise is related to.
// When this is available we should pass it along to the Timeline.
const displayName = (wakeable: any).displayName || '';
// TODO (timeline) Add component stack id
markAndClear(
`--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
);
wakeable.then(
() => markAndClear(`--suspense-resolved-${id}-${componentName}`),
() => markAndClear(`--suspense-rejected-${id}-${componentName}`),
);
}
}
}
export function markLayoutEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear(`--layout-effects-start-${lanes}`);
}
}
}
export function markLayoutEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--layout-effects-stop');
}
}
}
export function markPassiveEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear(`--passive-effects-start-${lanes}`);
}
}
}
export function markPassiveEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--passive-effects-stop');
}
}
}
export function markRenderStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear(`--render-start-${lanes}`);
}
}
}
export function markRenderYielded(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--render-yield');
}
}
}
export function markRenderStopped(): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear('--render-stop');
}
}
}
export function markRenderScheduled(lane: Lane): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
markAndClear(`--schedule-render-${lane}`);
}
}
}
export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
}
}
}
export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (supportsUserTimingV3) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
// TODO (timeline) Add component stack id
markAndClear(`--schedule-state-update-${lane}-${componentName}`);
}
}
}

View File

@ -51,6 +51,32 @@ describe('updaters', () => {
onCommitUnmount: jest.fn(() => {}),
onPostCommitRoot: jest.fn(() => {}),
onScheduleRoot: jest.fn(() => {}),
// Profiling APIs
markCommitStarted: jest.fn(() => {}),
markCommitStopped: jest.fn(() => {}),
markComponentRenderStarted: jest.fn(() => {}),
markComponentRenderStopped: jest.fn(() => {}),
markComponentPassiveEffectMountStarted: jest.fn(() => {}),
markComponentPassiveEffectMountStopped: jest.fn(() => {}),
markComponentPassiveEffectUnmountStarted: jest.fn(() => {}),
markComponentPassiveEffectUnmountStopped: jest.fn(() => {}),
markComponentLayoutEffectMountStarted: jest.fn(() => {}),
markComponentLayoutEffectMountStopped: jest.fn(() => {}),
markComponentLayoutEffectUnmountStarted: jest.fn(() => {}),
markComponentLayoutEffectUnmountStopped: jest.fn(() => {}),
markComponentErrored: jest.fn(() => {}),
markComponentSuspended: jest.fn(() => {}),
markLayoutEffectsStarted: jest.fn(() => {}),
markLayoutEffectsStopped: jest.fn(() => {}),
markPassiveEffectsStarted: jest.fn(() => {}),
markPassiveEffectsStopped: jest.fn(() => {}),
markRenderStarted: jest.fn(() => {}),
markRenderYielded: jest.fn(() => {}),
markRenderStopped: jest.fn(() => {}),
markRenderScheduled: jest.fn(() => {}),
markForceUpdateScheduled: jest.fn(() => {}),
markStateUpdateScheduled: jest.fn(() => {}),
};
jest.mock(

View File

@ -1,998 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
describe('SchedulingProfiler', () => {
let React;
let ReactTestRenderer;
let ReactNoop;
let Scheduler;
let act;
let clearedMarks;
let featureDetectionMarkName = null;
let marks;
function createUserTimingPolyfill() {
featureDetectionMarkName = null;
clearedMarks = [];
marks = [];
// This is not a true polyfill, but it gives us enough to capture marks.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
return {
clearMarks(markName) {
clearedMarks.push(markName);
marks = marks.filter(mark => mark !== markName);
},
mark(markName, markOptions) {
if (featureDetectionMarkName === null) {
featureDetectionMarkName = markName;
}
marks.push(markName);
if (markOptions != null) {
// This is triggers the feature detection.
markOptions.startTime++;
}
},
};
}
function clearPendingMarks() {
clearedMarks.splice(0);
}
function getMarks() {
return clearedMarks[0] === featureDetectionMarkName
? clearedMarks.slice(1)
: clearedMarks;
}
beforeEach(() => {
jest.resetModules();
global.performance = createUserTimingPolyfill();
React = require('react');
// ReactNoop must be imported after ReactTestRenderer!
ReactTestRenderer = require('react-test-renderer');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('jest-react').act;
});
afterEach(() => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);
delete global.performance;
});
// @gate !enableSchedulingProfiler
it('should not mark if enableSchedulingProfiler is false', () => {
ReactTestRenderer.create(<div />);
expect(getMarks()).toEqual([]);
});
it('should mark sync render without suspends or state updates', () => {
ReactTestRenderer.create(<div />);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-1",
"--render-start-1",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('should mark concurrent render without suspends or state updates', () => {
ReactTestRenderer.create(<div />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('should mark render yields', async () => {
function Bar() {
Scheduler.unstable_yieldValue('Bar');
return null;
}
function Foo() {
Scheduler.unstable_yieldValue('Foo');
return <Bar />;
}
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.startTransition(() => {
ReactNoop.render(<Foo />);
});
// Do one step of work.
expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-64",
"--render-start-64",
"--component-render-start-Foo",
"--component-render-stop",
"--render-yield",
]
`);
}
} else {
ReactNoop.render(<Foo />);
// Do one step of work.
expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array []
`);
}
}
});
it('should mark sync render with suspense that resolves', async () => {
const fakeSuspensePromise = Promise.resolve(true);
function Example() {
throw fakeSuspensePromise;
}
ReactTestRenderer.create(
<React.Suspense fallback={null}>
<Example />
</React.Suspense>,
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-1",
"--render-start-1",
"--component-render-start-Example",
"--component-render-stop",
"--suspense-suspend-0-Example-mount-1-",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
clearPendingMarks();
await fakeSuspensePromise;
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--suspense-resolved-0-Example",
]
`);
}
});
it('should mark sync render with suspense that rejects', async () => {
const fakeSuspensePromise = Promise.reject(new Error('error'));
function Example() {
throw fakeSuspensePromise;
}
ReactTestRenderer.create(
<React.Suspense fallback={null}>
<Example />
</React.Suspense>,
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-1",
"--render-start-1",
"--component-render-start-Example",
"--component-render-stop",
"--suspense-suspend-0-Example-mount-1-",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
clearPendingMarks();
await expect(fakeSuspensePromise).rejects.toThrow();
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--suspense-rejected-0-Example",
]
`);
}
});
it('should mark concurrent render with suspense that resolves', async () => {
const fakeSuspensePromise = Promise.resolve(true);
function Example() {
throw fakeSuspensePromise;
}
ReactTestRenderer.create(
<React.Suspense fallback={null}>
<Example />
</React.Suspense>,
{unstable_isConcurrent: true},
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--suspense-suspend-0-Example-mount-16-",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
clearPendingMarks();
await fakeSuspensePromise;
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--suspense-resolved-0-Example",
]
`);
}
});
it('should mark concurrent render with suspense that rejects', async () => {
const fakeSuspensePromise = Promise.reject(new Error('error'));
function Example() {
throw fakeSuspensePromise;
}
ReactTestRenderer.create(
<React.Suspense fallback={null}>
<Example />
</React.Suspense>,
{unstable_isConcurrent: true},
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--suspense-suspend-0-Example-mount-16-",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
clearPendingMarks();
await expect(fakeSuspensePromise).rejects.toThrow();
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--suspense-rejected-0-Example",
]
`);
}
});
it('should mark cascading class component state updates', () => {
class Example extends React.Component {
state = {didMount: false};
componentDidMount() {
this.setState({didMount: true});
}
render() {
return null;
}
}
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--schedule-state-update-1-Example",
"--layout-effects-stop",
"--render-start-1",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
"--commit-stop",
]
`);
}
});
it('should mark cascading class component force updates', () => {
class Example extends React.Component {
componentDidMount() {
this.forceUpdate();
}
render() {
return null;
}
}
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--schedule-forced-update-1-Example",
"--layout-effects-stop",
"--render-start-1",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
"--commit-stop",
]
`);
}
});
it('should mark render phase state updates for class component', () => {
class Example extends React.Component {
state = {didRender: false};
render() {
if (this.state.didRender === false) {
this.setState({didRender: true});
}
return null;
}
}
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(() => {
expect(Scheduler).toFlushUntilNextPaint([]);
}).toErrorDev('Cannot update during an existing state transition');
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--schedule-state-update-16-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('should mark render phase force updates for class component', () => {
class Example extends React.Component {
state = {didRender: false};
render() {
if (this.state.didRender === false) {
this.forceUpdate(() => this.setState({didRender: true}));
}
return null;
}
}
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(() => {
expect(Scheduler).toFlushUntilNextPaint([]);
}).toErrorDev('Cannot update during an existing state transition');
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--schedule-forced-update-16-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('should mark cascading layout updates', () => {
function Example() {
const [didMount, setDidMount] = React.useState(false);
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
return didMount;
}
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--component-layout-effect-mount-start-Example",
"--schedule-state-update-1-Example",
"--component-layout-effect-mount-stop",
"--layout-effects-stop",
"--render-start-1",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
"--commit-stop",
]
`);
}
});
// This test is coupled to lane implementation details, so I'm disabling it in
// the new fork until it stabilizes so we don't have to repeatedly update it.
it('should mark cascading passive updates', () => {
function Example() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
setDidMount(true);
}, []);
return didMount;
}
act(() => {
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
"--passive-effects-start-16",
"--component-passive-effect-mount-start-Example",
"--schedule-state-update-16-Example",
"--component-passive-effect-mount-stop",
"--passive-effects-stop",
"--render-start-16",
"--component-render-start-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
]
`);
}
});
it('should mark render phase updates', () => {
function Example() {
const [didRender, setDidRender] = React.useState(false);
if (!didRender) {
setDidRender(true);
}
return didRender;
}
act(() => {
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
});
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
"--render-start-16",
"--component-render-start-Example",
"--schedule-state-update-16-Example",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('should mark sync render that throws', async () => {
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
function ExampleThatThrows() {
throw Error('Expected error');
}
ReactTestRenderer.create(
<ErrorBoundary>
<ExampleThatThrows />
</ErrorBoundary>,
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-1",
"--render-start-1",
"--component-render-start-ErrorBoundary",
"--component-render-stop",
"--component-render-start-ExampleThatThrows",
"--component-render-start-ExampleThatThrows",
"--component-render-stop",
"--error-ExampleThatThrows-mount-Expected error",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--schedule-state-update-1-ErrorBoundary",
"--layout-effects-stop",
"--commit-stop",
"--render-start-1",
"--component-render-start-ErrorBoundary",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
]
`);
}
});
it('should mark concurrent render that throws', async () => {
spyOnProd(console, 'error');
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
function ExampleThatThrows() {
// eslint-disable-next-line no-throw-literal
throw 'Expected error';
}
ReactTestRenderer.create(
<ErrorBoundary>
<ExampleThatThrows />
</ErrorBoundary>,
{unstable_isConcurrent: true},
);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushUntilNextPaint([]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--render-start-16",
"--component-render-start-ErrorBoundary",
"--component-render-stop",
"--component-render-start-ExampleThatThrows",
"--component-render-start-ExampleThatThrows",
"--component-render-stop",
"--error-ExampleThatThrows-mount-Expected error",
"--render-stop",
"--render-start-16",
"--component-render-start-ErrorBoundary",
"--component-render-stop",
"--component-render-start-ExampleThatThrows",
"--component-render-start-ExampleThatThrows",
"--component-render-stop",
"--error-ExampleThatThrows-mount-Expected error",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--schedule-state-update-1-ErrorBoundary",
"--layout-effects-stop",
"--render-start-1",
"--component-render-start-ErrorBoundary",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--commit-stop",
"--commit-stop",
]
`);
}
});
it('should mark passive and layout effects', async () => {
function ComponentWithEffects() {
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout 1 mount');
return () => {
Scheduler.unstable_yieldValue('layout 1 unmount');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive 1 mount');
return () => {
Scheduler.unstable_yieldValue('passive 1 unmount');
};
}, []);
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('layout 2 mount');
return () => {
Scheduler.unstable_yieldValue('layout 2 unmount');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive 2 mount');
return () => {
Scheduler.unstable_yieldValue('passive 2 unmount');
};
}, []);
React.useEffect(() => {
Scheduler.unstable_yieldValue('passive 3 mount');
return () => {
Scheduler.unstable_yieldValue('passive 3 unmount');
};
}, []);
return null;
}
const renderer = ReactTestRenderer.create(<ComponentWithEffects />, {
unstable_isConcurrent: true,
});
expect(Scheduler).toFlushUntilNextPaint([
'layout 1 mount',
'layout 2 mount',
]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
"--render-start-16",
"--component-render-start-ComponentWithEffects",
"--component-render-stop",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-16",
"--component-layout-effect-mount-start-ComponentWithEffects",
"--component-layout-effect-mount-stop",
"--component-layout-effect-mount-start-ComponentWithEffects",
"--component-layout-effect-mount-stop",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
clearPendingMarks();
expect(Scheduler).toFlushAndYield([
'passive 1 mount',
'passive 2 mount',
'passive 3 mount',
]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--passive-effects-start-16",
"--component-passive-effect-mount-start-ComponentWithEffects",
"--component-passive-effect-mount-stop",
"--component-passive-effect-mount-start-ComponentWithEffects",
"--component-passive-effect-mount-stop",
"--component-passive-effect-mount-start-ComponentWithEffects",
"--component-passive-effect-mount-stop",
"--passive-effects-stop",
]
`);
}
clearPendingMarks();
renderer.unmount();
expect(Scheduler).toFlushAndYield([
'layout 1 unmount',
'layout 2 unmount',
'passive 1 unmount',
'passive 2 unmount',
'passive 3 unmount',
]);
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(getMarks()).toMatchInlineSnapshot(`
Array [
"--schedule-render-16",
"--render-start-16",
"--render-stop",
"--commit-start-16",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--component-layout-effect-unmount-start-ComponentWithEffects",
"--component-layout-effect-unmount-stop",
"--component-layout-effect-unmount-start-ComponentWithEffects",
"--component-layout-effect-unmount-stop",
"--layout-effects-start-16",
"--layout-effects-stop",
"--commit-stop",
"--passive-effects-start-16",
"--component-passive-effect-unmount-start-ComponentWithEffects",
"--component-passive-effect-unmount-stop",
"--component-passive-effect-unmount-start-ComponentWithEffects",
"--component-passive-effect-unmount-stop",
"--component-passive-effect-unmount-start-ComponentWithEffects",
"--component-passive-effect-unmount-stop",
"--passive-effects-stop",
]
`);
}
});
});

View File

@ -1,216 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
// This test schedules work for as many lanes as we can (easily) using public APIs.
// It will hopefully serve as a reminder to update getLabelsForLanes() when we update Lanes.
// It's okay to delete any individual test in this file if the public API changes.
describe('SchedulingProfiler labels', () => {
let React;
let ReactDOM;
let act;
let clearedMarks;
let featureDetectionMarkName = null;
let marks;
global.IS_REACT_ACT_ENVIRONMENT = true;
function polyfillJSDomUserTiming() {
featureDetectionMarkName = null;
clearedMarks = [];
marks = [];
// This is not a true polyfill, but it gives us enough to capture marks.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
// JSDom already implements global.performance and it can't be overridden.
// However it only supports now() and timeOrigin() so we need to "upgrade it" in place.
if (!global.performance) {
global.performance = {};
}
global.performance.clearMarks = function clearMarks(markName) {
clearedMarks.push(markName);
marks = marks.filter(mark => mark !== markName);
};
global.performance.mark = function mark(markName, markOptions) {
if (featureDetectionMarkName === null) {
featureDetectionMarkName = markName;
}
marks.push(markName);
if (markOptions != null) {
// This is triggers the feature detection.
markOptions.startTime++;
}
};
}
function dispatchAndSetCurrentEvent(element, event) {
try {
window.event = event;
element.dispatchEvent(event);
} finally {
window.event = undefined;
}
}
beforeEach(() => {
jest.resetModules();
polyfillJSDomUserTiming();
React = require('react');
ReactDOM = require('react-dom');
const TestUtils = require('react-dom/test-utils');
act = TestUtils.act;
});
afterEach(() => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);
delete global.performance;
});
it('regression test SyncLane', () => {
ReactDOM.render(<div />, document.createElement('div'));
if (gate(flags => flags.enableSchedulingProfiler)) {
expect(clearedMarks).toMatchInlineSnapshot(`
Array [
"__v3",
"--schedule-render-1",
"--render-start-1",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('regression test DefaultLane', () => {
if (gate(flags => flags.enableSchedulingProfiler)) {
act(() => {
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<div />);
expect(clearedMarks).toMatchInlineSnapshot(`
Array [
"__v3",
"--schedule-render-16",
]
`);
});
}
});
it('regression test InputDiscreteLane', () => {
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const targetRef = React.createRef(null);
function App() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
setCount(count + 1);
};
return <button ref={targetRef} onClick={handleClick} />;
}
if (
gate(
flags => flags.enableSchedulingProfiler && !flags.enableLegacyFBSupport,
)
) {
act(() => {
root.render(<App />);
});
clearedMarks.splice(0);
act(() => {
targetRef.current.click();
});
expect(clearedMarks).toMatchInlineSnapshot(`
Array [
"--schedule-state-update-1-App",
"--render-start-1",
"--component-render-start-App",
"--component-render-stop",
"--render-stop",
"--commit-start-1",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-1",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
it('regression test InputContinuousLane', () => {
const targetRef = React.createRef(null);
function App() {
const [count, setCount] = React.useState(0);
const handleMouseOver = () => setCount(count + 1);
return <div ref={targetRef} onMouseOver={handleMouseOver} />;
}
if (gate(flags => flags.enableSchedulingProfiler)) {
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
act(() => {
root.render(<App />);
});
clearedMarks.splice(0);
act(() => {
const event = document.createEvent('MouseEvents');
event.initEvent('mouseover', true, true);
dispatchAndSetCurrentEvent(targetRef.current, event);
});
expect(clearedMarks).toMatchInlineSnapshot(`
Array [
"--schedule-state-update-4-App",
"--render-start-4",
"--component-render-start-App",
"--component-render-stop",
"--render-stop",
"--commit-start-4",
"--react-version-17.0.3",
"--profiler-version-1",
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
"--layout-effects-start-4",
"--layout-effects-stop",
"--commit-stop",
]
`);
}
});
});