How to use markUpdateTimeFromFiberToRoot method in Playwright Internal

Best JavaScript code snippet using playwright-internal

ReactFiberScheduler.js

Source:ReactFiberScheduler.js Github

copy

Full Screen

...286 expirationTime: ExpirationTime,287) {288 checkForNestedUpdates();289 warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);290 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);291 if (root === null) {292 warnAboutUpdateOnUnmountedFiberInDEV(fiber);293 return;294 }295 root.pingTime = NoWork;296 checkForInterruption(fiber, expirationTime);297 recordScheduleUpdate();298 if (expirationTime === Sync) {299 if (workPhase === LegacyUnbatchedPhase) {300 // This is a legacy edge case. The initial mount of a ReactDOM.render-ed301 // root inside of batchedUpdates should be synchronous, but layout updates302 // should be deferred until the end of the batch.303 let callback = renderRoot(root, Sync, true);304 while (callback !== null) {305 callback = callback(true);306 }307 } else {308 scheduleCallbackForRoot(root, ImmediatePriority, Sync);309 if (workPhase === NotWorking) {310 // Flush the synchronous work now, wnless we're already working or inside311 // a batch. This is intentionally inside scheduleUpdateOnFiber instead of312 // scheduleCallbackForFiber to preserve the ability to schedule a callback313 // without immediately flushing it. We only do this for user-initated314 // updates, to preserve historical behavior of sync mode.315 flushImmediateQueue();316 }317 }318 } else {319 // TODO: computeExpirationForFiber also reads the priority. Pass the320 // priority as an argument to that function and this one.321 const priorityLevel = getCurrentPriorityLevel();322 if (priorityLevel === UserBlockingPriority) {323 // This is the result of a discrete event. Track the lowest priority324 // discrete update per root so we can flush them early, if needed.325 if (rootsWithPendingDiscreteUpdates === null) {326 rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);327 } else {328 const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);329 if (330 lastDiscreteTime === undefined ||331 lastDiscreteTime > expirationTime332 ) {333 rootsWithPendingDiscreteUpdates.set(root, expirationTime);334 }335 }336 }337 scheduleCallbackForRoot(root, priorityLevel, expirationTime);338 }339}340export const scheduleWork = scheduleUpdateOnFiber;341// This is split into a separate function so we can mark a fiber with pending342// work without treating it as a typical update that originates from an event;343// e.g. retrying a Suspense boundary isn't an update, but it does schedule work344// on a fiber.345function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {346 // Update the source fiber's expiration time347 if (fiber.expirationTime < expirationTime) {348 fiber.expirationTime = expirationTime;349 }350 let alternate = fiber.alternate;351 if (alternate !== null && alternate.expirationTime < expirationTime) {352 alternate.expirationTime = expirationTime;353 }354 // Walk the parent path to the root and update the child expiration time.355 let node = fiber.return;356 let root = null;357 if (node === null && fiber.tag === HostRoot) {358 root = fiber.stateNode;359 } else {360 while (node !== null) {361 alternate = node.alternate;362 if (node.childExpirationTime < expirationTime) {363 node.childExpirationTime = expirationTime;364 if (365 alternate !== null &&366 alternate.childExpirationTime < expirationTime367 ) {368 alternate.childExpirationTime = expirationTime;369 }370 } else if (371 alternate !== null &&372 alternate.childExpirationTime < expirationTime373 ) {374 alternate.childExpirationTime = expirationTime;375 }376 if (node.return === null && node.tag === HostRoot) {377 root = node.stateNode;378 break;379 }380 node = node.return;381 }382 }383 if (root !== null) {384 // Update the first and last pending expiration times in this root385 const firstPendingTime = root.firstPendingTime;386 if (expirationTime > firstPendingTime) {387 root.firstPendingTime = expirationTime;388 }389 const lastPendingTime = root.lastPendingTime;390 if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {391 root.lastPendingTime = expirationTime;392 }393 }394 return root;395}396// Use this function, along with runRootCallback, to ensure that only a single397// callback per root is scheduled. It's still possible to call renderRoot398// directly, but scheduling via this function helps avoid excessive callbacks.399// It works by storing the callback node and expiration time on the root. When a400// new callback comes in, it compares the expiration time to determine if it401// should cancel the previous one. It also relies on commitRoot scheduling a402// callback to render the next level, because that means we don't need a403// separate callback per expiration time.404function scheduleCallbackForRoot(405 root: FiberRoot,406 priorityLevel: ReactPriorityLevel,407 expirationTime: ExpirationTime,408) {409 const existingCallbackExpirationTime = root.callbackExpirationTime;410 if (existingCallbackExpirationTime < expirationTime) {411 // New callback has higher priority than the existing one.412 const existingCallbackNode = root.callbackNode;413 if (existingCallbackNode !== null) {414 cancelCallback(existingCallbackNode);415 }416 root.callbackExpirationTime = expirationTime;417 let options = null;418 if (expirationTime !== Sync && expirationTime !== Never) {419 let timeout = expirationTimeToMs(expirationTime) - now();420 if (timeout > 5000) {421 // Sanity check. Should never take longer than 5 seconds.422 // TODO: Add internal warning?423 timeout = 5000;424 }425 options = {timeout};426 }427 root.callbackNode = scheduleCallback(428 priorityLevel,429 runRootCallback.bind(430 null,431 root,432 renderRoot.bind(null, root, expirationTime),433 ),434 options,435 );436 if (437 enableUserTimingAPI &&438 expirationTime !== Sync &&439 workPhase !== RenderPhase &&440 workPhase !== CommitPhase441 ) {442 // Scheduled an async callback, and we're not already working. Add an443 // entry to the flamegraph that shows we're waiting for a callback444 // to fire.445 startRequestCallbackTimer();446 }447 }448 // Add the current set of interactions to the pending set associated with449 // this root.450 schedulePendingInteraction(root, expirationTime);451}452function runRootCallback(root, callback, isSync) {453 const prevCallbackNode = root.callbackNode;454 let continuation = null;455 try {456 continuation = callback(isSync);457 if (continuation !== null) {458 return runRootCallback.bind(null, root, continuation);459 } else {460 return null;461 }462 } finally {463 // If the callback exits without returning a continuation, remove the464 // corresponding callback node from the root. Unless the callback node465 // has changed, which implies that it was already cancelled by a high466 // priority update.467 if (continuation === null && prevCallbackNode === root.callbackNode) {468 root.callbackNode = null;469 root.callbackExpirationTime = NoWork;470 }471 }472}473export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {474 if (workPhase === RenderPhase || workPhase === CommitPhase) {475 invariant(476 false,477 'work.commit(): Cannot commit while already rendering. This likely ' +478 'means you attempted to commit from inside a lifecycle method.',479 );480 }481 scheduleCallback(482 ImmediatePriority,483 renderRoot.bind(null, root, expirationTime),484 );485 flushImmediateQueue();486}487export function flushInteractiveUpdates() {488 if (workPhase === RenderPhase || workPhase === CommitPhase) {489 // Can't synchronously flush interactive updates if React is already490 // working. This is currently a no-op.491 // TODO: Should we fire a warning? This happens if you synchronously invoke492 // an input event inside an effect, like with `element.click()`.493 return;494 }495 flushPendingDiscreteUpdates();496}497function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {498 const firstBatch = root.firstBatch;499 if (500 firstBatch !== null &&501 firstBatch._defer &&502 firstBatch._expirationTime >= expirationTime503 ) {504 root.finishedWork = root.current.alternate;505 root.pendingCommitExpirationTime = expirationTime;506 scheduleCallback(NormalPriority, () => {507 firstBatch._onComplete();508 return null;509 });510 return true;511 } else {512 return false;513 }514}515export function deferredUpdates<A>(fn: () => A): A {516 // TODO: Remove in favor of Scheduler.next517 return runWithPriority(NormalPriority, fn);518}519export function interactiveUpdates<A, B, C, R>(520 fn: (A, B, C) => R,521 a: A,522 b: B,523 c: C,524): R {525 if (workPhase === NotWorking) {526 // TODO: Remove this call. Instead of doing this automatically, the caller527 // should explicitly call flushInteractiveUpdates.528 flushPendingDiscreteUpdates();529 }530 return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));531}532export function syncUpdates<A, B, C, R>(533 fn: (A, B, C) => R,534 a: A,535 b: B,536 c: C,537): R {538 return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));539}540function flushPendingDiscreteUpdates() {541 if (rootsWithPendingDiscreteUpdates !== null) {542 // For each root with pending discrete updates, schedule a callback to543 // immediately flush them.544 const roots = rootsWithPendingDiscreteUpdates;545 rootsWithPendingDiscreteUpdates = null;546 roots.forEach((expirationTime, root) => {547 scheduleCallback(548 ImmediatePriority,549 renderRoot.bind(null, root, expirationTime),550 );551 });552 // Now flush the immediate queue.553 flushImmediateQueue();554 }555}556export function batchedUpdates<A, R>(fn: A => R, a: A): R {557 if (workPhase !== NotWorking) {558 // We're already working, or inside a batch, so batchedUpdates is a no-op.559 return fn(a);560 }561 workPhase = BatchedPhase;562 try {563 return fn(a);564 } finally {565 workPhase = NotWorking;566 // Flush the immediate callbacks that were scheduled during this batch567 flushImmediateQueue();568 }569}570export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {571 if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {572 // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is573 // a no-op.574 return fn(a);575 }576 const prevWorkPhase = workPhase;577 workPhase = LegacyUnbatchedPhase;578 try {579 return fn(a);580 } finally {581 workPhase = prevWorkPhase;582 }583}584export function flushSync<A, R>(fn: A => R, a: A): R {585 if (workPhase === RenderPhase || workPhase === CommitPhase) {586 invariant(587 false,588 'flushSync was called from inside a lifecycle method. It cannot be ' +589 'called when React is already rendering.',590 );591 }592 const prevWorkPhase = workPhase;593 workPhase = FlushSyncPhase;594 try {595 return runWithPriority(ImmediatePriority, fn.bind(null, a));596 } finally {597 workPhase = prevWorkPhase;598 // Flush the immediate callbacks that were scheduled during this batch.599 // Note that this will happen even if batchedUpdates is higher up600 // the stack.601 flushImmediateQueue();602 }603}604export function flushControlled(fn: () => mixed): void {605 const prevWorkPhase = workPhase;606 workPhase = BatchedPhase;607 try {608 runWithPriority(ImmediatePriority, fn);609 } finally {610 workPhase = prevWorkPhase;611 if (workPhase === NotWorking) {612 // Flush the immediate callbacks that were scheduled during this batch613 flushImmediateQueue();614 }615 }616}617function prepareFreshStack(root, expirationTime) {618 root.pendingCommitExpirationTime = NoWork;619 const timeoutHandle = root.timeoutHandle;620 if (timeoutHandle !== noTimeout) {621 // The root previous suspended and scheduled a timeout to commit a fallback622 // state. Now that we have additional work, cancel the timeout.623 root.timeoutHandle = noTimeout;624 // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above625 cancelTimeout(timeoutHandle);626 }627 if (workInProgress !== null) {628 let interruptedWork = workInProgress.return;629 while (interruptedWork !== null) {630 unwindInterruptedWork(interruptedWork);631 interruptedWork = interruptedWork.return;632 }633 }634 workInProgressRoot = root;635 workInProgress = createWorkInProgress(root.current, null, expirationTime);636 renderExpirationTime = expirationTime;637 workInProgressRootExitStatus = RootIncomplete;638 workInProgressRootMostRecentEventTime = Sync;639 if (__DEV__) {640 ReactStrictModeWarnings.discardPendingWarnings();641 }642}643function renderRoot(644 root: FiberRoot,645 expirationTime: ExpirationTime,646 isSync: boolean,647): SchedulerCallback | null {648 invariant(649 workPhase !== RenderPhase && workPhase !== CommitPhase,650 'Should not already be working.',651 );652 if (enableUserTimingAPI && expirationTime !== Sync) {653 const didExpire = isSync;654 stopRequestCallbackTimer(didExpire);655 }656 if (root.firstPendingTime < expirationTime) {657 // If there's no work left at this expiration time, exit immediately. This658 // happens when multiple callbacks are scheduled for a single root, but an659 // earlier callback flushes the work of a later one.660 return null;661 }662 if (root.pendingCommitExpirationTime === expirationTime) {663 // There's already a pending commit at this expiration time.664 root.pendingCommitExpirationTime = NoWork;665 return commitRoot.bind(null, root, expirationTime);666 }667 flushPassiveEffects();668 // If the root or expiration time have changed, throw out the existing stack669 // and prepare a fresh one. Otherwise we'll continue where we left off.670 if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {671 prepareFreshStack(root, expirationTime);672 startWorkOnPendingInteraction(root, expirationTime);673 }674 // If we have a work-in-progress fiber, it means there's still work to do675 // in this root.676 if (workInProgress !== null) {677 const prevWorkPhase = workPhase;678 workPhase = RenderPhase;679 let prevDispatcher = ReactCurrentDispatcher.current;680 if (prevDispatcher === null) {681 // The React isomorphic package does not include a default dispatcher.682 // Instead the first renderer will lazily attach one, in order to give683 // nicer error messages.684 prevDispatcher = ContextOnlyDispatcher;685 }686 ReactCurrentDispatcher.current = ContextOnlyDispatcher;687 let prevInteractions: Set<Interaction> | null = null;688 if (enableSchedulerTracing) {689 prevInteractions = __interactionsRef.current;690 __interactionsRef.current = root.memoizedInteractions;691 }692 startWorkLoopTimer(workInProgress);693 // TODO: Fork renderRoot into renderRootSync and renderRootAsync694 if (isSync) {695 if (expirationTime !== Sync) {696 // An async update expired. There may be other expired updates on697 // this root. We should render all the expired work in a698 // single batch.699 const currentTime = requestCurrentTime();700 if (currentTime < expirationTime) {701 // Restart at the current time.702 workPhase = prevWorkPhase;703 resetContextDependencies();704 ReactCurrentDispatcher.current = prevDispatcher;705 if (enableSchedulerTracing) {706 __interactionsRef.current = ((prevInteractions: any): Set<707 Interaction,708 >);709 }710 return renderRoot.bind(null, root, currentTime);711 }712 }713 } else {714 // Since we know we're in a React event, we can clear the current715 // event time. The next update will compute a new event time.716 currentEventTime = NoWork;717 }718 do {719 try {720 if (isSync) {721 workLoopSync();722 } else {723 workLoop();724 }725 break;726 } catch (thrownValue) {727 // Reset module-level state that was set during the render phase.728 resetContextDependencies();729 resetHooks();730 const sourceFiber = workInProgress;731 if (sourceFiber === null || sourceFiber.return === null) {732 // Expected to be working on a non-root fiber. This is a fatal error733 // because there's no ancestor that can handle it; the root is734 // supposed to capture all errors that weren't caught by an error735 // boundary.736 prepareFreshStack(root, expirationTime);737 workPhase = prevWorkPhase;738 throw thrownValue;739 }740 if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {741 // Record the time spent rendering before an error was thrown. This742 // avoids inaccurate Profiler durations in the case of a743 // suspended render.744 stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);745 }746 const returnFiber = sourceFiber.return;747 throwException(748 root,749 returnFiber,750 sourceFiber,751 thrownValue,752 renderExpirationTime,753 );754 workInProgress = completeUnitOfWork(sourceFiber);755 }756 } while (true);757 workPhase = prevWorkPhase;758 resetContextDependencies();759 ReactCurrentDispatcher.current = prevDispatcher;760 if (enableSchedulerTracing) {761 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);762 }763 if (workInProgress !== null) {764 // There's still work left over. Return a continuation.765 stopInterruptedWorkLoopTimer();766 if (expirationTime !== Sync) {767 startRequestCallbackTimer();768 }769 return renderRoot.bind(null, root, expirationTime);770 }771 }772 // We now have a consistent tree. The next step is either to commit it, or, if773 // something suspended, wait to commit it after a timeout.774 stopFinishedWorkLoopTimer();775 const isLocked = resolveLocksOnRoot(root, expirationTime);776 if (isLocked) {777 // This root has a lock that prevents it from committing. Exit. If we begin778 // work on the root again, without any intervening updates, it will finish779 // without doing additional work.780 return null;781 }782 // Set this to null to indicate there's no in-progress render.783 workInProgressRoot = null;784 switch (workInProgressRootExitStatus) {785 case RootIncomplete: {786 invariant(false, 'Should have a work-in-progress.');787 }788 // Flow knows about invariant, so it compains if I add a break statement,789 // but eslint doesn't know about invariant, so it complains if I do.790 // eslint-disable-next-line no-fallthrough791 case RootErrored: {792 // An error was thrown. First check if there is lower priority work793 // scheduled on this root.794 const lastPendingTime = root.lastPendingTime;795 if (root.lastPendingTime < expirationTime) {796 // There's lower priority work. Before raising the error, try rendering797 // at the lower priority to see if it fixes it. Use a continuation to798 // maintain the existing priority and position in the queue.799 return renderRoot.bind(null, root, lastPendingTime);800 }801 if (!isSync) {802 // If we're rendering asynchronously, it's possible the error was803 // caused by tearing due to a mutation during an event. Try rendering804 // one more time without yiedling to events.805 prepareFreshStack(root, expirationTime);806 scheduleCallback(807 ImmediatePriority,808 renderRoot.bind(null, root, expirationTime),809 );810 return null;811 }812 // If we're already rendering synchronously, commit the root in its813 // errored state.814 return commitRoot.bind(null, root, expirationTime);815 }816 case RootSuspended: {817 if (!isSync) {818 const lastPendingTime = root.lastPendingTime;819 if (root.lastPendingTime < expirationTime) {820 // There's lower priority work. It might be unsuspended. Try rendering821 // at that level.822 return renderRoot.bind(null, root, lastPendingTime);823 }824 // If workInProgressRootMostRecentEventTime is Sync, that means we didn't825 // track any event times. That can happen if we retried but nothing switched826 // from fallback to content. There's no reason to delay doing no work.827 if (workInProgressRootMostRecentEventTime !== Sync) {828 let msUntilTimeout = computeMsUntilTimeout(829 workInProgressRootMostRecentEventTime,830 expirationTime,831 );832 // Don't bother with a very short suspense time.833 if (msUntilTimeout > 10) {834 // The render is suspended, it hasn't timed out, and there's no lower835 // priority work to do. Instead of committing the fallback836 // immediately, wait for more data to arrive.837 root.timeoutHandle = scheduleTimeout(838 commitRoot.bind(null, root, expirationTime),839 msUntilTimeout,840 );841 return null;842 }843 }844 }845 // The work expired. Commit immediately.846 return commitRoot.bind(null, root, expirationTime);847 }848 case RootCompleted: {849 // The work completed. Ready to commit.850 return commitRoot.bind(null, root, expirationTime);851 }852 default: {853 invariant(false, 'Unknown root exit status.');854 }855 }856}857export function markRenderEventTime(expirationTime: ExpirationTime): void {858 if (expirationTime < workInProgressRootMostRecentEventTime) {859 workInProgressRootMostRecentEventTime = expirationTime;860 }861}862export function renderDidSuspend(): void {863 if (workInProgressRootExitStatus === RootIncomplete) {864 workInProgressRootExitStatus = RootSuspended;865 }866}867export function renderDidError() {868 if (869 workInProgressRootExitStatus === RootIncomplete ||870 workInProgressRootExitStatus === RootSuspended871 ) {872 workInProgressRootExitStatus = RootErrored;873 }874}875function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {876 // We don't know exactly when the update was scheduled, but we can infer an877 // approximate start time from the expiration time.878 const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);879 return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;880}881function workLoopSync() {882 // Already timed out, so perform work without checking if we need to yield.883 while (workInProgress !== null) {884 workInProgress = performUnitOfWork(workInProgress);885 }886}887function workLoop() {888 // Perform work until Scheduler asks us to yield889 while (workInProgress !== null && !shouldYield()) {890 workInProgress = performUnitOfWork(workInProgress);891 }892}893function performUnitOfWork(unitOfWork: Fiber): Fiber | null {894 // The current, flushed, state of this fiber is the alternate. Ideally895 // nothing should rely on this, but relying on it here means that we don't896 // need an additional field on the work in progress.897 const current = unitOfWork.alternate;898 startWorkTimer(unitOfWork);899 setCurrentDebugFiberInDEV(unitOfWork);900 let next;901 if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {902 startProfilerTimer(unitOfWork);903 next = beginWork(current, unitOfWork, renderExpirationTime);904 stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);905 } else {906 next = beginWork(current, unitOfWork, renderExpirationTime);907 }908 resetCurrentDebugFiberInDEV();909 unitOfWork.memoizedProps = unitOfWork.pendingProps;910 if (next === null) {911 // If this doesn't spawn new work, complete the current work.912 next = completeUnitOfWork(unitOfWork);913 }914 ReactCurrentOwner.current = null;915 return next;916}917function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {918 // Attempt to complete the current unit of work, then move to the next919 // sibling. If there are no more siblings, return to the parent fiber.920 workInProgress = unitOfWork;921 do {922 // The current, flushed, state of this fiber is the alternate. Ideally923 // nothing should rely on this, but relying on it here means that we don't924 // need an additional field on the work in progress.925 const current = workInProgress.alternate;926 const returnFiber = workInProgress.return;927 // Check if the work completed or if something threw.928 if ((workInProgress.effectTag & Incomplete) === NoEffect) {929 setCurrentDebugFiberInDEV(workInProgress);930 let next;931 if (932 !enableProfilerTimer ||933 (workInProgress.mode & ProfileMode) === NoContext934 ) {935 next = completeWork(current, workInProgress, renderExpirationTime);936 } else {937 startProfilerTimer(workInProgress);938 next = completeWork(current, workInProgress, renderExpirationTime);939 // Update render duration assuming we didn't error.940 stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);941 }942 stopWorkTimer(workInProgress);943 resetCurrentDebugFiberInDEV();944 resetChildExpirationTime(workInProgress);945 if (next !== null) {946 // Completing this fiber spawned new work. Work on that next.947 return next;948 }949 if (950 returnFiber !== null &&951 // Do not append effects to parents if a sibling failed to complete952 (returnFiber.effectTag & Incomplete) === NoEffect953 ) {954 // Append all the effects of the subtree and this fiber onto the effect955 // list of the parent. The completion order of the children affects the956 // side-effect order.957 if (returnFiber.firstEffect === null) {958 returnFiber.firstEffect = workInProgress.firstEffect;959 }960 if (workInProgress.lastEffect !== null) {961 if (returnFiber.lastEffect !== null) {962 returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;963 }964 returnFiber.lastEffect = workInProgress.lastEffect;965 }966 // If this fiber had side-effects, we append it AFTER the children's967 // side-effects. We can perform certain side-effects earlier if needed,968 // by doing multiple passes over the effect list. We don't want to969 // schedule our own side-effect on our own list because if end up970 // reusing children we'll schedule this effect onto itself since we're971 // at the end.972 const effectTag = workInProgress.effectTag;973 // Skip both NoWork and PerformedWork tags when creating the effect974 // list. PerformedWork effect is read by React DevTools but shouldn't be975 // committed.976 if (effectTag > PerformedWork) {977 if (returnFiber.lastEffect !== null) {978 returnFiber.lastEffect.nextEffect = workInProgress;979 } else {980 returnFiber.firstEffect = workInProgress;981 }982 returnFiber.lastEffect = workInProgress;983 }984 }985 } else {986 // This fiber did not complete because something threw. Pop values off987 // the stack without entering the complete phase. If this is a boundary,988 // capture values if possible.989 const next = unwindWork(workInProgress, renderExpirationTime);990 // Because this fiber did not complete, don't reset its expiration time.991 if (992 enableProfilerTimer &&993 (workInProgress.mode & ProfileMode) !== NoContext994 ) {995 // Record the render duration for the fiber that errored.996 stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);997 // Include the time spent working on failed children before continuing.998 let actualDuration = workInProgress.actualDuration;999 let child = workInProgress.child;1000 while (child !== null) {1001 actualDuration += child.actualDuration;1002 child = child.sibling;1003 }1004 workInProgress.actualDuration = actualDuration;1005 }1006 if (next !== null) {1007 // If completing this work spawned new work, do that next. We'll come1008 // back here again.1009 // Since we're restarting, remove anything that is not a host effect1010 // from the effect tag.1011 // TODO: The name stopFailedWorkTimer is misleading because Suspense1012 // also captures and restarts.1013 stopFailedWorkTimer(workInProgress);1014 next.effectTag &= HostEffectMask;1015 return next;1016 }1017 stopWorkTimer(workInProgress);1018 if (returnFiber !== null) {1019 // Mark the parent fiber as incomplete and clear its effect list.1020 returnFiber.firstEffect = returnFiber.lastEffect = null;1021 returnFiber.effectTag |= Incomplete;1022 }1023 }1024 const siblingFiber = workInProgress.sibling;1025 if (siblingFiber !== null) {1026 // If there is more work to do in this returnFiber, do that next.1027 return siblingFiber;1028 }1029 // Otherwise, return to the parent1030 workInProgress = returnFiber;1031 } while (workInProgress !== null);1032 // We've reached the root.1033 if (workInProgressRootExitStatus === RootIncomplete) {1034 workInProgressRootExitStatus = RootCompleted;1035 }1036 return null;1037}1038function resetChildExpirationTime(completedWork: Fiber) {1039 if (1040 renderExpirationTime !== Never &&1041 completedWork.childExpirationTime === Never1042 ) {1043 // The children of this component are hidden. Don't bubble their1044 // expiration times.1045 return;1046 }1047 let newChildExpirationTime = NoWork;1048 // Bubble up the earliest expiration time.1049 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {1050 // In profiling mode, resetChildExpirationTime is also used to reset1051 // profiler durations.1052 let actualDuration = completedWork.actualDuration;1053 let treeBaseDuration = completedWork.selfBaseDuration;1054 // When a fiber is cloned, its actualDuration is reset to 0. This value will1055 // only be updated if work is done on the fiber (i.e. it doesn't bailout).1056 // When work is done, it should bubble to the parent's actualDuration. If1057 // the fiber has not been cloned though, (meaning no work was done), then1058 // this value will reflect the amount of time spent working on a previous1059 // render. In that case it should not bubble. We determine whether it was1060 // cloned by comparing the child pointer.1061 const shouldBubbleActualDurations =1062 completedWork.alternate === null ||1063 completedWork.child !== completedWork.alternate.child;1064 let child = completedWork.child;1065 while (child !== null) {1066 const childUpdateExpirationTime = child.expirationTime;1067 const childChildExpirationTime = child.childExpirationTime;1068 if (childUpdateExpirationTime > newChildExpirationTime) {1069 newChildExpirationTime = childUpdateExpirationTime;1070 }1071 if (childChildExpirationTime > newChildExpirationTime) {1072 newChildExpirationTime = childChildExpirationTime;1073 }1074 if (shouldBubbleActualDurations) {1075 actualDuration += child.actualDuration;1076 }1077 treeBaseDuration += child.treeBaseDuration;1078 child = child.sibling;1079 }1080 completedWork.actualDuration = actualDuration;1081 completedWork.treeBaseDuration = treeBaseDuration;1082 } else {1083 let child = completedWork.child;1084 while (child !== null) {1085 const childUpdateExpirationTime = child.expirationTime;1086 const childChildExpirationTime = child.childExpirationTime;1087 if (childUpdateExpirationTime > newChildExpirationTime) {1088 newChildExpirationTime = childUpdateExpirationTime;1089 }1090 if (childChildExpirationTime > newChildExpirationTime) {1091 newChildExpirationTime = childChildExpirationTime;1092 }1093 child = child.sibling;1094 }1095 }1096 completedWork.childExpirationTime = newChildExpirationTime;1097}1098function commitRoot(root, expirationTime) {1099 runWithPriority(1100 ImmediatePriority,1101 commitRootImpl.bind(null, root, expirationTime),1102 );1103 // If there are passive effects, schedule a callback to flush them. This goes1104 // outside commitRootImpl so that it inherits the priority of the render.1105 if (rootWithPendingPassiveEffects !== null) {1106 const priorityLevel = getCurrentPriorityLevel();1107 scheduleCallback(priorityLevel, () => {1108 flushPassiveEffects();1109 return null;1110 });1111 }1112 return null;1113}1114function commitRootImpl(root, expirationTime) {1115 flushPassiveEffects();1116 flushRenderPhaseStrictModeWarningsInDEV();1117 invariant(1118 workPhase !== RenderPhase && workPhase !== CommitPhase,1119 'Should not already be working.',1120 );1121 const finishedWork = root.current.alternate;1122 invariant(finishedWork !== null, 'Should have a work-in-progress root.');1123 // commitRoot never returns a continuation; it always finishes synchronously.1124 // So we can clear these now to allow a new callback to be scheduled.1125 root.callbackNode = null;1126 root.callbackExpirationTime = NoWork;1127 startCommitTimer();1128 // Update the first and last pending times on this root. The new first1129 // pending time is whatever is left on the root fiber.1130 const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;1131 const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;1132 const firstPendingTimeBeforeCommit =1133 childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit1134 ? childExpirationTimeBeforeCommit1135 : updateExpirationTimeBeforeCommit;1136 root.firstPendingTime = firstPendingTimeBeforeCommit;1137 if (firstPendingTimeBeforeCommit < root.lastPendingTime) {1138 // This usually means we've finished all the work, but it can also happen1139 // when something gets downprioritized during render, like a hidden tree.1140 root.lastPendingTime = firstPendingTimeBeforeCommit;1141 }1142 if (root === workInProgressRoot) {1143 // We can reset these now that they are finished.1144 workInProgressRoot = null;1145 workInProgress = null;1146 renderExpirationTime = NoWork;1147 } else {1148 // This indicates that the last root we worked on is not the same one that1149 // we're committing now. This most commonly happens when a suspended root1150 // times out.1151 }1152 // Get the list of effects.1153 let firstEffect;1154 if (finishedWork.effectTag > PerformedWork) {1155 // A fiber's effect list consists only of its children, not itself. So if1156 // the root has an effect, we need to add it to the end of the list. The1157 // resulting list is the set that would belong to the root's parent, if it1158 // had one; that is, all the effects in the tree including the root.1159 if (finishedWork.lastEffect !== null) {1160 finishedWork.lastEffect.nextEffect = finishedWork;1161 firstEffect = finishedWork.firstEffect;1162 } else {1163 firstEffect = finishedWork;1164 }1165 } else {1166 // There is no effect on the root.1167 firstEffect = finishedWork.firstEffect;1168 }1169 if (firstEffect !== null) {1170 const prevWorkPhase = workPhase;1171 workPhase = CommitPhase;1172 let prevInteractions: Set<Interaction> | null = null;1173 if (enableSchedulerTracing) {1174 prevInteractions = __interactionsRef.current;1175 __interactionsRef.current = root.memoizedInteractions;1176 }1177 // Reset this to null before calling lifecycles1178 ReactCurrentOwner.current = null;1179 // The commit phase is broken into several sub-phases. We do a separate pass1180 // of the effect list for each phase: all mutation effects come before all1181 // layout effects, and so on.1182 // The first phase a "before mutation" phase. We use this phase to read the1183 // state of the host tree right before we mutate it. This is where1184 // getSnapshotBeforeUpdate is called.1185 startCommitSnapshotEffectsTimer();1186 prepareForCommit(root.containerInfo);1187 nextEffect = firstEffect;1188 do {1189 if (__DEV__) {1190 invokeGuardedCallback(null, commitBeforeMutationEffects, null);1191 if (hasCaughtError()) {1192 invariant(nextEffect !== null, 'Should be working on an effect.');1193 const error = clearCaughtError();1194 captureCommitPhaseError(nextEffect, error);1195 nextEffect = nextEffect.nextEffect;1196 }1197 } else {1198 try {1199 commitBeforeMutationEffects();1200 } catch (error) {1201 invariant(nextEffect !== null, 'Should be working on an effect.');1202 captureCommitPhaseError(nextEffect, error);1203 nextEffect = nextEffect.nextEffect;1204 }1205 }1206 } while (nextEffect !== null);1207 stopCommitSnapshotEffectsTimer();1208 if (enableProfilerTimer) {1209 // Mark the current commit time to be shared by all Profilers in this1210 // batch. This enables them to be grouped later.1211 recordCommitTime();1212 }1213 // The next phase is the mutation phase, where we mutate the host tree.1214 startCommitHostEffectsTimer();1215 nextEffect = firstEffect;1216 do {1217 if (__DEV__) {1218 invokeGuardedCallback(null, commitMutationEffects, null);1219 if (hasCaughtError()) {1220 invariant(nextEffect !== null, 'Should be working on an effect.');1221 const error = clearCaughtError();1222 captureCommitPhaseError(nextEffect, error);1223 nextEffect = nextEffect.nextEffect;1224 }1225 } else {1226 try {1227 commitMutationEffects();1228 } catch (error) {1229 invariant(nextEffect !== null, 'Should be working on an effect.');1230 captureCommitPhaseError(nextEffect, error);1231 nextEffect = nextEffect.nextEffect;1232 }1233 }1234 } while (nextEffect !== null);1235 stopCommitHostEffectsTimer();1236 resetAfterCommit(root.containerInfo);1237 // The work-in-progress tree is now the current tree. This must come after1238 // the mutation phase, so that the previous tree is still current during1239 // componentWillUnmount, but before the layout phase, so that the finished1240 // work is current during componentDidMount/Update.1241 root.current = finishedWork;1242 // The next phase is the layout phase, where we call effects that read1243 // the host tree after it's been mutated. The idiomatic use case for this is1244 // layout, but class component lifecycles also fire here for legacy reasons.1245 startCommitLifeCyclesTimer();1246 nextEffect = firstEffect;1247 do {1248 if (__DEV__) {1249 invokeGuardedCallback(1250 null,1251 commitLayoutEffects,1252 null,1253 root,1254 expirationTime,1255 );1256 if (hasCaughtError()) {1257 invariant(nextEffect !== null, 'Should be working on an effect.');1258 const error = clearCaughtError();1259 captureCommitPhaseError(nextEffect, error);1260 nextEffect = nextEffect.nextEffect;1261 }1262 } else {1263 try {1264 commitLayoutEffects(root, expirationTime);1265 } catch (error) {1266 invariant(nextEffect !== null, 'Should be working on an effect.');1267 captureCommitPhaseError(nextEffect, error);1268 nextEffect = nextEffect.nextEffect;1269 }1270 }1271 } while (nextEffect !== null);1272 stopCommitLifeCyclesTimer();1273 nextEffect = null;1274 if (enableSchedulerTracing) {1275 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);1276 }1277 workPhase = prevWorkPhase;1278 } else {1279 // No effects.1280 root.current = finishedWork;1281 // Measure these anyway so the flamegraph explicitly shows that there were1282 // no effects.1283 // TODO: Maybe there's a better way to report this.1284 startCommitSnapshotEffectsTimer();1285 stopCommitSnapshotEffectsTimer();1286 if (enableProfilerTimer) {1287 recordCommitTime();1288 }1289 startCommitHostEffectsTimer();1290 stopCommitHostEffectsTimer();1291 startCommitLifeCyclesTimer();1292 stopCommitLifeCyclesTimer();1293 }1294 stopCommitTimer();1295 if (rootDoesHavePassiveEffects) {1296 // This commit has passive effects. Stash a reference to them. But don't1297 // schedule a callback until after flushing layout work.1298 rootDoesHavePassiveEffects = false;1299 rootWithPendingPassiveEffects = root;1300 pendingPassiveEffectsExpirationTime = expirationTime;1301 } else {1302 if (enableSchedulerTracing) {1303 // If there are no passive effects, then we can complete the pending1304 // interactions. Otherwise, we'll wait until after the passive effects1305 // are flushed.1306 finishPendingInteractions(root, expirationTime);1307 }1308 }1309 // Check if there's remaining work on this root1310 const remainingExpirationTime = root.firstPendingTime;1311 if (remainingExpirationTime !== NoWork) {1312 const currentTime = requestCurrentTime();1313 const priorityLevel = inferPriorityFromExpirationTime(1314 currentTime,1315 remainingExpirationTime,1316 );1317 scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);1318 } else {1319 // If there's no remaining work, we can clear the set of already failed1320 // error boundaries.1321 legacyErrorBoundariesThatAlreadyFailed = null;1322 }1323 onCommitRoot(finishedWork.stateNode);1324 if (remainingExpirationTime === Sync) {1325 // Count the number of times the root synchronously re-renders without1326 // finishing. If there are too many, it indicates an infinite update loop.1327 if (root === rootWithNestedUpdates) {1328 nestedUpdateCount++;1329 } else {1330 nestedUpdateCount = 0;1331 rootWithNestedUpdates = root;1332 }1333 } else {1334 nestedUpdateCount = 0;1335 }1336 if (hasUncaughtError) {1337 hasUncaughtError = false;1338 const error = firstUncaughtError;1339 firstUncaughtError = null;1340 throw error;1341 }1342 if (workPhase === LegacyUnbatchedPhase) {1343 // This is a legacy edge case. We just committed the initial mount of1344 // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired1345 // synchronously, but layout updates should be deferred until the end1346 // of the batch.1347 return null;1348 }1349 // If layout work was scheduled, flush it now.1350 flushImmediateQueue();1351 return null;1352}1353function commitBeforeMutationEffects() {1354 while (nextEffect !== null) {1355 if ((nextEffect.effectTag & Snapshot) !== NoEffect) {1356 setCurrentDebugFiberInDEV(nextEffect);1357 recordEffect();1358 const current = nextEffect.alternate;1359 commitBeforeMutationEffectOnFiber(current, nextEffect);1360 resetCurrentDebugFiberInDEV();1361 }1362 nextEffect = nextEffect.nextEffect;1363 }1364}1365function commitMutationEffects() {1366 // TODO: Should probably move the bulk of this function to commitWork.1367 while (nextEffect !== null) {1368 setCurrentDebugFiberInDEV(nextEffect);1369 const effectTag = nextEffect.effectTag;1370 if (effectTag & ContentReset) {1371 commitResetTextContent(nextEffect);1372 }1373 if (effectTag & Ref) {1374 const current = nextEffect.alternate;1375 if (current !== null) {1376 commitDetachRef(current);1377 }1378 }1379 // The following switch statement is only concerned about placement,1380 // updates, and deletions. To avoid needing to add a case for every possible1381 // bitmap value, we remove the secondary effects from the effect tag and1382 // switch on that value.1383 let primaryEffectTag = effectTag & (Placement | Update | Deletion);1384 switch (primaryEffectTag) {1385 case Placement: {1386 commitPlacement(nextEffect);1387 // Clear the "placement" from effect tag so that we know that this is1388 // inserted, before any life-cycles like componentDidMount gets called.1389 // TODO: findDOMNode doesn't rely on this any more but isMounted does1390 // and isMounted is deprecated anyway so we should be able to kill this.1391 nextEffect.effectTag &= ~Placement;1392 break;1393 }1394 case PlacementAndUpdate: {1395 // Placement1396 commitPlacement(nextEffect);1397 // Clear the "placement" from effect tag so that we know that this is1398 // inserted, before any life-cycles like componentDidMount gets called.1399 nextEffect.effectTag &= ~Placement;1400 // Update1401 const current = nextEffect.alternate;1402 commitWork(current, nextEffect);1403 break;1404 }1405 case Update: {1406 const current = nextEffect.alternate;1407 commitWork(current, nextEffect);1408 break;1409 }1410 case Deletion: {1411 commitDeletion(nextEffect);1412 break;1413 }1414 }1415 // TODO: Only record a mutation effect if primaryEffectTag is non-zero.1416 recordEffect();1417 resetCurrentDebugFiberInDEV();1418 nextEffect = nextEffect.nextEffect;1419 }1420}1421function commitLayoutEffects(1422 root: FiberRoot,1423 committedExpirationTime: ExpirationTime,1424) {1425 // TODO: Should probably move the bulk of this function to commitWork.1426 while (nextEffect !== null) {1427 setCurrentDebugFiberInDEV(nextEffect);1428 const effectTag = nextEffect.effectTag;1429 if (effectTag & (Update | Callback)) {1430 recordEffect();1431 const current = nextEffect.alternate;1432 commitLayoutEffectOnFiber(1433 root,1434 current,1435 nextEffect,1436 committedExpirationTime,1437 );1438 }1439 if (effectTag & Ref) {1440 recordEffect();1441 commitAttachRef(nextEffect);1442 }1443 if (effectTag & Passive) {1444 rootDoesHavePassiveEffects = true;1445 }1446 resetCurrentDebugFiberInDEV();1447 nextEffect = nextEffect.nextEffect;1448 }1449}1450export function flushPassiveEffects() {1451 if (rootWithPendingPassiveEffects === null) {1452 return false;1453 }1454 const root = rootWithPendingPassiveEffects;1455 const expirationTime = pendingPassiveEffectsExpirationTime;1456 rootWithPendingPassiveEffects = null;1457 pendingPassiveEffectsExpirationTime = NoWork;1458 let prevInteractions: Set<Interaction> | null = null;1459 if (enableSchedulerTracing) {1460 prevInteractions = __interactionsRef.current;1461 __interactionsRef.current = root.memoizedInteractions;1462 }1463 invariant(1464 workPhase !== RenderPhase && workPhase !== CommitPhase,1465 'Cannot flush passive effects while already rendering.',1466 );1467 const prevWorkPhase = workPhase;1468 workPhase = CommitPhase;1469 // Note: This currently assumes there are no passive effects on the root1470 // fiber, because the root is not part of its own effect list. This could1471 // change in the future.1472 let effect = root.current.firstEffect;1473 while (effect !== null) {1474 if (__DEV__) {1475 setCurrentDebugFiberInDEV(effect);1476 invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);1477 if (hasCaughtError()) {1478 invariant(effect !== null, 'Should be working on an effect.');1479 const error = clearCaughtError();1480 captureCommitPhaseError(effect, error);1481 }1482 resetCurrentDebugFiberInDEV();1483 } else {1484 try {1485 commitPassiveHookEffects(effect);1486 } catch (error) {1487 invariant(effect !== null, 'Should be working on an effect.');1488 captureCommitPhaseError(effect, error);1489 }1490 }1491 effect = effect.nextEffect;1492 }1493 if (enableSchedulerTracing) {1494 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);1495 finishPendingInteractions(root, expirationTime);1496 }1497 workPhase = prevWorkPhase;1498 flushImmediateQueue();1499 // If additional passive effects were scheduled, increment a counter. If this1500 // exceeds the limit, we'll fire a warning.1501 nestedPassiveUpdateCount =1502 rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;1503 return true;1504}1505export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {1506 return (1507 legacyErrorBoundariesThatAlreadyFailed !== null &&1508 legacyErrorBoundariesThatAlreadyFailed.has(instance)1509 );1510}1511export function markLegacyErrorBoundaryAsFailed(instance: mixed) {1512 if (legacyErrorBoundariesThatAlreadyFailed === null) {1513 legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);1514 } else {1515 legacyErrorBoundariesThatAlreadyFailed.add(instance);1516 }1517}1518function prepareToThrowUncaughtError(error: mixed) {1519 if (!hasUncaughtError) {1520 hasUncaughtError = true;1521 firstUncaughtError = error;1522 }1523}1524export const onUncaughtError = prepareToThrowUncaughtError;1525function captureCommitPhaseErrorOnRoot(1526 rootFiber: Fiber,1527 sourceFiber: Fiber,1528 error: mixed,1529) {1530 const errorInfo = createCapturedValue(error, sourceFiber);1531 const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);1532 enqueueUpdate(rootFiber, update);1533 const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);1534 if (root !== null) {1535 scheduleCallbackForRoot(root, ImmediatePriority, Sync);1536 }1537}1538export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {1539 if (sourceFiber.tag === HostRoot) {1540 // Error was thrown at the root. There is no parent, so the root1541 // itself should capture it.1542 captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);1543 return;1544 }1545 let fiber = sourceFiber.return;1546 while (fiber !== null) {1547 if (fiber.tag === HostRoot) {1548 captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);1549 return;1550 } else if (fiber.tag === ClassComponent) {1551 const ctor = fiber.type;1552 const instance = fiber.stateNode;1553 if (1554 typeof ctor.getDerivedStateFromError === 'function' ||1555 (typeof instance.componentDidCatch === 'function' &&1556 !isAlreadyFailedLegacyErrorBoundary(instance))1557 ) {1558 const errorInfo = createCapturedValue(error, sourceFiber);1559 const update = createClassErrorUpdate(1560 fiber,1561 errorInfo,1562 // TODO: This is always sync1563 Sync,1564 );1565 enqueueUpdate(fiber, update);1566 const root = markUpdateTimeFromFiberToRoot(fiber, Sync);1567 if (root !== null) {1568 scheduleCallbackForRoot(root, ImmediatePriority, Sync);1569 }1570 return;1571 }1572 }1573 fiber = fiber.return;1574 }1575}1576export function pingSuspendedRoot(1577 root: FiberRoot,1578 thenable: Thenable,1579 suspendedTime: ExpirationTime,1580) {1581 const pingCache = root.pingCache;1582 if (pingCache !== null) {1583 // The thenable resolved, so we no longer need to memoize, because it will1584 // never be thrown again.1585 pingCache.delete(thenable);1586 }1587 if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {1588 // Received a ping at the same priority level at which we're currently1589 // rendering. Restart from the root. Don't need to schedule a ping because1590 // we're already working on this tree.1591 prepareFreshStack(root, renderExpirationTime);1592 return;1593 }1594 const lastPendingTime = root.lastPendingTime;1595 if (lastPendingTime < suspendedTime) {1596 // The root is no longer suspended at this time.1597 return;1598 }1599 const pingTime = root.pingTime;1600 if (pingTime !== NoWork && pingTime < suspendedTime) {1601 // There's already a lower priority ping scheduled.1602 return;1603 }1604 // Mark the time at which this ping was scheduled.1605 root.pingTime = suspendedTime;1606 const currentTime = requestCurrentTime();1607 const priorityLevel = inferPriorityFromExpirationTime(1608 currentTime,1609 suspendedTime,1610 );1611 scheduleCallbackForRoot(root, priorityLevel, suspendedTime);1612}1613export function retryTimedOutBoundary(boundaryFiber: Fiber) {1614 // The boundary fiber (a Suspense component) previously timed out and was1615 // rendered in its fallback state. One of the promises that suspended it has1616 // resolved, which means at least part of the tree was likely unblocked. Try1617 // rendering again, at a new expiration time.1618 const currentTime = requestCurrentTime();1619 const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);1620 // TODO: Special case idle priority?1621 const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);1622 const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);1623 if (root !== null) {1624 scheduleCallbackForRoot(root, priorityLevel, retryTime);1625 }1626}1627export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {1628 let retryCache: WeakSet<Thenable> | Set<Thenable> | null;1629 if (enableSuspenseServerRenderer) {1630 switch (boundaryFiber.tag) {1631 case SuspenseComponent:1632 retryCache = boundaryFiber.stateNode;1633 break;1634 case DehydratedSuspenseComponent:1635 retryCache = boundaryFiber.memoizedState;1636 break;...

Full Screen

Full Screen

ReactFiberScheduler.new.js

Source:ReactFiberScheduler.new.js Github

copy

Full Screen

...281 expirationTime: ExpirationTime,282) {283 checkForNestedUpdates();284 warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);285 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);286 if (root === null) {287 warnAboutUpdateOnUnmountedFiberInDEV(fiber);288 return;289 }290 root.pingTime = NoWork;291 checkForInterruption(fiber, expirationTime);292 recordScheduleUpdate();293 if (expirationTime === Sync) {294 if (workPhase === LegacyUnbatchedPhase) {295 // This is a legacy edge case. The initial mount of a ReactDOM.render-ed296 // root inside of batchedUpdates should be synchronous, but layout updates297 // should be deferred until the end of the batch.298 let callback = renderRoot(root, Sync, true);299 while (callback !== null) {300 callback = callback(true);301 }302 } else {303 scheduleCallbackForRoot(root, ImmediatePriority, Sync);304 if (workPhase === NotWorking) {305 // Flush the synchronous work now, wnless we're already working or inside306 // a batch. This is intentionally inside scheduleUpdateOnFiber instead of307 // scheduleCallbackForFiber to preserve the ability to schedule a callback308 // without immediately flushing it. We only do this for user-initated309 // updates, to preserve historical behavior of sync mode.310 flushImmediateQueue();311 }312 }313 } else {314 // TODO: computeExpirationForFiber also reads the priority. Pass the315 // priority as an argument to that function and this one.316 const priorityLevel = getCurrentPriorityLevel();317 if (priorityLevel === UserBlockingPriority) {318 // This is the result of a discrete event. Track the lowest priority319 // discrete update per root so we can flush them early, if needed.320 if (rootsWithPendingDiscreteUpdates === null) {321 rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);322 } else {323 const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);324 if (325 lastDiscreteTime === undefined ||326 lastDiscreteTime > expirationTime327 ) {328 rootsWithPendingDiscreteUpdates.set(root, expirationTime);329 }330 }331 }332 scheduleCallbackForRoot(root, priorityLevel, expirationTime);333 }334}335export const scheduleWork = scheduleUpdateOnFiber;336// This is split into a separate function so we can mark a fiber with pending337// work without treating it as a typical update that originates from an event;338// e.g. retrying a Suspense boundary isn't an update, but it does schedule work339// on a fiber.340function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {341 // Update the source fiber's expiration time342 if (fiber.expirationTime < expirationTime) {343 fiber.expirationTime = expirationTime;344 }345 let alternate = fiber.alternate;346 if (alternate !== null && alternate.expirationTime < expirationTime) {347 alternate.expirationTime = expirationTime;348 }349 // Walk the parent path to the root and update the child expiration time.350 let node = fiber.return;351 let root = null;352 if (node === null && fiber.tag === HostRoot) {353 root = fiber.stateNode;354 } else {355 while (node !== null) {356 alternate = node.alternate;357 if (node.childExpirationTime < expirationTime) {358 node.childExpirationTime = expirationTime;359 if (360 alternate !== null &&361 alternate.childExpirationTime < expirationTime362 ) {363 alternate.childExpirationTime = expirationTime;364 }365 } else if (366 alternate !== null &&367 alternate.childExpirationTime < expirationTime368 ) {369 alternate.childExpirationTime = expirationTime;370 }371 if (node.return === null && node.tag === HostRoot) {372 root = node.stateNode;373 break;374 }375 node = node.return;376 }377 }378 if (root !== null) {379 // Update the first and last pending expiration times in this root380 const firstPendingTime = root.firstPendingTime;381 if (expirationTime > firstPendingTime) {382 root.firstPendingTime = expirationTime;383 }384 const lastPendingTime = root.lastPendingTime;385 if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {386 root.lastPendingTime = expirationTime;387 }388 }389 return root;390}391// Use this function, along with runRootCallback, to ensure that only a single392// callback per root is scheduled. It's still possible to call renderRoot393// directly, but scheduling via this function helps avoid excessive callbacks.394// It works by storing the callback node and expiration time on the root. When a395// new callback comes in, it compares the expiration time to determine if it396// should cancel the previous one. It also relies on commitRoot scheduling a397// callback to render the next level, because that means we don't need a398// separate callback per expiration time.399function scheduleCallbackForRoot(400 root: FiberRoot,401 priorityLevel: ReactPriorityLevel,402 expirationTime: ExpirationTime,403) {404 const existingCallbackExpirationTime = root.callbackExpirationTime;405 if (existingCallbackExpirationTime < expirationTime) {406 // New callback has higher priority than the existing one.407 const existingCallbackNode = root.callbackNode;408 if (existingCallbackNode !== null) {409 cancelCallback(existingCallbackNode);410 }411 root.callbackExpirationTime = expirationTime;412 const options =413 expirationTime === Sync414 ? null415 : {timeout: expirationTimeToMs(expirationTime)};416 root.callbackNode = scheduleCallback(417 priorityLevel,418 runRootCallback.bind(419 null,420 root,421 renderRoot.bind(null, root, expirationTime),422 ),423 options,424 );425 if (426 enableUserTimingAPI &&427 expirationTime !== Sync &&428 workPhase !== RenderPhase &&429 workPhase !== CommitPhase430 ) {431 // Scheduled an async callback, and we're not already working. Add an432 // entry to the flamegraph that shows we're waiting for a callback433 // to fire.434 startRequestCallbackTimer();435 }436 }437 const timeoutHandle = root.timeoutHandle;438 if (timeoutHandle !== noTimeout) {439 // The root previous suspended and scheduled a timeout to commit a fallback440 // state. Now that we have additional work, cancel the timeout.441 root.timeoutHandle = noTimeout;442 // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above443 cancelTimeout(timeoutHandle);444 }445 // Add the current set of interactions to the pending set associated with446 // this root.447 schedulePendingInteraction(root, expirationTime);448}449function runRootCallback(root, callback, isSync) {450 const prevCallbackNode = root.callbackNode;451 let continuation = null;452 try {453 continuation = callback(isSync);454 if (continuation !== null) {455 return runRootCallback.bind(null, root, continuation);456 } else {457 return null;458 }459 } finally {460 // If the callback exits without returning a continuation, remove the461 // corresponding callback node from the root. Unless the callback node462 // has changed, which implies that it was already cancelled by a high463 // priority update.464 if (continuation === null && prevCallbackNode === root.callbackNode) {465 root.callbackNode = null;466 root.callbackExpirationTime = NoWork;467 }468 }469}470export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {471 if (workPhase === RenderPhase || workPhase === CommitPhase) {472 invariant(473 false,474 'work.commit(): Cannot commit while already rendering. This likely ' +475 'means you attempted to commit from inside a lifecycle method.',476 );477 }478 scheduleCallback(479 ImmediatePriority,480 renderRoot.bind(null, root, expirationTime),481 );482 flushImmediateQueue();483}484export function flushInteractiveUpdates() {485 if (workPhase === RenderPhase || workPhase === CommitPhase) {486 // Can't synchronously flush interactive updates if React is already487 // working. This is currently a no-op.488 // TODO: Should we fire a warning? This happens if you synchronously invoke489 // an input event inside an effect, like with `element.click()`.490 return;491 }492 flushPendingDiscreteUpdates();493}494function resolveLocksOnRoot(root: FiberRoot, expirationTime: ExpirationTime) {495 const firstBatch = root.firstBatch;496 if (497 firstBatch !== null &&498 firstBatch._defer &&499 firstBatch._expirationTime >= expirationTime500 ) {501 root.finishedWork = root.current.alternate;502 root.pendingCommitExpirationTime = expirationTime;503 scheduleCallback(NormalPriority, () => {504 firstBatch._onComplete();505 return null;506 });507 return true;508 } else {509 return false;510 }511}512export function deferredUpdates<A>(fn: () => A): A {513 // TODO: Remove in favor of Scheduler.next514 return runWithPriority(NormalPriority, fn);515}516export function interactiveUpdates<A, B, C, R>(517 fn: (A, B, C) => R,518 a: A,519 b: B,520 c: C,521): R {522 if (workPhase === NotWorking) {523 // TODO: Remove this call. Instead of doing this automatically, the caller524 // should explicitly call flushInteractiveUpdates.525 flushPendingDiscreteUpdates();526 }527 return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));528}529export function syncUpdates<A, B, C, R>(530 fn: (A, B, C) => R,531 a: A,532 b: B,533 c: C,534): R {535 return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));536}537function flushPendingDiscreteUpdates() {538 if (rootsWithPendingDiscreteUpdates !== null) {539 // For each root with pending discrete updates, schedule a callback to540 // immediately flush them.541 const roots = rootsWithPendingDiscreteUpdates;542 rootsWithPendingDiscreteUpdates = null;543 roots.forEach((expirationTime, root) => {544 scheduleCallback(545 ImmediatePriority,546 renderRoot.bind(null, root, expirationTime),547 );548 });549 // Now flush the immediate queue.550 flushImmediateQueue();551 }552}553export function batchedUpdates<A, R>(fn: A => R, a: A): R {554 if (workPhase !== NotWorking) {555 // We're already working, or inside a batch, so batchedUpdates is a no-op.556 return fn(a);557 }558 workPhase = BatchedPhase;559 try {560 return fn(a);561 } finally {562 workPhase = NotWorking;563 // Flush the immediate callbacks that were scheduled during this batch564 flushImmediateQueue();565 }566}567export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {568 if (workPhase !== BatchedPhase && workPhase !== FlushSyncPhase) {569 // We're not inside batchedUpdates or flushSync, so unbatchedUpdates is570 // a no-op.571 return fn(a);572 }573 const prevWorkPhase = workPhase;574 workPhase = LegacyUnbatchedPhase;575 try {576 return fn(a);577 } finally {578 workPhase = prevWorkPhase;579 }580}581export function flushSync<A, R>(fn: A => R, a: A): R {582 if (workPhase === RenderPhase || workPhase === CommitPhase) {583 invariant(584 false,585 'flushSync was called from inside a lifecycle method. It cannot be ' +586 'called when React is already rendering.',587 );588 }589 const prevWorkPhase = workPhase;590 workPhase = FlushSyncPhase;591 try {592 return runWithPriority(ImmediatePriority, fn.bind(null, a));593 } finally {594 workPhase = prevWorkPhase;595 // Flush the immediate callbacks that were scheduled during this batch.596 // Note that this will happen even if batchedUpdates is higher up597 // the stack.598 flushImmediateQueue();599 }600}601export function flushControlled(fn: () => mixed): void {602 const prevWorkPhase = workPhase;603 workPhase = BatchedPhase;604 try {605 runWithPriority(ImmediatePriority, fn);606 } finally {607 workPhase = prevWorkPhase;608 if (workPhase === NotWorking) {609 // Flush the immediate callbacks that were scheduled during this batch610 flushImmediateQueue();611 }612 }613}614function prepareFreshStack(root, expirationTime) {615 root.pendingCommitExpirationTime = NoWork;616 if (workInProgress !== null) {617 let interruptedWork = workInProgress.return;618 while (interruptedWork !== null) {619 unwindInterruptedWork(interruptedWork);620 interruptedWork = interruptedWork.return;621 }622 }623 workInProgressRoot = root;624 workInProgress = createWorkInProgress(root.current, null, expirationTime);625 renderExpirationTime = expirationTime;626 workInProgressRootExitStatus = RootIncomplete;627 workInProgressRootAbsoluteTimeoutMs = -1;628 if (__DEV__) {629 ReactStrictModeWarnings.discardPendingWarnings();630 }631}632function renderRoot(633 root: FiberRoot,634 expirationTime: ExpirationTime,635 isSync: boolean,636): SchedulerCallback | null {637 invariant(638 workPhase !== RenderPhase && workPhase !== CommitPhase,639 'Should not already be working.',640 );641 if (enableUserTimingAPI && expirationTime !== Sync) {642 const didExpire = isSync;643 const timeoutMs = expirationTimeToMs(expirationTime);644 stopRequestCallbackTimer(didExpire, timeoutMs);645 }646 if (root.firstPendingTime < expirationTime) {647 // If there's no work left at this expiration time, exit immediately. This648 // happens when multiple callbacks are scheduled for a single root, but an649 // earlier callback flushes the work of a later one.650 return null;651 }652 if (root.pendingCommitExpirationTime === expirationTime) {653 // There's already a pending commit at this expiration time.654 root.pendingCommitExpirationTime = NoWork;655 return commitRoot.bind(null, root, expirationTime);656 }657 flushPassiveEffects();658 // If the root or expiration time have changed, throw out the existing stack659 // and prepare a fresh one. Otherwise we'll continue where we left off.660 if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {661 prepareFreshStack(root, expirationTime);662 startWorkOnPendingInteraction(root, expirationTime);663 }664 // If we have a work-in-progress fiber, it means there's still work to do665 // in this root.666 if (workInProgress !== null) {667 const prevWorkPhase = workPhase;668 workPhase = RenderPhase;669 let prevDispatcher = ReactCurrentDispatcher.current;670 if (prevDispatcher === null) {671 // The React isomorphic package does not include a default dispatcher.672 // Instead the first renderer will lazily attach one, in order to give673 // nicer error messages.674 prevDispatcher = ContextOnlyDispatcher;675 }676 ReactCurrentDispatcher.current = ContextOnlyDispatcher;677 let prevInteractions: Set<Interaction> | null = null;678 if (enableSchedulerTracing) {679 prevInteractions = __interactionsRef.current;680 __interactionsRef.current = root.memoizedInteractions;681 }682 startWorkLoopTimer(workInProgress);683 // TODO: Fork renderRoot into renderRootSync and renderRootAsync684 if (isSync) {685 if (expirationTime !== Sync) {686 // An async update expired. There may be other expired updates on687 // this root. We should render all the expired work in a688 // single batch.689 const currentTime = requestCurrentTime();690 if (currentTime < expirationTime) {691 // Restart at the current time.692 workPhase = prevWorkPhase;693 resetContextDependencies();694 ReactCurrentDispatcher.current = prevDispatcher;695 if (enableSchedulerTracing) {696 __interactionsRef.current = ((prevInteractions: any): Set<697 Interaction,698 >);699 }700 return renderRoot.bind(null, root, currentTime);701 }702 }703 } else {704 // Since we know we're in a React event, we can clear the current705 // event time. The next update will compute a new event time.706 currentEventTime = NoWork;707 }708 do {709 try {710 if (isSync) {711 workLoopSync();712 } else {713 workLoop();714 }715 break;716 } catch (thrownValue) {717 // Reset module-level state that was set during the render phase.718 resetContextDependencies();719 resetHooks();720 const sourceFiber = workInProgress;721 if (sourceFiber === null || sourceFiber.return === null) {722 // Expected to be working on a non-root fiber. This is a fatal error723 // because there's no ancestor that can handle it; the root is724 // supposed to capture all errors that weren't caught by an error725 // boundary.726 prepareFreshStack(root, expirationTime);727 workPhase = prevWorkPhase;728 throw thrownValue;729 }730 if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {731 // Record the time spent rendering before an error was thrown. This732 // avoids inaccurate Profiler durations in the case of a733 // suspended render.734 stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);735 }736 const returnFiber = sourceFiber.return;737 throwException(738 root,739 returnFiber,740 sourceFiber,741 thrownValue,742 renderExpirationTime,743 );744 workInProgress = completeUnitOfWork(sourceFiber);745 }746 } while (true);747 workPhase = prevWorkPhase;748 resetContextDependencies();749 ReactCurrentDispatcher.current = prevDispatcher;750 if (enableSchedulerTracing) {751 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);752 }753 if (workInProgress !== null) {754 // There's still work left over. Return a continuation.755 stopInterruptedWorkLoopTimer();756 if (expirationTime !== Sync) {757 startRequestCallbackTimer();758 }759 return renderRoot.bind(null, root, expirationTime);760 }761 }762 // We now have a consistent tree. The next step is either to commit it, or, if763 // something suspended, wait to commit it after a timeout.764 stopFinishedWorkLoopTimer();765 const isLocked = resolveLocksOnRoot(root, expirationTime);766 if (isLocked) {767 // This root has a lock that prevents it from committing. Exit. If we begin768 // work on the root again, without any intervening updates, it will finish769 // without doing additional work.770 return null;771 }772 // Set this to null to indicate there's no in-progress render.773 workInProgressRoot = null;774 switch (workInProgressRootExitStatus) {775 case RootIncomplete: {776 invariant(false, 'Should have a work-in-progress.');777 }778 // Flow knows about invariant, so it compains if I add a break statement,779 // but eslint doesn't know about invariant, so it complains if I do.780 // eslint-disable-next-line no-fallthrough781 case RootErrored: {782 // An error was thrown. First check if there is lower priority work783 // scheduled on this root.784 const lastPendingTime = root.lastPendingTime;785 if (root.lastPendingTime < expirationTime) {786 // There's lower priority work. Before raising the error, try rendering787 // at the lower priority to see if it fixes it. Use a continuation to788 // maintain the existing priority and position in the queue.789 return renderRoot.bind(null, root, lastPendingTime);790 }791 if (!isSync) {792 // If we're rendering asynchronously, it's possible the error was793 // caused by tearing due to a mutation during an event. Try rendering794 // one more time without yiedling to events.795 prepareFreshStack(root, expirationTime);796 scheduleCallback(797 ImmediatePriority,798 renderRoot.bind(null, root, expirationTime),799 );800 return null;801 }802 // If we're already rendering synchronously, commit the root in its803 // errored state.804 return commitRoot.bind(null, root, expirationTime);805 }806 case RootSuspended: {807 const lastPendingTime = root.lastPendingTime;808 if (root.lastPendingTime < expirationTime) {809 // There's lower priority work. It might be unsuspended. Try rendering810 // at that level.811 return renderRoot.bind(null, root, lastPendingTime);812 }813 if (!isSync) {814 const msUntilTimeout = computeMsUntilTimeout(815 root,816 workInProgressRootAbsoluteTimeoutMs,817 );818 if (msUntilTimeout > 0) {819 // The render is suspended, it hasn't timed out, and there's no lower820 // priority work to do. Instead of committing the fallback821 // immediately, wait for more data to arrive.822 root.timeoutHandle = scheduleTimeout(823 commitRoot.bind(null, root, expirationTime),824 msUntilTimeout,825 );826 return null;827 }828 }829 // The work expired. Commit immediately.830 return commitRoot.bind(null, root, expirationTime);831 }832 case RootCompleted: {833 // The work completed. Ready to commit.834 return commitRoot.bind(null, root, expirationTime);835 }836 default: {837 invariant(false, 'Unknown root exit status.');838 }839 }840}841export function renderDidSuspend(842 root: FiberRoot,843 absoluteTimeoutMs: number,844 // TODO: Don't need this argument anymore845 suspendedTime: ExpirationTime,846) {847 if (848 absoluteTimeoutMs >= 0 &&849 workInProgressRootAbsoluteTimeoutMs < absoluteTimeoutMs850 ) {851 workInProgressRootAbsoluteTimeoutMs = absoluteTimeoutMs;852 if (workInProgressRootExitStatus === RootIncomplete) {853 workInProgressRootExitStatus = RootSuspended;854 }855 }856}857export function renderDidError() {858 if (859 workInProgressRootExitStatus === RootIncomplete ||860 workInProgressRootExitStatus === RootSuspended861 ) {862 workInProgressRootExitStatus = RootErrored;863 }864}865function workLoopSync() {866 // Already timed out, so perform work without checking if we need to yield.867 while (workInProgress !== null) {868 workInProgress = performUnitOfWork(workInProgress);869 }870}871function workLoop() {872 // Perform work until Scheduler asks us to yield873 while (workInProgress !== null && !shouldYield()) {874 workInProgress = performUnitOfWork(workInProgress);875 }876}877function performUnitOfWork(unitOfWork: Fiber): Fiber | null {878 // The current, flushed, state of this fiber is the alternate. Ideally879 // nothing should rely on this, but relying on it here means that we don't880 // need an additional field on the work in progress.881 const current = unitOfWork.alternate;882 startWorkTimer(unitOfWork);883 setCurrentDebugFiberInDEV(unitOfWork);884 let next;885 if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoContext) {886 startProfilerTimer(unitOfWork);887 next = beginWork(current, unitOfWork, renderExpirationTime);888 stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);889 } else {890 next = beginWork(current, unitOfWork, renderExpirationTime);891 }892 resetCurrentDebugFiberInDEV();893 unitOfWork.memoizedProps = unitOfWork.pendingProps;894 if (next === null) {895 // If this doesn't spawn new work, complete the current work.896 next = completeUnitOfWork(unitOfWork);897 }898 ReactCurrentOwner.current = null;899 return next;900}901function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {902 // Attempt to complete the current unit of work, then move to the next903 // sibling. If there are no more siblings, return to the parent fiber.904 workInProgress = unitOfWork;905 do {906 // The current, flushed, state of this fiber is the alternate. Ideally907 // nothing should rely on this, but relying on it here means that we don't908 // need an additional field on the work in progress.909 const current = workInProgress.alternate;910 const returnFiber = workInProgress.return;911 // Check if the work completed or if something threw.912 if ((workInProgress.effectTag & Incomplete) === NoEffect) {913 setCurrentDebugFiberInDEV(workInProgress);914 let next;915 if (916 !enableProfilerTimer ||917 (workInProgress.mode & ProfileMode) === NoContext918 ) {919 next = completeWork(current, workInProgress, renderExpirationTime);920 } else {921 startProfilerTimer(workInProgress);922 next = completeWork(current, workInProgress, renderExpirationTime);923 // Update render duration assuming we didn't error.924 stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);925 }926 stopWorkTimer(workInProgress);927 resetCurrentDebugFiberInDEV();928 resetChildExpirationTime(workInProgress);929 if (next !== null) {930 // Completing this fiber spawned new work. Work on that next.931 return next;932 }933 if (934 returnFiber !== null &&935 // Do not append effects to parents if a sibling failed to complete936 (returnFiber.effectTag & Incomplete) === NoEffect937 ) {938 // Append all the effects of the subtree and this fiber onto the effect939 // list of the parent. The completion order of the children affects the940 // side-effect order.941 if (returnFiber.firstEffect === null) {942 returnFiber.firstEffect = workInProgress.firstEffect;943 }944 if (workInProgress.lastEffect !== null) {945 if (returnFiber.lastEffect !== null) {946 returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;947 }948 returnFiber.lastEffect = workInProgress.lastEffect;949 }950 // If this fiber had side-effects, we append it AFTER the children's951 // side-effects. We can perform certain side-effects earlier if needed,952 // by doing multiple passes over the effect list. We don't want to953 // schedule our own side-effect on our own list because if end up954 // reusing children we'll schedule this effect onto itself since we're955 // at the end.956 const effectTag = workInProgress.effectTag;957 // Skip both NoWork and PerformedWork tags when creating the effect958 // list. PerformedWork effect is read by React DevTools but shouldn't be959 // committed.960 if (effectTag > PerformedWork) {961 if (returnFiber.lastEffect !== null) {962 returnFiber.lastEffect.nextEffect = workInProgress;963 } else {964 returnFiber.firstEffect = workInProgress;965 }966 returnFiber.lastEffect = workInProgress;967 }968 }969 } else {970 // This fiber did not complete because something threw. Pop values off971 // the stack without entering the complete phase. If this is a boundary,972 // capture values if possible.973 const next = unwindWork(workInProgress, renderExpirationTime);974 // Because this fiber did not complete, don't reset its expiration time.975 if (976 enableProfilerTimer &&977 (workInProgress.mode & ProfileMode) !== NoContext978 ) {979 // Record the render duration for the fiber that errored.980 stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);981 // Include the time spent working on failed children before continuing.982 let actualDuration = workInProgress.actualDuration;983 let child = workInProgress.child;984 while (child !== null) {985 actualDuration += child.actualDuration;986 child = child.sibling;987 }988 workInProgress.actualDuration = actualDuration;989 }990 if (next !== null) {991 // If completing this work spawned new work, do that next. We'll come992 // back here again.993 // Since we're restarting, remove anything that is not a host effect994 // from the effect tag.995 // TODO: The name stopFailedWorkTimer is misleading because Suspense996 // also captures and restarts.997 stopFailedWorkTimer(workInProgress);998 next.effectTag &= HostEffectMask;999 return next;1000 }1001 stopWorkTimer(workInProgress);1002 if (returnFiber !== null) {1003 // Mark the parent fiber as incomplete and clear its effect list.1004 returnFiber.firstEffect = returnFiber.lastEffect = null;1005 returnFiber.effectTag |= Incomplete;1006 }1007 }1008 const siblingFiber = workInProgress.sibling;1009 if (siblingFiber !== null) {1010 // If there is more work to do in this returnFiber, do that next.1011 return siblingFiber;1012 }1013 // Otherwise, return to the parent1014 workInProgress = returnFiber;1015 } while (workInProgress !== null);1016 // We've reached the root.1017 if (workInProgressRootExitStatus === RootIncomplete) {1018 workInProgressRootExitStatus = RootCompleted;1019 }1020 return null;1021}1022function resetChildExpirationTime(completedWork: Fiber) {1023 if (1024 renderExpirationTime !== Never &&1025 completedWork.childExpirationTime === Never1026 ) {1027 // The children of this component are hidden. Don't bubble their1028 // expiration times.1029 return;1030 }1031 let newChildExpirationTime = NoWork;1032 // Bubble up the earliest expiration time.1033 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoContext) {1034 // In profiling mode, resetChildExpirationTime is also used to reset1035 // profiler durations.1036 let actualDuration = completedWork.actualDuration;1037 let treeBaseDuration = completedWork.selfBaseDuration;1038 // When a fiber is cloned, its actualDuration is reset to 0. This value will1039 // only be updated if work is done on the fiber (i.e. it doesn't bailout).1040 // When work is done, it should bubble to the parent's actualDuration. If1041 // the fiber has not been cloned though, (meaning no work was done), then1042 // this value will reflect the amount of time spent working on a previous1043 // render. In that case it should not bubble. We determine whether it was1044 // cloned by comparing the child pointer.1045 const shouldBubbleActualDurations =1046 completedWork.alternate === null ||1047 completedWork.child !== completedWork.alternate.child;1048 let child = completedWork.child;1049 while (child !== null) {1050 const childUpdateExpirationTime = child.expirationTime;1051 const childChildExpirationTime = child.childExpirationTime;1052 if (childUpdateExpirationTime > newChildExpirationTime) {1053 newChildExpirationTime = childUpdateExpirationTime;1054 }1055 if (childChildExpirationTime > newChildExpirationTime) {1056 newChildExpirationTime = childChildExpirationTime;1057 }1058 if (shouldBubbleActualDurations) {1059 actualDuration += child.actualDuration;1060 }1061 treeBaseDuration += child.treeBaseDuration;1062 child = child.sibling;1063 }1064 completedWork.actualDuration = actualDuration;1065 completedWork.treeBaseDuration = treeBaseDuration;1066 } else {1067 let child = completedWork.child;1068 while (child !== null) {1069 const childUpdateExpirationTime = child.expirationTime;1070 const childChildExpirationTime = child.childExpirationTime;1071 if (childUpdateExpirationTime > newChildExpirationTime) {1072 newChildExpirationTime = childUpdateExpirationTime;1073 }1074 if (childChildExpirationTime > newChildExpirationTime) {1075 newChildExpirationTime = childChildExpirationTime;1076 }1077 child = child.sibling;1078 }1079 }1080 completedWork.childExpirationTime = newChildExpirationTime;1081}1082function commitRoot(root, expirationTime) {1083 runWithPriority(1084 ImmediatePriority,1085 commitRootImpl.bind(null, root, expirationTime),1086 );1087 // If there are passive effects, schedule a callback to flush them. This goes1088 // outside commitRootImpl so that it inherits the priority of the render.1089 if (rootWithPendingPassiveEffects !== null) {1090 const priorityLevel = getCurrentPriorityLevel();1091 scheduleCallback(priorityLevel, () => {1092 flushPassiveEffects();1093 return null;1094 });1095 }1096 return null;1097}1098function commitRootImpl(root, expirationTime) {1099 flushPassiveEffects();1100 flushRenderPhaseStrictModeWarningsInDEV();1101 invariant(1102 workPhase !== RenderPhase && workPhase !== CommitPhase,1103 'Should not already be working.',1104 );1105 const finishedWork = root.current.alternate;1106 invariant(finishedWork !== null, 'Should have a work-in-progress root.');1107 // commitRoot never returns a continuation; it always finishes synchronously.1108 // So we can clear these now to allow a new callback to be scheduled.1109 root.callbackNode = null;1110 root.callbackExpirationTime = NoWork;1111 startCommitTimer();1112 // Update the first and last pending times on this root. The new first1113 // pending time is whatever is left on the root fiber.1114 const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;1115 const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;1116 const firstPendingTimeBeforeCommit =1117 childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit1118 ? childExpirationTimeBeforeCommit1119 : updateExpirationTimeBeforeCommit;1120 root.firstPendingTime = firstPendingTimeBeforeCommit;1121 if (firstPendingTimeBeforeCommit < root.lastPendingTime) {1122 // This usually means we've finished all the work, but it can also happen1123 // when something gets downprioritized during render, like a hidden tree.1124 root.lastPendingTime = firstPendingTimeBeforeCommit;1125 }1126 if (root === workInProgressRoot) {1127 // We can reset these now that they are finished.1128 workInProgressRoot = null;1129 workInProgress = null;1130 renderExpirationTime = NoWork;1131 } else {1132 // This indicates that the last root we worked on is not the same one that1133 // we're committing now. This most commonly happens when a suspended root1134 // times out.1135 }1136 // Get the list of effects.1137 let firstEffect;1138 if (finishedWork.effectTag > PerformedWork) {1139 // A fiber's effect list consists only of its children, not itself. So if1140 // the root has an effect, we need to add it to the end of the list. The1141 // resulting list is the set that would belong to the root's parent, if it1142 // had one; that is, all the effects in the tree including the root.1143 if (finishedWork.lastEffect !== null) {1144 finishedWork.lastEffect.nextEffect = finishedWork;1145 firstEffect = finishedWork.firstEffect;1146 } else {1147 firstEffect = finishedWork;1148 }1149 } else {1150 // There is no effect on the root.1151 firstEffect = finishedWork.firstEffect;1152 }1153 if (firstEffect !== null) {1154 const prevWorkPhase = workPhase;1155 workPhase = CommitPhase;1156 let prevInteractions: Set<Interaction> | null = null;1157 if (enableSchedulerTracing) {1158 prevInteractions = __interactionsRef.current;1159 __interactionsRef.current = root.memoizedInteractions;1160 }1161 // Reset this to null before calling lifecycles1162 ReactCurrentOwner.current = null;1163 // The commit phase is broken into several sub-phases. We do a separate pass1164 // of the effect list for each phase: all mutation effects come before all1165 // layout effects, and so on.1166 // The first phase a "before mutation" phase. We use this phase to read the1167 // state of the host tree right before we mutate it. This is where1168 // getSnapshotBeforeUpdate is called.1169 startCommitSnapshotEffectsTimer();1170 prepareForCommit(root.containerInfo);1171 nextEffect = firstEffect;1172 do {1173 if (__DEV__) {1174 invokeGuardedCallback(null, commitBeforeMutationEffects, null);1175 if (hasCaughtError()) {1176 invariant(nextEffect !== null, 'Should be working on an effect.');1177 const error = clearCaughtError();1178 captureCommitPhaseError(nextEffect, error);1179 nextEffect = nextEffect.nextEffect;1180 }1181 } else {1182 try {1183 commitBeforeMutationEffects();1184 } catch (error) {1185 invariant(nextEffect !== null, 'Should be working on an effect.');1186 captureCommitPhaseError(nextEffect, error);1187 nextEffect = nextEffect.nextEffect;1188 }1189 }1190 } while (nextEffect !== null);1191 stopCommitSnapshotEffectsTimer();1192 if (enableProfilerTimer) {1193 // Mark the current commit time to be shared by all Profilers in this1194 // batch. This enables them to be grouped later.1195 recordCommitTime();1196 }1197 // The next phase is the mutation phase, where we mutate the host tree.1198 startCommitHostEffectsTimer();1199 nextEffect = firstEffect;1200 do {1201 if (__DEV__) {1202 invokeGuardedCallback(null, commitMutationEffects, null);1203 if (hasCaughtError()) {1204 invariant(nextEffect !== null, 'Should be working on an effect.');1205 const error = clearCaughtError();1206 captureCommitPhaseError(nextEffect, error);1207 nextEffect = nextEffect.nextEffect;1208 }1209 } else {1210 try {1211 commitMutationEffects();1212 } catch (error) {1213 invariant(nextEffect !== null, 'Should be working on an effect.');1214 captureCommitPhaseError(nextEffect, error);1215 nextEffect = nextEffect.nextEffect;1216 }1217 }1218 } while (nextEffect !== null);1219 stopCommitHostEffectsTimer();1220 resetAfterCommit(root.containerInfo);1221 // The work-in-progress tree is now the current tree. This must come after1222 // the mutation phase, so that the previous tree is still current during1223 // componentWillUnmount, but before the layout phase, so that the finished1224 // work is current during componentDidMount/Update.1225 root.current = finishedWork;1226 // The next phase is the layout phase, where we call effects that read1227 // the host tree after it's been mutated. The idiomatic use case for this is1228 // layout, but class component lifecycles also fire here for legacy reasons.1229 startCommitLifeCyclesTimer();1230 nextEffect = firstEffect;1231 do {1232 if (__DEV__) {1233 invokeGuardedCallback(1234 null,1235 commitLayoutEffects,1236 null,1237 root,1238 expirationTime,1239 );1240 if (hasCaughtError()) {1241 invariant(nextEffect !== null, 'Should be working on an effect.');1242 const error = clearCaughtError();1243 captureCommitPhaseError(nextEffect, error);1244 nextEffect = nextEffect.nextEffect;1245 }1246 } else {1247 try {1248 commitLayoutEffects(root, expirationTime);1249 } catch (error) {1250 invariant(nextEffect !== null, 'Should be working on an effect.');1251 captureCommitPhaseError(nextEffect, error);1252 nextEffect = nextEffect.nextEffect;1253 }1254 }1255 } while (nextEffect !== null);1256 stopCommitLifeCyclesTimer();1257 nextEffect = null;1258 if (enableSchedulerTracing) {1259 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);1260 }1261 workPhase = prevWorkPhase;1262 } else {1263 // No effects.1264 root.current = finishedWork;1265 // Measure these anyway so the flamegraph explicitly shows that there were1266 // no effects.1267 // TODO: Maybe there's a better way to report this.1268 startCommitSnapshotEffectsTimer();1269 stopCommitSnapshotEffectsTimer();1270 if (enableProfilerTimer) {1271 recordCommitTime();1272 }1273 startCommitHostEffectsTimer();1274 stopCommitHostEffectsTimer();1275 startCommitLifeCyclesTimer();1276 stopCommitLifeCyclesTimer();1277 }1278 stopCommitTimer();1279 if (rootDoesHavePassiveEffects) {1280 // This commit has passive effects. Stash a reference to them. But don't1281 // schedule a callback until after flushing layout work.1282 rootDoesHavePassiveEffects = false;1283 rootWithPendingPassiveEffects = root;1284 pendingPassiveEffectsExpirationTime = expirationTime;1285 } else {1286 if (enableSchedulerTracing) {1287 // If there are no passive effects, then we can complete the pending1288 // interactions. Otherwise, we'll wait until after the passive effects1289 // are flushed.1290 finishPendingInteractions(root, expirationTime);1291 }1292 }1293 // Check if there's remaining work on this root1294 const remainingExpirationTime = root.firstPendingTime;1295 if (remainingExpirationTime !== NoWork) {1296 const currentTime = requestCurrentTime();1297 const priorityLevel = inferPriorityFromExpirationTime(1298 currentTime,1299 remainingExpirationTime,1300 );1301 scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);1302 } else {1303 // If there's no remaining work, we can clear the set of already failed1304 // error boundaries.1305 legacyErrorBoundariesThatAlreadyFailed = null;1306 }1307 onCommitRoot(finishedWork.stateNode);1308 if (remainingExpirationTime === Sync) {1309 // Count the number of times the root synchronously re-renders without1310 // finishing. If there are too many, it indicates an infinite update loop.1311 if (root === rootWithNestedUpdates) {1312 nestedUpdateCount++;1313 } else {1314 nestedUpdateCount = 0;1315 rootWithNestedUpdates = root;1316 }1317 } else {1318 nestedUpdateCount = 0;1319 }1320 if (hasUncaughtError) {1321 hasUncaughtError = false;1322 const error = firstUncaughtError;1323 firstUncaughtError = null;1324 throw error;1325 }1326 if (workPhase === LegacyUnbatchedPhase) {1327 // This is a legacy edge case. We just committed the initial mount of1328 // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired1329 // synchronously, but layout updates should be deferred until the end1330 // of the batch.1331 return null;1332 }1333 // If layout work was scheduled, flush it now.1334 flushImmediateQueue();1335 return null;1336}1337function commitBeforeMutationEffects() {1338 while (nextEffect !== null) {1339 if ((nextEffect.effectTag & Snapshot) !== NoEffect) {1340 setCurrentDebugFiberInDEV(nextEffect);1341 recordEffect();1342 const current = nextEffect.alternate;1343 commitBeforeMutationEffectOnFiber(current, nextEffect);1344 resetCurrentDebugFiberInDEV();1345 }1346 nextEffect = nextEffect.nextEffect;1347 }1348}1349function commitMutationEffects() {1350 // TODO: Should probably move the bulk of this function to commitWork.1351 while (nextEffect !== null) {1352 setCurrentDebugFiberInDEV(nextEffect);1353 const effectTag = nextEffect.effectTag;1354 if (effectTag & ContentReset) {1355 commitResetTextContent(nextEffect);1356 }1357 if (effectTag & Ref) {1358 const current = nextEffect.alternate;1359 if (current !== null) {1360 commitDetachRef(current);1361 }1362 }1363 // The following switch statement is only concerned about placement,1364 // updates, and deletions. To avoid needing to add a case for every possible1365 // bitmap value, we remove the secondary effects from the effect tag and1366 // switch on that value.1367 let primaryEffectTag = effectTag & (Placement | Update | Deletion);1368 switch (primaryEffectTag) {1369 case Placement: {1370 commitPlacement(nextEffect);1371 // Clear the "placement" from effect tag so that we know that this is1372 // inserted, before any life-cycles like componentDidMount gets called.1373 // TODO: findDOMNode doesn't rely on this any more but isMounted does1374 // and isMounted is deprecated anyway so we should be able to kill this.1375 nextEffect.effectTag &= ~Placement;1376 break;1377 }1378 case PlacementAndUpdate: {1379 // Placement1380 commitPlacement(nextEffect);1381 // Clear the "placement" from effect tag so that we know that this is1382 // inserted, before any life-cycles like componentDidMount gets called.1383 nextEffect.effectTag &= ~Placement;1384 // Update1385 const current = nextEffect.alternate;1386 commitWork(current, nextEffect);1387 break;1388 }1389 case Update: {1390 const current = nextEffect.alternate;1391 commitWork(current, nextEffect);1392 break;1393 }1394 case Deletion: {1395 commitDeletion(nextEffect);1396 break;1397 }1398 }1399 // TODO: Only record a mutation effect if primaryEffectTag is non-zero.1400 recordEffect();1401 resetCurrentDebugFiberInDEV();1402 nextEffect = nextEffect.nextEffect;1403 }1404}1405function commitLayoutEffects(1406 root: FiberRoot,1407 committedExpirationTime: ExpirationTime,1408) {1409 // TODO: Should probably move the bulk of this function to commitWork.1410 while (nextEffect !== null) {1411 setCurrentDebugFiberInDEV(nextEffect);1412 const effectTag = nextEffect.effectTag;1413 if (effectTag & (Update | Callback)) {1414 recordEffect();1415 const current = nextEffect.alternate;1416 commitLayoutEffectOnFiber(1417 root,1418 current,1419 nextEffect,1420 committedExpirationTime,1421 );1422 }1423 if (effectTag & Ref) {1424 recordEffect();1425 commitAttachRef(nextEffect);1426 }1427 if (effectTag & Passive) {1428 rootDoesHavePassiveEffects = true;1429 }1430 resetCurrentDebugFiberInDEV();1431 nextEffect = nextEffect.nextEffect;1432 }1433}1434export function flushPassiveEffects() {1435 if (rootWithPendingPassiveEffects === null) {1436 return false;1437 }1438 const root = rootWithPendingPassiveEffects;1439 const expirationTime = pendingPassiveEffectsExpirationTime;1440 rootWithPendingPassiveEffects = null;1441 pendingPassiveEffectsExpirationTime = NoWork;1442 let prevInteractions: Set<Interaction> | null = null;1443 if (enableSchedulerTracing) {1444 prevInteractions = __interactionsRef.current;1445 __interactionsRef.current = root.memoizedInteractions;1446 }1447 invariant(1448 workPhase !== RenderPhase && workPhase !== CommitPhase,1449 'Cannot flush passive effects while already rendering.',1450 );1451 const prevWorkPhase = workPhase;1452 workPhase = CommitPhase;1453 // Note: This currently assumes there are no passive effects on the root1454 // fiber, because the root is not part of its own effect list. This could1455 // change in the future.1456 let effect = root.current.firstEffect;1457 while (effect !== null) {1458 if (__DEV__) {1459 setCurrentDebugFiberInDEV(effect);1460 invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);1461 if (hasCaughtError()) {1462 invariant(effect !== null, 'Should be working on an effect.');1463 const error = clearCaughtError();1464 captureCommitPhaseError(effect, error);1465 }1466 resetCurrentDebugFiberInDEV();1467 } else {1468 try {1469 commitPassiveHookEffects(effect);1470 } catch (error) {1471 invariant(effect !== null, 'Should be working on an effect.');1472 captureCommitPhaseError(effect, error);1473 }1474 }1475 effect = effect.nextEffect;1476 }1477 if (enableSchedulerTracing) {1478 __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);1479 finishPendingInteractions(root, expirationTime);1480 }1481 workPhase = prevWorkPhase;1482 flushImmediateQueue();1483 // If additional passive effects were scheduled, increment a counter. If this1484 // exceeds the limit, we'll fire a warning.1485 nestedPassiveUpdateCount =1486 rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;1487 return true;1488}1489export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {1490 return (1491 legacyErrorBoundariesThatAlreadyFailed !== null &&1492 legacyErrorBoundariesThatAlreadyFailed.has(instance)1493 );1494}1495export function markLegacyErrorBoundaryAsFailed(instance: mixed) {1496 if (legacyErrorBoundariesThatAlreadyFailed === null) {1497 legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);1498 } else {1499 legacyErrorBoundariesThatAlreadyFailed.add(instance);1500 }1501}1502function prepareToThrowUncaughtError(error: mixed) {1503 if (!hasUncaughtError) {1504 hasUncaughtError = true;1505 firstUncaughtError = error;1506 }1507}1508export const onUncaughtError = prepareToThrowUncaughtError;1509function captureCommitPhaseErrorOnRoot(1510 rootFiber: Fiber,1511 sourceFiber: Fiber,1512 error: mixed,1513) {1514 const errorInfo = createCapturedValue(error, sourceFiber);1515 const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);1516 enqueueUpdate(rootFiber, update);1517 const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);1518 if (root !== null) {1519 scheduleCallbackForRoot(root, ImmediatePriority, Sync);1520 }1521}1522export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {1523 if (sourceFiber.tag === HostRoot) {1524 // Error was thrown at the root. There is no parent, so the root1525 // itself should capture it.1526 captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);1527 return;1528 }1529 let fiber = sourceFiber.return;1530 while (fiber !== null) {1531 if (fiber.tag === HostRoot) {1532 captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);1533 return;1534 } else if (fiber.tag === ClassComponent) {1535 const ctor = fiber.type;1536 const instance = fiber.stateNode;1537 if (1538 typeof ctor.getDerivedStateFromError === 'function' ||1539 (typeof instance.componentDidCatch === 'function' &&1540 !isAlreadyFailedLegacyErrorBoundary(instance))1541 ) {1542 const errorInfo = createCapturedValue(error, sourceFiber);1543 const update = createClassErrorUpdate(1544 fiber,1545 errorInfo,1546 // TODO: This is always sync1547 Sync,1548 );1549 enqueueUpdate(fiber, update);1550 const root = markUpdateTimeFromFiberToRoot(fiber, Sync);1551 if (root !== null) {1552 scheduleCallbackForRoot(root, ImmediatePriority, Sync);1553 }1554 return;1555 }1556 }1557 fiber = fiber.return;1558 }1559}1560export function pingSuspendedRoot(1561 root: FiberRoot,1562 thenable: Thenable,1563 suspendedTime: ExpirationTime,1564) {1565 const pingCache = root.pingCache;1566 if (pingCache !== null) {1567 // The thenable resolved, so we no longer need to memoize, because it will1568 // never be thrown again.1569 pingCache.delete(thenable);1570 }1571 if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {1572 // Received a ping at the same priority level at which we're currently1573 // rendering. Restart from the root. Don't need to schedule a ping because1574 // we're already working on this tree.1575 prepareFreshStack(root, renderExpirationTime);1576 return;1577 }1578 const lastPendingTime = root.lastPendingTime;1579 if (lastPendingTime < suspendedTime) {1580 // The root is no longer suspended at this time.1581 return;1582 }1583 const pingTime = root.pingTime;1584 if (pingTime !== NoWork && pingTime < suspendedTime) {1585 // There's already a lower priority ping scheduled.1586 return;1587 }1588 // Mark the time at which this ping was scheduled.1589 root.pingTime = suspendedTime;1590 const currentTime = requestCurrentTime();1591 const priorityLevel = inferPriorityFromExpirationTime(1592 currentTime,1593 suspendedTime,1594 );1595 scheduleCallbackForRoot(root, priorityLevel, suspendedTime);1596}1597export function retryTimedOutBoundary(boundaryFiber: Fiber) {1598 // The boundary fiber (a Suspense component) previously timed out and was1599 // rendered in its fallback state. One of the promises that suspended it has1600 // resolved, which means at least part of the tree was likely unblocked. Try1601 // rendering again, at a new expiration time.1602 const currentTime = requestCurrentTime();1603 const retryTime = computeExpirationForFiber(currentTime, boundaryFiber);1604 // TODO: Special case idle priority?1605 const priorityLevel = inferPriorityFromExpirationTime(currentTime, retryTime);1606 const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);1607 if (root !== null) {1608 scheduleCallbackForRoot(root, priorityLevel, retryTime);1609 }1610}1611export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {1612 let retryCache: WeakSet<Thenable> | Set<Thenable> | null;1613 if (enableSuspenseServerRenderer) {1614 switch (boundaryFiber.tag) {1615 case SuspenseComponent:1616 retryCache = boundaryFiber.stateNode;1617 break;1618 case DehydratedSuspenseComponent:1619 retryCache = boundaryFiber.memoizedState;1620 break;...

Full Screen

Full Screen

scheduleWork.js

Source:scheduleWork.js Github

copy

Full Screen

...166 queue.lastUpdate.next = update;167 queue.lastUpdate = update;168 }169}170function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {171 if (fiber.expirationTime < expirationTime) {172 fiber.expirationTime = expirationTime;173 }174 let alternate = fiber.alternate;175 if (alternate !== null && alternate.expirationTime < expirationTime) {176 alternate.expirationTime = expirationTime;177 }178 let node = fiber.current;179 let root = null;180 if (root === null && fiber.tag === HostRoot) {181 root = fiber.stateNode;182 }183 while (node != null) {184 if (node.return === null && node.tag === HostRoot) {185 root = node.stateNode;186 break;187 }188 node = node.return;189 }190 return root;191}192/**193 * 把需要更新的放在update对象 然后放在fiber的lastUpdate或firstUpdate194 * 195 * @param {*} fiber 196 * @param {*} update 197 */198function enqueueUpdate(fiber, update) {199 /*// 当前父fiber中的位置200 index: 0,201 // fiber实例对象,指向当前组件实例202 stateNode: Card,203 204 // setState待更新状态,回调,DOM更新的队列205 updateQueue: null,206 207 // 当前UI的状态,反映了UI当前在屏幕上的表现状态208 memoizedState: {},209 210 // 前次渲染中用于决定UI的props211 memoizedProps: {},212 213 // 即将应用于下一次渲染更新的props214 pendingProps: {},*/215 // fiber更新时基于当前fiber克隆出的镜像,更新时记录两个fiber diff的变化;更新结束后alternate替换之前的fiber成为新的fiber节点216 const alternate = fiber.alternate;217 let queue1 = null;218 let queue2 = null;219 if (alternate === null) {220 queue1 = fiber.updateQueue;221 if (queue1 === null) {222 queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);223 }224 }225 if (queue2 == null) {226 appendUpdateToQueue(queue1, update);227 }228}229function scheduleWork(fiber, expirationTime) {230 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);231 if (root === null) {232 return;233 }234 if (expirationTime === Sync) {235 }236 let callback = renderRoot(root, Sync, true);237 while (callback) {238 callback = callback(true);239 }240}...

Full Screen

Full Screen

ReactFiberWorkLoop.js

Source:ReactFiberWorkLoop.js Github

copy

Full Screen

...48}49export function computeExpirationForFiber(currentTime, fiber) {50}51export function scheduleUpdateOnFiber(fiber, expirationTime) {52 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);53 prepareFreshStack(root, expirationTime);54 performSyncWorkOnRoot(root);55}56function prepareFreshStack(root, expirationTime) {57 root.finishedWork = null;58 if (workInProgress !== null) {59 }60 workInProgress = createWorkInProgress(root.current, null);61}62export function unbatchedUpdates(fn, a) {63 try {64 return fn(a);65 } finally {66 }67}68function completeUnitOfWork(unitOfWork) {69 workInProgress = unitOfWork;70 do {71 const current = workInProgress.alternate;72 const returnFiber = workInProgress.return;73 if (true) {74 let next = completeWork(current, workInProgress);75 if (next) {76 return next;77 }78 if (returnFiber) {79 if (!returnFiber.firstEffect) {80 returnFiber.firstEffect = workInProgress.firstEffect;81 }82 if (workInProgress.lastEffect) {83 if (returnFiber.lastEffect) {84 returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;85 }86 returnFiber.lastEffect = workInProgress.lastEffect;87 }88 const effectTag = workInProgress.effectTag;89 if (effectTag) {90 if (returnFiber.lastEffect) {91 returnFiber.lastEffect.nextEffect = workInProgress;92 } else {93 returnFiber.firstEffect = workInProgress;94 }95 returnFiber.lastEffect = workInProgress;96 }97 }98 const sibling = workInProgress.sibling;99 if (sibling) {100 return sibling;101 }102 workInProgress = returnFiber;103 }104 } while (workInProgress)105 return null;106}107function commitRoot(root) {108 const finishedWork = root.finishedWork;109 if (!finishedWork) {110 return null;111 }112 root.finishedWork = null;113 let firstEffect;114 if (root.effectTag) {115 if (finishedWork.lastEffect) {116 finishedWork.lastEffect.nextEffect = finishedWork;117 firstEffect = finishedWork.firstEffect;118 } else {119 firstEffect = finishedWork;120 }121 } else {122 firstEffect = finishedWork.firstEffect;123 }124 let nextEffect;125 if (firstEffect) {126 nextEffect = firstEffect;127 do {128 try {129 nextEffect = commitBeforeMutationEffects(nextEffect);130 } catch (e) {131 nextEffect = nextEffect.nextEffect;132 }133 } while (nextEffect)134 nextEffect = firstEffect;135 do {136 try {137 nextEffect = commitMutationEffects(root, nextEffect);138 } catch (e) {139 nextEffect = nextEffect.nextEffect;140 }141 } while (nextEffect)142 }143}144// 执行工作单元145function performUnitOfWork(unitOfWork) {146 const current = unitOfWork.alternate;147 let next = beginWork(current, unitOfWork);148 if (!next) {149 next = completeUnitOfWork(unitOfWork);150 }151 return next;152}153// 同步任务入口154function performSyncWorkOnRoot(root) {155 // 如果 workInProgress存在 开始同步更新156 if (workInProgress) {157 do {158 workLoopSync();159 break;160 } while (true)161 }162 root.finishedWork = root.current.alternate;163 commitRoot(root);164 return null;165}166function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {167 let node = fiber.return;168 let root;169 if (!node && fiber.tag === HostRoot) {170 root = fiber.stateNode;171 } else {172 while (node) {173 if (!node.return && node.tag === HostRoot) {174 root = node.stateNode;175 break;176 }177 node = node.return;178 }179 }180 return root;...

