main
js 711 lines 22.8 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11
12 import type {SuspendedReason} from './ReactFiberWorkLoop';
13
14 import type {Lane, Lanes} from './ReactFiberLane';
15
16 import type {CapturedValue} from './ReactCapturedValue';
17
18 import {
19 isTransitionLane,
20 isBlockingLane,
21 isGestureRender,
22 includesTransitionLane,
23 includesBlockingLane,
24 NoLanes,
25 } from './ReactFiberLane';
26
27 import {resolveEventType, resolveEventTimeStamp} from './ReactFiberConfig';
28
29 import {
30 enableProfilerCommitHooks,
31 enableProfilerNestedUpdatePhase,
32 enableProfilerTimer,
33 enableComponentPerformanceTrack,
34 } from 'shared/ReactFeatureFlags';
35
36 import getComponentNameFromFiber from './getComponentNameFromFiber';
37 import {isAlreadyRendering} from './ReactFiberWorkLoop';
38
39 // Intentionally not named imports because Rollup would use dynamic dispatch for
40 // CommonJS interop named imports.
41 import * as Scheduler from 'scheduler';
42
43 const {unstable_now: now} = Scheduler;
44
45 const createTask =
46 // eslint-disable-next-line react-internal/no-production-logging
47 __DEV__ && console.createTask
48 ? // eslint-disable-next-line react-internal/no-production-logging
49 console.createTask
50 : (name: string) => null;
51
52 export const REGULAR_UPDATE: UpdateType = 0;
53 export const SPAWNED_UPDATE: UpdateType = 1;
54 export const PINGED_UPDATE: UpdateType = 2;
55 export opaque type UpdateType = 0 | 1 | 2;
56
57 export let renderStartTime: number = -0;
58 export let commitStartTime: number = -0;
59 export let commitEndTime: number = -0;
60 export let commitErrors: null | Array<CapturedValue<mixed>> = null;
61 export let profilerStartTime: number = -1.1;
62 export let profilerEffectDuration: number = -0;
63 export let componentEffectDuration: number = -0;
64 export let componentEffectStartTime: number = -1.1;
65 export let componentEffectEndTime: number = -1.1;
66 export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
67 export let componentEffectSpawnedUpdate: boolean = false;
68
69 export let blockingClampTime: number = -0;
70 export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
71 export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
72 export let blockingUpdateType: UpdateType = 0;
73 export let blockingUpdateMethodName: null | string = null; // The name of the method that caused first sync update.
74 export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened.
75 export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
76 export let blockingEventType: null | string = null; // Event type of the first setState.
77 export let blockingEventRepeatTime: number = -1.1;
78 export let blockingSuspendedTime: number = -1.1;
79
80 export let gestureClampTime: number = -0;
81 export let gestureUpdateTime: number = -1.1; // First setOptimistic scheduled inside startGestureTransition.
82 export let gestureUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
83 export let gestureUpdateType: UpdateType = 0;
84 export let gestureUpdateMethodName: null | string = null; // The name of the method that caused first gesture update.
85 export let gestureUpdateComponentName: null | string = null; // The name of the component where first gesture update happened.
86 export let gestureEventTime: number = -1.1; // Event timeStamp of the first setState.
87 export let gestureEventType: null | string = null; // Event type of the first setState.
88 export let gestureEventRepeatTime: number = -1.1;
89 export let gestureSuspendedTime: number = -1.1;
90
91 // TODO: This should really be one per Transition lane.
92 export let transitionClampTime: number = -0;
93 export let transitionStartTime: number = -1.1; // First startTransition call before setState.
94 export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
95 export let transitionUpdateType: UpdateType = 0;
96 export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
97 export let transitionUpdateMethodName: null | string = null; // The name of the method that caused first transition update.
98 export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened.
99 export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
100 export let transitionEventType: null | string = null; // Event type of the first transition.
101 export let transitionEventRepeatTime: number = -1.1;
102 export let transitionSuspendedTime: number = -1.1;
103
104 export let retryClampTime: number = -0;
105 export let idleClampTime: number = -0;
106
107 export let animatingLanes: Lanes = NoLanes;
108 export let animatingTask: null | ConsoleTask = null; // First ViewTransition applying an Animation.
109
110 export let yieldReason: SuspendedReason = 0 as any;
111 export let yieldStartTime: number = -1.1; // The time when we yielded to the event loop
112
113 export function startYieldTimer(reason: SuspendedReason) {
114 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
115 return;
116 }
117 yieldStartTime = now();
118 yieldReason = reason;
119 }
120
121 export function startUpdateTimerByLane(
122 lane: Lane,
123 method: string,
124 fiber: Fiber | null,
125 ): void {
126 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
127 return;
128 }
129 if (isGestureRender(lane)) {
130 if (gestureUpdateTime < 0) {
131 gestureUpdateTime = now();
132 gestureUpdateTask = createTask(method);
133 gestureUpdateMethodName = method;
134 if (__DEV__ && fiber != null) {
135 gestureUpdateComponentName = getComponentNameFromFiber(fiber);
136 }
137 const newEventTime = resolveEventTimeStamp();
138 const newEventType = resolveEventType();
139 if (
140 newEventTime !== gestureEventRepeatTime ||
141 newEventType !== gestureEventType
142 ) {
143 gestureEventRepeatTime = -1.1;
144 }
145 gestureEventTime = newEventTime;
146 gestureEventType = newEventType;
147 }
148 } else if (isBlockingLane(lane)) {
149 if (blockingUpdateTime < 0) {
150 blockingUpdateTime = now();
151 blockingUpdateTask = createTask(method);
152 blockingUpdateMethodName = method;
153 if (__DEV__ && fiber != null) {
154 blockingUpdateComponentName = getComponentNameFromFiber(fiber);
155 }
156 if (isAlreadyRendering()) {
157 componentEffectSpawnedUpdate = true;
158 blockingUpdateType = SPAWNED_UPDATE;
159 }
160 const newEventTime = resolveEventTimeStamp();
161 const newEventType = resolveEventType();
162 if (
163 newEventTime !== blockingEventRepeatTime ||
164 newEventType !== blockingEventType
165 ) {
166 blockingEventRepeatTime = -1.1;
167 } else if (newEventType !== null) {
168 // If this is a second update in the same event, we treat it as a spawned update.
169 // This might be a microtask spawned from useEffect, multiple flushSync or
170 // a setState in a microtask spawned after the first setState. Regardless it's bad.
171 blockingUpdateType = SPAWNED_UPDATE;
172 }
173 blockingEventTime = newEventTime;
174 blockingEventType = newEventType;
175 }
176 } else if (isTransitionLane(lane)) {
177 if (transitionUpdateTime < 0) {
178 transitionUpdateTime = now();
179 transitionUpdateTask = createTask(method);
180 transitionUpdateMethodName = method;
181 if (__DEV__ && fiber != null) {
182 transitionUpdateComponentName = getComponentNameFromFiber(fiber);
183 }
184 if (transitionStartTime < 0) {
185 const newEventTime = resolveEventTimeStamp();
186 const newEventType = resolveEventType();
187 if (
188 newEventTime !== transitionEventRepeatTime ||
189 newEventType !== transitionEventType
190 ) {
191 transitionEventRepeatTime = -1.1;
192 }
193 transitionEventTime = newEventTime;
194 transitionEventType = newEventType;
195 }
196 }
197 }
198 }
199
200 export function startHostActionTimer(fiber: Fiber): void {
201 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
202 return;
203 }
204 // This schedules an update on both the blocking lane for the pending state and on the
205 // transition lane for the action update. Using the debug task from the host fiber.
206 if (blockingUpdateTime < 0) {
207 blockingUpdateTime = now();
208 blockingUpdateTask =
209 __DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
210 if (isAlreadyRendering()) {
211 blockingUpdateType = SPAWNED_UPDATE;
212 }
213 const newEventTime = resolveEventTimeStamp();
214 const newEventType = resolveEventType();
215 if (
216 newEventTime !== blockingEventRepeatTime ||
217 newEventType !== blockingEventType
218 ) {
219 blockingEventRepeatTime = -1.1;
220 } else if (newEventType !== null) {
221 // If this is a second update in the same event, we treat it as a spawned update.
222 // This might be a microtask spawned from useEffect, multiple flushSync or
223 // a setState in a microtask spawned after the first setState. Regardless it's bad.
224 blockingUpdateType = SPAWNED_UPDATE;
225 }
226 blockingEventTime = newEventTime;
227 blockingEventType = newEventType;
228 }
229 if (transitionUpdateTime < 0) {
230 transitionUpdateTime = now();
231 transitionUpdateTask =
232 __DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
233 if (transitionStartTime < 0) {
234 const newEventTime = resolveEventTimeStamp();
235 const newEventType = resolveEventType();
236 if (
237 newEventTime !== transitionEventRepeatTime ||
238 newEventType !== transitionEventType
239 ) {
240 transitionEventRepeatTime = -1.1;
241 }
242 transitionEventTime = newEventTime;
243 transitionEventType = newEventType;
244 }
245 }
246 }
247
248 export function startPingTimerByLanes(lanes: Lanes): void {
249 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
250 return;
251 }
252 // Mark the update time and clamp anything before it because we don't want
253 // to show the event time for pings but we also don't want to clear it
254 // because we still need to track if this was a repeat.
255 if (isGestureRender(lanes)) {
256 if (gestureUpdateTime < 0) {
257 gestureClampTime = gestureUpdateTime = now();
258 gestureUpdateTask = createTask('Promise Resolved');
259 gestureUpdateType = PINGED_UPDATE;
260 }
261 } else if (includesBlockingLane(lanes)) {
262 if (blockingUpdateTime < 0) {
263 blockingClampTime = blockingUpdateTime = now();
264 blockingUpdateTask = createTask('Promise Resolved');
265 blockingUpdateType = PINGED_UPDATE;
266 }
267 } else if (includesTransitionLane(lanes)) {
268 if (transitionUpdateTime < 0) {
269 transitionClampTime = transitionUpdateTime = now();
270 transitionUpdateTask = createTask('Promise Resolved');
271 transitionUpdateType = PINGED_UPDATE;
272 }
273 }
274 }
275
276 export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
277 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
278 return;
279 }
280 if (isGestureRender(lanes)) {
281 gestureSuspendedTime = renderEndTime;
282 } else if (includesBlockingLane(lanes)) {
283 blockingSuspendedTime = renderEndTime;
284 } else if (includesTransitionLane(lanes)) {
285 transitionSuspendedTime = renderEndTime;
286 }
287 }
288
289 export function clearBlockingTimers(): void {
290 blockingUpdateTime = -1.1;
291 blockingUpdateType = 0;
292 blockingUpdateMethodName = null;
293 blockingUpdateComponentName = null;
294 blockingSuspendedTime = -1.1;
295 blockingEventRepeatTime = blockingEventTime;
296 blockingEventTime = -1.1;
297 blockingClampTime = now();
298 }
299
300 export function startAsyncTransitionTimer(): void {
301 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
302 return;
303 }
304 if (transitionStartTime < 0 && transitionUpdateTime < 0) {
305 transitionStartTime = now();
306 const newEventTime = resolveEventTimeStamp();
307 const newEventType = resolveEventType();
308 if (
309 newEventTime !== transitionEventRepeatTime ||
310 newEventType !== transitionEventType
311 ) {
312 transitionEventRepeatTime = -1.1;
313 }
314 transitionEventTime = newEventTime;
315 transitionEventType = newEventType;
316 }
317 }
318
319 export function hasScheduledTransitionWork(): boolean {
320 // If we have setState on a transition or scheduled useActionState update.
321 return transitionUpdateTime > -1;
322 }
323
324 export function clearAsyncTransitionTimer(): void {
325 transitionStartTime = -1.1;
326 }
327
328 export function clearTransitionTimers(): void {
329 transitionStartTime = -1.1;
330 transitionUpdateTime = -1.1;
331 transitionUpdateType = 0;
332 transitionSuspendedTime = -1.1;
333 transitionEventRepeatTime = transitionEventTime;
334 transitionEventTime = -1.1;
335 transitionClampTime = now();
336 }
337
338 export function hasScheduledGestureTransitionWork(): boolean {
339 // If we have call setOptimistic on a gesture
340 return gestureUpdateTime > -1;
341 }
342
343 export function clearGestureTimers(): void {
344 gestureUpdateTime = -1.1;
345 gestureUpdateType = 0;
346 gestureSuspendedTime = -1.1;
347 gestureEventRepeatTime = gestureEventTime;
348 gestureEventTime = -1.1;
349 gestureClampTime = now();
350 }
351
352 export function clearGestureUpdates(): void {
353 // Same as clearGestureTimers but doesn't reset the clamp time because we didn't
354 // actually emit a render.
355 gestureUpdateTime = -1.1;
356 gestureUpdateType = 0;
357 gestureSuspendedTime = -1.1;
358 gestureEventRepeatTime = gestureEventTime;
359 gestureEventTime = -1.1;
360 }
361
362 export function clampBlockingTimers(finalTime: number): void {
363 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
364 return;
365 }
366 // If we had new updates come in while we were still rendering or committing, we don't want
367 // those update times to create overlapping tracks in the performance timeline so we clamp
368 // them to the end of the commit phase.
369 blockingClampTime = finalTime;
370 }
371
372 export function clampGestureTimers(finalTime: number): void {
373 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
374 return;
375 }
376 // If we had new updates come in while we were still rendering or committing, we don't want
377 // those update times to create overlapping tracks in the performance timeline so we clamp
378 // them to the end of the commit phase.
379 gestureClampTime = finalTime;
380 }
381
382 export function clampTransitionTimers(finalTime: number): void {
383 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
384 return;
385 }
386 // If we had new updates come in while we were still rendering or committing, we don't want
387 // those update times to create overlapping tracks in the performance timeline so we clamp
388 // them to the end of the commit phase.
389 transitionClampTime = finalTime;
390 }
391
392 export function clampRetryTimers(finalTime: number): void {
393 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
394 return;
395 }
396 retryClampTime = finalTime;
397 }
398
399 export function clampIdleTimers(finalTime: number): void {
400 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
401 return;
402 }
403 idleClampTime = finalTime;
404 }
405
406 export function pushNestedEffectDurations(): number {
407 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
408 return 0;
409 }
410 const prevEffectDuration = profilerEffectDuration;
411 profilerEffectDuration = 0; // Reset counter.
412 return prevEffectDuration;
413 }
414
415 export function popNestedEffectDurations(prevEffectDuration: number): number {
416 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
417 return 0;
418 }
419 const elapsedTime = profilerEffectDuration;
420 profilerEffectDuration = prevEffectDuration;
421 return elapsedTime;
422 }
423
424 // Like pop but it also adds the current elapsed time to the parent scope.
425 export function bubbleNestedEffectDurations(
426 prevEffectDuration: number,
427 ): number {
428 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
429 return 0;
430 }
431 const elapsedTime = profilerEffectDuration;
432 profilerEffectDuration += prevEffectDuration;
433 return elapsedTime;
434 }
435
436 export function resetComponentEffectTimers(): void {
437 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
438 return;
439 }
440 componentEffectStartTime = -1.1;
441 componentEffectEndTime = -1.1;
442 }
443
444 export function pushComponentEffectStart(): number {
445 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
446 return 0;
447 }
448 const prevEffectStart = componentEffectStartTime;
449 componentEffectStartTime = -1.1; // Track the next start.
450 return prevEffectStart;
451 }
452
453 export function popComponentEffectStart(prevEffectStart: number): void {
454 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
455 return;
456 }
457 // If the parent component didn't have a start time, we let this current time persist.
458 if (prevEffectStart >= 0) {
459 // Otherwise, we restore the previous parent's start time.
460 componentEffectStartTime = prevEffectStart;
461 }
462 }
463
464 export function pushComponentEffectDuration(): number {
465 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
466 return 0;
467 }
468 const prevEffectDuration = componentEffectDuration;
469 componentEffectDuration = -0; // Reset component level duration.
470 return prevEffectDuration;
471 }
472
473 export function popComponentEffectDuration(prevEffectDuration: number): void {
474 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
475 return;
476 }
477 // If the parent component didn't have a start time, we let this current time persist.
478 if (prevEffectDuration >= 0) {
479 // Otherwise, we restore the previous parent's start time.
480 componentEffectDuration = prevEffectDuration;
481 }
482 }
483
484 export function pushComponentEffectErrors(): null | Array<
485 CapturedValue<mixed>,
486 > {
487 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
488 return null;
489 }
490 const prevErrors = componentEffectErrors;
491 componentEffectErrors = null;
492 return prevErrors;
493 }
494
495 export function popComponentEffectErrors(
496 prevErrors: null | Array<CapturedValue<mixed>>,
497 ): void {
498 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
499 return;
500 }
501 componentEffectErrors = prevErrors;
502 }
503
504 export function pushComponentEffectDidSpawnUpdate(): boolean {
505 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
506 return false;
507 }
508
509 const prev = componentEffectSpawnedUpdate;
510 componentEffectSpawnedUpdate = false; // Reset.
511 return prev;
512 }
513
514 export function popComponentEffectDidSpawnUpdate(previousValue: boolean): void {
515 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
516 return;
517 }
518
519 componentEffectSpawnedUpdate = previousValue;
520 }
521
522 /**
523 * Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
524 *
525 * The overall sequence is:
526 * 1. render
527 * 2. commit (and call `onRender`, `onCommit`)
528 * 3. check for nested updates
529 * 4. flush passive effects (and call `onPostCommit`)
530 *
531 * Nested updates are identified in step 3 above,
532 * but step 4 still applies to the work that was just committed.
533 * We use two flags to track nested updates then:
534 * one tracks whether the upcoming update is a nested update,
535 * and the other tracks whether the current update was a nested update.
536 * The first value gets synced to the second at the start of the render phase.
537 */
538 let currentUpdateIsNested: boolean = false;
539 let nestedUpdateScheduled: boolean = false;
540
541 export function isCurrentUpdateNested(): boolean {
542 return currentUpdateIsNested;
543 }
544
545 export function markNestedUpdateScheduled(): void {
546 if (enableProfilerNestedUpdatePhase) {
547 nestedUpdateScheduled = true;
548 }
549 }
550
551 export function resetNestedUpdateFlag(): void {
552 if (enableProfilerNestedUpdatePhase) {
553 currentUpdateIsNested = false;
554 nestedUpdateScheduled = false;
555 }
556 }
557
558 export function syncNestedUpdateFlag(): void {
559 if (enableProfilerNestedUpdatePhase) {
560 currentUpdateIsNested = nestedUpdateScheduled;
561 nestedUpdateScheduled = false;
562 }
563 }
564
565 export function recordRenderTime(): void {
566 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
567 return;
568 }
569 renderStartTime = now();
570 }
571
572 export function recordCommitTime(): void {
573 if (!enableProfilerTimer) {
574 return;
575 }
576 commitStartTime = now();
577 }
578
579 export function recordCommitEndTime(): void {
580 if (!enableProfilerTimer) {
581 return;
582 }
583 commitEndTime = now();
584 }
585
586 export function startProfilerTimer(fiber: Fiber): void {
587 if (!enableProfilerTimer) {
588 return;
589 }
590
591 profilerStartTime = now();
592
593 if ((fiber.actualStartTime as any as number) < 0) {
594 fiber.actualStartTime = profilerStartTime;
595 }
596 }
597
598 export function stopProfilerTimerIfRunning(fiber: Fiber): void {
599 if (!enableProfilerTimer) {
600 return;
601 }
602 profilerStartTime = -1;
603 }
604
605 export function stopProfilerTimerIfRunningAndRecordDuration(
606 fiber: Fiber,
607 ): void {
608 if (!enableProfilerTimer) {
609 return;
610 }
611
612 if (profilerStartTime >= 0) {
613 const elapsedTime = now() - profilerStartTime;
614 fiber.actualDuration += elapsedTime;
615 fiber.selfBaseDuration = elapsedTime;
616 profilerStartTime = -1;
617 }
618 }
619
620 export function stopProfilerTimerIfRunningAndRecordIncompleteDuration(
621 fiber: Fiber,
622 ): void {
623 if (!enableProfilerTimer) {
624 return;
625 }
626
627 if (profilerStartTime >= 0) {
628 const elapsedTime = now() - profilerStartTime;
629 fiber.actualDuration += elapsedTime;
630 // We don't update the selfBaseDuration here because we errored.
631 profilerStartTime = -1;
632 }
633 }
634
635 export function recordEffectDuration(fiber: Fiber): void {
636 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
637 return;
638 }
639
640 if (profilerStartTime >= 0) {
641 const endTime = now();
642 const elapsedTime = endTime - profilerStartTime;
643
644 profilerStartTime = -1;
645
646 // Store duration on the next nearest Profiler ancestor
647 // Or the root (for the DevTools Profiler to read)
648 profilerEffectDuration += elapsedTime;
649 componentEffectDuration += elapsedTime;
650
651 // Keep track of the last end time of the effects.
652 componentEffectEndTime = endTime;
653 }
654 }
655
656 export function recordEffectError(errorInfo: CapturedValue<mixed>): void {
657 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
658 return;
659 }
660 if (componentEffectErrors === null) {
661 componentEffectErrors = [];
662 }
663 componentEffectErrors.push(errorInfo);
664 if (commitErrors === null) {
665 commitErrors = [];
666 }
667 commitErrors.push(errorInfo);
668 }
669
670 export function resetCommitErrors(): void {
671 commitErrors = null;
672 }
673
674 export function startEffectTimer(): void {
675 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
676 return;
677 }
678 profilerStartTime = now();
679 if (componentEffectStartTime < 0) {
680 // Keep track of the first time we start an effect as the component's effect start time.
681 componentEffectStartTime = profilerStartTime;
682 }
683 }
684
685 export function transferActualDuration(fiber: Fiber): void {
686 // Transfer time spent rendering these children so we don't lose it
687 // after we rerender. This is used as a helper in special cases
688 // where we should count the work of multiple passes.
689 let child = fiber.child;
690 while (child) {
691 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
692 fiber.actualDuration += child.actualDuration;
693 child = child.sibling;
694 }
695 }
696
697 export function startAnimating(lanes: Lanes): void {
698 animatingLanes |= lanes;
699 animatingTask = null;
700 }
701
702 export function stopAnimating(lanes: Lanes): void {
703 animatingLanes &= ~lanes;
704 animatingTask = null;
705 }
706
707 export function trackAnimatingTask(task: ConsoleTask): void {
708 if (animatingTask === null) {
709 animatingTask = task;
710 }
711 }