[Transition Tracing] Add Tracing Markers (#24686)
This PR adds support for Tracing Markers as well as onTracingMarkerComplete
Luna Ruan committed
Jun 27, 2022 at 12:46 UTC
a4bed469697a1e23c398fe80228df31ef6d4b612
14 files changed
+543
-12
packages/react-reconciler/src/ReactFiber.new.js
+6
@@ -19,6 +19,7 @@ import type {
19
OffscreenProps,
20
OffscreenInstance,
21
} from './ReactFiberOffscreenComponent';
22
+import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
23
24
import {
25
createRootStrictEffectsByDefault,
@@ -757,6 +758,11 @@ export function createFiberFromTracingMarker(
758
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
759
fiber.elementType = REACT_TRACING_MARKER_TYPE;
760
fiber.lanes = lanes;
761
+ const tracingMarkerInstance: TracingMarkerInstance = {
762
+ transitions: null,
763
+ pendingSuspenseBoundaries: null,
764
+ };
765
+ fiber.stateNode = tracingMarkerInstance;
766
return fiber;
767
}
768
packages/react-reconciler/src/ReactFiber.old.js
+6
@@ -19,6 +19,7 @@ import type {
19
OffscreenProps,
20
OffscreenInstance,
21
} from './ReactFiberOffscreenComponent';
22
+import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
23
24
import {
25
createRootStrictEffectsByDefault,
@@ -757,6 +758,11 @@ export function createFiberFromTracingMarker(
758
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
759
fiber.elementType = REACT_TRACING_MARKER_TYPE;
760
fiber.lanes = lanes;
761
+ const tracingMarkerInstance: TracingMarkerInstance = {
762
+ transitions: null,
763
+ pendingSuspenseBoundaries: null,
764
+ };
765
+ fiber.stateNode = tracingMarkerInstance;
766
return fiber;
767
}
768
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+27
-4
@@ -35,6 +35,7 @@ import type {
35
} from './ReactFiberCacheComponent.new';
36
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
37
import type {RootState} from './ReactFiberRoot.new';
38
+import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
39
import {
40
enableSuspenseAvoidThisFallback,
41
enableCPUSuspense,
@@ -255,9 +256,12 @@ import {
256
getSuspendedCache,
257
pushTransition,
258
getOffscreenDeferredCache,
258
- getSuspendedTransitions,
259
+ getPendingTransitions,
260
} from './ReactFiberTransition.new';
260
-import {pushTracingMarker} from './ReactFiberTracingMarkerComponent.new';
261
+import {
262
+ getTracingMarkers,
263
+ pushTracingMarker,
264
+} from './ReactFiberTracingMarkerComponent.new';
265
266
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
267
@@ -891,6 +895,20 @@ function updateTracingMarkerComponent(
895
return null;
896
}
897
898
+ // TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
899
+ // A tracing marker is only associated with the transitions that rendered
900
+ // or updated it, so we can create a new set of transitions each time
901
+ if (current === null) {
902
+ const currentTransitions = getPendingTransitions();
903
+ if (currentTransitions !== null) {
904
+ const markerInstance: TracingMarkerInstance = {
905
+ transitions: new Set(currentTransitions),
906
+ pendingSuspenseBoundaries: new Map(),
907
+ };
908
+ workInProgress.stateNode = markerInstance;
909
+ }
910
+ }
911
+
912
pushTracingMarker(workInProgress);
913
const nextChildren = workInProgress.pendingProps.children;
914
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
@@ -2093,10 +2111,13 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2111
);
2112
workInProgress.memoizedState = SUSPENDED_MARKER;
2113
if (enableTransitionTracing) {
2096
- const currentTransitions = getSuspendedTransitions();
2114
+ const currentTransitions = getPendingTransitions();
2115
if (currentTransitions !== null) {
2116
+ // If there are no transitions, we don't need to keep track of tracing markers
2117
+ const currentTracingMarkers = getTracingMarkers();
2118
const primaryChildUpdateQueue: OffscreenQueue = {
2119
transitions: currentTransitions,
2120
+ tracingMarkers: currentTracingMarkers,
2121
};
2122
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
2123
}
@@ -2177,10 +2198,12 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2198
? mountSuspenseOffscreenState(renderLanes)
2199
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
2200
if (enableTransitionTracing) {
2180
- const currentTransitions = getSuspendedTransitions();
2201
+ const currentTransitions = getPendingTransitions();
2202
if (currentTransitions !== null) {
2203
+ const currentTracingMarkers = getTracingMarkers();
2204
const primaryChildUpdateQueue: OffscreenQueue = {
2205
transitions: currentTransitions,
2206
+ tracingMarkers: currentTracingMarkers,
2207
};
2208
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
2209
}
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+27
-4
@@ -35,6 +35,7 @@ import type {
35
} from './ReactFiberCacheComponent.old';
36
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
37
import type {RootState} from './ReactFiberRoot.old';
38
+import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
39
import {
40
enableSuspenseAvoidThisFallback,
41
enableCPUSuspense,
@@ -255,9 +256,12 @@ import {
256
getSuspendedCache,
257
pushTransition,
258
getOffscreenDeferredCache,
258
- getSuspendedTransitions,
259
+ getPendingTransitions,
260
} from './ReactFiberTransition.old';
260
-import {pushTracingMarker} from './ReactFiberTracingMarkerComponent.old';
261
+import {
262
+ getTracingMarkers,
263
+ pushTracingMarker,
264
+} from './ReactFiberTracingMarkerComponent.old';
265
266
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
267
@@ -891,6 +895,20 @@ function updateTracingMarkerComponent(
895
return null;
896
}
897
898
+ // TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
899
+ // A tracing marker is only associated with the transitions that rendered
900
+ // or updated it, so we can create a new set of transitions each time
901
+ if (current === null) {
902
+ const currentTransitions = getPendingTransitions();
903
+ if (currentTransitions !== null) {
904
+ const markerInstance: TracingMarkerInstance = {
905
+ transitions: new Set(currentTransitions),
906
+ pendingSuspenseBoundaries: new Map(),
907
+ };
908
+ workInProgress.stateNode = markerInstance;
909
+ }
910
+ }
911
+
912
pushTracingMarker(workInProgress);
913
const nextChildren = workInProgress.pendingProps.children;
914
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
@@ -2093,10 +2111,13 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2111
);
2112
workInProgress.memoizedState = SUSPENDED_MARKER;
2113
if (enableTransitionTracing) {
2096
- const currentTransitions = getSuspendedTransitions();
2114
+ const currentTransitions = getPendingTransitions();
2115
if (currentTransitions !== null) {
2116
+ // If there are no transitions, we don't need to keep track of tracing markers
2117
+ const currentTracingMarkers = getTracingMarkers();
2118
const primaryChildUpdateQueue: OffscreenQueue = {
2119
transitions: currentTransitions,
2120
+ tracingMarkers: currentTracingMarkers,
2121
};
2122
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
2123
}
@@ -2177,10 +2198,12 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2198
? mountSuspenseOffscreenState(renderLanes)
2199
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
2200
if (enableTransitionTracing) {
2180
- const currentTransitions = getSuspendedTransitions();
2201
+ const currentTransitions = getPendingTransitions();
2202
if (currentTransitions !== null) {
2203
+ const currentTracingMarkers = getTracingMarkers();
2204
const primaryChildUpdateQueue: OffscreenQueue = {
2205
transitions: currentTransitions,
2206
+ tracingMarkers: currentTracingMarkers,
2207
};
2208
primaryChildFragment.updateQueue = primaryChildUpdateQueue;
2209
}
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+41
@@ -138,6 +138,7 @@ import {
138
restorePendingUpdaters,
139
addTransitionStartCallbackToPendingTransition,
140
addTransitionCompleteCallbackToPendingTransition,
141
+ addMarkerCompleteCallbackToPendingTransition,
142
setIsRunningInsertionEffect,
143
} from './ReactFiberWorkLoop.new';
144
import {
@@ -2910,6 +2911,7 @@ function commitPassiveMountOnFiber(
2911
instance.transitions = prevTransitions = new Set();
2912
}
2913
2914
+ // TODO(luna): Combine the root code with the tracing marker code
2915
if (transitions !== null) {
2916
transitions.forEach(transition => {
2917
// Add all the transitions saved in the update queue during
@@ -2931,6 +2933,23 @@ function commitPassiveMountOnFiber(
2933
}
2934
});
2935
}
2936
+
2937
+ const tracingMarkers = queue.tracingMarkers;
2938
+ if (tracingMarkers !== null) {
2939
+ tracingMarkers.forEach(marker => {
2940
+ const markerInstance = marker.stateNode;
2941
+ // There should only be a few tracing marker transitions because
2942
+ // they should be only associated with the transition that
2943
+ // caused them
2944
+ markerInstance.transitions.forEach(transition => {
2945
+ if (instance.transitions.has(transition)) {
2946
+ instance.pendingMarkers.add(
2947
+ markerInstance.pendingSuspenseBoundaries,
2948
+ );
2949
+ }
2950
+ });
2951
+ });
2952
+ }
2953
}
2954
2955
commitTransitionProgress(finishedWork);
@@ -2967,6 +2986,28 @@ function commitPassiveMountOnFiber(
2986
}
2987
break;
2988
}
2989
+ case TracingMarkerComponent: {
2990
+ if (enableTransitionTracing) {
2991
+ // Get the transitions that were initiatized during the render
2992
+ // and add a start transition callback for each of them
2993
+ const instance = finishedWork.stateNode;
2994
+ if (
2995
+ instance.pendingSuspenseBoundaries === null ||
2996
+ instance.pendingSuspenseBoundaries.size === 0
2997
+ ) {
2998
+ instance.transitions.forEach(transition => {
2999
+ addMarkerCompleteCallbackToPendingTransition({
3000
+ transitionName: transition.name,
3001
+ startTime: transition.startTime,
3002
+ markerName: finishedWork.memoizedProps.name,
3003
+ });
3004
+ });
3005
+ instance.transitions = null;
3006
+ instance.pendingSuspenseBoundaries = null;
3007
+ }
3008
+ }
3009
+ break;
3010
+ }
3011
}
3012
}
3013
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+41
@@ -138,6 +138,7 @@ import {
138
restorePendingUpdaters,
139
addTransitionStartCallbackToPendingTransition,
140
addTransitionCompleteCallbackToPendingTransition,
141
+ addMarkerCompleteCallbackToPendingTransition,
142
setIsRunningInsertionEffect,
143
} from './ReactFiberWorkLoop.old';
144
import {
@@ -2910,6 +2911,7 @@ function commitPassiveMountOnFiber(
2911
instance.transitions = prevTransitions = new Set();
2912
}
2913
2914
+ // TODO(luna): Combine the root code with the tracing marker code
2915
if (transitions !== null) {
2916
transitions.forEach(transition => {
2917
// Add all the transitions saved in the update queue during
@@ -2931,6 +2933,23 @@ function commitPassiveMountOnFiber(
2933
}
2934
});
2935
}
2936
+
2937
+ const tracingMarkers = queue.tracingMarkers;
2938
+ if (tracingMarkers !== null) {
2939
+ tracingMarkers.forEach(marker => {
2940
+ const markerInstance = marker.stateNode;
2941
+ // There should only be a few tracing marker transitions because
2942
+ // they should be only associated with the transition that
2943
+ // caused them
2944
+ markerInstance.transitions.forEach(transition => {
2945
+ if (instance.transitions.has(transition)) {
2946
+ instance.pendingMarkers.add(
2947
+ markerInstance.pendingSuspenseBoundaries,
2948
+ );
2949
+ }
2950
+ });
2951
+ });
2952
+ }
2953
}
2954
2955
commitTransitionProgress(finishedWork);
@@ -2967,6 +2986,28 @@ function commitPassiveMountOnFiber(
2986
}
2987
break;
2988
}
2989
+ case TracingMarkerComponent: {
2990
+ if (enableTransitionTracing) {
2991
+ // Get the transitions that were initiatized during the render
2992
+ // and add a start transition callback for each of them
2993
+ const instance = finishedWork.stateNode;
2994
+ if (
2995
+ instance.pendingSuspenseBoundaries === null ||
2996
+ instance.pendingSuspenseBoundaries.size === 0
2997
+ ) {
2998
+ instance.transitions.forEach(transition => {
2999
+ addMarkerCompleteCallbackToPendingTransition({
3000
+ transitionName: transition.name,
3001
+ startTime: transition.startTime,
3002
+ markerName: finishedWork.memoizedProps.name,
3003
+ });
3004
+ });
3005
+ instance.transitions = null;
3006
+ instance.pendingSuspenseBoundaries = null;
3007
+ }
3008
+ }
3009
+ break;
3010
+ }
3011
}
3012
}
3013
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+10
-1
@@ -1581,9 +1581,18 @@ function completeWork(
1581
}
1582
case TracingMarkerComponent: {
1583
if (enableTransitionTracing) {
1584
- // Bubble subtree flags before so we can set the flag property
1584
popTracingMarker(workInProgress);
1585
bubbleProperties(workInProgress);
1586
+
1587
+ if (
1588
+ current === null ||
1589
+ (workInProgress.subtreeFlags & Visibility) !== NoFlags
1590
+ ) {
1591
+ // If any of our suspense children toggle visibility, this means that
1592
+ // the pending boundaries array needs to be updated, which we only
1593
+ // do in the passive phase.
1594
+ workInProgress.flags |= Passive;
1595
+ }
1596
}
1597
return null;
1598
}
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+10
-1
@@ -1581,9 +1581,18 @@ function completeWork(
1581
}
1582
case TracingMarkerComponent: {
1583
if (enableTransitionTracing) {
1584
- // Bubble subtree flags before so we can set the flag property
1584
popTracingMarker(workInProgress);
1585
bubbleProperties(workInProgress);
1586
+
1587
+ if (
1588
+ current === null ||
1589
+ (workInProgress.subtreeFlags & Visibility) !== NoFlags
1590
+ ) {
1591
+ // If any of our suspense children toggle visibility, this means that
1592
+ // the pending boundaries array needs to be updated, which we only
1593
+ // do in the passive phase.
1594
+ workInProgress.flags |= Passive;
1595
+ }
1596
}
1597
return null;
1598
}
packages/react-reconciler/src/ReactFiberOffscreenComponent.js
+2
@@ -8,6 +8,7 @@
8
*/
9
10
import type {ReactNodeList, OffscreenMode} from 'shared/ReactTypes';
11
+import type {Fiber} from './ReactInternalTypes';
12
import type {Lanes} from './ReactFiberLane.old';
13
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
14
import type {
@@ -38,6 +39,7 @@ export type OffscreenState = {|
39
40
export type OffscreenQueue = {|
41
transitions: Array<Transition> | null,
42
+ tracingMarkers: Array<Fiber> | null,
43
|} | null;
44
45
export type OffscreenInstance = {|
packages/react-reconciler/src/ReactFiberTracingMarkerComponent.new.js
+5
@@ -39,6 +39,11 @@ export type BatchConfigTransition = {
39
_updatedFibers?: Set<Fiber>,
40
};
41
42
+export type TracingMarkerInstance = {|
43
+ pendingSuspenseBoundaries: PendingSuspenseBoundaries | null,
44
+ transitions: Set<Transition> | null,
45
+|} | null;
46
+
47
export type PendingSuspenseBoundaries = Map<OffscreenInstance, SuspenseInfo>;
48
49
export function processTransitionCallbacks(
packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js
+5
@@ -39,6 +39,11 @@ export type BatchConfigTransition = {
39
_updatedFibers?: Set<Fiber>,
40
};
41
42
+export type TracingMarkerInstance = {|
43
+ pendingSuspenseBoundaries: PendingSuspenseBoundaries | null,
44
+ transitions: Set<Transition> | null,
45
+|} | null;
46
+
47
export type PendingSuspenseBoundaries = Map<OffscreenInstance, SuspenseInfo>;
48
49
export function processTransitionCallbacks(
packages/react-reconciler/src/ReactFiberTransition.new.js
+1
-1
@@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) {
149
}
150
}
151
152
-export function getSuspendedTransitions(): Array<Transition> | null {
152
+export function getPendingTransitions(): Array<Transition> | null {
153
if (!enableTransitionTracing) {
154
return null;
155
}
packages/react-reconciler/src/ReactFiberTransition.old.js
+1
-1
@@ -149,7 +149,7 @@ export function popTransition(workInProgress: Fiber, current: Fiber | null) {
149
}
150
}
151
152
-export function getSuspendedTransitions(): Array<Transition> | null {
152
+export function getPendingTransitions(): Array<Transition> | null {
153
if (!enableTransitionTracing) {
154
return null;
155
}
packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
+361
@@ -477,4 +477,365 @@ describe('ReactInteractionTracing', () => {
477
]);
478
});
479
});
480
+
481
+ // @gate enableTransitionTracing
482
+ it('should correctly trace interactions for tracing markers complete', async () => {
483
+ const transitionCallbacks = {
484
+ onTransitionStart: (name, startTime) => {
485
+ Scheduler.unstable_yieldValue(
486
+ `onTransitionStart(${name}, ${startTime})`,
487
+ );
488
+ },
489
+ onTransitionComplete: (name, startTime, endTime) => {
490
+ Scheduler.unstable_yieldValue(
491
+ `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
492
+ );
493
+ },
494
+ onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
495
+ Scheduler.unstable_yieldValue(
496
+ `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
497
+ );
498
+ },
499
+ };
500
+ let navigateToPageTwo;
501
+ function App() {
502
+ const [navigate, setNavigate] = useState(false);
503
+ navigateToPageTwo = () => {
504
+ setNavigate(true);
505
+ };
506
+
507
+ return (
508
+ <div>
509
+ {navigate ? (
510
+ <Suspense
511
+ fallback={<Text text="Loading..." />}
512
+ name="suspense page">
513
+ <AsyncText text="Page Two" />
514
+ <React.unstable_TracingMarker name="sync marker" />
515
+ <React.unstable_TracingMarker name="async marker">
516
+ <Suspense
517
+ fallback={<Text text="Loading..." />}
518
+ name="marker suspense">
519
+ <AsyncText text="Marker Text" />
520
+ </Suspense>
521
+ </React.unstable_TracingMarker>
522
+ </Suspense>
523
+ ) : (
524
+ <Text text="Page One" />
525
+ )}
526
+ </div>
527
+ );
528
+ }
529
+
530
+ const root = ReactNoop.createRoot({transitionCallbacks});
531
+ await act(async () => {
532
+ root.render(<App />);
533
+ ReactNoop.expire(1000);
534
+ await advanceTimers(1000);
535
+
536
+ expect(Scheduler).toFlushAndYield(['Page One']);
537
+ });
538
+
539
+ await act(async () => {
540
+ startTransition(() => navigateToPageTwo(), {name: 'page transition'});
541
+
542
+ ReactNoop.expire(1000);
543
+ await advanceTimers(1000);
544
+
545
+ expect(Scheduler).toFlushAndYield([
546
+ 'Suspend [Page Two]',
547
+ 'Suspend [Marker Text]',
548
+ 'Loading...',
549
+ 'Loading...',
550
+ 'onTransitionStart(page transition, 1000)',
551
+ ]);
552
+
553
+ ReactNoop.expire(1000);
554
+ await advanceTimers(1000);
555
+ await resolveText('Page Two');
556
+
557
+ expect(Scheduler).toFlushAndYield([
558
+ 'Page Two',
559
+ 'Suspend [Marker Text]',
560
+ 'Loading...',
561
+ 'onMarkerComplete(page transition, sync marker, 1000, 3000)',
562
+ ]);
563
+
564
+ ReactNoop.expire(1000);
565
+ await advanceTimers(1000);
566
+ await resolveText('Marker Text');
567
+
568
+ expect(Scheduler).toFlushAndYield([
569
+ 'Marker Text',
570
+ 'onMarkerComplete(page transition, async marker, 1000, 4000)',
571
+ 'onTransitionComplete(page transition, 1000, 4000)',
572
+ ]);
573
+ });
574
+ });
575
+
576
+ // @gate enableTransitionTracing
577
+ it('trace interaction with multiple tracing markers', async () => {
578
+ const transitionCallbacks = {
579
+ onTransitionStart: (name, startTime) => {
580
+ Scheduler.unstable_yieldValue(
581
+ `onTransitionStart(${name}, ${startTime})`,
582
+ );
583
+ },
584
+ onTransitionComplete: (name, startTime, endTime) => {
585
+ Scheduler.unstable_yieldValue(
586
+ `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
587
+ );
588
+ },
589
+ onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
590
+ Scheduler.unstable_yieldValue(
591
+ `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
592
+ );
593
+ },
594
+ };
595
+
596
+ let navigateToPageTwo;
597
+ function App() {
598
+ const [navigate, setNavigate] = useState(false);
599
+ navigateToPageTwo = () => {
600
+ setNavigate(true);
601
+ };
602
+
603
+ return (
604
+ <div>
605
+ {navigate ? (
606
+ <React.unstable_TracingMarker name="outer marker">
607
+ <Suspense fallback={<Text text="Outer..." />}>
608
+ <AsyncText text="Outer Text" />
609
+ <Suspense fallback={<Text text="Inner One..." />}>
610
+ <React.unstable_TracingMarker name="marker one">
611
+ <AsyncText text="Inner Text One" />
612
+ </React.unstable_TracingMarker>
613
+ </Suspense>
614
+ <Suspense fallback={<Text text="Inner Two..." />}>
615
+ <React.unstable_TracingMarker name="marker two">
616
+ <AsyncText text="Inner Text Two" />
617
+ </React.unstable_TracingMarker>
618
+ </Suspense>
619
+ </Suspense>
620
+ </React.unstable_TracingMarker>
621
+ ) : (
622
+ <Text text="Page One" />
623
+ )}
624
+ </div>
625
+ );
626
+ }
627
+
628
+ const root = ReactNoop.createRoot({transitionCallbacks});
629
+ await act(async () => {
630
+ root.render(<App />);
631
+ ReactNoop.expire(1000);
632
+ await advanceTimers(1000);
633
+
634
+ expect(Scheduler).toFlushAndYield(['Page One']);
635
+ });
636
+
637
+ await act(async () => {
638
+ startTransition(() => navigateToPageTwo(), {name: 'page transition'});
639
+
640
+ ReactNoop.expire(1000);
641
+ await advanceTimers(1000);
642
+
643
+ expect(Scheduler).toFlushAndYield([
644
+ 'Suspend [Outer Text]',
645
+ 'Suspend [Inner Text One]',
646
+ 'Inner One...',
647
+ 'Suspend [Inner Text Two]',
648
+ 'Inner Two...',
649
+ 'Outer...',
650
+ 'onTransitionStart(page transition, 1000)',
651
+ ]);
652
+
653
+ ReactNoop.expire(1000);
654
+ await advanceTimers(1000);
655
+ await resolveText('Inner Text Two');
656
+ expect(Scheduler).toFlushAndYield([]);
657
+
658
+ ReactNoop.expire(1000);
659
+ await advanceTimers(1000);
660
+ await resolveText('Outer Text');
661
+ expect(Scheduler).toFlushAndYield([
662
+ 'Outer Text',
663
+ 'Suspend [Inner Text One]',
664
+ 'Inner One...',
665
+ 'Inner Text Two',
666
+ 'onMarkerComplete(page transition, marker two, 1000, 4000)',
667
+ ]);
668
+
669
+ ReactNoop.expire(1000);
670
+ await advanceTimers(1000);
671
+ await resolveText('Inner Text One');
672
+ expect(Scheduler).toFlushAndYield([
673
+ 'Inner Text One',
674
+ 'onMarkerComplete(page transition, marker one, 1000, 5000)',
675
+ 'onMarkerComplete(page transition, outer marker, 1000, 5000)',
676
+ 'onTransitionComplete(page transition, 1000, 5000)',
677
+ ]);
678
+ });
679
+ });
680
+
681
+ // @gate enableTransitionTracing
682
+ it.skip('marker interaction cancelled when name changes', async () => {
683
+ const transitionCallbacks = {
684
+ onTransitionStart: (name, startTime) => {
685
+ Scheduler.unstable_yieldValue(
686
+ `onTransitionStart(${name}, ${startTime})`,
687
+ );
688
+ },
689
+ onTransitionComplete: (name, startTime, endTime) => {
690
+ Scheduler.unstable_yieldValue(
691
+ `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
692
+ );
693
+ },
694
+ onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
695
+ Scheduler.unstable_yieldValue(
696
+ `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
697
+ );
698
+ },
699
+ };
700
+
701
+ let navigateToPageTwo;
702
+ let setMarkerNameFn;
703
+ function App() {
704
+ const [navigate, setNavigate] = useState(false);
705
+ navigateToPageTwo = () => {
706
+ setNavigate(true);
707
+ };
708
+
709
+ const [markerName, setMarkerName] = useState('old marker');
710
+ setMarkerNameFn = () => setMarkerName('new marker');
711
+
712
+ return (
713
+ <div>
714
+ {navigate ? (
715
+ <React.unstable_TracingMarker name={markerName}>
716
+ <Suspense fallback={<Text text="Loading..." />}>
717
+ <AsyncText text="Page Two" />
718
+ </Suspense>
719
+ </React.unstable_TracingMarker>
720
+ ) : (
721
+ <Text text="Page One" />
722
+ )}
723
+ </div>
724
+ );
725
+ }
726
+
727
+ const root = ReactNoop.createRoot({transitionCallbacks});
728
+ await act(async () => {
729
+ root.render(<App />);
730
+ ReactNoop.expire(1000);
731
+ await advanceTimers(1000);
732
+
733
+ expect(Scheduler).toFlushAndYield(['Page One']);
734
+
735
+ startTransition(() => navigateToPageTwo(), {name: 'page transition'});
736
+ expect(Scheduler).toFlushAndYield([
737
+ 'Suspend [Page Two]',
738
+ 'Loading...',
739
+ 'onTransitionStart(page transition, 1000)',
740
+ ]);
741
+
742
+ ReactNoop.expire(1000);
743
+ await advanceTimers(1000);
744
+ setMarkerNameFn();
745
+
746
+ expect(Scheduler).toFlushAndYield(['Suspend [Page Two]', 'Loading...']);
747
+ ReactNoop.expire(1000);
748
+ await advanceTimers(1000);
749
+ resolveText('Page Two');
750
+
751
+ // Marker complete is not called because the marker name changed
752
+ expect(Scheduler).toFlushAndYield([
753
+ 'Page Two',
754
+ 'onTransitionComplete(page transition, 1000, 3000)',
755
+ ]);
756
+ });
757
+ });
758
+
759
+ // @gate enableTransitionTracing
760
+ it.skip('marker changes to new interaction when name changes', async () => {
761
+ const transitionCallbacks = {
762
+ onTransitionStart: (name, startTime) => {
763
+ Scheduler.unstable_yieldValue(
764
+ `onTransitionStart(${name}, ${startTime})`,
765
+ );
766
+ },
767
+ onTransitionComplete: (name, startTime, endTime) => {
768
+ Scheduler.unstable_yieldValue(
769
+ `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
770
+ );
771
+ },
772
+ onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
773
+ Scheduler.unstable_yieldValue(
774
+ `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
775
+ );
776
+ },
777
+ };
778
+
779
+ let navigateToPageTwo;
780
+ let setMarkerNameFn;
781
+ function App() {
782
+ const [navigate, setNavigate] = useState(false);
783
+ navigateToPageTwo = () => {
784
+ setNavigate(true);
785
+ };
786
+
787
+ const [markerName, setMarkerName] = useState('old marker');
788
+ setMarkerNameFn = () => setMarkerName('new marker');
789
+
790
+ return (
791
+ <div>
792
+ {navigate ? (
793
+ <React.unstable_TracingMarker name={markerName}>
794
+ <Suspense fallback={<Text text="Loading..." />}>
795
+ <AsyncText text="Page Two" />
796
+ </Suspense>
797
+ </React.unstable_TracingMarker>
798
+ ) : (
799
+ <Text text="Page One" />
800
+ )}
801
+ </div>
802
+ );
803
+ }
804
+
805
+ const root = ReactNoop.createRoot({transitionCallbacks});
806
+ await act(async () => {
807
+ root.render(<App />);
808
+ ReactNoop.expire(1000);
809
+ await advanceTimers(1000);
810
+
811
+ expect(Scheduler).toFlushAndYield(['Page One']);
812
+
813
+ startTransition(() => navigateToPageTwo(), {name: 'page transition'});
814
+ expect(Scheduler).toFlushAndYield([
815
+ 'Suspend [Page Two]',
816
+ 'Loading...',
817
+ 'onTransitionStart(page transition, 1000)',
818
+ ]);
819
+
820
+ ReactNoop.expire(1000);
821
+ await advanceTimers(1000);
822
+ startTransition(() => setMarkerNameFn(), {name: 'marker transition'});
823
+
824
+ expect(Scheduler).toFlushAndYield([
825
+ 'Suspend [Page Two]',
826
+ 'Loading...',
827
+ 'onTransitionStart(marker transition, 2000)',
828
+ ]);
829
+ ReactNoop.expire(1000);
830
+ await advanceTimers(1000);
831
+ resolveText('Page Two');
832
+
833
+ // Marker complete is not called because the marker name changed
834
+ expect(Scheduler).toFlushAndYield([
835
+ 'Page Two',
836
+ 'onMarkerComplete(new marker, 2000, 3000)',
837
+ 'onTransitionComplete(page transition, 1000, 3000)',
838
+ ]);
839
+ });
840
+ });
841
});