Full Screen

Full Screen

fiberScheduler.js

Source:fiberScheduler.js Github

copy

Full Screen

...64// }65// /**66// * 用正在进行的work 标记fiber67// */68// function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {69// // 更新fiber的过期时间 time越大 说明优先级越高70// if (fiber.expirationTime < expirationTime) {71// fiber.expirationTime = expirationTime72// }73// // 同时更新 work In Process 的expiration Time74// if (fiber.alternate !== null && fiber.alternate.expirationTime < expirationTime) {75// fiber.alternate.expirationTime = expirationTime76// }77// // 从当前fiber向上更新父fiber的childExpirationTime78// let node = fiber.return79// let root = null80// if (node === null) {81// root = node.stateNode82// } else {83// while(node !== null) {84// // 更新fiber 与 alternate上的 childExpirationTime85// alternate = node.alternate;86// if (node.childExpirationTime < expirationTime) {87// node.childExpirationTime = expirationTime88// if (alternate !== null && alternate.childExpirationTime < expirationTime) {89// alternate.childExpirationTime = expirationTime90// }91// } else if (alternate !== null && alternate.childExpirationTime < expirationTime) {92// alternate.childExpirationTime = expirationTime93// }94// if (node.return === null && node.tag === HostRoot) {95// root = node.stateNode96// break97// }98// node = node.return99// }100// }101// if (root !== null) {102// // Update the first and last pending expiration times in this root103// const firstPendingTime = root.firstPendingTime;104// if (expirationTime > firstPendingTime) {105// root.firstPendingTime = expirationTime;106// }107// const lastPendingTime = root.lastPendingTime;108// // lastPendingTime为什么取最小值? 上一次更新的过期时间109// if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {110// root.lastPendingTime = expirationTime;111// }112// }113// return root114// }115// export function shedulerWork(fiber, expirationTime) {116// const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime)117 ...

