@samitouri / QOS-React / commits / 024a764310

Implemented Profiler onCommit() and onPostCommit() hooks (#17910)

* Implemented Profiler onCommit() and onPostCommit() hooks * Added enableProfilerCommitHooks feature flag for commit hooks * Moved onCommit and onPassiveCommit behind separate feature flag

Brian Vaughn committed Mar 5, 2020 at 11:02 UTC 024a764310e64ef38963df16970364b9942a60cd
15 files changed +2795 -1060
packages/react-reconciler/src/ReactFiber.js
+9 -7
@@ -826,13 +826,8 @@ function createFiberFromProfiler(
826 key: null | string,
827 ): Fiber {
828 if (__DEV__) {
829 - if (
830 - typeof pendingProps.id !== 'string' ||
831 - typeof pendingProps.onRender !== 'function'
832 - ) {
833 - console.error(
834 - 'Profiler must specify an "id" string and "onRender" function as props',
835 - );
829 + if (typeof pendingProps.id !== 'string') {
830 + console.error('Profiler must specify an "id" as a prop');
831 }
832 }
833
@@ -842,6 +837,13 @@ function createFiberFromProfiler(
837 fiber.type = REACT_PROFILER_TYPE;
838 fiber.expirationTime = expirationTime;
839
840 + if (enableProfilerTimer) {
841 + fiber.stateNode = {
842 + effectDuration: 0,
843 + passiveEffectDuration: 0,
844 + };
845 + }
846 +
847 return fiber;
848 }
849
packages/react-reconciler/src/ReactFiberBeginWork.js
+12
@@ -580,6 +580,12 @@ function updateProfiler(
580 ) {
581 if (enableProfilerTimer) {
582 workInProgress.effectTag |= Update;
583 +
584 + // Reset effect durations for the next eventual effect phase.
585 + // These are reset during render to allow the DevTools commit hook a chance to read them,
586 + const stateNode = workInProgress.stateNode;
587 + stateNode.effectDuration = 0;
588 + stateNode.passiveEffectDuration = 0;
589 }
590 const nextProps = workInProgress.pendingProps;
591 const nextChildren = nextProps.children;
@@ -2944,6 +2950,12 @@ function beginWork(
2950 if (hasChildWork) {
2951 workInProgress.effectTag |= Update;
2952 }
2953 +
2954 + // Reset effect durations for the next eventual effect phase.
2955 + // These are reset during render to allow the DevTools commit hook a chance to read them,
2956 + const stateNode = workInProgress.stateNode;
2957 + stateNode.effectDuration = 0;
2958 + stateNode.passiveEffectDuration = 0;
2959 }
2960 break;
2961 case SuspenseComponent: {
packages/react-reconciler/src/ReactFiberCommitWork.js
+256 -19
@@ -29,6 +29,7 @@ import {
29 deferPassiveEffectCleanupDuringUnmount,
30 enableSchedulerTracing,
31 enableProfilerTimer,
32 + enableProfilerCommitHooks,
33 enableSuspenseServerRenderer,
34 enableDeprecatedFlareAPI,
35 enableFundamentalAPI,
@@ -76,7 +77,14 @@ import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
77 import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
78 import {logCapturedError} from './ReactFiberErrorLogger';
79 import {resolveDefaultProps} from './ReactFiberLazyComponent';
79 -import {getCommitTime} from './ReactProfilerTimer';
80 +import {
81 + getCommitTime,
82 + recordLayoutEffectDuration,
83 + recordPassiveEffectDuration,
84 + startLayoutEffectTimer,
85 + startPassiveEffectTimer,
86 +} from './ReactProfilerTimer';
87 +import {ProfileMode} from './ReactTypeOfMode';
88 import {commitUpdateQueue} from './ReactUpdateQueue';
89 import {
90 getPublicInstance,
@@ -113,6 +121,7 @@ import {
121 markCommitTimeOfFallback,
122 enqueuePendingPassiveHookEffectMount,
123 enqueuePendingPassiveHookEffectUnmount,
124 + enqueuePendingPassiveProfilerEffect,
125 } from './ReactFiberWorkLoop';
126 import {
127 NoEffect as NoHookEffect,
@@ -175,7 +184,20 @@ const callComponentWillUnmountWithTimer = function(current, instance) {
184 startPhaseTimer(current, 'componentWillUnmount');
185 instance.props = current.memoizedProps;
186 instance.state = current.memoizedState;
178 - instance.componentWillUnmount();
187 + if (
188 + enableProfilerTimer &&
189 + enableProfilerCommitHooks &&
190 + current.mode & ProfileMode
191 + ) {
192 + try {
193 + startLayoutEffectTimer();
194 + instance.componentWillUnmount();
195 + } finally {
196 + recordLayoutEffectDuration(current);
197 + }
198 + } else {
199 + instance.componentWillUnmount();
200 + }
201 stopPhaseTimer();
202 };
203
@@ -430,8 +452,31 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void {
452 // TODO (#17945) We should call all passive destroy functions (for all fibers)
453 // before calling any create functions. The current approach only serializes
454 // these for a single fiber.
433 - commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
434 - commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
455 + if (
456 + enableProfilerTimer &&
457 + enableProfilerCommitHooks &&
458 + finishedWork.mode & ProfileMode
459 + ) {
460 + try {
461 + startPassiveEffectTimer();
462 + commitHookEffectListUnmount(
463 + HookPassive | HookHasEffect,
464 + finishedWork,
465 + );
466 + commitHookEffectListMount(
467 + HookPassive | HookHasEffect,
468 + finishedWork,
469 + );
470 + } finally {
471 + recordPassiveEffectDuration(finishedWork);
472 + }
473 + } else {
474 + commitHookEffectListUnmount(
475 + HookPassive | HookHasEffect,
476 + finishedWork,
477 + );
478 + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
479 + }
480 break;
481 }
482 default:
@@ -440,6 +485,61 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void {
485 }
486 }
487
488 +export function commitPassiveEffectDurations(
489 + finishedRoot: FiberRoot,
490 + finishedWork: Fiber,
491 +): void {
492 + if (enableProfilerTimer && enableProfilerCommitHooks) {
493 + // Only Profilers with work in their subtree will have an Update effect scheduled.
494 + if ((finishedWork.effectTag & Update) !== NoEffect) {
495 + switch (finishedWork.tag) {
496 + case Profiler: {
497 + const {passiveEffectDuration} = finishedWork.stateNode;
498 + const {id, onPostCommit} = finishedWork.memoizedProps;
499 +
500 + // This value will still reflect the previous commit phase.
501 + // It does not get reset until the start of the next commit phase.
502 + const commitTime = getCommitTime();
503 +
504 + if (typeof onPostCommit === 'function') {
505 + if (enableSchedulerTracing) {
506 + onPostCommit(
507 + id,
508 + finishedWork.alternate === null ? 'mount' : 'update',
509 + passiveEffectDuration,
510 + commitTime,
511 + finishedRoot.memoizedInteractions,
512 + );
513 + } else {
514 + onPostCommit(
515 + id,
516 + finishedWork.alternate === null ? 'mount' : 'update',
517 + passiveEffectDuration,
518 + commitTime,
519 + );
520 + }
521 + }
522 +
523 + // Bubble times to the next nearest ancestor Profiler.
524 + // After we process that Profiler, we'll bubble further up.
525 + let parentFiber = finishedWork.return;
526 + while (parentFiber !== null) {
527 + if (parentFiber.tag === Profiler) {
528 + const parentStateNode = parentFiber.stateNode;
529 + parentStateNode.passiveEffectDuration += passiveEffectDuration;
530 + break;
531 + }
532 + parentFiber = parentFiber.return;
533 + }
534 + break;
535 + }
536 + default:
537 + break;
538 + }
539 + }
540 + }
541 +}
542 +
543 function commitLifeCycles(
544 finishedRoot: FiberRoot,
545 current: Fiber | null,
@@ -455,7 +555,20 @@ function commitLifeCycles(
555 // This is done to prevent sibling component effects from interfering with each other,
556 // e.g. a destroy function in one component should never override a ref set
557 // by a create function in another component during the same commit.
458 - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
558 + if (
559 + enableProfilerTimer &&
560 + enableProfilerCommitHooks &&
561 + finishedWork.mode & ProfileMode
562 + ) {
563 + try {
564 + startLayoutEffectTimer();
565 + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
566 + } finally {
567 + recordLayoutEffectDuration(finishedWork);
568 + }
569 + } else {
570 + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
571 + }
572
573 if (runAllPassiveEffectDestroysBeforeCreates) {
574 schedulePassiveEffects(finishedWork);
@@ -497,7 +610,20 @@ function commitLifeCycles(
610 }
611 }
612 }
500 - instance.componentDidMount();
613 + if (
614 + enableProfilerTimer &&
615 + enableProfilerCommitHooks &&
616 + finishedWork.mode & ProfileMode
617 + ) {
618 + try {
619 + startLayoutEffectTimer();
620 + instance.componentDidMount();
621 + } finally {
622 + recordLayoutEffectDuration(finishedWork);
623 + }
624 + } else {
625 + instance.componentDidMount();
626 + }
627 stopPhaseTimer();
628 } else {
629 const prevProps =
@@ -536,11 +662,28 @@ function commitLifeCycles(
662 }
663 }
664 }
539 - instance.componentDidUpdate(
540 - prevProps,
541 - prevState,
542 - instance.__reactInternalSnapshotBeforeUpdate,
543 - );
665 + if (
666 + enableProfilerTimer &&
667 + enableProfilerCommitHooks &&
668 + finishedWork.mode & ProfileMode
669 + ) {
670 + try {
671 + startLayoutEffectTimer();
672 + instance.componentDidUpdate(
673 + prevProps,
674 + prevState,
675 + instance.__reactInternalSnapshotBeforeUpdate,
676 + );
677 + } finally {
678 + recordLayoutEffectDuration(finishedWork);
679 + }
680 + } else {
681 + instance.componentDidUpdate(
682 + prevProps,
683 + prevState,
684 + instance.__reactInternalSnapshotBeforeUpdate,
685 + );
686 + }
687 stopPhaseTimer();
688 }
689 }
@@ -623,7 +766,10 @@ function commitLifeCycles(
766 }
767 case Profiler: {
768 if (enableProfilerTimer) {
626 - const onRender = finishedWork.memoizedProps.onRender;
769 + const {onCommit, onRender} = finishedWork.memoizedProps;
770 + const {effectDuration} = finishedWork.stateNode;
771 +
772 + const commitTime = getCommitTime();
773
774 if (typeof onRender === 'function') {
775 if (enableSchedulerTracing) {
@@ -633,7 +779,7 @@ function commitLifeCycles(
779 finishedWork.actualDuration,
780 finishedWork.treeBaseDuration,
781 finishedWork.actualStartTime,
636 - getCommitTime(),
782 + commitTime,
783 finishedRoot.memoizedInteractions,
784 );
785 } else {
@@ -643,10 +789,48 @@ function commitLifeCycles(
789 finishedWork.actualDuration,
790 finishedWork.treeBaseDuration,
791 finishedWork.actualStartTime,
646 - getCommitTime(),
792 + commitTime,
793 );
794 }
795 }
796 +
797 + if (enableProfilerCommitHooks) {
798 + if (typeof onCommit === 'function') {
799 + if (enableSchedulerTracing) {
800 + onCommit(
801 + finishedWork.memoizedProps.id,
802 + current === null ? 'mount' : 'update',
803 + effectDuration,
804 + commitTime,
805 + finishedRoot.memoizedInteractions,
806 + );
807 + } else {
808 + onCommit(
809 + finishedWork.memoizedProps.id,
810 + current === null ? 'mount' : 'update',
811 + effectDuration,
812 + commitTime,
813 + );
814 + }
815 + }
816 +
817 + // Schedule a passive effect for this Profiler to call onPostCommit hooks.
818 + // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
819 + // because the effect is also where times bubble to parent Profilers.
820 + enqueuePendingPassiveProfilerEffect(finishedWork);
821 +
822 + // Propagate layout effect durations to the next nearest Profiler ancestor.
823 + // Do not reset these values until the next render so DevTools has a chance to read them first.
824 + let parentFiber = finishedWork.return;
825 + while (parentFiber !== null) {
826 + if (parentFiber.tag === Profiler) {
827 + const parentStateNode = parentFiber.stateNode;
828 + parentStateNode.effectDuration += effectDuration;
829 + break;
830 + }
831 + parentFiber = parentFiber.return;
832 + }
833 + }
834 }
835 return;
836 }
@@ -797,7 +981,17 @@ function commitUnmount(
981 if ((tag & HookPassive) !== NoHookEffect) {
982 enqueuePendingPassiveHookEffectUnmount(current, effect);
983 } else {
800 - safelyCallDestroy(current, destroy);
984 + if (
985 + enableProfilerTimer &&
986 + enableProfilerCommitHooks &&
987 + current.mode & ProfileMode
988 + ) {
989 + startLayoutEffectTimer();
990 + safelyCallDestroy(current, destroy);
991 + recordLayoutEffectDuration(current);
992 + } else {
993 + safelyCallDestroy(current, destroy);
994 + }
995 }
996 }
997 effect = effect.next;
@@ -822,9 +1016,23 @@ function commitUnmount(
1016 runWithPriority(priorityLevel, () => {
1017 let effect = firstEffect;
1018 do {
825 - const destroy = effect.destroy;
1019 + const {destroy, tag} = effect;
1020 if (destroy !== undefined) {
827 - safelyCallDestroy(current, destroy);
1021 + if (
1022 + enableProfilerTimer &&
1023 + enableProfilerCommitHooks &&
1024 + current.mode & ProfileMode
1025 + ) {
1026 + if ((tag & HookPassive) !== NoHookEffect) {
1027 + safelyCallDestroy(current, destroy);
1028 + } else {
1029 + startLayoutEffectTimer();
1030 + safelyCallDestroy(current, destroy);
1031 + recordLayoutEffectDuration(current);
1032 + }
1033 + } else {
1034 + safelyCallDestroy(current, destroy);
1035 + }
1036 }
1037 effect = effect.next;
1038 } while (effect !== firstEffect);
@@ -1366,7 +1574,23 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1574 // This prevents sibling component effects from interfering with each other,
1575 // e.g. a destroy function in one component should never override a ref set
1576 // by a create function in another component during the same commit.
1369 - commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1577 + if (
1578 + enableProfilerTimer &&
1579 + enableProfilerCommitHooks &&
1580 + finishedWork.mode & ProfileMode
1581 + ) {
1582 + try {
1583 + startLayoutEffectTimer();
1584 + commitHookEffectListUnmount(
1585 + HookLayout | HookHasEffect,
1586 + finishedWork,
1587 + );
1588 + } finally {
1589 + recordLayoutEffectDuration(finishedWork);
1590 + }
1591 + } else {
1592 + commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1593 + }
1594 return;
1595 }
1596 case Profiler: {
@@ -1409,7 +1633,20 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1633 // This prevents sibling component effects from interfering with each other,
1634 // e.g. a destroy function in one component should never override a ref set
1635 // by a create function in another component during the same commit.
1412 - commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1636 + if (
1637 + enableProfilerTimer &&
1638 + enableProfilerCommitHooks &&
1639 + finishedWork.mode & ProfileMode
1640 + ) {
1641 + try {
1642 + startLayoutEffectTimer();
1643 + commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1644 + } finally {
1645 + recordLayoutEffectDuration(finishedWork);
1646 + }
1647 + } else {
1648 + commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1649 + }
1650 return;
1651 }
1652 case ClassComponent: {
packages/react-reconciler/src/ReactFiberWorkLoop.js
+79 -5
@@ -24,6 +24,7 @@ import {
24 enableSuspenseServerRenderer,
25 replayFailedUnitOfWorkWithInvokeGuardedCallback,
26 enableProfilerTimer,
27 + enableProfilerCommitHooks,
28 enableSchedulerTracing,
29 warnAboutUnmockedScheduler,
30 flushSuspenseFallbacksInTests,
@@ -139,6 +140,7 @@ import {
140 commitDeletion,
141 commitDetachRef,
142 commitAttachRef,
143 + commitPassiveEffectDurations,
144 commitResetTextContent,
145 } from './ReactFiberCommitWork';
146 import {enqueueUpdate} from './ReactUpdateQueue';
@@ -148,6 +150,8 @@ import {createCapturedValue} from './ReactCapturedValue';
150
151 import {
152 recordCommitTime,
153 + recordPassiveEffectDuration,
154 + startPassiveEffectTimer,
155 startProfilerTimer,
156 stopProfilerTimerIfRunningAndRecordDelta,
157 } from './ReactProfilerTimer';
@@ -261,6 +265,7 @@ let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority;
265 let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
266 let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
267 let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
268 +let pendingPassiveProfilerEffects: Array<Fiber> = [];
269
270 let rootsWithPendingDiscreteUpdates: Map<
271 FiberRoot,
@@ -2209,6 +2214,19 @@ export function flushPassiveEffects() {
2214 }
2215 }
2216
2217 +export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
2218 + if (enableProfilerTimer && enableProfilerCommitHooks) {
2219 + pendingPassiveProfilerEffects.push(fiber);
2220 + if (!rootDoesHavePassiveEffects) {
2221 + rootDoesHavePassiveEffects = true;
2222 + scheduleCallback(NormalPriority, () => {
2223 + flushPassiveEffects();
2224 + return null;
2225 + });
2226 + }
2227 + }
2228 +}
2229 +
2230 export function enqueuePendingPassiveHookEffectMount(
2231 fiber: Fiber,
2232 effect: HookEffect,
@@ -2250,6 +2268,7 @@ function flushPassiveEffectsImpl() {
2268 if (rootWithPendingPassiveEffects === null) {
2269 return false;
2270 }
2271 +
2272 const root = rootWithPendingPassiveEffects;
2273 const expirationTime = pendingPassiveEffectsExpirationTime;
2274 rootWithPendingPassiveEffects = null;
@@ -2282,7 +2301,17 @@ function flushPassiveEffectsImpl() {
2301 if (typeof destroy === 'function') {
2302 if (__DEV__) {
2303 setCurrentDebugFiberInDEV(fiber);
2285 - invokeGuardedCallback(null, destroy, null);
2304 + if (
2305 + enableProfilerTimer &&
2306 + enableProfilerCommitHooks &&
2307 + fiber.mode & ProfileMode
2308 + ) {
2309 + startPassiveEffectTimer();
2310 + invokeGuardedCallback(null, destroy, null);
2311 + recordPassiveEffectDuration(fiber);
2312 + } else {
2313 + invokeGuardedCallback(null, destroy, null);
2314 + }
2315 if (hasCaughtError()) {
2316 invariant(fiber !== null, 'Should be working on an effect.');
2317 const error = clearCaughtError();
@@ -2291,7 +2320,20 @@ function flushPassiveEffectsImpl() {
2320 resetCurrentDebugFiberInDEV();
2321 } else {
2322 try {
2294 - destroy();
2323 + if (
2324 + enableProfilerTimer &&
2325 + enableProfilerCommitHooks &&
2326 + fiber.mode & ProfileMode
2327 + ) {
2328 + try {
2329 + startPassiveEffectTimer();
2330 + destroy();
2331 + } finally {
2332 + recordPassiveEffectDuration(fiber);
2333 + }
2334 + } else {
2335 + destroy();
2336 + }
2337 } catch (error) {
2338 invariant(fiber !== null, 'Should be working on an effect.');
2339 captureCommitPhaseError(fiber, error);
@@ -2299,7 +2341,6 @@ function flushPassiveEffectsImpl() {
2341 }
2342 }
2343 }
2302 -
2344 // Second pass: Create new passive effects.
2345 let mountEffects = pendingPassiveHookEffectsMount;
2346 pendingPassiveHookEffectsMount = [];
@@ -2308,7 +2349,17 @@ function flushPassiveEffectsImpl() {
2349 const fiber = ((mountEffects[i + 1]: any): Fiber);
2350 if (__DEV__) {
2351 setCurrentDebugFiberInDEV(fiber);
2311 - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2352 + if (
2353 + enableProfilerTimer &&
2354 + enableProfilerCommitHooks &&
2355 + fiber.mode & ProfileMode
2356 + ) {
2357 + startPassiveEffectTimer();
2358 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2359 + recordPassiveEffectDuration(fiber);
2360 + } else {
2361 + invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2362 + }
2363 if (hasCaughtError()) {
2364 invariant(fiber !== null, 'Should be working on an effect.');
2365 const error = clearCaughtError();
@@ -2318,7 +2369,20 @@ function flushPassiveEffectsImpl() {
2369 } else {
2370 try {
2371 const create = effect.create;
2321 - effect.destroy = create();
2372 + if (
2373 + enableProfilerTimer &&
2374 + enableProfilerCommitHooks &&
2375 + fiber.mode & ProfileMode
2376 + ) {
2377 + try {
2378 + startPassiveEffectTimer();
2379 + effect.destroy = create();
2380 + } finally {
2381 + recordPassiveEffectDuration(fiber);
2382 + }
2383 + } else {
2384 + effect.destroy = create();
2385 + }
2386 } catch (error) {
2387 invariant(fiber !== null, 'Should be working on an effect.');
2388 captureCommitPhaseError(fiber, error);
@@ -2348,6 +2412,7 @@ function flushPassiveEffectsImpl() {
2412 captureCommitPhaseError(effect, error);
2413 }
2414 }
2415 +
2416 const nextNextEffect = effect.nextEffect;
2417 // Remove nextEffect pointer to assist GC
2418 effect.nextEffect = null;
@@ -2355,6 +2420,15 @@ function flushPassiveEffectsImpl() {
2420 }
2421 }
2422
2423 + if (enableProfilerTimer && enableProfilerCommitHooks) {
2424 + let profilerEffects = pendingPassiveProfilerEffects;
2425 + pendingPassiveProfilerEffects = [];
2426 + for (let i = 0; i < profilerEffects.length; i++) {
2427 + const fiber = ((profilerEffects[i]: any): Fiber);
2428 + commitPassiveEffectDurations(root, fiber);
2429 + }
2430 + }
2431 +
2432 if (enableSchedulerTracing) {
2433 popInteractions(((prevInteractions: any): Set<Interaction>));
2434 finishPendingInteractions(root, expirationTime);
packages/react-reconciler/src/ReactProfilerTimer.js
+76 -1
@@ -9,7 +9,11 @@
9
10 import type {Fiber} from './ReactFiber';
11
12 -import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
12 +import {
13 + enableProfilerTimer,
14 + enableProfilerCommitHooks,
15 +} from 'shared/ReactFeatureFlags';
16 +import {Profiler} from 'shared/ReactWorkTags';
17
18 // Intentionally not named imports because Rollup would use dynamic dispatch for
19 // CommonJS interop named imports.
@@ -27,7 +31,9 @@ export type ProfilerTimer = {
31 };
32
33 let commitTime: number = 0;
34 +let layoutEffectStartTime: number = -1;
35 let profilerStartTime: number = -1;
36 +let passiveEffectStartTime: number = -1;
37
38 function getCommitTime(): number {
39 return commitTime;
@@ -77,9 +83,78 @@ function stopProfilerTimerIfRunningAndRecordDelta(
83 }
84 }
85
86 +function recordLayoutEffectDuration(fiber: Fiber): void {
87 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
88 + return;
89 + }
90 +
91 + if (layoutEffectStartTime >= 0) {
92 + const elapsedTime = now() - layoutEffectStartTime;
93 +
94 + layoutEffectStartTime = -1;
95 +
96 + // Store duration on the next nearest Profiler ancestor.
97 + let parentFiber = fiber.return;
98 + while (parentFiber !== null) {
99 + if (parentFiber.tag === Profiler) {
100 + const parentStateNode = parentFiber.stateNode;
101 + parentStateNode.effectDuration += elapsedTime;
102 + break;
103 + }
104 + parentFiber = parentFiber.return;
105 + }
106 + }
107 +}
108 +
109 +function recordPassiveEffectDuration(fiber: Fiber): void {
110 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
111 + return;
112 + }
113 +
114 + if (passiveEffectStartTime >= 0) {
115 + const elapsedTime = now() - passiveEffectStartTime;
116 +
117 + passiveEffectStartTime = -1;
118 +
119 + // Store duration on the next nearest Profiler ancestor.
120 + let parentFiber = fiber.return;
121 + while (parentFiber !== null) {
122 + if (parentFiber.tag === Profiler) {
123 + const parentStateNode = parentFiber.stateNode;
124 + if (parentStateNode !== null) {
125 + // Detached fibers have their state node cleared out.
126 + // In this case, the return pointer is also cleared out,
127 + // so we won't be able to report the time spent in this Profiler's subtree.
128 + parentStateNode.passiveEffectDuration += elapsedTime;
129 + }
130 + break;
131 + }
132 + parentFiber = parentFiber.return;
133 + }
134 + }
135 +}
136 +
137 +function startLayoutEffectTimer(): void {
138 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
139 + return;
140 + }
141 + layoutEffectStartTime = now();
142 +}
143 +
144 +function startPassiveEffectTimer(): void {
145 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
146 + return;
147 + }
148 + passiveEffectStartTime = now();
149 +}
150 +
151 export {
152 getCommitTime,
153 recordCommitTime,
154 + recordLayoutEffectDuration,
155 + recordPassiveEffectDuration,
156 + startLayoutEffectTimer,
157 + startPassiveEffectTimer,
158 startProfilerTimer,
159 stopProfilerTimerIfRunning,
160 stopProfilerTimerIfRunningAndRecordDelta,
packages/react/src/__tests__/ReactProfiler-test.internal.js
+2352 -1028
@@ -24,14 +24,19 @@ let TextResource;
24 let resourcePromise;
25
26 function loadModules({
27 + deferPassiveEffectCleanupDuringUnmount = false,
28 enableProfilerTimer = true,
29 + enableProfilerCommitHooks = true,
30 enableSchedulerTracing = true,
31 replayFailedUnitOfWorkWithInvokeGuardedCallback = false,
32 useNoopRenderer = false,
33 } = {}) {
34 ReactFeatureFlags = require('shared/ReactFeatureFlags');
35 ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
36 + ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = deferPassiveEffectCleanupDuringUnmount;
37 + ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates = deferPassiveEffectCleanupDuringUnmount;
38 ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
39 + ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks;
40 ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing;
41 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback;
42
@@ -120,10 +125,9 @@ describe('Profiler', () => {
125 it('should warn if required params are missing', () => {
126 expect(() => {
127 ReactTestRenderer.create(<React.Profiler />);
123 - }).toErrorDev(
124 - 'Profiler must specify an "id" string and "onRender" function as props',
125 - {withoutStack: true},
126 - );
128 + }).toErrorDev('Profiler must specify an "id" as a prop', {
129 + withoutStack: true,
130 + });
131 });
132 }
133
@@ -182,1084 +186,2282 @@ describe('Profiler', () => {
186 });
187 });
188
185 - [true, false].forEach(enableSchedulerTracing => {
186 - describe('onRender callback', () => {
187 - beforeEach(() => {
188 - jest.resetModules();
189 + [true, false].forEach(deferPassiveEffectCleanupDuringUnmount => {
190 + [true, false].forEach(enableSchedulerTracing => {
191 + describe(`onRender enableSchedulerTracing:${
192 + enableSchedulerTracing ? 'enabled' : 'disabled'
193 + } deferPassiveEffectCleanupDuringUnmount:${
194 + deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
195 + }`, () => {
196 + beforeEach(() => {
197 + jest.resetModules();
198 +
199 + loadModules({
200 + deferPassiveEffectCleanupDuringUnmount,
201 + enableSchedulerTracing,
202 + });
203 + });
204
190 - loadModules({enableSchedulerTracing});
191 - });
205 + it('should handle errors thrown', () => {
206 + const callback = jest.fn(id => {
207 + if (id === 'throw') {
208 + throw Error('expected');
209 + }
210 + });
211
193 - it('should handle errors thrown', () => {
194 - const callback = jest.fn(id => {
195 - if (id === 'throw') {
196 - throw Error('expected');
212 + let didMount = false;
213 + class ClassComponent extends React.Component {
214 + componentDidMount() {
215 + didMount = true;
216 + }
217 + render() {
218 + return this.props.children;
219 + }
220 }
221 +
222 + // Errors thrown from onRender should not break the commit phase,
223 + // Or prevent other lifecycles from being called.
224 + expect(() =>
225 + ReactTestRenderer.create(
226 + <ClassComponent>
227 + <React.Profiler id="do-not-throw" onRender={callback}>
228 + <React.Profiler id="throw" onRender={callback}>
229 + <div />
230 + </React.Profiler>
231 + </React.Profiler>
232 + </ClassComponent>,
233 + ),
234 + ).toThrow('expected');
235 + expect(didMount).toBe(true);
236 + expect(callback).toHaveBeenCalledTimes(2);
237 });
238
200 - let didMount = false;
201 - class ClassComponent extends React.Component {
202 - componentDidMount() {
203 - didMount = true;
204 - }
205 - render() {
206 - return this.props.children;
207 - }
208 - }
239 + it('is not invoked until the commit phase', () => {
240 + const callback = jest.fn();
241
210 - // Errors thrown from onRender should not break the commit phase,
211 - // Or prevent other lifecycles from being called.
212 - expect(() =>
213 - ReactTestRenderer.create(
214 - <ClassComponent>
215 - <React.Profiler id="do-not-throw" onRender={callback}>
216 - <React.Profiler id="throw" onRender={callback}>
217 - <div />
218 - </React.Profiler>
219 - </React.Profiler>
220 - </ClassComponent>,
221 - ),
222 - ).toThrow('expected');
223 - expect(didMount).toBe(true);
224 - expect(callback).toHaveBeenCalledTimes(2);
225 - });
242 + const Yield = ({value}) => {
243 + Scheduler.unstable_yieldValue(value);
244 + return null;
245 + };
246
227 - it('is not invoked until the commit phase', () => {
228 - const callback = jest.fn();
247 + ReactTestRenderer.create(
248 + <React.Profiler id="test" onRender={callback}>
249 + <Yield value="first" />
250 + <Yield value="last" />
251 + </React.Profiler>,
252 + {
253 + unstable_isConcurrent: true,
254 + },
255 + );
256
230 - const Yield = ({value}) => {
231 - Scheduler.unstable_yieldValue(value);
232 - return null;
233 - };
257 + // Times are logged until a render is committed.
258 + expect(Scheduler).toFlushAndYieldThrough(['first']);
259 + expect(callback).toHaveBeenCalledTimes(0);
260 + expect(Scheduler).toFlushAndYield(['last']);
261 + expect(callback).toHaveBeenCalledTimes(1);
262 + });
263
235 - ReactTestRenderer.create(
236 - <React.Profiler id="test" onRender={callback}>
237 - <Yield value="first" />
238 - <Yield value="last" />
239 - </React.Profiler>,
240 - {
241 - unstable_isConcurrent: true,
242 - },
243 - );
264 + it('does not record times for components outside of Profiler tree', () => {
265 + // Mock the Scheduler module so we can track how many times the current
266 + // time is read
267 + jest.mock('scheduler', obj => {
268 + const ActualScheduler = require.requireActual(
269 + 'scheduler/unstable_mock',
270 + );
271 + return {
272 + ...ActualScheduler,
273 + unstable_now: function mockUnstableNow() {
274 + ActualScheduler.unstable_yieldValue('read current time');
275 + return ActualScheduler.unstable_now();
276 + },
277 + };
278 + });
279
245 - // Times are logged until a render is committed.
246 - expect(Scheduler).toFlushAndYieldThrough(['first']);
247 - expect(callback).toHaveBeenCalledTimes(0);
248 - expect(Scheduler).toFlushAndYield(['last']);
249 - expect(callback).toHaveBeenCalledTimes(1);
250 - });
280 + jest.resetModules();
281
252 - it('does not record times for components outside of Profiler tree', () => {
253 - // Mock the Scheduler module so we can track how many times the current
254 - // time is read
255 - jest.mock('scheduler', obj => {
256 - const ActualScheduler = require.requireActual(
257 - 'scheduler/unstable_mock',
258 - );
259 - return {
260 - ...ActualScheduler,
261 - unstable_now: function mockUnstableNow() {
262 - ActualScheduler.unstable_yieldValue('read current time');
263 - return ActualScheduler.unstable_now();
264 - },
265 - };
266 - });
282 + loadModules({enableSchedulerTracing});
283
268 - jest.resetModules();
284 + // Clear yields in case the current time is read during initialization.
285 + Scheduler.unstable_clearYields();
286
270 - loadModules({enableSchedulerTracing});
287 + ReactTestRenderer.create(
288 + <div>
289 + <AdvanceTime />
290 + <AdvanceTime />
291 + <AdvanceTime />
292 + <AdvanceTime />
293 + <AdvanceTime />
294 + </div>,
295 + );
296
272 - // Clear yields in case the current time is read during initialization.
273 - Scheduler.unstable_clearYields();
297 + // Should be called two times:
298 + // 2. To compute the update expiration time
299 + // 3. To record the commit time
300 + // No additional calls from ProfilerTimer are expected.
301 + expect(Scheduler).toHaveYielded([
302 + 'read current time',
303 + 'read current time',
304 + ]);
305
275 - ReactTestRenderer.create(
276 - <div>
277 - <AdvanceTime />
278 - <AdvanceTime />
279 - <AdvanceTime />
280 - <AdvanceTime />
281 - <AdvanceTime />
282 - </div>,
283 - );
306 + // Restore original mock
307 + jest.mock('scheduler', () =>
308 + require.requireActual('scheduler/unstable_mock'),
309 + );
310 + });
311
285 - // Should be called two times:
286 - // 2. To compute the update expiration time
287 - // 3. To record the commit time
288 - // No additional calls from ProfilerTimer are expected.
289 - expect(Scheduler).toHaveYielded([
290 - 'read current time',
291 - 'read current time',
292 - ]);
312 + it('does not report work done on a sibling', () => {
313 + const callback = jest.fn();
314
294 - // Restore original mock
295 - jest.mock('scheduler', () =>
296 - require.requireActual('scheduler/unstable_mock'),
297 - );
298 - });
315 + const DoesNotUpdate = React.memo(
316 + function DoesNotUpdateInner() {
317 + Scheduler.unstable_advanceTime(10);
318 + return null;
319 + },
320 + () => true,
321 + );
322
300 - it('does not report work done on a sibling', () => {
301 - const callback = jest.fn();
323 + let updateProfilerSibling;
324
303 - const DoesNotUpdate = React.memo(
304 - function DoesNotUpdateInner() {
305 - Scheduler.unstable_advanceTime(10);
325 + function ProfilerSibling() {
326 + const [count, setCount] = React.useState(0);
327 + updateProfilerSibling = () => setCount(count + 1);
328 return null;
307 - },
308 - () => true,
309 - );
310 -
311 - let updateProfilerSibling;
329 + }
330
313 - function ProfilerSibling() {
314 - const [count, setCount] = React.useState(0);
315 - updateProfilerSibling = () => setCount(count + 1);
316 - return null;
317 - }
331 + function App() {
332 + return (
333 + <React.Fragment>
334 + <React.Profiler id="test" onRender={callback}>
335 + <DoesNotUpdate />
336 + </React.Profiler>
337 + <ProfilerSibling />
338 + </React.Fragment>
339 + );
340 + }
341
319 - function App() {
320 - return (
321 - <React.Fragment>
322 - <React.Profiler id="test" onRender={callback}>
323 - <DoesNotUpdate />
324 - </React.Profiler>
325 - <ProfilerSibling />
326 - </React.Fragment>
327 - );
328 - }
342 + const renderer = ReactTestRenderer.create(<App />);
343
330 - const renderer = ReactTestRenderer.create(<App />);
344 + expect(callback).toHaveBeenCalledTimes(1);
345
332 - expect(callback).toHaveBeenCalledTimes(1);
346 + let call = callback.mock.calls[0];
347
334 - let call = callback.mock.calls[0];
348 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
349 + expect(call[0]).toBe('test');
350 + expect(call[1]).toBe('mount');
351 + expect(call[2]).toBe(10); // actual time
352 + expect(call[3]).toBe(10); // base time
353 + expect(call[4]).toBe(0); // start time
354 + expect(call[5]).toBe(10); // commit time
355 + expect(call[6]).toEqual(
356 + enableSchedulerTracing ? new Set() : undefined,
357 + ); // interaction events
358
336 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
337 - expect(call[0]).toBe('test');
338 - expect(call[1]).toBe('mount');
339 - expect(call[2]).toBe(10); // actual time
340 - expect(call[3]).toBe(10); // base time
341 - expect(call[4]).toBe(0); // start time
342 - expect(call[5]).toBe(10); // commit time
343 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
359 + callback.mockReset();
360
345 - callback.mockReset();
361 + Scheduler.unstable_advanceTime(20); // 10 -> 30
362
347 - Scheduler.unstable_advanceTime(20); // 10 -> 30
363 + // Updating a parent should report a re-render,
364 + // since React technically did a little bit of work between the Profiler and the bailed out subtree.
365 + renderer.update(<App />);
366
349 - // Updating a parent should report a re-render,
350 - // since React technically did a little bit of work between the Profiler and the bailed out subtree.
351 - renderer.update(<App />);
367 + expect(callback).toHaveBeenCalledTimes(1);
368
353 - expect(callback).toHaveBeenCalledTimes(1);
369 + call = callback.mock.calls[0];
370
355 - call = callback.mock.calls[0];
371 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
372 + expect(call[0]).toBe('test');
373 + expect(call[1]).toBe('update');
374 + expect(call[2]).toBe(0); // actual time
375 + expect(call[3]).toBe(10); // base time
376 + expect(call[4]).toBe(30); // start time
377 + expect(call[5]).toBe(30); // commit time
378 + expect(call[6]).toEqual(
379 + enableSchedulerTracing ? new Set() : undefined,
380 + ); // interaction events
381
357 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
358 - expect(call[0]).toBe('test');
359 - expect(call[1]).toBe('update');
360 - expect(call[2]).toBe(0); // actual time
361 - expect(call[3]).toBe(10); // base time
362 - expect(call[4]).toBe(30); // start time
363 - expect(call[5]).toBe(30); // commit time
364 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
382 + callback.mockReset();
383
366 - callback.mockReset();
384 + Scheduler.unstable_advanceTime(20); // 30 -> 50
385
368 - Scheduler.unstable_advanceTime(20); // 30 -> 50
386 + // Updating a sibling should not report a re-render.
387 + ReactTestRenderer.act(updateProfilerSibling);
388
370 - // Updating a sibling should not report a re-render.
371 - ReactTestRenderer.act(updateProfilerSibling);
389 + expect(callback).not.toHaveBeenCalled();
390 + });
391
373 - expect(callback).not.toHaveBeenCalled();
374 - });
392 + it('logs render times for both mount and update', () => {
393 + const callback = jest.fn();
394
376 - it('logs render times for both mount and update', () => {
377 - const callback = jest.fn();
395 + Scheduler.unstable_advanceTime(5); // 0 -> 5
396
379 - Scheduler.unstable_advanceTime(5); // 0 -> 5
397 + const renderer = ReactTestRenderer.create(
398 + <React.Profiler id="test" onRender={callback}>
399 + <AdvanceTime />
400 + </React.Profiler>,
401 + );
402
381 - const renderer = ReactTestRenderer.create(
382 - <React.Profiler id="test" onRender={callback}>
383 - <AdvanceTime />
384 - </React.Profiler>,
385 - );
403 + expect(callback).toHaveBeenCalledTimes(1);
404
387 - expect(callback).toHaveBeenCalledTimes(1);
405 + let [call] = callback.mock.calls;
406
389 - let [call] = callback.mock.calls;
407 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
408 + expect(call[0]).toBe('test');
409 + expect(call[1]).toBe('mount');
410 + expect(call[2]).toBe(10); // actual time
411 + expect(call[3]).toBe(10); // base time
412 + expect(call[4]).toBe(5); // start time
413 + expect(call[5]).toBe(15); // commit time
414 + expect(call[6]).toEqual(
415 + enableSchedulerTracing ? new Set() : undefined,
416 + ); // interaction events
417
391 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
392 - expect(call[0]).toBe('test');
393 - expect(call[1]).toBe('mount');
394 - expect(call[2]).toBe(10); // actual time
395 - expect(call[3]).toBe(10); // base time
396 - expect(call[4]).toBe(5); // start time
397 - expect(call[5]).toBe(15); // commit time
398 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
418 + callback.mockReset();
419
400 - callback.mockReset();
420 + Scheduler.unstable_advanceTime(20); // 15 -> 35
421
402 - Scheduler.unstable_advanceTime(20); // 15 -> 35
422 + renderer.update(
423 + <React.Profiler id="test" onRender={callback}>
424 + <AdvanceTime />
425 + </React.Profiler>,
426 + );
427
404 - renderer.update(
405 - <React.Profiler id="test" onRender={callback}>
406 - <AdvanceTime />
407 - </React.Profiler>,
408 - );
428 + expect(callback).toHaveBeenCalledTimes(1);
429
410 - expect(callback).toHaveBeenCalledTimes(1);
430 + [call] = callback.mock.calls;
431
412 - [call] = callback.mock.calls;
432 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
433 + expect(call[0]).toBe('test');
434 + expect(call[1]).toBe('update');
435 + expect(call[2]).toBe(10); // actual time
436 + expect(call[3]).toBe(10); // base time
437 + expect(call[4]).toBe(35); // start time
438 + expect(call[5]).toBe(45); // commit time
439 + expect(call[6]).toEqual(
440 + enableSchedulerTracing ? new Set() : undefined,
441 + ); // interaction events
442
414 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
415 - expect(call[0]).toBe('test');
416 - expect(call[1]).toBe('update');
417 - expect(call[2]).toBe(10); // actual time
418 - expect(call[3]).toBe(10); // base time
419 - expect(call[4]).toBe(35); // start time
420 - expect(call[5]).toBe(45); // commit time
421 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
443 + callback.mockReset();
444
423 - callback.mockReset();
445 + Scheduler.unstable_advanceTime(20); // 45 -> 65
446
425 - Scheduler.unstable_advanceTime(20); // 45 -> 65
447 + renderer.update(
448 + <React.Profiler id="test" onRender={callback}>
449 + <AdvanceTime byAmount={4} />
450 + </React.Profiler>,
451 + );
452
427 - renderer.update(
428 - <React.Profiler id="test" onRender={callback}>
429 - <AdvanceTime byAmount={4} />
430 - </React.Profiler>,
431 - );
453 + expect(callback).toHaveBeenCalledTimes(1);
454
433 - expect(callback).toHaveBeenCalledTimes(1);
455 + [call] = callback.mock.calls;
456 +
457 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
458 + expect(call[0]).toBe('test');
459 + expect(call[1]).toBe('update');
460 + expect(call[2]).toBe(4); // actual time
461 + expect(call[3]).toBe(4); // base time
462 + expect(call[4]).toBe(65); // start time
463 + expect(call[5]).toBe(69); // commit time
464 + expect(call[6]).toEqual(
465 + enableSchedulerTracing ? new Set() : undefined,
466 + ); // interaction events
467 + });
468
435 - [call] = callback.mock.calls;
469 + it('includes render times of nested Profilers in their parent times', () => {
470 + const callback = jest.fn();
471
437 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
438 - expect(call[0]).toBe('test');
439 - expect(call[1]).toBe('update');
440 - expect(call[2]).toBe(4); // actual time
441 - expect(call[3]).toBe(4); // base time
442 - expect(call[4]).toBe(65); // start time
443 - expect(call[5]).toBe(69); // commit time
444 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
445 - });
472 + Scheduler.unstable_advanceTime(5); // 0 -> 5
473
447 - it('includes render times of nested Profilers in their parent times', () => {
448 - const callback = jest.fn();
474 + ReactTestRenderer.create(
475 + <React.Fragment>
476 + <React.Profiler id="parent" onRender={callback}>
477 + <AdvanceTime byAmount={10}>
478 + <React.Profiler id="child" onRender={callback}>
479 + <AdvanceTime byAmount={20} />
480 + </React.Profiler>
481 + </AdvanceTime>
482 + </React.Profiler>
483 + </React.Fragment>,
484 + );
485
450 - Scheduler.unstable_advanceTime(5); // 0 -> 5
486 + expect(callback).toHaveBeenCalledTimes(2);
487
452 - ReactTestRenderer.create(
453 - <React.Fragment>
454 - <React.Profiler id="parent" onRender={callback}>
455 - <AdvanceTime byAmount={10}>
456 - <React.Profiler id="child" onRender={callback}>
457 - <AdvanceTime byAmount={20} />
458 - </React.Profiler>
459 - </AdvanceTime>
460 - </React.Profiler>
461 - </React.Fragment>,
462 - );
488 + // Callbacks bubble (reverse order).
489 + const [childCall, parentCall] = callback.mock.calls;
490 + expect(childCall[0]).toBe('child');
491 + expect(parentCall[0]).toBe('parent');
492 +
493 + // Parent times should include child times
494 + expect(childCall[2]).toBe(20); // actual time
495 + expect(childCall[3]).toBe(20); // base time
496 + expect(childCall[4]).toBe(15); // start time
497 + expect(childCall[5]).toBe(35); // commit time
498 + expect(parentCall[2]).toBe(30); // actual time
499 + expect(parentCall[3]).toBe(30); // base time
500 + expect(parentCall[4]).toBe(5); // start time
501 + expect(parentCall[5]).toBe(35); // commit time
502 + });
503
464 - expect(callback).toHaveBeenCalledTimes(2);
465 -
466 - // Callbacks bubble (reverse order).
467 - const [childCall, parentCall] = callback.mock.calls;
468 - expect(childCall[0]).toBe('child');
469 - expect(parentCall[0]).toBe('parent');
470 -
471 - // Parent times should include child times
472 - expect(childCall[2]).toBe(20); // actual time
473 - expect(childCall[3]).toBe(20); // base time
474 - expect(childCall[4]).toBe(15); // start time
475 - expect(childCall[5]).toBe(35); // commit time
476 - expect(parentCall[2]).toBe(30); // actual time
477 - expect(parentCall[3]).toBe(30); // base time
478 - expect(parentCall[4]).toBe(5); // start time
479 - expect(parentCall[5]).toBe(35); // commit time
480 - });
504 + it('traces sibling Profilers separately', () => {
505 + const callback = jest.fn();
506
482 - it('traces sibling Profilers separately', () => {
483 - const callback = jest.fn();
507 + Scheduler.unstable_advanceTime(5); // 0 -> 5
508
485 - Scheduler.unstable_advanceTime(5); // 0 -> 5
509 + ReactTestRenderer.create(
510 + <React.Fragment>
511 + <React.Profiler id="first" onRender={callback}>
512 + <AdvanceTime byAmount={20} />
513 + </React.Profiler>
514 + <React.Profiler id="second" onRender={callback}>
515 + <AdvanceTime byAmount={5} />
516 + </React.Profiler>
517 + </React.Fragment>,
518 + );
519
487 - ReactTestRenderer.create(
488 - <React.Fragment>
489 - <React.Profiler id="first" onRender={callback}>
490 - <AdvanceTime byAmount={20} />
491 - </React.Profiler>
492 - <React.Profiler id="second" onRender={callback}>
493 - <AdvanceTime byAmount={5} />
494 - </React.Profiler>
495 - </React.Fragment>,
496 - );
520 + expect(callback).toHaveBeenCalledTimes(2);
521
498 - expect(callback).toHaveBeenCalledTimes(2);
499 -
500 - const [firstCall, secondCall] = callback.mock.calls;
501 - expect(firstCall[0]).toBe('first');
502 - expect(secondCall[0]).toBe('second');
503 -
504 - // Parent times should include child times
505 - expect(firstCall[2]).toBe(20); // actual time
506 - expect(firstCall[3]).toBe(20); // base time
507 - expect(firstCall[4]).toBe(5); // start time
508 - expect(firstCall[5]).toBe(30); // commit time
509 - expect(secondCall[2]).toBe(5); // actual time
510 - expect(secondCall[3]).toBe(5); // base time
511 - expect(secondCall[4]).toBe(25); // start time
512 - expect(secondCall[5]).toBe(30); // commit time
513 - });
522 + const [firstCall, secondCall] = callback.mock.calls;
523 + expect(firstCall[0]).toBe('first');
524 + expect(secondCall[0]).toBe('second');
525 +
526 + // Parent times should include child times
527 + expect(firstCall[2]).toBe(20); // actual time
528 + expect(firstCall[3]).toBe(20); // base time
529 + expect(firstCall[4]).toBe(5); // start time
530 + expect(firstCall[5]).toBe(30); // commit time
531 + expect(secondCall[2]).toBe(5); // actual time
532 + expect(secondCall[3]).toBe(5); // base time
533 + expect(secondCall[4]).toBe(25); // start time
534 + expect(secondCall[5]).toBe(30); // commit time
535 + });
536
515 - it('does not include time spent outside of profile root', () => {
516 - const callback = jest.fn();
537 + it('does not include time spent outside of profile root', () => {
538 + const callback = jest.fn();
539
518 - Scheduler.unstable_advanceTime(5); // 0 -> 5
540 + Scheduler.unstable_advanceTime(5); // 0 -> 5
541
520 - ReactTestRenderer.create(
521 - <React.Fragment>
522 - <AdvanceTime byAmount={20} />
523 - <React.Profiler id="test" onRender={callback}>
524 - <AdvanceTime byAmount={5} />
525 - </React.Profiler>
526 - <AdvanceTime byAmount={20} />
527 - </React.Fragment>,
528 - );
542 + ReactTestRenderer.create(
543 + <React.Fragment>
544 + <AdvanceTime byAmount={20} />
545 + <React.Profiler id="test" onRender={callback}>
546 + <AdvanceTime byAmount={5} />
547 + </React.Profiler>
548 + <AdvanceTime byAmount={20} />
549 + </React.Fragment>,
550 + );
551
530 - expect(callback).toHaveBeenCalledTimes(1);
552 + expect(callback).toHaveBeenCalledTimes(1);
553
532 - const [call] = callback.mock.calls;
533 - expect(call[0]).toBe('test');
534 - expect(call[2]).toBe(5); // actual time
535 - expect(call[3]).toBe(5); // base time
536 - expect(call[4]).toBe(25); // start time
537 - expect(call[5]).toBe(50); // commit time
538 - });
554 + const [call] = callback.mock.calls;
555 + expect(call[0]).toBe('test');
556 + expect(call[2]).toBe(5); // actual time
557 + expect(call[3]).toBe(5); // base time
558 + expect(call[4]).toBe(25); // start time
559 + expect(call[5]).toBe(50); // commit time
560 + });
561
540 - it('is not called when blocked by sCU false', () => {
541 - const callback = jest.fn();
562 + it('is not called when blocked by sCU false', () => {
563 + const callback = jest.fn();
564
543 - let instance;
544 - class Updater extends React.Component {
545 - state = {};
546 - render() {
547 - instance = this;
548 - return this.props.children;
565 + let instance;
566 + class Updater extends React.Component {
567 + state = {};
568 + render() {
569 + instance = this;
570 + return this.props.children;
571 + }
572 }
550 - }
573
552 - const renderer = ReactTestRenderer.create(
553 - <React.Profiler id="outer" onRender={callback}>
554 - <Updater>
555 - <React.Profiler id="inner" onRender={callback}>
556 - <div />
557 - </React.Profiler>
558 - </Updater>
559 - </React.Profiler>,
560 - );
574 + const renderer = ReactTestRenderer.create(
575 + <React.Profiler id="outer" onRender={callback}>
576 + <Updater>
577 + <React.Profiler id="inner" onRender={callback}>
578 + <div />
579 + </React.Profiler>
580 + </Updater>
581 + </React.Profiler>,
582 + );
583
562 - // All profile callbacks are called for initial render
563 - expect(callback).toHaveBeenCalledTimes(2);
584 + // All profile callbacks are called for initial render
585 + expect(callback).toHaveBeenCalledTimes(2);
586
565 - callback.mockReset();
587 + callback.mockReset();
588
567 - renderer.unstable_flushSync(() => {
568 - instance.setState({
569 - count: 1,
589 + renderer.unstable_flushSync(() => {
590 + instance.setState({
591 + count: 1,
592 + });
593 });
571 - });
594
573 - // Only call onRender for paths that have re-rendered.
574 - // Since the Updater's props didn't change,
575 - // React does not re-render its children.
576 - expect(callback).toHaveBeenCalledTimes(1);
577 - expect(callback.mock.calls[0][0]).toBe('outer');
578 - });
595 + // Only call onRender for paths that have re-rendered.
596 + // Since the Updater's props didn't change,
597 + // React does not re-render its children.
598 + expect(callback).toHaveBeenCalledTimes(1);
599 + expect(callback.mock.calls[0][0]).toBe('outer');
600 + });
601
580 - it('decreases actual time but not base time when sCU prevents an update', () => {
581 - const callback = jest.fn();
602 + it('decreases actual time but not base time when sCU prevents an update', () => {
603 + const callback = jest.fn();
604
583 - Scheduler.unstable_advanceTime(5); // 0 -> 5
605 + Scheduler.unstable_advanceTime(5); // 0 -> 5
606
585 - const renderer = ReactTestRenderer.create(
586 - <React.Profiler id="test" onRender={callback}>
587 - <AdvanceTime byAmount={10}>
588 - <AdvanceTime byAmount={13} shouldComponentUpdate={false} />
589 - </AdvanceTime>
590 - </React.Profiler>,
591 - );
607 + const renderer = ReactTestRenderer.create(
608 + <React.Profiler id="test" onRender={callback}>
609 + <AdvanceTime byAmount={10}>
610 + <AdvanceTime byAmount={13} shouldComponentUpdate={false} />
611 + </AdvanceTime>
612 + </React.Profiler>,
613 + );
614
593 - expect(callback).toHaveBeenCalledTimes(1);
615 + expect(callback).toHaveBeenCalledTimes(1);
616
595 - Scheduler.unstable_advanceTime(30); // 28 -> 58
617 + Scheduler.unstable_advanceTime(30); // 28 -> 58
618
597 - renderer.update(
598 - <React.Profiler id="test" onRender={callback}>
599 - <AdvanceTime byAmount={4}>
600 - <AdvanceTime byAmount={7} shouldComponentUpdate={false} />
601 - </AdvanceTime>
602 - </React.Profiler>,
603 - );
619 + renderer.update(
620 + <React.Profiler id="test" onRender={callback}>
621 + <AdvanceTime byAmount={4}>
622 + <AdvanceTime byAmount={7} shouldComponentUpdate={false} />
623 + </AdvanceTime>
624 + </React.Profiler>,
625 + );
626
605 - expect(callback).toHaveBeenCalledTimes(2);
627 + expect(callback).toHaveBeenCalledTimes(2);
628
607 - const [mountCall, updateCall] = callback.mock.calls;
629 + const [mountCall, updateCall] = callback.mock.calls;
630
609 - expect(mountCall[1]).toBe('mount');
610 - expect(mountCall[2]).toBe(23); // actual time
611 - expect(mountCall[3]).toBe(23); // base time
612 - expect(mountCall[4]).toBe(5); // start time
613 - expect(mountCall[5]).toBe(28); // commit time
631 + expect(mountCall[1]).toBe('mount');
632 + expect(mountCall[2]).toBe(23); // actual time
633 + expect(mountCall[3]).toBe(23); // base time
634 + expect(mountCall[4]).toBe(5); // start time
635 + expect(mountCall[5]).toBe(28); // commit time
636
615 - expect(updateCall[1]).toBe('update');
616 - expect(updateCall[2]).toBe(4); // actual time
617 - expect(updateCall[3]).toBe(17); // base time
618 - expect(updateCall[4]).toBe(58); // start time
619 - expect(updateCall[5]).toBe(62); // commit time
620 - });
637 + expect(updateCall[1]).toBe('update');
638 + expect(updateCall[2]).toBe(4); // actual time
639 + expect(updateCall[3]).toBe(17); // base time
640 + expect(updateCall[4]).toBe(58); // start time
641 + expect(updateCall[5]).toBe(62); // commit time
642 + });
643
622 - it('includes time spent in render phase lifecycles', () => {
623 - class WithLifecycles extends React.Component {
624 - state = {};
625 - static getDerivedStateFromProps() {
626 - Scheduler.unstable_advanceTime(3);
627 - return null;
628 - }
629 - shouldComponentUpdate() {
630 - Scheduler.unstable_advanceTime(7);
631 - return true;
632 - }
633 - render() {
634 - Scheduler.unstable_advanceTime(5);
635 - return null;
644 + it('includes time spent in render phase lifecycles', () => {
645 + class WithLifecycles extends React.Component {
646 + state = {};
647 + static getDerivedStateFromProps() {
648 + Scheduler.unstable_advanceTime(3);
649 + return null;
650 + }
651 + shouldComponentUpdate() {
652 + Scheduler.unstable_advanceTime(7);
653 + return true;
654 + }
655 + render() {
656 + Scheduler.unstable_advanceTime(5);
657 + return null;
658 + }
659 }
637 - }
638 -
639 - const callback = jest.fn();
640 -
641 - Scheduler.unstable_advanceTime(5); // 0 -> 5
660
643 - const renderer = ReactTestRenderer.create(
644 - <React.Profiler id="test" onRender={callback}>
645 - <WithLifecycles />
646 - </React.Profiler>,
647 - );
661 + const callback = jest.fn();
662
649 - Scheduler.unstable_advanceTime(15); // 13 -> 28
663 + Scheduler.unstable_advanceTime(5); // 0 -> 5
664
651 - renderer.update(
652 - <React.Profiler id="test" onRender={callback}>
653 - <WithLifecycles />
654 - </React.Profiler>,
655 - );
665 + const renderer = ReactTestRenderer.create(
666 + <React.Profiler id="test" onRender={callback}>
667 + <WithLifecycles />
668 + </React.Profiler>,
669 + );
670
657 - expect(callback).toHaveBeenCalledTimes(2);
671 + Scheduler.unstable_advanceTime(15); // 13 -> 28
672
659 - const [mountCall, updateCall] = callback.mock.calls;
673 + renderer.update(
674 + <React.Profiler id="test" onRender={callback}>
675 + <WithLifecycles />
676 + </React.Profiler>,
677 + );
678
661 - expect(mountCall[1]).toBe('mount');
662 - expect(mountCall[2]).toBe(8); // actual time
663 - expect(mountCall[3]).toBe(8); // base time
664 - expect(mountCall[4]).toBe(5); // start time
665 - expect(mountCall[5]).toBe(13); // commit time
679 + expect(callback).toHaveBeenCalledTimes(2);
680
667 - expect(updateCall[1]).toBe('update');
668 - expect(updateCall[2]).toBe(15); // actual time
669 - expect(updateCall[3]).toBe(15); // base time
670 - expect(updateCall[4]).toBe(28); // start time
671 - expect(updateCall[5]).toBe(43); // commit time
672 - });
681 + const [mountCall, updateCall] = callback.mock.calls;
682
674 - describe('with regard to interruptions', () => {
675 - it('should accumulate actual time after a scheduling interruptions', () => {
676 - const callback = jest.fn();
683 + expect(mountCall[1]).toBe('mount');
684 + expect(mountCall[2]).toBe(8); // actual time
685 + expect(mountCall[3]).toBe(8); // base time
686 + expect(mountCall[4]).toBe(5); // start time
687 + expect(mountCall[5]).toBe(13); // commit time
688
678 - const Yield = ({renderTime}) => {
679 - Scheduler.unstable_advanceTime(renderTime);
680 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
681 - return null;
682 - };
689 + expect(updateCall[1]).toBe('update');
690 + expect(updateCall[2]).toBe(15); // actual time
691 + expect(updateCall[3]).toBe(15); // base time
692 + expect(updateCall[4]).toBe(28); // start time
693 + expect(updateCall[5]).toBe(43); // commit time
694 + });
695
684 - Scheduler.unstable_advanceTime(5); // 0 -> 5
696 + describe('with regard to interruptions', () => {
697 + it('should accumulate actual time after a scheduling interruptions', () => {
698 + const callback = jest.fn();
699
686 - // Render partially, but run out of time before completing.
687 - ReactTestRenderer.create(
688 - <React.Profiler id="test" onRender={callback}>
689 - <Yield renderTime={2} />
690 - <Yield renderTime={3} />
691 - </React.Profiler>,
692 - {unstable_isConcurrent: true},
693 - );
694 - expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
695 - expect(callback).toHaveBeenCalledTimes(0);
700 + const Yield = ({renderTime}) => {
701 + Scheduler.unstable_advanceTime(renderTime);
702 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
703 + return null;
704 + };
705
697 - // Resume render for remaining children.
698 - expect(Scheduler).toFlushAndYield(['Yield:3']);
706 + Scheduler.unstable_advanceTime(5); // 0 -> 5
707
700 - // Verify that logged times include both durations above.
701 - expect(callback).toHaveBeenCalledTimes(1);
702 - const [call] = callback.mock.calls;
703 - expect(call[2]).toBe(5); // actual time
704 - expect(call[3]).toBe(5); // base time
705 - expect(call[4]).toBe(5); // start time
706 - expect(call[5]).toBe(10); // commit time
708 + // Render partially, but run out of time before completing.
709 + ReactTestRenderer.create(
710 + <React.Profiler id="test" onRender={callback}>
711 + <Yield renderTime={2} />
712 + <Yield renderTime={3} />
713 + </React.Profiler>,
714 + {unstable_isConcurrent: true},
715 + );
716 + expect(Scheduler).toFlushAndYieldThrough(['Yield:2']);
717 + expect(callback).toHaveBeenCalledTimes(0);
718 +
719 + // Resume render for remaining children.
720 + expect(Scheduler).toFlushAndYield(['Yield:3']);
721 +
722 + // Verify that logged times include both durations above.
723 + expect(callback).toHaveBeenCalledTimes(1);
724 + const [call] = callback.mock.calls;
725 + expect(call[2]).toBe(5); // actual time
726 + expect(call[3]).toBe(5); // base time
727 + expect(call[4]).toBe(5); // start time
728 + expect(call[5]).toBe(10); // commit time
729 + });
730 +
731 + it('should not include time between frames', () => {
732 + const callback = jest.fn();
733 +
734 + const Yield = ({renderTime}) => {
735 + Scheduler.unstable_advanceTime(renderTime);
736 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
737 + return null;
738 + };
739 +
740 + Scheduler.unstable_advanceTime(5); // 0 -> 5
741 +
742 + // Render partially, but don't finish.
743 + // This partial render should take 5ms of simulated time.
744 + ReactTestRenderer.create(
745 + <React.Profiler id="outer" onRender={callback}>
746 + <Yield renderTime={5} />
747 + <Yield renderTime={10} />
748 + <React.Profiler id="inner" onRender={callback}>
749 + <Yield renderTime={17} />
750 + </React.Profiler>
751 + </React.Profiler>,
752 + {unstable_isConcurrent: true},
753 + );
754 + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
755 + expect(callback).toHaveBeenCalledTimes(0);
756 +
757 + // Simulate time moving forward while frame is paused.
758 + Scheduler.unstable_advanceTime(50); // 10 -> 60
759 +
760 + // Flush the remaining work,
761 + // Which should take an additional 10ms of simulated time.
762 + expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
763 + expect(callback).toHaveBeenCalledTimes(2);
764 +
765 + const [innerCall, outerCall] = callback.mock.calls;
766 +
767 + // Verify that the actual time includes all work times,
768 + // But not the time that elapsed between frames.
769 + expect(innerCall[0]).toBe('inner');
770 + expect(innerCall[2]).toBe(17); // actual time
771 + expect(innerCall[3]).toBe(17); // base time
772 + expect(innerCall[4]).toBe(70); // start time
773 + expect(innerCall[5]).toBe(87); // commit time
774 + expect(outerCall[0]).toBe('outer');
775 + expect(outerCall[2]).toBe(32); // actual time
776 + expect(outerCall[3]).toBe(32); // base time
777 + expect(outerCall[4]).toBe(5); // start time
778 + expect(outerCall[5]).toBe(87); // commit time
779 + });
780 +
781 + it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
782 + const callback = jest.fn();
783 +
784 + const Yield = ({renderTime}) => {
785 + Scheduler.unstable_advanceTime(renderTime);
786 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
787 + return null;
788 + };
789 +
790 + Scheduler.unstable_advanceTime(5); // 0 -> 5
791 +
792 + // Render a partially update, but don't finish.
793 + // This partial render should take 10ms of simulated time.
794 + const renderer = ReactTestRenderer.create(
795 + <React.Profiler id="test" onRender={callback}>
796 + <Yield renderTime={10} />
797 + <Yield renderTime={20} />
798 + </React.Profiler>,
799 + {unstable_isConcurrent: true},
800 + );
801 + expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
802 + expect(callback).toHaveBeenCalledTimes(0);
803 +
804 + // Simulate time moving forward while frame is paused.
805 + Scheduler.unstable_advanceTime(100); // 15 -> 115
806 +
807 + // Interrupt with higher priority work.
808 + // The interrupted work simulates an additional 5ms of time.
809 + renderer.unstable_flushSync(() => {
810 + renderer.update(
811 + <React.Profiler id="test" onRender={callback}>
812 + <Yield renderTime={5} />
813 + </React.Profiler>,
814 + );
815 + });
816 + expect(Scheduler).toHaveYielded(['Yield:5']);
817 +
818 + // The initial work was thrown away in this case,
819 + // So the actual and base times should only include the final rendered tree times.
820 + expect(callback).toHaveBeenCalledTimes(1);
821 + let call = callback.mock.calls[0];
822 + expect(call[2]).toBe(5); // actual time
823 + expect(call[3]).toBe(5); // base time
824 + expect(call[4]).toBe(115); // start time
825 + expect(call[5]).toBe(120); // commit time
826 +
827 + callback.mockReset();
828 +
829 + // Verify no more unexpected callbacks from low priority work
830 + expect(Scheduler).toFlushWithoutYielding();
831 + expect(callback).toHaveBeenCalledTimes(0);
832 + });
833 +
834 + it('should report the expected times when a high-priority update replaces a low-priority update', () => {
835 + const callback = jest.fn();
836 +
837 + const Yield = ({renderTime}) => {
838 + Scheduler.unstable_advanceTime(renderTime);
839 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
840 + return null;
841 + };
842 +
843 + Scheduler.unstable_advanceTime(5); // 0 -> 5
844 +
845 + const renderer = ReactTestRenderer.create(
846 + <React.Profiler id="test" onRender={callback}>
847 + <Yield renderTime={6} />
848 + <Yield renderTime={15} />
849 + </React.Profiler>,
850 + {unstable_isConcurrent: true},
851 + );
852 +
853 + // Render everything initially.
854 + // This should take 21 seconds of actual and base time.
855 + expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
856 + expect(callback).toHaveBeenCalledTimes(1);
857 + let call = callback.mock.calls[0];
858 + expect(call[2]).toBe(21); // actual time
859 + expect(call[3]).toBe(21); // base time
860 + expect(call[4]).toBe(5); // start time
861 + expect(call[5]).toBe(26); // commit time
862 +
863 + callback.mockReset();
864 +
865 + Scheduler.unstable_advanceTime(30); // 26 -> 56
866 +
867 + // Render a partially update, but don't finish.
868 + // This partial render should take 3ms of simulated time.
869 + renderer.update(
870 + <React.Profiler id="test" onRender={callback}>
871 + <Yield renderTime={3} />
872 + <Yield renderTime={5} />
873 + <Yield renderTime={9} />
874 + </React.Profiler>,
875 + );
876 + expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
877 + expect(callback).toHaveBeenCalledTimes(0);
878 +
879 + // Simulate time moving forward while frame is paused.
880 + Scheduler.unstable_advanceTime(100); // 59 -> 159
881 +
882 + // Render another 5ms of simulated time.
883 + expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
884 + expect(callback).toHaveBeenCalledTimes(0);
885 +
886 + // Simulate time moving forward while frame is paused.
887 + Scheduler.unstable_advanceTime(100); // 164 -> 264
888 +
889 + // Interrupt with higher priority work.
890 + // The interrupted work simulates an additional 11ms of time.
891 + renderer.unstable_flushSync(() => {
892 + renderer.update(
893 + <React.Profiler id="test" onRender={callback}>
894 + <Yield renderTime={11} />
895 + </React.Profiler>,
896 + );
897 + });
898 + expect(Scheduler).toHaveYielded(['Yield:11']);
899 +
900 + // The actual time should include only the most recent render,
901 + // Because this lets us avoid a lot of commit phase reset complexity.
902 + // The base time includes only the final rendered tree times.
903 + expect(callback).toHaveBeenCalledTimes(1);
904 + call = callback.mock.calls[0];
905 + expect(call[2]).toBe(11); // actual time
906 + expect(call[3]).toBe(11); // base time
907 + expect(call[4]).toBe(264); // start time
908 + expect(call[5]).toBe(275); // commit time
909 +
910 + // Verify no more unexpected callbacks from low priority work
911 + expect(Scheduler).toFlushAndYield([]);
912 + expect(callback).toHaveBeenCalledTimes(1);
913 + });
914 +
915 + it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
916 + const callback = jest.fn();
917 +
918 + const Yield = ({renderTime}) => {
919 + Scheduler.unstable_advanceTime(renderTime);
920 + Scheduler.unstable_yieldValue('Yield:' + renderTime);
921 + return null;
922 + };
923 +
924 + let first;
925 + class FirstComponent extends React.Component {
926 + state = {renderTime: 1};
927 + render() {
928 + first = this;
929 + Scheduler.unstable_advanceTime(this.state.renderTime);
930 + Scheduler.unstable_yieldValue(
931 + 'FirstComponent:' + this.state.renderTime,
932 + );
933 + return <Yield renderTime={4} />;
934 + }
935 + }
936 + let second;
937 + class SecondComponent extends React.Component {
938 + state = {renderTime: 2};
939 + render() {
940 + second = this;
941 + Scheduler.unstable_advanceTime(this.state.renderTime);
942 + Scheduler.unstable_yieldValue(
943 + 'SecondComponent:' + this.state.renderTime,
944 + );
945 + return <Yield renderTime={7} />;
946 + }
947 + }
948 +
949 + Scheduler.unstable_advanceTime(5); // 0 -> 5
950 +
951 + const renderer = ReactTestRenderer.create(
952 + <React.Profiler id="test" onRender={callback}>
953 + <FirstComponent />
954 + <SecondComponent />
955 + </React.Profiler>,
956 + {unstable_isConcurrent: true},
957 + );
958 +
959 + // Render everything initially.
960 + // This simulates a total of 14ms of actual render time.
961 + // The base render time is also 14ms for the initial render.
962 + expect(Scheduler).toFlushAndYield([
963 + 'FirstComponent:1',
964 + 'Yield:4',
965 + 'SecondComponent:2',
966 + 'Yield:7',
967 + ]);
968 + expect(callback).toHaveBeenCalledTimes(1);
969 + let call = callback.mock.calls[0];
970 + expect(call[2]).toBe(14); // actual time
971 + expect(call[3]).toBe(14); // base time
972 + expect(call[4]).toBe(5); // start time
973 + expect(call[5]).toBe(19); // commit time
974 +
975 + callback.mockClear();
976 +
977 + Scheduler.unstable_advanceTime(100); // 19 -> 119
978 +
979 + // Render a partially update, but don't finish.
980 + // This partial render will take 10ms of actual render time.
981 + first.setState({renderTime: 10});
982 + expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
983 + expect(callback).toHaveBeenCalledTimes(0);
984 +
985 + // Simulate time moving forward while frame is paused.
986 + Scheduler.unstable_advanceTime(100); // 129 -> 229
987 +
988 + // Interrupt with higher priority work.
989 + // This simulates a total of 37ms of actual render time.
990 + renderer.unstable_flushSync(() =>
991 + second.setState({renderTime: 30}),
992 + );
993 + expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
994 +
995 + // The actual time should include only the most recent render (37ms),
996 + // Because this greatly simplifies the commit phase logic.
997 + // The base time should include the more recent times for the SecondComponent subtree,
998 + // As well as the original times for the FirstComponent subtree.
999 + expect(callback).toHaveBeenCalledTimes(1);
1000 + call = callback.mock.calls[0];
1001 + expect(call[2]).toBe(37); // actual time
1002 + expect(call[3]).toBe(42); // base time
1003 + expect(call[4]).toBe(229); // start time
1004 + expect(call[5]).toBe(266); // commit time
1005 +
1006 + callback.mockClear();
1007 +
1008 + // Simulate time moving forward while frame is paused.
1009 + Scheduler.unstable_advanceTime(100); // 266 -> 366
1010 +
1011 + // Resume the original low priority update, with rebased state.
1012 + // This simulates a total of 14ms of actual render time,
1013 + // And does not include the original (interrupted) 10ms.
1014 + // The tree contains 42ms of base render time at this point,
1015 + // Reflecting the most recent (longer) render durations.
1016 + // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
1017 + expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
1018 + expect(callback).toHaveBeenCalledTimes(1);
1019 + call = callback.mock.calls[0];
1020 + expect(call[2]).toBe(14); // actual time
1021 + expect(call[3]).toBe(51); // base time
1022 + expect(call[4]).toBe(366); // start time
1023 + expect(call[5]).toBe(380); // commit time
1024 + });
1025 +
1026 + [true, false].forEach(
1027 + replayFailedUnitOfWorkWithInvokeGuardedCallback => {
1028 + describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
1029 + replayFailedUnitOfWorkWithInvokeGuardedCallback
1030 + ? 'enabled'
1031 + : 'disabled'
1032 + }`, () => {
1033 + beforeEach(() => {
1034 + jest.resetModules();
1035 +
1036 + loadModules({
1037 + replayFailedUnitOfWorkWithInvokeGuardedCallback,
1038 + });
1039 + });
1040 +
1041 + it('should accumulate actual time after an error handled by componentDidCatch()', () => {
1042 + const callback = jest.fn();
1043 +
1044 + const ThrowsError = () => {
1045 + Scheduler.unstable_advanceTime(3);
1046 + throw Error('expected error');
1047 + };
1048 +
1049 + class ErrorBoundary extends React.Component {
1050 + state = {error: null};
1051 + componentDidCatch(error) {
1052 + this.setState({error});
1053 + }
1054 + render() {
1055 + Scheduler.unstable_advanceTime(2);
1056 + return this.state.error === null ? (
1057 + this.props.children
1058 + ) : (
1059 + <AdvanceTime byAmount={20} />
1060 + );
1061 + }
1062 + }
1063 +
1064 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1065 +
1066 + ReactTestRenderer.create(
1067 + <React.Profiler id="test" onRender={callback}>
1068 + <ErrorBoundary>
1069 + <AdvanceTime byAmount={9} />
1070 + <ThrowsError />
1071 + </ErrorBoundary>
1072 + </React.Profiler>,
1073 + );
1074 +
1075 + expect(callback).toHaveBeenCalledTimes(2);
1076 +
1077 + // Callbacks bubble (reverse order).
1078 + let [mountCall, updateCall] = callback.mock.calls;
1079 +
1080 + // The initial mount only includes the ErrorBoundary (which takes 2)
1081 + // But it spends time rendering all of the failed subtree also.
1082 + expect(mountCall[1]).toBe('mount');
1083 + // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
1084 + // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1085 + expect(mountCall[2]).toBe(14);
1086 + // base time includes: 2 (ErrorBoundary)
1087 + // Since the tree is empty for the initial commit
1088 + expect(mountCall[3]).toBe(2);
1089 + // start time
1090 + expect(mountCall[4]).toBe(5);
1091 + // commit time: 5 initially + 14 of work
1092 + // Add an additional 3 (ThrowsError) if we replayed the failed work
1093 + expect(mountCall[5]).toBe(
1094 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1095 + ? 22
1096 + : 19,
1097 + );
1098 +
1099 + // The update includes the ErrorBoundary and its fallback child
1100 + expect(updateCall[1]).toBe('update');
1101 + // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1102 + expect(updateCall[2]).toBe(22);
1103 + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1104 + expect(updateCall[3]).toBe(22);
1105 + // start time
1106 + expect(updateCall[4]).toBe(
1107 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1108 + ? 22
1109 + : 19,
1110 + );
1111 + // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
1112 + // Add an additional 3 (ThrowsError) if we replayed the failed work
1113 + expect(updateCall[5]).toBe(
1114 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1115 + ? 44
1116 + : 41,
1117 + );
1118 + });
1119 +
1120 + it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
1121 + const callback = jest.fn();
1122 +
1123 + const ThrowsError = () => {
1124 + Scheduler.unstable_advanceTime(10);
1125 + throw Error('expected error');
1126 + };
1127 +
1128 + class ErrorBoundary extends React.Component {
1129 + state = {error: null};
1130 + static getDerivedStateFromError(error) {
1131 + return {error};
1132 + }
1133 + render() {
1134 + Scheduler.unstable_advanceTime(2);
1135 + return this.state.error === null ? (
1136 + this.props.children
1137 + ) : (
1138 + <AdvanceTime byAmount={20} />
1139 + );
1140 + }
1141 + }
1142 +
1143 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1144 +
1145 + ReactTestRenderer.create(
1146 + <React.Profiler id="test" onRender={callback}>
1147 + <ErrorBoundary>
1148 + <AdvanceTime byAmount={5} />
1149 + <ThrowsError />
1150 + </ErrorBoundary>
1151 + </React.Profiler>,
1152 + );
1153 +
1154 + expect(callback).toHaveBeenCalledTimes(1);
1155 +
1156 + // Callbacks bubble (reverse order).
1157 + let [mountCall] = callback.mock.calls;
1158 +
1159 + // The initial mount includes the ErrorBoundary's error state,
1160 + // But it also spends actual time rendering UI that fails and isn't included.
1161 + expect(mountCall[1]).toBe('mount');
1162 + // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
1163 + // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
1164 + // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1165 + expect(mountCall[2]).toBe(39);
1166 + // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1167 + expect(mountCall[3]).toBe(22);
1168 + // start time
1169 + expect(mountCall[4]).toBe(5);
1170 + // commit time
1171 + expect(mountCall[5]).toBe(
1172 + __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1173 + ? 54
1174 + : 44,
1175 + );
1176 + });
1177 +
1178 + it('should reset the fiber stack correct after a "complete" phase error', () => {
1179 + jest.resetModules();
1180 +
1181 + loadModules({
1182 + useNoopRenderer: true,
1183 + replayFailedUnitOfWorkWithInvokeGuardedCallback,
1184 + });
1185 +
1186 + // Simulate a renderer error during the "complete" phase.
1187 + // This mimics behavior like React Native's View/Text nesting validation.
1188 + ReactNoop.render(
1189 + <React.Profiler id="profiler" onRender={jest.fn()}>
1190 + <errorInCompletePhase>hi</errorInCompletePhase>
1191 + </React.Profiler>,
1192 + );
1193 + expect(Scheduler).toFlushAndThrow('Error in host config.');
1194 +
1195 + // A similar case we've seen caused by an invariant in ReactDOM.
1196 + // It didn't reproduce without a host component inside.
1197 + ReactNoop.render(
1198 + <React.Profiler id="profiler" onRender={jest.fn()}>
1199 + <errorInCompletePhase>
1200 + <span>hi</span>
1201 + </errorInCompletePhase>
1202 + </React.Profiler>,
1203 + );
1204 + expect(Scheduler).toFlushAndThrow('Error in host config.');
1205 +
1206 + // So long as the profiler timer's fiber stack is reset correctly,
1207 + // Subsequent renders should not error.
1208 + ReactNoop.render(
1209 + <React.Profiler id="profiler" onRender={jest.fn()}>
1210 + <span>hi</span>
1211 + </React.Profiler>,
1212 + );
1213 + expect(Scheduler).toFlushWithoutYielding();
1214 + });
1215 + });
1216 + },
1217 + );
1218 });
1219
709 - it('should not include time between frames', () => {
1220 + it('reflects the most recently rendered id value', () => {
1221 const callback = jest.fn();
1222
712 - const Yield = ({renderTime}) => {
713 - Scheduler.unstable_advanceTime(renderTime);
714 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
1223 + Scheduler.unstable_advanceTime(5); // 0 -> 5
1224 +
1225 + const renderer = ReactTestRenderer.create(
1226 + <React.Profiler id="one" onRender={callback}>
1227 + <AdvanceTime byAmount={2} />
1228 + </React.Profiler>,
1229 + );
1230 +
1231 + expect(callback).toHaveBeenCalledTimes(1);
1232 +
1233 + Scheduler.unstable_advanceTime(20); // 7 -> 27
1234 +
1235 + renderer.update(
1236 + <React.Profiler id="two" onRender={callback}>
1237 + <AdvanceTime byAmount={1} />
1238 + </React.Profiler>,
1239 + );
1240 +
1241 + expect(callback).toHaveBeenCalledTimes(2);
1242 +
1243 + const [mountCall, updateCall] = callback.mock.calls;
1244 +
1245 + expect(mountCall[0]).toBe('one');
1246 + expect(mountCall[1]).toBe('mount');
1247 + expect(mountCall[2]).toBe(2); // actual time
1248 + expect(mountCall[3]).toBe(2); // base time
1249 + expect(mountCall[4]).toBe(5); // start time
1250 +
1251 + expect(updateCall[0]).toBe('two');
1252 + expect(updateCall[1]).toBe('update');
1253 + expect(updateCall[2]).toBe(1); // actual time
1254 + expect(updateCall[3]).toBe(1); // base time
1255 + expect(updateCall[4]).toBe(27); // start time
1256 + });
1257 +
1258 + it('should not be called until after mutations', () => {
1259 + let classComponentMounted = false;
1260 + const callback = jest.fn(
1261 + (
1262 + id,
1263 + phase,
1264 + actualDuration,
1265 + baseDuration,
1266 + startTime,
1267 + commitTime,
1268 + ) => {
1269 + // Don't call this hook until after mutations
1270 + expect(classComponentMounted).toBe(true);
1271 + // But the commit time should reflect pre-mutation
1272 + expect(commitTime).toBe(2);
1273 + },
1274 + );
1275 +
1276 + class ClassComponent extends React.Component {
1277 + componentDidMount() {
1278 + Scheduler.unstable_advanceTime(5);
1279 + classComponentMounted = true;
1280 + }
1281 + render() {
1282 + Scheduler.unstable_advanceTime(2);
1283 + return null;
1284 + }
1285 + }
1286 +
1287 + ReactTestRenderer.create(
1288 + <React.Profiler id="test" onRender={callback}>
1289 + <ClassComponent />
1290 + </React.Profiler>,
1291 + );
1292 +
1293 + expect(callback).toHaveBeenCalledTimes(1);
1294 + });
1295 + });
1296 +
1297 + describe(`onCommit enableSchedulerTracing:${
1298 + enableSchedulerTracing ? 'enabled' : 'disabled'
1299 + } deferPassiveEffectCleanupDuringUnmount:${
1300 + deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
1301 + }`, () => {
1302 + beforeEach(() => {
1303 + jest.resetModules();
1304 +
1305 + loadModules({
1306 + deferPassiveEffectCleanupDuringUnmount,
1307 + enableSchedulerTracing,
1308 + });
1309 + });
1310 +
1311 + it('should report time spent in layout effects and commit lifecycles', () => {
1312 + const callback = jest.fn();
1313 +
1314 + const ComponetWithEffects = () => {
1315 + React.useLayoutEffect(() => {
1316 + Scheduler.unstable_advanceTime(10);
1317 + return () => {
1318 + Scheduler.unstable_advanceTime(100);
1319 + };
1320 + }, []);
1321 + React.useLayoutEffect(() => {
1322 + Scheduler.unstable_advanceTime(1000);
1323 + return () => {
1324 + Scheduler.unstable_advanceTime(10000);
1325 + };
1326 + });
1327 + React.useEffect(() => {
1328 + // This passive effect is here to verify that its time isn't reported.
1329 + Scheduler.unstable_advanceTime(5);
1330 + return () => {
1331 + Scheduler.unstable_advanceTime(7);
1332 + };
1333 + });
1334 return null;
1335 };
1336
718 - Scheduler.unstable_advanceTime(5); // 0 -> 5
1337 + class ComponentWithCommitHooks extends React.Component {
1338 + componentDidMount() {
1339 + Scheduler.unstable_advanceTime(100000);
1340 + }
1341 + componentDidUpdate() {
1342 + Scheduler.unstable_advanceTime(1000000);
1343 + }
1344 + render() {
1345 + return null;
1346 + }
1347 + }
1348
720 - // Render partially, but don't finish.
721 - // This partial render should take 5ms of simulated time.
722 - ReactTestRenderer.create(
723 - <React.Profiler id="outer" onRender={callback}>
724 - <Yield renderTime={5} />
725 - <Yield renderTime={10} />
726 - <React.Profiler id="inner" onRender={callback}>
727 - <Yield renderTime={17} />
728 - </React.Profiler>
1349 + Scheduler.unstable_advanceTime(1);
1350 +
1351 + const renderer = ReactTestRenderer.create(
1352 + <React.Profiler id="mount-test" onCommit={callback}>
1353 + <ComponetWithEffects />
1354 + <ComponentWithCommitHooks />
1355 </React.Profiler>,
730 - {unstable_isConcurrent: true},
1356 );
732 - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
733 - expect(callback).toHaveBeenCalledTimes(0);
1357
735 - // Simulate time moving forward while frame is paused.
736 - Scheduler.unstable_advanceTime(50); // 10 -> 60
1358 + expect(callback).toHaveBeenCalledTimes(1);
1359 +
1360 + let call = callback.mock.calls[0];
1361 +
1362 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1363 + expect(call[0]).toBe('mount-test');
1364 + expect(call[1]).toBe('mount');
1365 + expect(call[2]).toBe(101010); // durations
1366 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1367 + expect(call[4]).toEqual(
1368 + enableSchedulerTracing ? new Set() : undefined,
1369 + ); // interaction events
1370 +
1371 + Scheduler.unstable_advanceTime(1);
1372 +
1373 + renderer.update(
1374 + <React.Profiler id="update-test" onCommit={callback}>
1375 + <ComponetWithEffects />
1376 + <ComponentWithCommitHooks />
1377 + </React.Profiler>,
1378 + );
1379 +
1380 + expect(callback).toHaveBeenCalledTimes(2);
1381 +
1382 + call = callback.mock.calls[1];
1383 +
1384 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1385 + expect(call[0]).toBe('update-test');
1386 + expect(call[1]).toBe('update');
1387 + expect(call[2]).toBe(1011000); // durations
1388 + expect(call[3]).toBe(101017); // commit start time (before mutations or effects)
1389 + expect(call[4]).toEqual(
1390 + enableSchedulerTracing ? new Set() : undefined,
1391 + ); // interaction events
1392 +
1393 + Scheduler.unstable_advanceTime(1);
1394 +
1395 + renderer.update(
1396 + <React.Profiler id="unmount-test" onCommit={callback} />,
1397 + );
1398 +
1399 + expect(callback).toHaveBeenCalledTimes(3);
1400 +
1401 + call = callback.mock.calls[2];
1402 +
1403 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1404 + expect(call[0]).toBe('unmount-test');
1405 + expect(call[1]).toBe('update');
1406 + expect(call[2]).toBe(10100); // durations
1407 + expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)
1408 + expect(call[4]).toEqual(
1409 + enableSchedulerTracing ? new Set() : undefined,
1410 + ); // interaction events
1411 + });
1412 +
1413 + it('should report time spent in layout effects and commit lifecycles with cascading renders', () => {
1414 + const callback = jest.fn();
1415 +
1416 + const ComponetWithEffects = ({shouldCascade}) => {
1417 + const [didCascade, setDidCascade] = React.useState(false);
1418 + React.useLayoutEffect(() => {
1419 + if (shouldCascade && !didCascade) {
1420 + setDidCascade(true);
1421 + }
1422 + Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
1423 + return () => {
1424 + Scheduler.unstable_advanceTime(100);
1425 + };
1426 + }, [didCascade, shouldCascade]);
1427 + return null;
1428 + };
1429 +
1430 + class ComponentWithCommitHooks extends React.Component {
1431 + state = {
1432 + didCascade: false,
1433 + };
1434 + componentDidMount() {
1435 + Scheduler.unstable_advanceTime(1000);
1436 + }
1437 + componentDidUpdate() {
1438 + Scheduler.unstable_advanceTime(10000);
1439 + if (this.props.shouldCascade && !this.state.didCascade) {
1440 + this.setState({didCascade: true});
1441 + }
1442 + }
1443 + render() {
1444 + return null;
1445 + }
1446 + }
1447 +
1448 + Scheduler.unstable_advanceTime(1);
1449 +
1450 + const renderer = ReactTestRenderer.create(
1451 + <React.Profiler id="mount-test" onCommit={callback}>
1452 + <ComponetWithEffects shouldCascade={true} />
1453 + <ComponentWithCommitHooks />
1454 + </React.Profiler>,
1455 + );
1456 +
1457 + expect(callback).toHaveBeenCalledTimes(2);
1458 +
1459 + let call = callback.mock.calls[0];
1460 +
1461 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1462 + expect(call[0]).toBe('mount-test');
1463 + expect(call[1]).toBe('mount');
1464 + expect(call[2]).toBe(1010); // durations
1465 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1466 + expect(call[4]).toEqual(
1467 + enableSchedulerTracing ? new Set() : undefined,
1468 + ); // interaction events
1469 +
1470 + call = callback.mock.calls[1];
1471 +
1472 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1473 + expect(call[0]).toBe('mount-test');
1474 + expect(call[1]).toBe('update');
1475 + expect(call[2]).toBe(130); // durations
1476 + expect(call[3]).toBe(1011); // commit start time (before mutations or effects)
1477 + expect(call[4]).toEqual(
1478 + enableSchedulerTracing ? new Set() : undefined,
1479 + ); // interaction events
1480 +
1481 + Scheduler.unstable_advanceTime(1);
1482 +
1483 + renderer.update(
1484 + <React.Profiler id="update-test" onCommit={callback}>
1485 + <ComponetWithEffects />
1486 + <ComponentWithCommitHooks shouldCascade={true} />
1487 + </React.Profiler>,
1488 + );
1489 +
1490 + expect(callback).toHaveBeenCalledTimes(4);
1491 +
1492 + call = callback.mock.calls[2];
1493 +
1494 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1495 + expect(call[0]).toBe('update-test');
1496 + expect(call[1]).toBe('update');
1497 + expect(call[2]).toBe(10130); // durations
1498 + expect(call[3]).toBe(1142); // commit start time (before mutations or effects)
1499 + expect(call[4]).toEqual(
1500 + enableSchedulerTracing ? new Set() : undefined,
1501 + ); // interaction events
1502 +
1503 + call = callback.mock.calls[3];
1504 +
1505 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1506 + expect(call[0]).toBe('update-test');
1507 + expect(call[1]).toBe('update');
1508 + expect(call[2]).toBe(10000); // durations
1509 + expect(call[3]).toBe(11272); // commit start time (before mutations or effects)
1510 + expect(call[4]).toEqual(
1511 + enableSchedulerTracing ? new Set() : undefined,
1512 + ); // interaction events
1513 + });
1514 +
1515 + it('should bubble time spent in layout effects to higher profilers', () => {
1516 + const callback = jest.fn();
1517 +
1518 + const ComponetWithEffects = ({
1519 + cleanupDuration,
1520 + duration,
1521 + setCountRef,
1522 + }) => {
1523 + const setCount = React.useState(0)[1];
1524 + if (setCountRef != null) {
1525 + setCountRef.current = setCount;
1526 + }
1527 + React.useLayoutEffect(() => {
1528 + Scheduler.unstable_advanceTime(duration);
1529 + return () => {
1530 + Scheduler.unstable_advanceTime(cleanupDuration);
1531 + };
1532 + });
1533 + Scheduler.unstable_advanceTime(1);
1534 + return null;
1535 + };
1536 +
1537 + const setCountRef = React.createRef(null);
1538 +
1539 + let renderer = null;
1540 + ReactTestRenderer.act(() => {
1541 + renderer = ReactTestRenderer.create(
1542 + <React.Profiler id="root-mount" onCommit={callback}>
1543 + <React.Profiler id="a">
1544 + <ComponetWithEffects
1545 + duration={10}
1546 + cleanupDuration={100}
1547 + setCountRef={setCountRef}
1548 + />
1549 + </React.Profiler>
1550 + <React.Profiler id="b">
1551 + <ComponetWithEffects
1552 + duration={1000}
1553 + cleanupDuration={10000}
1554 + />
1555 + </React.Profiler>
1556 + </React.Profiler>,
1557 + );
1558 + });
1559 +
1560 + expect(callback).toHaveBeenCalledTimes(1);
1561 +
1562 + let call = callback.mock.calls[0];
1563 +
1564 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1565 + expect(call[0]).toBe('root-mount');
1566 + expect(call[1]).toBe('mount');
1567 + expect(call[2]).toBe(1010); // durations
1568 + expect(call[3]).toBe(2); // commit start time (before mutations or effects)
1569 + expect(call[4]).toEqual(
1570 + enableSchedulerTracing ? new Set() : undefined,
1571 + ); // interaction events
1572 +
1573 + ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
1574 +
1575 + expect(callback).toHaveBeenCalledTimes(2);
1576 +
1577 + call = callback.mock.calls[1];
1578 +
1579 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1580 + expect(call[0]).toBe('root-mount');
1581 + expect(call[1]).toBe('update');
1582 + expect(call[2]).toBe(110); // durations
1583 + expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
1584 + expect(call[4]).toEqual(
1585 + enableSchedulerTracing ? new Set() : undefined,
1586 + ); // interaction events
1587 +
1588 + ReactTestRenderer.act(() => {
1589 + renderer.update(
1590 + <React.Profiler id="root-update" onCommit={callback}>
1591 + <React.Profiler id="b">
1592 + <ComponetWithEffects
1593 + duration={1000}
1594 + cleanupDuration={10000}
1595 + />
1596 + </React.Profiler>
1597 + </React.Profiler>,
1598 + );
1599 + });
1600 +
1601 + expect(callback).toHaveBeenCalledTimes(3);
1602 +
1603 + call = callback.mock.calls[2];
1604 +
1605 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1606 + expect(call[0]).toBe('root-update');
1607 + expect(call[1]).toBe('update');
1608 + expect(call[2]).toBe(1100); // durations
1609 + expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
1610 + expect(call[4]).toEqual(
1611 + enableSchedulerTracing ? new Set() : undefined,
1612 + ); // interaction events
1613 + });
1614 +
1615 + it('should properly report time in layout effects even when there are errors', () => {
1616 + const callback = jest.fn();
1617 +
1618 + class ErrorBoundary extends React.Component {
1619 + state = {error: null};
1620 + static getDerivedStateFromError(error) {
1621 + return {error};
1622 + }
1623 + render() {
1624 + return this.state.error === null
1625 + ? this.props.children
1626 + : this.props.fallback;
1627 + }
1628 + }
1629 +
1630 + const ComponetWithEffects = ({
1631 + cleanupDuration,
1632 + duration,
1633 + effectDuration,
1634 + shouldThrow,
1635 + }) => {
1636 + React.useLayoutEffect(() => {
1637 + Scheduler.unstable_advanceTime(effectDuration);
1638 + if (shouldThrow) {
1639 + throw Error('expected');
1640 + }
1641 + return () => {
1642 + Scheduler.unstable_advanceTime(cleanupDuration);
1643 + };
1644 + });
1645 + Scheduler.unstable_advanceTime(duration);
1646 + return null;
1647 + };
1648 +
1649 + Scheduler.unstable_advanceTime(1);
1650 +
1651 + // Test an error that happens during an effect
1652 +
1653 + ReactTestRenderer.act(() => {
1654 + ReactTestRenderer.create(
1655 + <React.Profiler id="root" onCommit={callback}>
1656 + <ErrorBoundary
1657 + fallback={
1658 + <ComponetWithEffects
1659 + duration={10000000}
1660 + effectDuration={100000000}
1661 + cleanupDuration={1000000000}
1662 + />
1663 + }>
1664 + <ComponetWithEffects
1665 + duration={10}
1666 + effectDuration={100}
1667 + cleanupDuration={1000}
1668 + shouldThrow={true}
1669 + />
1670 + </ErrorBoundary>
1671 + <ComponetWithEffects
1672 + duration={10000}
1673 + effectDuration={100000}
1674 + cleanupDuration={1000000}
1675 + />
1676 + </React.Profiler>,
1677 + );
1678 + });
1679 +
1680 + expect(callback).toHaveBeenCalledTimes(2);
1681 +
1682 + let call = callback.mock.calls[0];
1683 +
1684 + // Initial render (with error)
1685 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1686 + expect(call[0]).toBe('root');
1687 + expect(call[1]).toBe('mount');
1688 + expect(call[2]).toBe(100100); // durations
1689 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1690 + expect(call[4]).toEqual(
1691 + enableSchedulerTracing ? new Set() : undefined,
1692 + ); // interaction events
1693 +
1694 + call = callback.mock.calls[1];
1695 +
1696 + // Cleanup render from error boundary
1697 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1698 + expect(call[0]).toBe('root');
1699 + expect(call[1]).toBe('update');
1700 + expect(call[2]).toBe(100000000); // durations
1701 + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
1702 + expect(call[4]).toEqual(
1703 + enableSchedulerTracing ? new Set() : undefined,
1704 + ); // interaction events
1705 + });
1706 +
1707 + it('should properly report time in layout effect cleanup functions even when there are errors', () => {
1708 + const callback = jest.fn();
1709 +
1710 + class ErrorBoundary extends React.Component {
1711 + state = {error: null};
1712 + static getDerivedStateFromError(error) {
1713 + return {error};
1714 + }
1715 + render() {
1716 + return this.state.error === null
1717 + ? this.props.children
1718 + : this.props.fallback;
1719 + }
1720 + }
1721 +
1722 + const ComponetWithEffects = ({
1723 + cleanupDuration,
1724 + duration,
1725 + effectDuration,
1726 + shouldThrow = false,
1727 + }) => {
1728 + React.useLayoutEffect(() => {
1729 + Scheduler.unstable_advanceTime(effectDuration);
1730 + return () => {
1731 + Scheduler.unstable_advanceTime(cleanupDuration);
1732 + if (shouldThrow) {
1733 + throw Error('expected');
1734 + }
1735 + };
1736 + });
1737 + Scheduler.unstable_advanceTime(duration);
1738 + return null;
1739 + };
1740 +
1741 + Scheduler.unstable_advanceTime(1);
1742 +
1743 + let renderer = null;
1744 +
1745 + ReactTestRenderer.act(() => {
1746 + renderer = ReactTestRenderer.create(
1747 + <React.Profiler id="root" onCommit={callback}>
1748 + <ErrorBoundary
1749 + fallback={
1750 + <ComponetWithEffects
1751 + duration={10000000}
1752 + effectDuration={100000000}
1753 + cleanupDuration={1000000000}
1754 + />
1755 + }>
1756 + <ComponetWithEffects
1757 + duration={10}
1758 + effectDuration={100}
1759 + cleanupDuration={1000}
1760 + shouldThrow={true}
1761 + />
1762 + </ErrorBoundary>
1763 + <ComponetWithEffects
1764 + duration={10000}
1765 + effectDuration={100000}
1766 + cleanupDuration={1000000}
1767 + />
1768 + </React.Profiler>,
1769 + );
1770 + });
1771 +
1772 + expect(callback).toHaveBeenCalledTimes(1);
1773 +
1774 + let call = callback.mock.calls[0];
1775 +
1776 + // Initial render
1777 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1778 + expect(call[0]).toBe('root');
1779 + expect(call[1]).toBe('mount');
1780 + expect(call[2]).toBe(100100); // durations
1781 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
1782 + expect(call[4]).toEqual(
1783 + enableSchedulerTracing ? new Set() : undefined,
1784 + ); // interaction events
1785 +
1786 + callback.mockClear();
1787 +
1788 + // Test an error that happens during an cleanup function
1789 +
1790 + ReactTestRenderer.act(() => {
1791 + renderer.update(
1792 + <React.Profiler id="root" onCommit={callback}>
1793 + <ErrorBoundary
1794 + fallback={
1795 + <ComponetWithEffects
1796 + duration={10000000}
1797 + effectDuration={100000000}
1798 + cleanupDuration={1000000000}
1799 + />
1800 + }>
1801 + <ComponetWithEffects
1802 + duration={10}
1803 + effectDuration={100}
1804 + cleanupDuration={1000}
1805 + shouldThrow={false}
1806 + />
1807 + </ErrorBoundary>
1808 + <ComponetWithEffects
1809 + duration={10000}
1810 + effectDuration={100000}
1811 + cleanupDuration={1000000}
1812 + />
1813 + </React.Profiler>,
1814 + );
1815 + });
1816 +
1817 + expect(callback).toHaveBeenCalledTimes(2);
1818 +
1819 + call = callback.mock.calls[0];
1820 +
1821 + // Update (that throws)
1822 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1823 + expect(call[0]).toBe('root');
1824 + expect(call[1]).toBe('update');
1825 + expect(call[2]).toBe(1101100); // durations
1826 + expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
1827 + expect(call[4]).toEqual(
1828 + enableSchedulerTracing ? new Set() : undefined,
1829 + ); // interaction events
1830 +
1831 + call = callback.mock.calls[1];
1832 +
1833 + // Cleanup render from error boundary
1834 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1835 + expect(call[0]).toBe('root');
1836 + expect(call[1]).toBe('update');
1837 + expect(call[2]).toBe(100001000); // durations
1838 + expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)
1839 + expect(call[4]).toEqual(
1840 + enableSchedulerTracing ? new Set() : undefined,
1841 + ); // interaction events
1842 + });
1843 +
1844 + if (enableSchedulerTracing) {
1845 + it('should report interactions that were active', () => {
1846 + const callback = jest.fn();
1847 +
1848 + const ComponetWithEffects = () => {
1849 + const [didMount, setDidMount] = React.useState(false);
1850 + React.useLayoutEffect(() => {
1851 + Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
1852 + if (!didMount) {
1853 + setDidMount(true);
1854 + }
1855 + return () => {
1856 + Scheduler.unstable_advanceTime(10000);
1857 + };
1858 + }, [didMount]);
1859 + Scheduler.unstable_advanceTime(10);
1860 + return null;
1861 + };
1862 +
1863 + const interaction = {
1864 + id: 0,
1865 + name: 'mount',
1866 + timestamp: Scheduler.unstable_now(),
1867 + };
1868 +
1869 + Scheduler.unstable_advanceTime(1);
1870 +
1871 + SchedulerTracing.unstable_trace(
1872 + interaction.name,
1873 + interaction.timestamp,
1874 + () => {
1875 + ReactTestRenderer.create(
1876 + <React.Profiler id="root" onCommit={callback}>
1877 + <ComponetWithEffects />
1878 + </React.Profiler>,
1879 + );
1880 + },
1881 + );
1882 +
1883 + expect(callback).toHaveBeenCalledTimes(2);
1884 +
1885 + let call = callback.mock.calls[0];
1886 +
1887 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1888 + expect(call[0]).toBe('root');
1889 + expect(call[1]).toBe('mount');
1890 + expect(call[4]).toMatchInteractions([interaction]);
1891 +
1892 + call = callback.mock.calls[1];
1893 +
1894 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1895 + expect(call[0]).toBe('root');
1896 + expect(call[1]).toBe('update');
1897 + expect(call[4]).toMatchInteractions([interaction]);
1898 + });
1899 + }
1900 + });
1901 +
1902 + describe(`onPostCommit enableSchedulerTracing:${
1903 + enableSchedulerTracing ? 'enabled' : 'disabled'
1904 + } deferPassiveEffectCleanupDuringUnmount:${
1905 + deferPassiveEffectCleanupDuringUnmount ? 'enabled' : 'disabled'
1906 + }`, () => {
1907 + beforeEach(() => {
1908 + jest.resetModules();
1909 +
1910 + loadModules({
1911 + deferPassiveEffectCleanupDuringUnmount,
1912 + enableSchedulerTracing,
1913 + });
1914 + });
1915 +
1916 + it('should report time spent in passive effects', () => {
1917 + const callback = jest.fn();
1918 +
1919 + const ComponetWithEffects = () => {
1920 + React.useLayoutEffect(() => {
1921 + // This layout effect is here to verify that its time isn't reported.
1922 + Scheduler.unstable_advanceTime(5);
1923 + return () => {
1924 + Scheduler.unstable_advanceTime(7);
1925 + };
1926 + });
1927 + React.useEffect(() => {
1928 + Scheduler.unstable_advanceTime(10);
1929 + return () => {
1930 + Scheduler.unstable_advanceTime(100);
1931 + };
1932 + }, []);
1933 + React.useEffect(() => {
1934 + Scheduler.unstable_advanceTime(1000);
1935 + return () => {
1936 + Scheduler.unstable_advanceTime(10000);
1937 + };
1938 + });
1939 + return null;
1940 + };
1941 +
1942 + Scheduler.unstable_advanceTime(1);
1943 +
1944 + let renderer;
1945 + ReactTestRenderer.act(() => {
1946 + renderer = ReactTestRenderer.create(
1947 + <React.Profiler id="mount-test" onPostCommit={callback}>
1948 + <ComponetWithEffects />
1949 + </React.Profiler>,
1950 + );
1951 + });
1952 + Scheduler.unstable_flushAll();
1953 +
1954 + expect(callback).toHaveBeenCalledTimes(1);
1955 +
1956 + let call = callback.mock.calls[0];
1957 +
1958 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1959 + expect(call[0]).toBe('mount-test');
1960 + expect(call[1]).toBe('mount');
1961 + expect(call[2]).toBe(1010); // durations
1962 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
1963 + expect(call[4]).toEqual(
1964 + enableSchedulerTracing ? new Set() : undefined,
1965 + ); // interaction events
1966 +
1967 + Scheduler.unstable_advanceTime(1);
1968 +
1969 + ReactTestRenderer.act(() => {
1970 + renderer.update(
1971 + <React.Profiler id="update-test" onPostCommit={callback}>
1972 + <ComponetWithEffects />
1973 + </React.Profiler>,
1974 + );
1975 + });
1976 + Scheduler.unstable_flushAll();
1977
738 - // Flush the remaining work,
739 - // Which should take an additional 10ms of simulated time.
740 - expect(Scheduler).toFlushAndYield(['Yield:10', 'Yield:17']);
1978 expect(callback).toHaveBeenCalledTimes(2);
1979
743 - const [innerCall, outerCall] = callback.mock.calls;
744 -
745 - // Verify that the actual time includes all work times,
746 - // But not the time that elapsed between frames.
747 - expect(innerCall[0]).toBe('inner');
748 - expect(innerCall[2]).toBe(17); // actual time
749 - expect(innerCall[3]).toBe(17); // base time
750 - expect(innerCall[4]).toBe(70); // start time
751 - expect(innerCall[5]).toBe(87); // commit time
752 - expect(outerCall[0]).toBe('outer');
753 - expect(outerCall[2]).toBe(32); // actual time
754 - expect(outerCall[3]).toBe(32); // base time
755 - expect(outerCall[4]).toBe(5); // start time
756 - expect(outerCall[5]).toBe(87); // commit time
1980 + call = callback.mock.calls[1];
1981 +
1982 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
1983 + expect(call[0]).toBe('update-test');
1984 + expect(call[1]).toBe('update');
1985 + expect(call[2]).toBe(11000); // durations
1986 + expect(call[3]).toBe(1017); // commit start time (before mutations or effects)
1987 + expect(call[4]).toEqual(
1988 + enableSchedulerTracing ? new Set() : undefined,
1989 + ); // interaction events
1990 +
1991 + Scheduler.unstable_advanceTime(1);
1992 +
1993 + ReactTestRenderer.act(() => {
1994 + renderer.update(
1995 + <React.Profiler id="unmount-test" onPostCommit={callback} />,
1996 + );
1997 + });
1998 + Scheduler.unstable_flushAll();
1999 +
2000 + expect(callback).toHaveBeenCalledTimes(3);
2001 +
2002 + call = callback.mock.calls[2];
2003 +
2004 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2005 + expect(call[0]).toBe('unmount-test');
2006 + expect(call[1]).toBe('update');
2007 + // TODO (bvaughn) The duration reported below should be 10100, but is 0
2008 + // by the time the passive effect is flushed its parent Fiber pointer is gone.
2009 + // If we refactor to preserve the unmounted Fiber tree we could fix this.
2010 + // The current implementation would require too much extra overhead to track this.
2011 + expect(call[2]).toBe(0); // durations
2012 + expect(call[3]).toBe(12030); // commit start time (before mutations or effects)
2013 + expect(call[4]).toEqual(
2014 + enableSchedulerTracing ? new Set() : undefined,
2015 + ); // interaction events
2016 });
2017
759 - it('should report the expected times when a high-pri update replaces a mount in-progress', () => {
2018 + it('should report time spent in passive effects with cascading renders', () => {
2019 const callback = jest.fn();
2020
762 - const Yield = ({renderTime}) => {
763 - Scheduler.unstable_advanceTime(renderTime);
764 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
2021 + const ComponetWithEffects = () => {
2022 + const [didMount, setDidMount] = React.useState(false);
2023 + React.useEffect(() => {
2024 + if (!didMount) {
2025 + setDidMount(true);
2026 + }
2027 + Scheduler.unstable_advanceTime(didMount ? 30 : 10);
2028 + return () => {
2029 + Scheduler.unstable_advanceTime(100);
2030 + };
2031 + }, [didMount]);
2032 return null;
2033 };
2034
768 - Scheduler.unstable_advanceTime(5); // 0 -> 5
769 -
770 - // Render a partially update, but don't finish.
771 - // This partial render should take 10ms of simulated time.
772 - const renderer = ReactTestRenderer.create(
773 - <React.Profiler id="test" onRender={callback}>
774 - <Yield renderTime={10} />
775 - <Yield renderTime={20} />
776 - </React.Profiler>,
777 - {unstable_isConcurrent: true},
778 - );
779 - expect(Scheduler).toFlushAndYieldThrough(['Yield:10']);
780 - expect(callback).toHaveBeenCalledTimes(0);
781 -
782 - // Simulate time moving forward while frame is paused.
783 - Scheduler.unstable_advanceTime(100); // 15 -> 115
2035 + Scheduler.unstable_advanceTime(1);
2036
785 - // Interrupt with higher priority work.
786 - // The interrupted work simulates an additional 5ms of time.
787 - renderer.unstable_flushSync(() => {
788 - renderer.update(
789 - <React.Profiler id="test" onRender={callback}>
790 - <Yield renderTime={5} />
2037 + ReactTestRenderer.act(() => {
2038 + ReactTestRenderer.create(
2039 + <React.Profiler id="mount-test" onPostCommit={callback}>
2040 + <ComponetWithEffects />
2041 </React.Profiler>,
2042 );
2043 });
794 - expect(Scheduler).toHaveYielded(['Yield:5']);
2044
796 - // The initial work was thrown away in this case,
797 - // So the actual and base times should only include the final rendered tree times.
798 - expect(callback).toHaveBeenCalledTimes(1);
799 - let call = callback.mock.calls[0];
800 - expect(call[2]).toBe(5); // actual time
801 - expect(call[3]).toBe(5); // base time
802 - expect(call[4]).toBe(115); // start time
803 - expect(call[5]).toBe(120); // commit time
2045 + expect(callback).toHaveBeenCalledTimes(2);
2046
805 - callback.mockReset();
2047 + let call = callback.mock.calls[0];
2048
807 - // Verify no more unexpected callbacks from low priority work
808 - expect(Scheduler).toFlushWithoutYielding();
809 - expect(callback).toHaveBeenCalledTimes(0);
2049 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2050 + expect(call[0]).toBe('mount-test');
2051 + expect(call[1]).toBe('mount');
2052 + expect(call[2]).toBe(10); // durations
2053 + expect(call[3]).toBe(1); // commit start time (before mutations or effects)
2054 + expect(call[4]).toEqual(
2055 + enableSchedulerTracing ? new Set() : undefined,
2056 + ); // interaction events
2057 +
2058 + call = callback.mock.calls[1];
2059 +
2060 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2061 + expect(call[0]).toBe('mount-test');
2062 + expect(call[1]).toBe('update');
2063 + expect(call[2]).toBe(130); // durations
2064 + expect(call[3]).toBe(11); // commit start time (before mutations or effects)
2065 + expect(call[4]).toEqual(
2066 + enableSchedulerTracing ? new Set() : undefined,
2067 + ); // interaction events
2068 });
2069
812 - it('should report the expected times when a high-priority update replaces a low-priority update', () => {
2070 + it('should bubble time spent in effects to higher profilers', () => {
2071 const callback = jest.fn();
2072
815 - const Yield = ({renderTime}) => {
816 - Scheduler.unstable_advanceTime(renderTime);
817 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
2073 + const ComponetWithEffects = ({
2074 + cleanupDuration,
2075 + duration,
2076 + setCountRef,
2077 + }) => {
2078 + const setCount = React.useState(0)[1];
2079 + if (setCountRef != null) {
2080 + setCountRef.current = setCount;
2081 + }
2082 + React.useEffect(() => {
2083 + Scheduler.unstable_advanceTime(duration);
2084 + return () => {
2085 + Scheduler.unstable_advanceTime(cleanupDuration);
2086 + };
2087 + });
2088 + Scheduler.unstable_advanceTime(1);
2089 return null;
2090 };
2091
821 - Scheduler.unstable_advanceTime(5); // 0 -> 5
2092 + const setCountRef = React.createRef(null);
2093
823 - const renderer = ReactTestRenderer.create(
824 - <React.Profiler id="test" onRender={callback}>
825 - <Yield renderTime={6} />
826 - <Yield renderTime={15} />
827 - </React.Profiler>,
828 - {unstable_isConcurrent: true},
829 - );
2094 + let renderer = null;
2095 + ReactTestRenderer.act(() => {
2096 + renderer = ReactTestRenderer.create(
2097 + <React.Profiler id="root-mount" onPostCommit={callback}>
2098 + <React.Profiler id="a">
2099 + <ComponetWithEffects
2100 + duration={10}
2101 + cleanupDuration={100}
2102 + setCountRef={setCountRef}
2103 + />
2104 + </React.Profiler>
2105 + <React.Profiler id="b">
2106 + <ComponetWithEffects
2107 + duration={1000}
2108 + cleanupDuration={10000}
2109 + />
2110 + </React.Profiler>
2111 + </React.Profiler>,
2112 + );
2113 + });
2114
831 - // Render everything initially.
832 - // This should take 21 seconds of actual and base time.
833 - expect(Scheduler).toFlushAndYield(['Yield:6', 'Yield:15']);
2115 expect(callback).toHaveBeenCalledTimes(1);
835 - let call = callback.mock.calls[0];
836 - expect(call[2]).toBe(21); // actual time
837 - expect(call[3]).toBe(21); // base time
838 - expect(call[4]).toBe(5); // start time
839 - expect(call[5]).toBe(26); // commit time
2116
841 - callback.mockReset();
2117 + let call = callback.mock.calls[0];
2118
843 - Scheduler.unstable_advanceTime(30); // 26 -> 56
2119 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2120 + expect(call[0]).toBe('root-mount');
2121 + expect(call[1]).toBe('mount');
2122 + expect(call[2]).toBe(1010); // durations
2123 + expect(call[3]).toBe(2); // commit start time (before mutations or effects)
2124 + expect(call[4]).toEqual(
2125 + enableSchedulerTracing ? new Set() : undefined,
2126 + ); // interaction events
2127
845 - // Render a partially update, but don't finish.
846 - // This partial render should take 3ms of simulated time.
847 - renderer.update(
848 - <React.Profiler id="test" onRender={callback}>
849 - <Yield renderTime={3} />
850 - <Yield renderTime={5} />
851 - <Yield renderTime={9} />
852 - </React.Profiler>,
853 - );
854 - expect(Scheduler).toFlushAndYieldThrough(['Yield:3']);
855 - expect(callback).toHaveBeenCalledTimes(0);
2128 + ReactTestRenderer.act(() => setCountRef.current(count => count + 1));
2129
857 - // Simulate time moving forward while frame is paused.
858 - Scheduler.unstable_advanceTime(100); // 59 -> 159
2130 + expect(callback).toHaveBeenCalledTimes(2);
2131
860 - // Render another 5ms of simulated time.
861 - expect(Scheduler).toFlushAndYieldThrough(['Yield:5']);
862 - expect(callback).toHaveBeenCalledTimes(0);
2132 + call = callback.mock.calls[1];
2133
864 - // Simulate time moving forward while frame is paused.
865 - Scheduler.unstable_advanceTime(100); // 164 -> 264
2134 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2135 + expect(call[0]).toBe('root-mount');
2136 + expect(call[1]).toBe('update');
2137 + expect(call[2]).toBe(110); // durations
2138 + expect(call[3]).toBe(1013); // commit start time (before mutations or effects)
2139 + expect(call[4]).toEqual(
2140 + enableSchedulerTracing ? new Set() : undefined,
2141 + ); // interaction events
2142
867 - // Interrupt with higher priority work.
868 - // The interrupted work simulates an additional 11ms of time.
869 - renderer.unstable_flushSync(() => {
2143 + ReactTestRenderer.act(() => {
2144 renderer.update(
871 - <React.Profiler id="test" onRender={callback}>
872 - <Yield renderTime={11} />
2145 + <React.Profiler id="root-update" onPostCommit={callback}>
2146 + <React.Profiler id="b">
2147 + <ComponetWithEffects
2148 + duration={1000}
2149 + cleanupDuration={10000}
2150 + />
2151 + </React.Profiler>
2152 </React.Profiler>,
2153 );
2154 });
876 - expect(Scheduler).toHaveYielded(['Yield:11']);
2155
878 - // The actual time should include only the most recent render,
879 - // Because this lets us avoid a lot of commit phase reset complexity.
880 - // The base time includes only the final rendered tree times.
881 - expect(callback).toHaveBeenCalledTimes(1);
882 - call = callback.mock.calls[0];
883 - expect(call[2]).toBe(11); // actual time
884 - expect(call[3]).toBe(11); // base time
885 - expect(call[4]).toBe(264); // start time
886 - expect(call[5]).toBe(275); // commit time
2156 + expect(callback).toHaveBeenCalledTimes(3);
2157
888 - // Verify no more unexpected callbacks from low priority work
889 - expect(Scheduler).toFlushAndYield([]);
890 - expect(callback).toHaveBeenCalledTimes(1);
2158 + call = callback.mock.calls[2];
2159 +
2160 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2161 + expect(call[0]).toBe('root-update');
2162 + expect(call[1]).toBe('update');
2163 + expect(call[2]).toBe(1100); // durations
2164 + expect(call[3]).toBe(1124); // commit start time (before mutations or effects)
2165 + expect(call[4]).toEqual(
2166 + enableSchedulerTracing ? new Set() : undefined,
2167 + ); // interaction events
2168 });
2169
893 - it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
2170 + it('should properly report time in passive effects even when there are errors', () => {
2171 const callback = jest.fn();
2172
896 - const Yield = ({renderTime}) => {
897 - Scheduler.unstable_advanceTime(renderTime);
898 - Scheduler.unstable_yieldValue('Yield:' + renderTime);
899 - return null;
900 - };
901 -
902 - let first;
903 - class FirstComponent extends React.Component {
904 - state = {renderTime: 1};
905 - render() {
906 - first = this;
907 - Scheduler.unstable_advanceTime(this.state.renderTime);
908 - Scheduler.unstable_yieldValue(
909 - 'FirstComponent:' + this.state.renderTime,
910 - );
911 - return <Yield renderTime={4} />;
2173 + class ErrorBoundary extends React.Component {
2174 + state = {error: null};
2175 + static getDerivedStateFromError(error) {
2176 + return {error};
2177 }
913 - }
914 - let second;
915 - class SecondComponent extends React.Component {
916 - state = {renderTime: 2};
2178 render() {
918 - second = this;
919 - Scheduler.unstable_advanceTime(this.state.renderTime);
920 - Scheduler.unstable_yieldValue(
921 - 'SecondComponent:' + this.state.renderTime,
922 - );
923 - return <Yield renderTime={7} />;
2179 + return this.state.error === null
2180 + ? this.props.children
2181 + : this.props.fallback;
2182 }
2183 }
2184
927 - Scheduler.unstable_advanceTime(5); // 0 -> 5
928 -
929 - const renderer = ReactTestRenderer.create(
930 - <React.Profiler id="test" onRender={callback}>
931 - <FirstComponent />
932 - <SecondComponent />
933 - </React.Profiler>,
934 - {unstable_isConcurrent: true},
935 - );
936 -
937 - // Render everything initially.
938 - // This simulates a total of 14ms of actual render time.
939 - // The base render time is also 14ms for the initial render.
940 - expect(Scheduler).toFlushAndYield([
941 - 'FirstComponent:1',
942 - 'Yield:4',
943 - 'SecondComponent:2',
944 - 'Yield:7',
945 - ]);
946 - expect(callback).toHaveBeenCalledTimes(1);
947 - let call = callback.mock.calls[0];
948 - expect(call[2]).toBe(14); // actual time
949 - expect(call[3]).toBe(14); // base time
950 - expect(call[4]).toBe(5); // start time
951 - expect(call[5]).toBe(19); // commit time
952 -
953 - callback.mockClear();
954 -
955 - Scheduler.unstable_advanceTime(100); // 19 -> 119
956 -
957 - // Render a partially update, but don't finish.
958 - // This partial render will take 10ms of actual render time.
959 - first.setState({renderTime: 10});
960 - expect(Scheduler).toFlushAndYieldThrough(['FirstComponent:10']);
961 - expect(callback).toHaveBeenCalledTimes(0);
2185 + const ComponetWithEffects = ({
2186 + cleanupDuration,
2187 + duration,
2188 + effectDuration,
2189 + shouldThrow,
2190 + }) => {
2191 + React.useEffect(() => {
2192 + Scheduler.unstable_advanceTime(effectDuration);
2193 + if (shouldThrow) {
2194 + throw Error('expected');
2195 + }
2196 + return () => {
2197 + Scheduler.unstable_advanceTime(cleanupDuration);
2198 + };
2199 + });
2200 + Scheduler.unstable_advanceTime(duration);
2201 + return null;
2202 + };
2203
963 - // Simulate time moving forward while frame is paused.
964 - Scheduler.unstable_advanceTime(100); // 129 -> 229
2204 + Scheduler.unstable_advanceTime(1);
2205
966 - // Interrupt with higher priority work.
967 - // This simulates a total of 37ms of actual render time.
968 - renderer.unstable_flushSync(() => second.setState({renderTime: 30}));
969 - expect(Scheduler).toHaveYielded(['SecondComponent:30', 'Yield:7']);
2206 + // Test an error that happens during an effect
2207
971 - // The actual time should include only the most recent render (37ms),
972 - // Because this greatly simplifies the commit phase logic.
973 - // The base time should include the more recent times for the SecondComponent subtree,
974 - // As well as the original times for the FirstComponent subtree.
975 - expect(callback).toHaveBeenCalledTimes(1);
976 - call = callback.mock.calls[0];
977 - expect(call[2]).toBe(37); // actual time
978 - expect(call[3]).toBe(42); // base time
979 - expect(call[4]).toBe(229); // start time
980 - expect(call[5]).toBe(266); // commit time
2208 + ReactTestRenderer.act(() => {
2209 + ReactTestRenderer.create(
2210 + <React.Profiler id="root" onPostCommit={callback}>
2211 + <ErrorBoundary
2212 + fallback={
2213 + <ComponetWithEffects
2214 + duration={10000000}
2215 + effectDuration={100000000}
2216 + cleanupDuration={1000000000}
2217 + />
2218 + }>
2219 + <ComponetWithEffects
2220 + duration={10}
2221 + effectDuration={100}
2222 + cleanupDuration={1000}
2223 + shouldThrow={true}
2224 + />
2225 + </ErrorBoundary>
2226 + <ComponetWithEffects
2227 + duration={10000}
2228 + effectDuration={100000}
2229 + cleanupDuration={1000000}
2230 + />
2231 + </React.Profiler>,
2232 + );
2233 + });
2234
982 - callback.mockClear();
2235 + expect(callback).toHaveBeenCalledTimes(2);
2236
984 - // Simulate time moving forward while frame is paused.
985 - Scheduler.unstable_advanceTime(100); // 266 -> 366
2237 + let call = callback.mock.calls[0];
2238
987 - // Resume the original low priority update, with rebased state.
988 - // This simulates a total of 14ms of actual render time,
989 - // And does not include the original (interrupted) 10ms.
990 - // The tree contains 42ms of base render time at this point,
991 - // Reflecting the most recent (longer) render durations.
992 - // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.
993 - expect(Scheduler).toFlushAndYield(['FirstComponent:10', 'Yield:4']);
994 - expect(callback).toHaveBeenCalledTimes(1);
995 - call = callback.mock.calls[0];
996 - expect(call[2]).toBe(14); // actual time
997 - expect(call[3]).toBe(51); // base time
998 - expect(call[4]).toBe(366); // start time
999 - expect(call[5]).toBe(380); // commit time
2239 + // Initial render (with error)
2240 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2241 + expect(call[0]).toBe('root');
2242 + expect(call[1]).toBe('mount');
2243 + expect(call[2]).toBe(100100); // durations
2244 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2245 + expect(call[4]).toEqual(
2246 + enableSchedulerTracing ? new Set() : undefined,
2247 + ); // interaction events
2248 +
2249 + call = callback.mock.calls[1];
2250 +
2251 + // Cleanup render from error boundary
2252 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2253 + expect(call[0]).toBe('root');
2254 + expect(call[1]).toBe('update');
2255 + expect(call[2]).toBe(100000000); // durations
2256 + expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)
2257 + expect(call[4]).toEqual(
2258 + enableSchedulerTracing ? new Set() : undefined,
2259 + ); // interaction events
2260 });
2261
1002 - [true, false].forEach(
1003 - replayFailedUnitOfWorkWithInvokeGuardedCallback => {
1004 - describe(`replayFailedUnitOfWorkWithInvokeGuardedCallback ${
1005 - replayFailedUnitOfWorkWithInvokeGuardedCallback
1006 - ? 'enabled'
1007 - : 'disabled'
1008 - }`, () => {
1009 - beforeEach(() => {
1010 - jest.resetModules();
1011 -
1012 - loadModules({
1013 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
1014 - });
1015 - });
1016 -
1017 - it('should accumulate actual time after an error handled by componentDidCatch()', () => {
1018 - const callback = jest.fn();
2262 + it('should properly report time in passive effect cleanup functions even when there are errors', () => {
2263 + const callback = jest.fn();
2264
1020 - const ThrowsError = () => {
1021 - Scheduler.unstable_advanceTime(3);
1022 - throw Error('expected error');
1023 - };
2265 + class ErrorBoundary extends React.Component {
2266 + state = {error: null};
2267 + static getDerivedStateFromError(error) {
2268 + return {error};
2269 + }
2270 + render() {
2271 + return this.state.error === null
2272 + ? this.props.children
2273 + : this.props.fallback;
2274 + }
2275 + }
2276
1025 - class ErrorBoundary extends React.Component {
1026 - state = {error: null};
1027 - componentDidCatch(error) {
1028 - this.setState({error});
1029 - }
1030 - render() {
1031 - Scheduler.unstable_advanceTime(2);
1032 - return this.state.error === null ? (
1033 - this.props.children
1034 - ) : (
1035 - <AdvanceTime byAmount={20} />
1036 - );
1037 - }
2277 + const ComponetWithEffects = ({
2278 + cleanupDuration,
2279 + duration,
2280 + effectDuration,
2281 + shouldThrow = false,
2282 + id,
2283 + }) => {
2284 + React.useEffect(() => {
2285 + Scheduler.unstable_advanceTime(effectDuration);
2286 + return () => {
2287 + Scheduler.unstable_advanceTime(cleanupDuration);
2288 + if (shouldThrow) {
2289 + throw Error('expected');
2290 }
2291 + };
2292 + });
2293 + Scheduler.unstable_advanceTime(duration);
2294 + return null;
2295 + };
2296
1040 - Scheduler.unstable_advanceTime(5); // 0 -> 5
1041 -
1042 - ReactTestRenderer.create(
1043 - <React.Profiler id="test" onRender={callback}>
1044 - <ErrorBoundary>
1045 - <AdvanceTime byAmount={9} />
1046 - <ThrowsError />
1047 - </ErrorBoundary>
1048 - </React.Profiler>,
1049 - );
2297 + Scheduler.unstable_advanceTime(1);
2298
1051 - expect(callback).toHaveBeenCalledTimes(2);
1052 -
1053 - // Callbacks bubble (reverse order).
1054 - let [mountCall, updateCall] = callback.mock.calls;
1055 -
1056 - // The initial mount only includes the ErrorBoundary (which takes 2)
1057 - // But it spends time rendering all of the failed subtree also.
1058 - expect(mountCall[1]).toBe('mount');
1059 - // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)
1060 - // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1061 - expect(mountCall[2]).toBe(14);
1062 - // base time includes: 2 (ErrorBoundary)
1063 - // Since the tree is empty for the initial commit
1064 - expect(mountCall[3]).toBe(2);
1065 - // start time
1066 - expect(mountCall[4]).toBe(5);
1067 - // commit time: 5 initially + 14 of work
1068 - // Add an additional 3 (ThrowsError) if we replayed the failed work
1069 - expect(mountCall[5]).toBe(
1070 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1071 - ? 22
1072 - : 19,
1073 - );
2299 + let renderer = null;
2300
1075 - // The update includes the ErrorBoundary and its fallback child
1076 - expect(updateCall[1]).toBe('update');
1077 - // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1078 - expect(updateCall[2]).toBe(22);
1079 - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1080 - expect(updateCall[3]).toBe(22);
1081 - // start time
1082 - expect(updateCall[4]).toBe(
1083 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1084 - ? 22
1085 - : 19,
1086 - );
1087 - // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)
1088 - // Add an additional 3 (ThrowsError) if we replayed the failed work
1089 - expect(updateCall[5]).toBe(
1090 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1091 - ? 44
1092 - : 41,
1093 - );
1094 - });
2301 + ReactTestRenderer.act(() => {
2302 + renderer = ReactTestRenderer.create(
2303 + <React.Profiler id="root" onPostCommit={callback}>
2304 + <ErrorBoundary
2305 + fallback={
2306 + <ComponetWithEffects
2307 + duration={10000000}
2308 + effectDuration={100000000}
2309 + cleanupDuration={1000000000}
2310 + />
2311 + }>
2312 + <ComponetWithEffects
2313 + duration={10}
2314 + effectDuration={100}
2315 + cleanupDuration={1000}
2316 + shouldThrow={true}
2317 + />
2318 + </ErrorBoundary>
2319 + <ComponetWithEffects
2320 + duration={10000}
2321 + effectDuration={100000}
2322 + cleanupDuration={1000000}
2323 + />
2324 + </React.Profiler>,
2325 + );
2326 + });
2327
1096 - it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
1097 - const callback = jest.fn();
2328 + expect(callback).toHaveBeenCalledTimes(1);
2329
1099 - const ThrowsError = () => {
1100 - Scheduler.unstable_advanceTime(10);
1101 - throw Error('expected error');
1102 - };
2330 + let call = callback.mock.calls[0];
2331
1104 - class ErrorBoundary extends React.Component {
1105 - state = {error: null};
1106 - static getDerivedStateFromError(error) {
1107 - return {error};
1108 - }
1109 - render() {
1110 - Scheduler.unstable_advanceTime(2);
1111 - return this.state.error === null ? (
1112 - this.props.children
1113 - ) : (
1114 - <AdvanceTime byAmount={20} />
1115 - );
1116 - }
1117 - }
2332 + // Initial render
2333 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2334 + expect(call[0]).toBe('root');
2335 + expect(call[1]).toBe('mount');
2336 + expect(call[2]).toBe(100100); // durations
2337 + expect(call[3]).toBe(10011); // commit start time (before mutations or effects)
2338 + expect(call[4]).toEqual(
2339 + enableSchedulerTracing ? new Set() : undefined,
2340 + ); // interaction events
2341
1119 - Scheduler.unstable_advanceTime(5); // 0 -> 5
2342 + callback.mockClear();
2343
1121 - ReactTestRenderer.create(
1122 - <React.Profiler id="test" onRender={callback}>
1123 - <ErrorBoundary>
1124 - <AdvanceTime byAmount={5} />
1125 - <ThrowsError />
1126 - </ErrorBoundary>
1127 - </React.Profiler>,
1128 - );
2344 + // Test an error that happens during an cleanup function
2345
1130 - expect(callback).toHaveBeenCalledTimes(1);
1131 -
1132 - // Callbacks bubble (reverse order).
1133 - let [mountCall] = callback.mock.calls;
1134 -
1135 - // The initial mount includes the ErrorBoundary's error state,
1136 - // But it also spends actual time rendering UI that fails and isn't included.
1137 - expect(mountCall[1]).toBe('mount');
1138 - // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)
1139 - // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)
1140 - // We don't count the time spent in replaying the failed unit of work (ThrowsError)
1141 - expect(mountCall[2]).toBe(39);
1142 - // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)
1143 - expect(mountCall[3]).toBe(22);
1144 - // start time
1145 - expect(mountCall[4]).toBe(5);
1146 - // commit time
1147 - expect(mountCall[5]).toBe(
1148 - __DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback
1149 - ? 54
1150 - : 44,
1151 - );
1152 - });
2346 + ReactTestRenderer.act(() => {
2347 + renderer.update(
2348 + <React.Profiler id="root" onPostCommit={callback}>
2349 + <ErrorBoundary
2350 + fallback={
2351 + <ComponetWithEffects
2352 + duration={10000000}
2353 + effectDuration={100000000}
2354 + cleanupDuration={1000000000}
2355 + />
2356 + }>
2357 + <ComponetWithEffects
2358 + duration={10}
2359 + effectDuration={100}
2360 + cleanupDuration={1000}
2361 + shouldThrow={false}
2362 + />
2363 + </ErrorBoundary>
2364 + <ComponetWithEffects
2365 + duration={10000}
2366 + effectDuration={100000}
2367 + cleanupDuration={1000000}
2368 + />
2369 + </React.Profiler>,
2370 + );
2371 + });
2372
1154 - it('should reset the fiber stack correct after a "complete" phase error', () => {
1155 - jest.resetModules();
2373 + expect(callback).toHaveBeenCalledTimes(2);
2374
1157 - loadModules({
1158 - useNoopRenderer: true,
1159 - replayFailedUnitOfWorkWithInvokeGuardedCallback,
1160 - });
2375 + call = callback.mock.calls[0];
2376
1162 - // Simulate a renderer error during the "complete" phase.
1163 - // This mimics behavior like React Native's View/Text nesting validation.
1164 - ReactNoop.render(
1165 - <React.Profiler id="profiler" onRender={jest.fn()}>
1166 - <errorInCompletePhase>hi</errorInCompletePhase>
1167 - </React.Profiler>,
1168 - );
1169 - expect(Scheduler).toFlushAndThrow('Error in host config.');
2377 + // Update (that throws)
2378 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2379 + expect(call[0]).toBe('root');
2380 + expect(call[1]).toBe('update');
2381 + // The duration varies because the flushing behavior varies when this flag is on.
2382 + // We continue flushing pending effects even if one throws.
2383 + expect(call[2]).toBe(
2384 + deferPassiveEffectCleanupDuringUnmount ? 1101100 : 1101000,
2385 + ); // durations
2386 + expect(call[3]).toBe(120121); // commit start time (before mutations or effects)
2387 + expect(call[4]).toEqual(
2388 + enableSchedulerTracing ? new Set() : undefined,
2389 + ); // interaction events
2390 +
2391 + call = callback.mock.calls[1];
2392 +
2393 + // Cleanup render from error boundary
2394 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2395 + expect(call[0]).toBe('root');
2396 + expect(call[1]).toBe('update');
2397 + expect(call[2]).toBe(100000000); // durations
2398 + // The commit time varies because the above duration time varies
2399 + expect(call[3]).toBe(
2400 + deferPassiveEffectCleanupDuringUnmount ? 11221221 : 11221121,
2401 + ); // commit start time (before mutations or effects)
2402 + expect(call[4]).toEqual(
2403 + enableSchedulerTracing ? new Set() : undefined,
2404 + ); // interaction events
2405 + });
2406
1171 - // A similar case we've seen caused by an invariant in ReactDOM.
1172 - // It didn't reproduce without a host component inside.
1173 - ReactNoop.render(
1174 - <React.Profiler id="profiler" onRender={jest.fn()}>
1175 - <errorInCompletePhase>
1176 - <span>hi</span>
1177 - </errorInCompletePhase>
1178 - </React.Profiler>,
1179 - );
1180 - expect(Scheduler).toFlushAndThrow('Error in host config.');
2407 + if (enableSchedulerTracing) {
2408 + it('should report interactions that were active', () => {
2409 + const callback = jest.fn();
2410
1182 - // So long as the profiler timer's fiber stack is reset correctly,
1183 - // Subsequent renders should not error.
1184 - ReactNoop.render(
1185 - <React.Profiler id="profiler" onRender={jest.fn()}>
1186 - <span>hi</span>
1187 - </React.Profiler>,
1188 - );
1189 - expect(Scheduler).toFlushWithoutYielding();
1190 - });
2411 + const ComponetWithEffects = () => {
2412 + const [didMount, setDidMount] = React.useState(false);
2413 + React.useEffect(() => {
2414 + Scheduler.unstable_advanceTime(didMount ? 1000 : 100);
2415 + if (!didMount) {
2416 + setDidMount(true);
2417 + }
2418 + return () => {
2419 + Scheduler.unstable_advanceTime(10000);
2420 + };
2421 + }, [didMount]);
2422 + Scheduler.unstable_advanceTime(10);
2423 + return null;
2424 + };
2425 +
2426 + const interaction = {
2427 + id: 0,
2428 + name: 'mount',
2429 + timestamp: Scheduler.unstable_now(),
2430 + };
2431 +
2432 + Scheduler.unstable_advanceTime(1);
2433 +
2434 + ReactTestRenderer.act(() => {
2435 + SchedulerTracing.unstable_trace(
2436 + interaction.name,
2437 + interaction.timestamp,
2438 + () => {
2439 + ReactTestRenderer.create(
2440 + <React.Profiler id="root" onPostCommit={callback}>
2441 + <ComponetWithEffects />
2442 + </React.Profiler>,
2443 + );
2444 + },
2445 + );
2446 });
1192 - },
1193 - );
1194 - });
1195 -
1196 - it('reflects the most recently rendered id value', () => {
1197 - const callback = jest.fn();
1198 -
1199 - Scheduler.unstable_advanceTime(5); // 0 -> 5
1200 -
1201 - const renderer = ReactTestRenderer.create(
1202 - <React.Profiler id="one" onRender={callback}>
1203 - <AdvanceTime byAmount={2} />
1204 - </React.Profiler>,
1205 - );
1206 -
1207 - expect(callback).toHaveBeenCalledTimes(1);
2447
1209 - Scheduler.unstable_advanceTime(20); // 7 -> 27
2448 + expect(callback).toHaveBeenCalledTimes(2);
2449
1211 - renderer.update(
1212 - <React.Profiler id="two" onRender={callback}>
1213 - <AdvanceTime byAmount={1} />
1214 - </React.Profiler>,
1215 - );
1216 -
1217 - expect(callback).toHaveBeenCalledTimes(2);
1218 -
1219 - const [mountCall, updateCall] = callback.mock.calls;
2450 + let call = callback.mock.calls[0];
2451
1221 - expect(mountCall[0]).toBe('one');
1222 - expect(mountCall[1]).toBe('mount');
1223 - expect(mountCall[2]).toBe(2); // actual time
1224 - expect(mountCall[3]).toBe(2); // base time
1225 - expect(mountCall[4]).toBe(5); // start time
2452 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2453 + expect(call[0]).toBe('root');
2454 + expect(call[1]).toBe('mount');
2455 + expect(call[4]).toMatchInteractions([interaction]);
2456
1227 - expect(updateCall[0]).toBe('two');
1228 - expect(updateCall[1]).toBe('update');
1229 - expect(updateCall[2]).toBe(1); // actual time
1230 - expect(updateCall[3]).toBe(1); // base time
1231 - expect(updateCall[4]).toBe(27); // start time
1232 - });
1233 -
1234 - it('should not be called until after mutations', () => {
1235 - let classComponentMounted = false;
1236 - const callback = jest.fn(
1237 - (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
1238 - // Don't call this hook until after mutations
1239 - expect(classComponentMounted).toBe(true);
1240 - // But the commit time should reflect pre-mutation
1241 - expect(commitTime).toBe(2);
1242 - },
1243 - );
2457 + call = callback.mock.calls[1];
2458
1245 - class ClassComponent extends React.Component {
1246 - componentDidMount() {
1247 - Scheduler.unstable_advanceTime(5);
1248 - classComponentMounted = true;
1249 - }
1250 - render() {
1251 - Scheduler.unstable_advanceTime(2);
1252 - return null;
1253 - }
2459 + expect(call).toHaveLength(enableSchedulerTracing ? 5 : 4);
2460 + expect(call[0]).toBe('root');
2461 + expect(call[1]).toBe('update');
2462 + expect(call[4]).toMatchInteractions([interaction]);
2463 + });
2464 }
1255 -
1256 - ReactTestRenderer.create(
1257 - <React.Profiler id="test" onRender={callback}>
1258 - <ClassComponent />
1259 - </React.Profiler>,
1260 - );
1261 -
1262 - expect(callback).toHaveBeenCalledTimes(1);
2465 });
2466 });
2467 });
@@ -1540,14 +2742,16 @@ describe('Profiler', () => {
2742 timestamp: Scheduler.unstable_now(),
2743 };
2744
1543 - const onRender = jest.fn();
2745 + const onPostCommit = jest.fn(() => {
2746 + Scheduler.unstable_yieldValue('onPostCommit');
2747 + });
2748 let renderer;
2749 SchedulerTracing.unstable_trace(
2750 interactionCreation.name,
2751 Scheduler.unstable_now(),
2752 () => {
2753 renderer = ReactTestRenderer.create(
1550 - <React.Profiler id="test-profiler" onRender={onRender}>
2754 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2755 <Example />
2756 </React.Profiler>,
2757 {
@@ -1578,13 +2782,13 @@ describe('Profiler', () => {
2782 expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2783
2784 // Mount
1581 - expect(Scheduler).toFlushAndYield(['first', 'last']);
1582 - expect(onRender).toHaveBeenCalledTimes(1);
1583 - let call = onRender.mock.calls[0];
2785 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2786 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2787 + let call = onPostCommit.mock.calls[0];
2788 expect(call[0]).toEqual('test-profiler');
1585 - expect(call[5]).toEqual(Scheduler.unstable_now());
2789 + expect(call[3]).toEqual(Scheduler.unstable_now());
2790 if (ReactFeatureFlags.enableSchedulerTracing) {
1587 - expect(call[6]).toMatchInteractions([interactionCreation]);
2791 + expect(call[4]).toMatchInteractions([interactionCreation]);
2792 }
2793
2794 expect(onInteractionTraced).toHaveBeenCalledTimes(1);
@@ -1601,7 +2805,7 @@ describe('Profiler', () => {
2805 interactionCreation,
2806 ]);
2807
1604 - onRender.mockClear();
2808 + onPostCommit.mockClear();
2809 onWorkScheduled.mockClear();
2810 onWorkStarted.mockClear();
2811 onWorkStopped.mockClear();
@@ -1639,7 +2843,7 @@ describe('Profiler', () => {
2843 expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2844
2845 expect(Scheduler).toFlushAndYieldThrough(['first']);
1642 - expect(onRender).not.toHaveBeenCalled();
2846 + expect(onPostCommit).not.toHaveBeenCalled();
2847
2848 expect(onInteractionTraced).toHaveBeenCalledTimes(2);
2849 expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
@@ -1652,14 +2856,14 @@ describe('Profiler', () => {
2856 ).toMatchInteractions([interactionOne]);
2857 expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2858
1655 - expect(Scheduler).toFlushAndYield(['last']);
1656 - expect(onRender).toHaveBeenCalledTimes(1);
2859 + expect(Scheduler).toFlushAndYield(['last', 'onPostCommit']);
2860 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2861
1658 - call = onRender.mock.calls[0];
2862 + call = onPostCommit.mock.calls[0];
2863 expect(call[0]).toEqual('test-profiler');
1660 - expect(call[5]).toEqual(Scheduler.unstable_now());
2864 + expect(call[3]).toEqual(Scheduler.unstable_now());
2865 if (ReactFeatureFlags.enableSchedulerTracing) {
1662 - expect(call[6]).toMatchInteractions([interactionOne]);
2866 + expect(call[4]).toMatchInteractions([interactionOne]);
2867 }
2868
2869 didRunCallback = true;
@@ -1677,7 +2881,7 @@ describe('Profiler', () => {
2881
2882 expect(didRunCallback).toBe(true);
2883
1680 - onRender.mockClear();
2884 + onPostCommit.mockClear();
2885 onWorkScheduled.mockClear();
2886 onWorkStarted.mockClear();
2887 onWorkStopped.mockClear();
@@ -1686,14 +2890,14 @@ describe('Profiler', () => {
2890
2891 // Verify that updating state again does not re-log our interaction.
2892 instance.setState({count: 3});
1689 - expect(Scheduler).toFlushAndYield(['first', 'last']);
2893 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2894
1691 - expect(onRender).toHaveBeenCalledTimes(1);
1692 - call = onRender.mock.calls[0];
2895 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2896 + call = onPostCommit.mock.calls[0];
2897 expect(call[0]).toEqual('test-profiler');
1694 - expect(call[5]).toEqual(Scheduler.unstable_now());
2898 + expect(call[3]).toEqual(Scheduler.unstable_now());
2899 if (ReactFeatureFlags.enableSchedulerTracing) {
1696 - expect(call[6]).toMatchInteractions([]);
2900 + expect(call[4]).toMatchInteractions([]);
2901 }
2902
2903 expect(onInteractionTraced).toHaveBeenCalledTimes(2);
@@ -1704,7 +2908,7 @@ describe('Profiler', () => {
2908 expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
2909 expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
2910
1707 - onRender.mockClear();
2911 + onPostCommit.mockClear();
2912
2913 Scheduler.unstable_advanceTime(3);
2914
@@ -1719,7 +2923,7 @@ describe('Profiler', () => {
2923 Scheduler.unstable_now(),
2924 () => {
2925 renderer.update(
1722 - <React.Profiler id="test-profiler" onRender={onRender}>
2926 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
2927 <Example />
2928 </React.Profiler>,
2929 );
@@ -1746,14 +2950,14 @@ describe('Profiler', () => {
2950 ]);
2951 expect(onWorkScheduled.mock.calls[0][1] > 0).toBe(true);
2952
1749 - expect(Scheduler).toFlushAndYield(['first', 'last']);
2953 + expect(Scheduler).toFlushAndYield(['first', 'last', 'onPostCommit']);
2954
1751 - expect(onRender).toHaveBeenCalledTimes(1);
1752 - call = onRender.mock.calls[0];
2955 + expect(onPostCommit).toHaveBeenCalledTimes(1);
2956 + call = onPostCommit.mock.calls[0];
2957 expect(call[0]).toEqual('test-profiler');
1754 - expect(call[5]).toEqual(Scheduler.unstable_now());
2958 + expect(call[3]).toEqual(Scheduler.unstable_now());
2959 if (ReactFeatureFlags.enableSchedulerTracing) {
1756 - expect(call[6]).toMatchInteractions([interactionTwo]);
2960 + expect(call[4]).toMatchInteractions([interactionTwo]);
2961 }
2962
2963 expect(onInteractionTraced).toHaveBeenCalledTimes(3);
@@ -1771,8 +2975,51 @@ describe('Profiler', () => {
2975 ]);
2976 });
2977
2978 + it('should not mark an interaction complete while passive effects are outstanding', () => {
2979 + const onCommit = jest.fn();
2980 + const onPostCommit = jest.fn(() => {
2981 + Scheduler.unstable_yieldValue('onPostCommit');
2982 + });
2983 +
2984 + const ComponetWithEffects = () => {
2985 + React.useEffect(() => {
2986 + Scheduler.unstable_yieldValue('passive effect');
2987 + });
2988 + React.useLayoutEffect(() => {
2989 + Scheduler.unstable_yieldValue('layout effect');
2990 + });
2991 + Scheduler.unstable_yieldValue('render');
2992 + return null;
2993 + };
2994 +
2995 + SchedulerTracing.unstable_trace('mount', Scheduler.unstable_now(), () => {
2996 + ReactTestRenderer.create(
2997 + <React.Profiler
2998 + id="root"
2999 + onCommit={onCommit}
3000 + onPostCommit={onPostCommit}>
3001 + <ComponetWithEffects />
3002 + </React.Profiler>,
3003 + );
3004 + });
3005 +
3006 + expect(Scheduler).toHaveYielded(['render', 'layout effect']);
3007 +
3008 + expect(onCommit).toHaveBeenCalled();
3009 + expect(onPostCommit).not.toHaveBeenCalled();
3010 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3011 +
3012 + expect(Scheduler).toFlushAndYield(['passive effect', 'onPostCommit']);
3013 +
3014 + expect(onCommit).toHaveBeenCalled();
3015 + expect(onPostCommit).toHaveBeenCalled();
3016 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3017 + });
3018 +
3019 it('should report the expected times when a high-priority update interrupts a low-priority update', () => {
1775 - const onRender = jest.fn();
3020 + const onPostCommit = jest.fn(() => {
3021 + Scheduler.unstable_yieldValue('onPostCommit');
3022 + });
3023
3024 let first;
3025 class FirstComponent extends React.Component {
@@ -1796,7 +3043,7 @@ describe('Profiler', () => {
3043 Scheduler.unstable_advanceTime(5);
3044
3045 const renderer = ReactTestRenderer.create(
1799 - <React.Profiler id="test" onRender={onRender}>
3046 + <React.Profiler id="test" onPostCommit={onPostCommit}>
3047 <FirstComponent />
3048 <SecondComponent />
3049 </React.Profiler>,
@@ -1804,12 +3051,16 @@ describe('Profiler', () => {
3051 );
3052
3053 // Initial mount.
1807 - expect(Scheduler).toFlushAndYield(['FirstComponent', 'SecondComponent']);
3054 + expect(Scheduler).toFlushAndYield([
3055 + 'FirstComponent',
3056 + 'SecondComponent',
3057 + 'onPostCommit',
3058 + ]);
3059
3060 expect(onInteractionTraced).not.toHaveBeenCalled();
3061 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3062
1812 - onRender.mockClear();
3063 + onPostCommit.mockClear();
3064
3065 Scheduler.unstable_advanceTime(100);
3066
@@ -1832,7 +3083,7 @@ describe('Profiler', () => {
3083 ]);
3084
3085 expect(Scheduler).toFlushAndYieldThrough(['FirstComponent']);
1835 - expect(onRender).not.toHaveBeenCalled();
3086 + expect(onPostCommit).not.toHaveBeenCalled();
3087
3088 expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3089 expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
@@ -1875,7 +3126,13 @@ describe('Profiler', () => {
3126 },
3127 );
3128 });
3129 +
3130 + // Profiler tag causes passive effects to be scheduled,
3131 + // so the interactions are still not completed.
3132 expect(Scheduler).toHaveYielded(['SecondComponent']);
3133 + expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3134 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3135 + expect(Scheduler).toFlushAndYieldThrough(['onPostCommit']);
3136
3137 expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3138 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
@@ -1884,28 +3141,28 @@ describe('Profiler', () => {
3141 ).toHaveBeenLastNotifiedOfInteraction(interactionHighPri);
3142
3143 // Verify the high priority update was associated with the high priority event.
1887 - expect(onRender).toHaveBeenCalledTimes(1);
1888 - let call = onRender.mock.calls[0];
3144 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3145 + let call = onPostCommit.mock.calls[0];
3146 expect(call[0]).toEqual('test');
1890 - expect(call[5]).toEqual(Scheduler.unstable_now());
1891 - expect(call[6]).toMatchInteractions(
3147 + expect(call[3]).toEqual(Scheduler.unstable_now());
3148 + expect(call[4]).toMatchInteractions(
3149 ReactFeatureFlags.enableSchedulerTracing
3150 ? [interactionLowPri, interactionHighPri]
3151 : [],
3152 );
3153
1897 - onRender.mockClear();
3154 + onPostCommit.mockClear();
3155
3156 Scheduler.unstable_advanceTime(100);
3157
3158 // Resume the original low priority update, with rebased state.
3159 // Verify the low priority update was retained.
1903 - expect(Scheduler).toFlushAndYield(['FirstComponent']);
1904 - expect(onRender).toHaveBeenCalledTimes(1);
1905 - call = onRender.mock.calls[0];
3160 + expect(Scheduler).toFlushAndYield(['FirstComponent', 'onPostCommit']);
3161 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3162 + call = onPostCommit.mock.calls[0];
3163 expect(call[0]).toEqual('test');
1907 - expect(call[5]).toEqual(Scheduler.unstable_now());
1908 - expect(call[6]).toMatchInteractions(
3164 + expect(call[3]).toEqual(Scheduler.unstable_now());
3165 + expect(call[4]).toMatchInteractions(
3166 ReactFeatureFlags.enableSchedulerTracing ? [interactionLowPri] : [],
3167 );
3168
@@ -1970,14 +3227,16 @@ describe('Profiler', () => {
3227 };
3228
3229 // Initial mount.
1973 - const onRender = jest.fn();
3230 + const onPostCommit = jest.fn(() => {
3231 + Scheduler.unstable_yieldValue('onPostCommit');
3232 + });
3233 let firstCommitTime = Scheduler.unstable_now();
3234 SchedulerTracing.unstable_trace(
3235 interactionOne.name,
3236 Scheduler.unstable_now(),
3237 () => {
3238 ReactTestRenderer.create(
1980 - <React.Profiler id="test" onRender={onRender}>
3239 + <React.Profiler id="test" onPostCommit={onPostCommit}>
3240 <Example />
3241 </React.Profiler>,
3242 {unstable_isConcurrent: true},
@@ -1993,7 +3252,12 @@ describe('Profiler', () => {
3252 expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3253 expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3254
1996 - expect(Scheduler).toFlushAndYield(['Example:0', 'Example:1']);
3255 + expect(Scheduler).toFlushAndYield([
3256 + 'Example:0',
3257 + 'onPostCommit',
3258 + 'Example:1',
3259 + 'onPostCommit',
3260 + ]);
3261
3262 expect(onInteractionTraced).toHaveBeenCalledTimes(1);
3263 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
@@ -2015,21 +3279,21 @@ describe('Profiler', () => {
3279 interactionOne,
3280 ]);
3281
2018 - expect(onRender).toHaveBeenCalledTimes(2);
2019 - let call = onRender.mock.calls[0];
3282 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3283 + let call = onPostCommit.mock.calls[0];
3284 expect(call[0]).toEqual('test');
2021 - expect(call[5]).toEqual(firstCommitTime);
2022 - expect(call[6]).toMatchInteractions(
3285 + expect(call[3]).toEqual(firstCommitTime);
3286 + expect(call[4]).toMatchInteractions(
3287 ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3288 );
2025 - call = onRender.mock.calls[1];
3289 + call = onPostCommit.mock.calls[1];
3290 expect(call[0]).toEqual('test');
2027 - expect(call[5]).toEqual(Scheduler.unstable_now());
2028 - expect(call[6]).toMatchInteractions(
3291 + expect(call[3]).toEqual(Scheduler.unstable_now());
3292 + expect(call[4]).toMatchInteractions(
3293 ReactFeatureFlags.enableSchedulerTracing ? [interactionOne] : [],
3294 );
3295
2032 - onRender.mockClear();
3296 + onPostCommit.mockClear();
3297
3298 const interactionTwo = {
3299 id: 1,
@@ -2045,7 +3309,7 @@ describe('Profiler', () => {
3309 instance.setState({count: 2});
3310 },
3311 );
2048 - expect(onRender).not.toHaveBeenCalled();
3312 + expect(onPostCommit).not.toHaveBeenCalled();
3313 expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3314 expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
3315 interactionTwo,
@@ -2059,7 +3323,12 @@ describe('Profiler', () => {
3323 // Flush async work (outside of traced scope)
3324 // This will cause an intentional cascading update from did-update
3325 firstCommitTime = Scheduler.unstable_now();
2062 - expect(Scheduler).toFlushAndYield(['Example:2', 'Example:3']);
3326 + expect(Scheduler).toFlushAndYield([
3327 + 'Example:2',
3328 + 'onPostCommit',
3329 + 'Example:3',
3330 + 'onPostCommit',
3331 + ]);
3332
3333 expect(onInteractionTraced).toHaveBeenCalledTimes(2);
3334 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
@@ -2082,21 +3351,21 @@ describe('Profiler', () => {
3351 ]);
3352
3353 // Verify the cascading commit is associated with the origin event
2085 - expect(onRender).toHaveBeenCalledTimes(2);
2086 - call = onRender.mock.calls[0];
3354 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3355 + call = onPostCommit.mock.calls[0];
3356 expect(call[0]).toEqual('test');
2088 - expect(call[5]).toEqual(firstCommitTime);
2089 - expect(call[6]).toMatchInteractions(
3357 + expect(call[3]).toEqual(firstCommitTime);
3358 + expect(call[4]).toMatchInteractions(
3359 ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3360 );
2092 - call = onRender.mock.calls[1];
3361 + call = onPostCommit.mock.calls[1];
3362 expect(call[0]).toEqual('test');
2094 - expect(call[5]).toEqual(Scheduler.unstable_now());
2095 - expect(call[6]).toMatchInteractions(
3363 + expect(call[3]).toEqual(Scheduler.unstable_now());
3364 + expect(call[4]).toMatchInteractions(
3365 ReactFeatureFlags.enableSchedulerTracing ? [interactionTwo] : [],
3366 );
3367
2099 - onRender.mockClear();
3368 + onPostCommit.mockClear();
3369
3370 const interactionThree = {
3371 id: 2,
@@ -2115,7 +3384,7 @@ describe('Profiler', () => {
3384 instance.setState({count: 5}, callback);
3385 },
3386 );
2118 - expect(onRender).not.toHaveBeenCalled();
3387 + expect(onPostCommit).not.toHaveBeenCalled();
3388
3389 expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3390 expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
@@ -2128,7 +3397,12 @@ describe('Profiler', () => {
3397 // Flush async work (outside of traced scope)
3398 // This will cause an intentional cascading update from the setState callback
3399 firstCommitTime = Scheduler.unstable_now();
2131 - expect(Scheduler).toFlushAndYield(['Example:5', 'Example:6']);
3400 + expect(Scheduler).toFlushAndYield([
3401 + 'Example:5',
3402 + 'onPostCommit',
3403 + 'Example:6',
3404 + 'onPostCommit',
3405 + ]);
3406
3407 expect(onInteractionTraced).toHaveBeenCalledTimes(3);
3408 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(3);
@@ -2151,23 +3425,25 @@ describe('Profiler', () => {
3425 ]);
3426
3427 // Verify the cascading commit is associated with the origin event
2154 - expect(onRender).toHaveBeenCalledTimes(2);
2155 - call = onRender.mock.calls[0];
3428 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3429 + call = onPostCommit.mock.calls[0];
3430 expect(call[0]).toEqual('test');
2157 - expect(call[5]).toEqual(firstCommitTime);
2158 - expect(call[6]).toMatchInteractions(
3431 + expect(call[3]).toEqual(firstCommitTime);
3432 + expect(call[4]).toMatchInteractions(
3433 ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3434 );
2161 - call = onRender.mock.calls[1];
3435 + call = onPostCommit.mock.calls[1];
3436 expect(call[0]).toEqual('test');
2163 - expect(call[5]).toEqual(Scheduler.unstable_now());
2164 - expect(call[6]).toMatchInteractions(
3437 + expect(call[3]).toEqual(Scheduler.unstable_now());
3438 + expect(call[4]).toMatchInteractions(
3439 ReactFeatureFlags.enableSchedulerTracing ? [interactionThree] : [],
3440 );
3441 });
3442
3443 it('should trace interactions associated with a parent component state update', () => {
2170 - const onRender = jest.fn();
3444 + const onPostCommit = jest.fn(() => {
3445 + Scheduler.unstable_yieldValue('onPostCommit');
3446 + });
3447 let parentInstance = null;
3448
3449 class Child extends React.Component {
@@ -2184,7 +3460,7 @@ describe('Profiler', () => {
3460 render() {
3461 parentInstance = this;
3462 return (
2187 - <React.Profiler id="test-profiler" onRender={onRender}>
3463 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
3464 <Child count={this.state.count} />
3465 </React.Profiler>
3466 );
@@ -2196,8 +3472,8 @@ describe('Profiler', () => {
3472 ReactTestRenderer.create(<Parent />, {
3473 unstable_isConcurrent: true,
3474 });
2199 - expect(Scheduler).toFlushAndYield(['Child:0']);
2200 - onRender.mockClear();
3475 + expect(Scheduler).toFlushAndYield(['Child:0', 'onPostCommit']);
3476 + onPostCommit.mockClear();
3477
3478 const interaction = {
3479 id: 0,
@@ -2221,12 +3497,12 @@ describe('Profiler', () => {
3497 expect(getWorkForReactThreads(onWorkStarted)).toHaveLength(0);
3498 expect(getWorkForReactThreads(onWorkStopped)).toHaveLength(0);
3499
2224 - expect(onRender).not.toHaveBeenCalled();
2225 - expect(Scheduler).toFlushAndYield(['Child:1']);
2226 - expect(onRender).toHaveBeenCalledTimes(1);
2227 - let call = onRender.mock.calls[0];
3500 + expect(onPostCommit).not.toHaveBeenCalled();
3501 + expect(Scheduler).toFlushAndYield(['Child:1', 'onPostCommit']);
3502 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3503 + let call = onPostCommit.mock.calls[0];
3504 expect(call[0]).toEqual('test-profiler');
2229 - expect(call[6]).toMatchInteractions(
3505 + expect(call[4]).toMatchInteractions(
3506 ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3507 );
3508
@@ -2270,13 +3546,15 @@ describe('Profiler', () => {
3546 }
3547 }
3548
2273 - const onRender = jest.fn();
3549 + const onPostCommit = jest.fn(() => {
3550 + Scheduler.unstable_yieldValue('onPostCommit');
3551 + });
3552 SchedulerTracing.unstable_trace(
3553 interaction.name,
3554 Scheduler.unstable_now(),
3555 () => {
3556 ReactNoop.render(
2279 - <React.Profiler id="test-profiler" onRender={onRender}>
3557 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
3558 <React.Suspense fallback={<Text text="Loading..." />}>
3559 <AsyncText text="Async" ms={20000} />
3560 </React.Suspense>
@@ -2300,14 +3578,15 @@ describe('Profiler', () => {
3578 'Text [Loading...]',
3579 'Text [Sync]',
3580 'Monkey',
3581 + 'onPostCommit',
3582 ]);
3583 // Should have committed the placeholder.
3584 expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
2306 - expect(onRender).toHaveBeenCalledTimes(1);
3585 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3586
2308 - let call = onRender.mock.calls[0];
3587 + let call = onPostCommit.mock.calls[0];
3588 expect(call[0]).toEqual('test-profiler');
2310 - expect(call[6]).toMatchInteractions(
3589 + expect(call[4]).toMatchInteractions(
3590 ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3591 );
3592
@@ -2316,19 +3595,22 @@ describe('Profiler', () => {
3595
3596 // An unrelated update in the middle shouldn't affect things...
3597 monkey.current.forceUpdate();
2319 - expect(Scheduler).toFlushAndYield(['Monkey']);
2320 - expect(onRender).toHaveBeenCalledTimes(2);
3598 + expect(Scheduler).toFlushAndYield(['Monkey', 'onPostCommit']);
3599 + expect(onPostCommit).toHaveBeenCalledTimes(2);
3600
3601 // Once the promise resolves, we render the suspended view
3602 await awaitableAdvanceTimers(20000);
3603 expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
2325 - expect(Scheduler).toFlushAndYield(['AsyncText [Async]']);
3604 + expect(Scheduler).toFlushAndYield([
3605 + 'AsyncText [Async]',
3606 + 'onPostCommit',
3607 + ]);
3608 expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
2327 - expect(onRender).toHaveBeenCalledTimes(3);
3609 + expect(onPostCommit).toHaveBeenCalledTimes(3);
3610
2329 - call = onRender.mock.calls[2];
3611 + call = onPostCommit.mock.calls[2];
3612 expect(call[0]).toEqual('test-profiler');
2331 - expect(call[6]).toMatchInteractions(
3613 + expect(call[4]).toMatchInteractions(
3614 ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
3615 );
3616
@@ -2346,13 +3628,15 @@ describe('Profiler', () => {
3628 timestamp: Scheduler.unstable_now(),
3629 };
3630
2349 - const onRender = jest.fn();
3631 + const onPostCommit = jest.fn(() =>
3632 + Scheduler.unstable_yieldValue('onPostCommit'),
3633 + );
3634 SchedulerTracing.unstable_trace(
3635 interaction.name,
3636 interaction.timestamp,
3637 () => {
3638 ReactTestRenderer.create(
2355 - <React.Profiler id="app" onRender={onRender}>
3639 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3640 <React.Suspense fallback={<Text text="loading" />}>
3641 <AsyncText text="loaded" ms={500} />
3642 </React.Suspense>
@@ -2369,7 +3653,11 @@ describe('Profiler', () => {
3653 await resourcePromise;
3654
3655 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2372 - expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
3656 + expect(Scheduler).toFlushExpired([
3657 + 'onPostCommit',
3658 + 'AsyncText [loaded]',
3659 + ]);
3660 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3661 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3662 expect(
3663 onInteractionScheduledWorkCompleted,
@@ -2390,6 +3678,7 @@ describe('Profiler', () => {
3678 }
3679
3680 render() {
3681 + Scheduler.unstable_yieldValue('render');
3682 const {ms, text} = this.props;
3683 TextResource.read([text, ms]);
3684 return <span prop={text}>{this.state.hasMounted}</span>;
@@ -2402,13 +3691,15 @@ describe('Profiler', () => {
3691 timestamp: Scheduler.unstable_now(),
3692 };
3693
2405 - const onRender = jest.fn();
3694 + const onPostCommit = jest.fn(() =>
3695 + Scheduler.unstable_yieldValue('onPostCommit'),
3696 + );
3697 SchedulerTracing.unstable_trace(
3698 interaction.name,
3699 interaction.timestamp,
3700 () => {
3701 ReactTestRenderer.create(
2411 - <React.Profiler id="app" onRender={onRender}>
3702 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3703 <React.Suspense fallback={<Text text="loading" />}>
3704 <AsyncComponentWithCascadingWork text="loaded" ms={500} />
3705 </React.Suspense>
@@ -2419,17 +3710,19 @@ describe('Profiler', () => {
3710
3711 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3712
2422 - expect(Scheduler).toHaveYielded(['Text [loading]']);
3713 + expect(Scheduler).toHaveYielded(['render', 'Text [loading]']);
3714
3715 jest.runAllTimers();
3716 await resourcePromise;
3717
3718 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2428 - expect(Scheduler).toFlushExpired([]);
3719 + expect(Scheduler).toFlushExpired(['onPostCommit', 'render']);
3720
3721 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3722
3723 wrappedCascadingFn();
3724 + expect(Scheduler).toHaveYielded(['onPostCommit', 'render']);
3725 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3726
3727 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3728 expect(
@@ -2444,13 +3737,15 @@ describe('Profiler', () => {
3737 timestamp: Scheduler.unstable_now(),
3738 };
3739
2447 - const onRender = jest.fn();
3740 + const onPostCommit = jest.fn(() => {
3741 + Scheduler.unstable_yieldValue('onPostCommit');
3742 + });
3743 SchedulerTracing.unstable_trace(
3744 interaction.name,
3745 interaction.timestamp,
3746 () => {
3747 ReactTestRenderer.create(
2453 - <React.Profiler id="app" onRender={onRender}>
3748 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3749 <React.Suspense fallback={<Text text="loading" />}>
3750 <AsyncText text="loaded" ms={500} />
3751 </React.Suspense>
@@ -2468,6 +3763,7 @@ describe('Profiler', () => {
3763 expect(Scheduler).toFlushAndYield([
3764 'Suspend [loaded]',
3765 'Text [loading]',
3766 + 'onPostCommit',
3767 ]);
3768 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
3769
@@ -2475,7 +3771,10 @@ describe('Profiler', () => {
3771 await awaitableAdvanceTimers(500);
3772
3773 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2478 - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
3774 + expect(Scheduler).toFlushAndYield([
3775 + 'AsyncText [loaded]',
3776 + 'onPostCommit',
3777 + ]);
3778 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3779 expect(
3780 onInteractionScheduledWorkCompleted,
@@ -2489,13 +3788,15 @@ describe('Profiler', () => {
3788 timestamp: Scheduler.unstable_now(),
3789 };
3790
2492 - const onRender = jest.fn();
3791 + const onPostCommit = jest.fn(() => {
3792 + Scheduler.unstable_yieldValue('onPostCommit');
3793 + });
3794 SchedulerTracing.unstable_trace(
3795 interaction.name,
3796 interaction.timestamp,
3797 () => {
3798 ReactTestRenderer.create(
2498 - <React.Profiler id="app" onRender={onRender}>
3799 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3800 <React.Suspense fallback={<Text text="loading" />}>
3801 <AsyncText text="loaded" ms={100} />
3802 </React.Suspense>
@@ -2507,6 +3808,7 @@ describe('Profiler', () => {
3808 expect(Scheduler).toFlushAndYield([
3809 'Suspend [loaded]',
3810 'Text [loading]',
3811 + 'onPostCommit',
3812 ]);
3813
3814 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
@@ -2514,7 +3816,10 @@ describe('Profiler', () => {
3816 jest.advanceTimersByTime(100);
3817 await resourcePromise;
3818 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2517 - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
3819 + expect(Scheduler).toFlushAndYield([
3820 + 'AsyncText [loaded]',
3821 + 'onPostCommit',
3822 + ]);
3823
3824 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3825 expect(
@@ -2529,14 +3834,16 @@ describe('Profiler', () => {
3834 timestamp: Scheduler.unstable_now(),
3835 };
3836
2532 - const onRender = jest.fn();
3837 + const onPostCommit = jest.fn(() =>
3838 + Scheduler.unstable_yieldValue('onPostCommit'),
3839 + );
3840 let renderer;
3841 SchedulerTracing.unstable_trace(
3842 initialRenderInteraction.name,
3843 initialRenderInteraction.timestamp,
3844 () => {
3845 renderer = ReactTestRenderer.create(
2539 - <React.Profiler id="app" onRender={onRender}>
3846 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3847 <React.Suspense fallback={<Text text="loading" />}>
3848 <AsyncText text="loaded" ms={100} />
3849 </React.Suspense>
@@ -2546,13 +3853,19 @@ describe('Profiler', () => {
3853 },
3854 );
3855 expect(renderer.toJSON()).toEqual(['loading', 'initial']);
3856 + expect(Scheduler).toHaveYielded([
3857 + 'Suspend [loaded]',
3858 + 'Text [loading]',
3859 + 'Text [initial]',
3860 + ]);
3861 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3862
3863 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2551 - expect(onRender).toHaveBeenCalledTimes(1);
2552 - expect(onRender.mock.calls[0][6]).toMatchInteractions([
3864 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3865 + expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
3866 initialRenderInteraction,
3867 ]);
2555 - onRender.mockClear();
3868 + onPostCommit.mockClear();
3869
3870 const highPriUpdateInteraction = {
3871 id: 1,
@@ -2568,7 +3881,7 @@ describe('Profiler', () => {
3881 highPriUpdateInteraction.timestamp,
3882 () => {
3883 renderer.update(
2571 - <React.Profiler id="app" onRender={onRender}>
3884 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3885 <React.Suspense fallback={<Text text="loading" />}>
3886 <AsyncText text="loaded" ms={100} />
3887 </React.Suspense>
@@ -2578,21 +3891,19 @@ describe('Profiler', () => {
3891 },
3892 );
3893 });
3894 + expect(renderer.toJSON()).toEqual(['loading', 'updated']);
3895 expect(Scheduler).toHaveYielded([
2582 - 'Suspend [loaded]',
2583 - 'Text [loading]',
2584 - 'Text [initial]',
3896 'Suspend [loaded]',
3897 'Text [loading]',
3898 'Text [updated]',
3899 ]);
2589 - expect(renderer.toJSON()).toEqual(['loading', 'updated']);
3900 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3901
2591 - expect(onRender).toHaveBeenCalledTimes(1);
2592 - expect(onRender.mock.calls[0][6]).toMatchInteractions([
3902 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3903 + expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
3904 highPriUpdateInteraction,
3905 ]);
2595 - onRender.mockClear();
3906 + onPostCommit.mockClear();
3907
3908 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
3909 expect(
@@ -2607,9 +3918,10 @@ describe('Profiler', () => {
3918 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
3919 expect(Scheduler).toFlushExpired(['AsyncText [loaded]']);
3920 expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
3921 + expect(Scheduler).toFlushAndYield(['onPostCommit']);
3922
2611 - expect(onRender).toHaveBeenCalledTimes(1);
2612 - expect(onRender.mock.calls[0][6]).toMatchInteractions([
3923 + expect(onPostCommit).toHaveBeenCalledTimes(1);
3924 + expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
3925 initialRenderInteraction,
3926 ]);
3927
@@ -2636,13 +3948,15 @@ describe('Profiler', () => {
3948 timestamp: Scheduler.unstable_now(),
3949 };
3950
2639 - const onRender = jest.fn();
3951 + const onPostCommit = jest.fn(() => {
3952 + Scheduler.unstable_yieldValue('onPostCommit');
3953 + });
3954 SchedulerTracing.unstable_trace(
3955 initialRenderInteraction.name,
3956 initialRenderInteraction.timestamp,
3957 () => {
3958 renderer.update(
2645 - <React.Profiler id="app" onRender={onRender}>
3959 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3960 <React.Suspense fallback={<Text text="loading" />}>
3961 <AsyncText text="loaded" ms={100} />
3962 </React.Suspense>
@@ -2658,7 +3972,7 @@ describe('Profiler', () => {
3972 ]);
3973
3974 expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
2661 - expect(onRender).not.toHaveBeenCalled();
3975 + expect(onPostCommit).not.toHaveBeenCalled();
3976
3977 Scheduler.unstable_advanceTime(50);
3978 jest.advanceTimersByTime(50);
@@ -2677,7 +3991,7 @@ describe('Profiler', () => {
3991 highPriUpdateInteraction.timestamp,
3992 () => {
3993 renderer.update(
2680 - <React.Profiler id="app" onRender={onRender}>
3994 + <React.Profiler id="app" onPostCommit={onPostCommit}>
3995 <React.Suspense fallback={<Text text="loading" />}>
3996 <AsyncText text="loaded" ms={100} />
3997 </React.Suspense>
@@ -2692,13 +4006,14 @@ describe('Profiler', () => {
4006 'Text [loading]',
4007 'Text [updated]',
4008 ]);
4009 + expect(Scheduler).toFlushAndYieldThrough(['onPostCommit']);
4010 expect(renderer.toJSON()).toEqual(['loading', 'updated']);
4011
2697 - expect(onRender).toHaveBeenCalledTimes(1);
2698 - expect(onRender.mock.calls[0][6]).toMatchInteractions([
4012 + expect(onPostCommit).toHaveBeenCalledTimes(1);
4013 + expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
4014 highPriUpdateInteraction,
4015 ]);
2701 - onRender.mockClear();
4016 + onPostCommit.mockClear();
4017
4018 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);
4019
@@ -2706,11 +4021,14 @@ describe('Profiler', () => {
4021 jest.advanceTimersByTime(50);
4022 await originalPromise;
4023 expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
2709 - expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
4024 + expect(Scheduler).toFlushAndYield([
4025 + 'AsyncText [loaded]',
4026 + 'onPostCommit',
4027 + ]);
4028 expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
4029
2712 - expect(onRender).toHaveBeenCalledTimes(1);
2713 - expect(onRender.mock.calls[0][6]).toMatchInteractions([
4030 + expect(onPostCommit).toHaveBeenCalledTimes(1);
4031 + expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([
4032 initialRenderInteraction,
4033 highPriUpdateInteraction,
4034 ]);
@@ -2750,13 +4068,15 @@ describe('Profiler', () => {
4068 }
4069 };
4070
2753 - const onRender = jest.fn();
4071 + const onPostCommit = jest.fn(() => {
4072 + Scheduler.unstable_yieldValue('onPostCommit');
4073 + });
4074 SchedulerTracing.unstable_trace(
4075 interaction.name,
4076 Scheduler.unstable_now(),
4077 () => {
4078 ReactNoop.render(
2759 - <React.Profiler id="test-profiler" onRender={onRender}>
4079 + <React.Profiler id="test-profiler" onPostCommit={onPostCommit}>
4080 <React.Suspense fallback={<Text text="Loading..." />}>
4081 <AsyncText text="Async" ms={20000} />
4082 </React.Suspense>
@@ -2778,14 +4098,15 @@ describe('Profiler', () => {
4098 'Suspend [Async]',
4099 'Text [Loading...]',
4100 'Text [Sync]',
4101 + 'onPostCommit',
4102 ]);
4103 // Should have committed the placeholder.
4104 expect(ReactNoop.getChildrenAsJSX()).toEqual('Loading...Sync');
2784 - expect(onRender).toHaveBeenCalledTimes(1);
4105 + expect(onPostCommit).toHaveBeenCalledTimes(1);
4106
2786 - let call = onRender.mock.calls[0];
4107 + let call = onPostCommit.mock.calls[0];
4108 expect(call[0]).toEqual('test-profiler');
2788 - expect(call[6]).toMatchInteractions(
4109 + expect(call[4]).toMatchInteractions(
4110 ReactFeatureFlags.enableSchedulerTracing ? [interaction] : [],
4111 );
4112
@@ -2799,14 +4120,17 @@ describe('Profiler', () => {
4120 // Once the promise resolves, we render the suspended view
4121 await awaitableAdvanceTimers(20000);
4122 expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
2802 - expect(Scheduler).toFlushAndYield(['AsyncText [Async]']);
4123 + expect(Scheduler).toFlushAndYield([
4124 + 'AsyncText [Async]',
4125 + 'onPostCommit',
4126 + ]);
4127 expect(ReactNoop.getChildrenAsJSX()).toEqual('AsyncSync');
2804 - expect(onRender).toHaveBeenCalledTimes(2);
4128 + expect(onPostCommit).toHaveBeenCalledTimes(2);
4129
4130 // No interactions should be associated with this update.
2807 - call = onRender.mock.calls[1];
4131 + call = onPostCommit.mock.calls[1];
4132 expect(call[0]).toEqual('test-profiler');
2809 - expect(call[6]).toMatchInteractions([]);
4133 + expect(call[4]).toMatchInteractions([]);
4134 });
4135 });
4136 });
packages/shared/ReactFeatureFlags.js
+3
@@ -23,6 +23,9 @@ export const warnAboutDeprecatedLifecycles = true;
23 // Gather advanced timing metrics for Profiler subtrees.
24 export const enableProfilerTimer = __PROFILE__;
25
26 +// Record durations for commit and passive effects phases.
27 +export const enableProfilerCommitHooks = false;
28 +
29 // Trace which interactions trigger each commit.
30 export const enableSchedulerTracing = __PROFILE__;
31
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -15,6 +15,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.native-fb';
15 // The rest of the flags are static for better dead code elimination.
16 export const enableUserTimingAPI = __DEV__;
17 export const enableProfilerTimer = __PROFILE__;
18 +export const enableProfilerCommitHooks = false;
19 export const enableSchedulerTracing = __PROFILE__;
20 export const enableSuspenseServerRenderer = false;
21 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = __DEV__;
17 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
18 export const warnAboutDeprecatedLifecycles = true;
19 export const enableProfilerTimer = __PROFILE__;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = __PROFILE__;
22 export const enableSuspenseServerRenderer = false;
23 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = __DEV__;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
19 export const enableProfilerTimer = __PROFILE__;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = __PROFILE__;
22 export const enableSuspenseServerRenderer = false;
23 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = __DEV__;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
19 export const enableProfilerTimer = __PROFILE__;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = __PROFILE__;
22 export const enableSuspenseServerRenderer = false;
23 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = __DEV__;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
19 export const enableProfilerTimer = __PROFILE__;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = __PROFILE__;
22 export const enableSuspenseServerRenderer = false;
23 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = __DEV__;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
19 export const enableProfilerTimer = __PROFILE__;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = __PROFILE__;
22 export const enableSuspenseServerRenderer = false;
23 export const enableSelectiveHydration = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -17,6 +17,7 @@ export const enableUserTimingAPI = false;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
19 export const enableProfilerTimer = false;
20 +export const enableProfilerCommitHooks = false;
21 export const enableSchedulerTracing = false;
22 export const enableSuspenseServerRenderer = true;
23 export const enableSelectiveHydration = true;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -35,6 +35,7 @@ export const {
35 export let enableUserTimingAPI = __DEV__ && !__EXPERIMENTAL__;
36
37 export const enableProfilerTimer = __PROFILE__;
38 +export const enableProfilerCommitHooks = false;
39 export const enableSchedulerTracing = __PROFILE__;
40 export const enableSchedulerDebugging = true;
41