Full Screen

Full Screen

scheduleUpdateOnFiber.js

Source:scheduleUpdateOnFiber.js Github

copy

Full Screen

1function scheduleUpdateOnFiber(fiber, expirationTime) {2 checkForNestedUpdates();3 warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);4 var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);5 if (root === null) {6 warnAboutUpdateOnUnmountedFiberInDEV(fiber);7 return;8 }9 checkForInterruption(fiber, expirationTime);10 recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the11 // priority as an argument to that function and this one.12 var priorityLevel = getCurrentPriorityLevel();13 if (expirationTime === Sync) {14 if ( // Check if we're inside unbatchedUpdates15 (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering16 (executionContext & (RenderContext | CommitContext)) === NoContext) {17 // Register pending interactions on the root to avoid losing traced interaction data.18 schedulePendingInteractions(root, expirationTime); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed...

Full Screen

Full Screen

1-2__scheduleUpdateOnFiber.js

Source:1-2__scheduleUpdateOnFiber.js Github

copy

Full Screen

1function scheduleUpdateOnFiber(fiber, expirationTime) {2 checkForNestedUpdates();3 warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);4 var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);5 if (root === null) {6 warnAboutUpdateOnUnmountedFiberInDEV(fiber);7 return;8 }9 checkForInterruption(fiber, expirationTime);10 recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the11 // priority as an argument to that function and this one.12 var priorityLevel = getCurrentPriorityLevel();13 if (expirationTime === Sync) {14 if ( // Check if we're inside unbatchedUpdates15 (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering16 (executionContext & (RenderContext | CommitContext)) === NoContext) {17 // Register pending interactions on the root to avoid losing traced interaction data.18 schedulePendingInteractions(root, expirationTime); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed...

Full Screen

Full Screen

updateEqueue.js

Source:updateEqueue.js Github

copy

Full Screen

...47 fiber: Fiber,48 expirationTime: ExpirationTime,49) {50 checkForNestedUpdates();51 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);52 if (root === null) {53 warnAboutUpdateOnUnmountedFiberInDEV(fiber);54 return;55 }56 checkForInterruption(fiber, expirationTime);57 const priorityLevel = getCurrentPriorityLevel();58 if (expirationTime === Sync) {59 if (60 (executionContext & LegacyUnbatchedContext) !== NoContext &&61 (executionContext & (RenderContext | CommitContext)) === NoContext62 ) {63 schedulePendingInteractions(root, expirationTime);64 performSyncWorkOnRoot(root);65 } else {...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.screenshot({ path: `example.png` });7 await browser.close();8})();

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.click('text=Get Started');7 await page.click('text=Docs');8 await page.close();9 await context.close();10 await browser.close();11})();12module.exports = {13 {14 use: {15 viewport: { width: 1280, height: 720 },16 },17 },18};19{20 "scripts": {21 },22 "dependencies": {23 }24}

Full Screen

Using AI Code Generation

copy

Full Screen

1const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const { chromium } = require('playwright');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 await markUpdateTimeFromFiberToRoot(page.mainFrame(), 1000);8})();9const { markUpdateTime } = require('playwright/lib/server/supplements/recorder/recorderSupplement');10const { chromium } = require('playwright');11(async () => {12 const browser = await chromium.launch();13 const context = await browser.newContext();14 const page = await context.newPage();15 await markUpdateTime(page.mainFrame(), 1000);16})();17const { markUpdateTimeFromFiber } = require('playwright/lib/server/supplements/recorder/recorderSupplement');18const { chromium } = require('playwright');19(async () => {20 const browser = await chromium.launch();21 const context = await browser.newContext();22 const page = await context.newPage();23 await markUpdateTimeFromFiber(page.mainFrame(), 1000);24})();25const { markUpdateTimeFromRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');26const { chromium } = require('playwright');27(async () => {28 const browser = await chromium.launch();29 const context = await browser.newContext();30 const page = await context.newPage();31 await markUpdateTimeFromRoot(page.mainFrame(), 1000);32})();33const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');

Full Screen

Using AI Code Generation

copy

Full Screen

1const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');3const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');4const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');5const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');6const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');7const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');8const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');9const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');10const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');11const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');12const { markUpdateTimeFromFiberToRoot } = require('play

Full Screen

Using AI Code Generation

copy

Full Screen

1const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');2const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');3const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');4const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');5const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');6const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');7const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');8const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');9const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');10const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');11const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');12const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/trace/recorder/recorderApp');

Full Screen

Using AI Code Generation

copy

Full Screen

1const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');2const { Page } = require('playwright');3markUpdateTimeFromFiberToRoot(Page, 'click');4const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');5const { Page } = require('playwright');6markUpdateTimeFromFiberToRoot(Page, 'click');7const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');8const { Page } = require('playwright');9markUpdateTimeFromFiberToRoot(Page, 'click');10const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');11const { Page } = require('playwright');12markUpdateTimeFromFiberToRoot(Page, 'click');13const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');14const { Page } = require('playwright');15markUpdateTimeFromFiberToRoot(Page, 'click');16const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/server/supplements/recorder/recorderSupplement');17const { Page } = require('playwright');18markUpdateTimeFromFiberToRoot(Page, 'click');

Full Screen

Using AI Code Generation

copy

Full Screen

1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.click('text=Get started');7 await page.waitForTimeout(1000);8 await page.screenshot({ path: 'example.png' });9 await browser.close();10})();11const { test, expect } = require('@playwright/test');12const { chromium } = require('playwright');13test('should measure time taken to update the UI', async ({ page }) => {14 await page.click('text=Get started');15 await page.waitForTimeout(1000);16 await page.screenshot({ path: 'example.png' });17 const timeTaken = await page.evaluate(() => {18 return window.playwright.internal.markUpdateTimeFromFiberToRoot();19 });20 expect(timeTaken).toBeLessThan(1000);21});22import { test, expect } from '@playwright/test';23import { chromium } from 'playwright';24test('should measure time taken to update the UI', async ({ page }) => {25 await page.click('text=Get started');26 await page.waitForTimeout(1000);27 await page.screenshot({ path: 'example.png' });28 const timeTaken = await page.evaluate(() => {29 return window.playwright.internal.markUpdateTimeFromFiberToRoot();30 });31 expect(timeTaken).toBeLessThan(1000);32});33markUpdateTimeFromFiberToRoot()34const { chromium } = require('playwright');35(async () => {

Full Screen

Using AI Code Generation

copy

Full Screen

1const { Page } = require('playwright');2const { markUpdateTimeFromFiberToRoot } = require('playwright/lib/internal/recorder/recorderUtils');3const page = new Page();4const fiber = { name: 'test' };5markUpdateTimeFromFiberToRoot(page, fiber, 10);6const { test, expect } = require('@playwright/test');7test('test', async ({ page }) => {8 const fiber = { name: 'test' };9 expect(fiber).toBe(true);10});11test('test', async ({ page }) => {12 const fiber = { name: 'test' };13 expect(fiber).toBe(true);14 await page.pause();15});

Full Screen

Using AI Code Generation

copy

Full Screen

1const {markUpdateTimeFromFiberToRoot} = require('playwright/lib/server/webkit/wkPage.js');2const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');3const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');4const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');5const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');6const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');7const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');8const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');9const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');10const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');11const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');12const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');13const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');14const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');15const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');16const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');17const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');18const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');19const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');20const {getFiberForPage} = require('playwright/lib/server/webkit/wkPage.js');21const {getFiberForFrame} = require('playwright/lib/server/webkit/wkPage.js');22const {getFiberForPage} = require('playwright/lib/server/webkit

Full Screen

Playwright tutorial

LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.

Chapters:

  1. What is Playwright : Playwright is comparatively new but has gained good popularity. Get to know some history of the Playwright with some interesting facts connected with it.
  2. How To Install Playwright : Learn in detail about what basic configuration and dependencies are required for installing Playwright and run a test. Get a step-by-step direction for installing the Playwright automation framework.
  3. Playwright Futuristic Features: Launched in 2020, Playwright gained huge popularity quickly because of some obliging features such as Playwright Test Generator and Inspector, Playwright Reporter, Playwright auto-waiting mechanism and etc. Read up on those features to master Playwright testing.
  4. What is Component Testing: Component testing in Playwright is a unique feature that allows a tester to test a single component of a web application without integrating them with other elements. Learn how to perform Component testing on the Playwright automation framework.
  5. Inputs And Buttons In Playwright: Every website has Input boxes and buttons; learn about testing inputs and buttons with different scenarios and examples.
  6. Functions and Selectors in Playwright: Learn how to launch the Chromium browser with Playwright. Also, gain a better understanding of some important functions like “BrowserContext,” which allows you to run multiple browser sessions, and “newPage” which interacts with a page.
  7. Handling Alerts and Dropdowns in Playwright : Playwright interact with different types of alerts and pop-ups, such as simple, confirmation, and prompt, and different types of dropdowns, such as single selector and multi-selector get your hands-on with handling alerts and dropdown in Playright testing.
  8. Playwright vs Puppeteer: Get to know about the difference between two testing frameworks and how they are different than one another, which browsers they support, and what features they provide.
  9. Run Playwright Tests on LambdaTest: Playwright testing with LambdaTest leverages test performance to the utmost. You can run multiple Playwright tests in Parallel with the LammbdaTest test cloud. Get a step-by-step guide to run your Playwright test on the LambdaTest platform.
  10. Playwright Python Tutorial: Playwright automation framework support all major languages such as Python, JavaScript, TypeScript, .NET and etc. However, there are various advantages to Python end-to-end testing with Playwright because of its versatile utility. Get the hang of Playwright python testing with this chapter.
  11. Playwright End To End Testing Tutorial: Get your hands on with Playwright end-to-end testing and learn to use some exciting features such as TraceViewer, Debugging, Networking, Component testing, Visual testing, and many more.
  12. Playwright Video Tutorial: Watch the video tutorials on Playwright testing from experts and get a consecutive in-depth explanation of Playwright automation testing.

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful