From b4ac045757b4b45771610bfc9b9ed68c43b9f055 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jun 2022 13:27:21 -0400 Subject: [PATCH] Land changes in main reconciler fork --- .../src/ReactFiberClassUpdateQueue.old.js | 35 ++- .../src/ReactFiberConcurrentUpdates.old.js | 282 +++++++++++------- .../src/ReactFiberHooks.old.js | 46 ++- .../src/ReactFiberLane.old.js | 33 ++ .../src/ReactFiberRoot.old.js | 2 + .../src/ReactFiberWorkLoop.old.js | 17 +- .../src/__tests__/ReactOffscreen-test.js | 3 +- .../__tests__/ReactOffscreenSuspense-test.js | 5 +- scripts/merge-fork/forked-revisions | 3 - 9 files changed, 259 insertions(+), 167 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js index cc1643eeefed6..3b39da9ae4181 100644 --- a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js +++ b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js @@ -90,8 +90,10 @@ import type {Lanes, Lane} from './ReactFiberLane.old'; import { NoLane, NoLanes, + OffscreenLane, isSubsetOfLanes, mergeLanes, + removeLanes, isTransitionLane, intersectLanes, markRootEntangled, @@ -108,6 +110,7 @@ import {StrictLegacyMode} from './ReactTypeOfMode'; import { markSkippedUpdateLanes, isUnsafeClassRenderPhaseUpdate, + getWorkInProgressRootRenderLanes, } from './ReactFiberWorkLoop.old'; import { enqueueConcurrentClassUpdate, @@ -132,7 +135,6 @@ export type Update = {| export type SharedQueue = {| pending: Update | null, - interleaved: Update | null, lanes: Lanes, |}; @@ -172,7 +174,6 @@ export function initializeUpdateQueue(fiber: Fiber): void { lastBaseUpdate: null, shared: { pending: null, - interleaved: null, lanes: NoLanes, }, effects: null, @@ -525,9 +526,23 @@ export function processUpdateQueue( let update = firstBaseUpdate; do { - const updateLane = update.lane; + // TODO: Don't need this field anymore const updateEventTime = update.eventTime; - if (!isSubsetOfLanes(renderLanes, updateLane)) { + + // An extra OffscreenLane bit is added to updates that were made to + // a hidden tree, so that we can distinguish them from updates that were + // already there when the tree was hidden. + const updateLane = removeLanes(update.lane, OffscreenLane); + const isHiddenUpdate = updateLane !== update.lane; + + // Check if this update was made while the tree was hidden. If so, then + // it's not a "base" update and we should disregard the extra base lanes + // that were added to renderLanes when we entered the Offscreen tree. + const shouldSkipUpdate = isHiddenUpdate + ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane) + : !isSubsetOfLanes(renderLanes, updateLane); + + if (shouldSkipUpdate) { // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. @@ -622,17 +637,7 @@ export function processUpdateQueue( queue.firstBaseUpdate = newFirstBaseUpdate; queue.lastBaseUpdate = newLastBaseUpdate; - // Interleaved updates are stored on a separate queue. We aren't going to - // process them during this render, but we do need to track which lanes - // are remaining. - const lastInterleaved = queue.shared.interleaved; - if (lastInterleaved !== null) { - let interleaved = lastInterleaved; - do { - newLanes = mergeLanes(newLanes, interleaved.lane); - interleaved = ((interleaved: any).next: Update); - } while (interleaved !== lastInterleaved); - } else if (firstBaseUpdate === null) { + if (firstBaseUpdate === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. queue.shared.lanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js index 42b6520238c15..3cba3bec0650e 100644 --- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js @@ -16,100 +16,128 @@ import type { SharedQueue as ClassQueue, Update as ClassUpdate, } from './ReactFiberClassUpdateQueue.old'; -import type {Lane} from './ReactFiberLane.old'; +import type {Lane, Lanes} from './ReactFiberLane.old'; +import type {OffscreenInstance} from './ReactFiberOffscreenComponent'; import { warnAboutUpdateOnNotYetMountedFiberInDEV, throwIfInfiniteUpdateLoopDetected, } from './ReactFiberWorkLoop.old'; -import {mergeLanes} from './ReactFiberLane.old'; +import { + NoLane, + NoLanes, + mergeLanes, + markHiddenUpdate, +} from './ReactFiberLane.old'; import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; -import {HostRoot} from './ReactWorkTags'; - -// An array of all update queues that received updates during the current -// render. When this render exits, either because it finishes or because it is -// interrupted, the interleaved updates will be transferred onto the main part -// of the queue. -let concurrentQueues: Array< - HookQueue | ClassQueue, -> | null = null; - -export function pushConcurrentUpdateQueue( - queue: HookQueue | ClassQueue, -) { - if (concurrentQueues === null) { - concurrentQueues = [queue]; - } else { - concurrentQueues.push(queue); - } -} +import {HostRoot, OffscreenComponent} from './ReactWorkTags'; + +export type ConcurrentUpdate = { + next: ConcurrentUpdate, + lane: Lane, +}; + +type ConcurrentQueue = { + pending: ConcurrentUpdate | null, +}; + +// If a render is in progress, and we receive an update from a concurrent event, +// we wait until the current render is over (either finished or interrupted) +// before adding it to the fiber/hook queue. Push to this array so we can +// access the queue, fiber, update, et al later. +const concurrentQueues: Array = []; +let concurrentQueuesIndex = 0; + +let concurrentlyUpdatedLanes: Lanes = NoLanes; + +export function finishQueueingConcurrentUpdates(): void { + const endIndex = concurrentQueuesIndex; + concurrentQueuesIndex = 0; + + concurrentlyUpdatedLanes = NoLanes; -export function finishQueueingConcurrentUpdates() { - // Transfer the interleaved updates onto the main queue. Each queue has a - // `pending` field and an `interleaved` field. When they are not null, they - // point to the last node in a circular linked list. We need to append the - // interleaved list to the end of the pending list by joining them into a - // single, circular list. - if (concurrentQueues !== null) { - for (let i = 0; i < concurrentQueues.length; i++) { - const queue = concurrentQueues[i]; - const lastInterleavedUpdate = queue.interleaved; - if (lastInterleavedUpdate !== null) { - queue.interleaved = null; - const firstInterleavedUpdate = lastInterleavedUpdate.next; - const lastPendingUpdate = queue.pending; - if (lastPendingUpdate !== null) { - const firstPendingUpdate = lastPendingUpdate.next; - lastPendingUpdate.next = (firstInterleavedUpdate: any); - lastInterleavedUpdate.next = (firstPendingUpdate: any); - } - queue.pending = (lastInterleavedUpdate: any); + let i = 0; + while (i < endIndex) { + const fiber: Fiber = concurrentQueues[i]; + concurrentQueues[i++] = null; + const queue: ConcurrentQueue = concurrentQueues[i]; + concurrentQueues[i++] = null; + const update: ConcurrentUpdate = concurrentQueues[i]; + concurrentQueues[i++] = null; + const lane: Lane = concurrentQueues[i]; + concurrentQueues[i++] = null; + + if (queue !== null && update !== null) { + const pending = queue.pending; + if (pending === null) { + // This is the first update. Create a circular list. + update.next = update; + } else { + update.next = pending.next; + pending.next = update; } + queue.pending = update; + } + + if (lane !== NoLane) { + markUpdateLaneFromFiberToRoot(fiber, update, lane); } - concurrentQueues = null; } } -export function enqueueConcurrentHookUpdate( +export function getConcurrentlyUpdatedLanes(): Lanes { + return concurrentlyUpdatedLanes; +} + +function enqueueUpdate( fiber: Fiber, - queue: HookQueue, - update: HookUpdate, + queue: ConcurrentQueue | null, + update: ConcurrentUpdate | null, lane: Lane, ) { - const interleaved = queue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushConcurrentUpdateQueue(queue); - } else { - update.next = interleaved.next; - interleaved.next = update; + // Don't update the `childLanes` on the return path yet. If we already in + // the middle of rendering, wait until after it has completed. + concurrentQueues[concurrentQueuesIndex++] = fiber; + concurrentQueues[concurrentQueuesIndex++] = queue; + concurrentQueues[concurrentQueuesIndex++] = update; + concurrentQueues[concurrentQueuesIndex++] = lane; + + concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane); + + // The fiber's `lane` field is used in some places to check if any work is + // scheduled, to perform an eager bailout, so we need to update it immediately. + // TODO: We should probably move this to the "shared" queue instead. + fiber.lanes = mergeLanes(fiber.lanes, lane); + const alternate = fiber.alternate; + if (alternate !== null) { + alternate.lanes = mergeLanes(alternate.lanes, lane); } - queue.interleaved = update; +} - return markUpdateLaneFromFiberToRoot(fiber, lane); +export function enqueueConcurrentHookUpdate( + fiber: Fiber, + queue: HookQueue, + update: HookUpdate, + lane: Lane, +): FiberRoot | null { + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); + return getRootForUpdatedFiber(fiber); } export function enqueueConcurrentHookUpdateAndEagerlyBailout( fiber: Fiber, queue: HookQueue, update: HookUpdate, - lane: Lane, ): void { - const interleaved = queue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushConcurrentUpdateQueue(queue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - queue.interleaved = update; + // This function is used to queue an update that doesn't need a rerender. The + // only reason we queue it is in case there's a subsequent higher priority + // update that causes it to be rebased. + const lane = NoLane; + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); } export function enqueueConcurrentClassUpdate( @@ -117,78 +145,104 @@ export function enqueueConcurrentClassUpdate( queue: ClassQueue, update: ClassUpdate, lane: Lane, -) { - const interleaved = queue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushConcurrentUpdateQueue(queue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - queue.interleaved = update; - - return markUpdateLaneFromFiberToRoot(fiber, lane); +): FiberRoot | null { + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); + return getRootForUpdatedFiber(fiber); } -export function enqueueConcurrentRenderForLane(fiber: Fiber, lane: Lane) { - return markUpdateLaneFromFiberToRoot(fiber, lane); +export function enqueueConcurrentRenderForLane( + fiber: Fiber, + lane: Lane, +): FiberRoot | null { + enqueueUpdate(fiber, null, null, lane); + return getRootForUpdatedFiber(fiber); } // Calling this function outside this module should only be done for backwards // compatibility and should always be accompanied by a warning. -export const unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot; - -function markUpdateLaneFromFiberToRoot( +export function unsafe_markUpdateLaneFromFiberToRoot( sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { - // TODO: We will detect and infinite update loop and throw even if this fiber - // has already unmounted. This isn't really necessary but it happens to be the - // current behavior we've used for several release cycles. Consider not - // performing this check if the updated fiber already unmounted, since it's - // not possible for that to cause an infinite update loop. - throwIfInfiniteUpdateLoopDetected(); + markUpdateLaneFromFiberToRoot(sourceFiber, null, lane); + return getRootForUpdatedFiber(sourceFiber); +} +function markUpdateLaneFromFiberToRoot( + sourceFiber: Fiber, + update: ConcurrentUpdate | null, + lane: Lane, +): void { // Update the source fiber's lanes sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } - if (__DEV__) { - if ( - alternate === null && - (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } - } // Walk the parent path to the root and update the child lanes. - let node = sourceFiber; + let isHidden = false; let parent = sourceFiber.return; + let node = sourceFiber; while (parent !== null) { parent.childLanes = mergeLanes(parent.childLanes, lane); alternate = parent.alternate; if (alternate !== null) { alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } else { - if (__DEV__) { - if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } + } + + if (parent.tag === OffscreenComponent) { + const offscreenInstance: OffscreenInstance = parent.stateNode; + if (offscreenInstance.isHidden) { + isHidden = true; } } + node = parent; parent = parent.return; } - if (node.tag === HostRoot) { + + if (isHidden && update !== null && node.tag === HostRoot) { const root: FiberRoot = node.stateNode; - return root; - } else { - return null; + markHiddenUpdate(root, update, lane); + } +} + +function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null { + // TODO: We will detect and infinite update loop and throw even if this fiber + // has already unmounted. This isn't really necessary but it happens to be the + // current behavior we've used for several release cycles. Consider not + // performing this check if the updated fiber already unmounted, since it's + // not possible for that to cause an infinite update loop. + throwIfInfiniteUpdateLoopDetected(); + + // When a setState happens, we must ensure the root is scheduled. Because + // update queues do not have a backpointer to the root, the only way to do + // this currently is to walk up the return path. This used to not be a big + // deal because we would have to walk up the return path to set + // the `childLanes`, anyway, but now those two traversals happen at + // different times. + // TODO: Consider adding a `root` backpointer on the update queue. + detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber); + let node = sourceFiber; + let parent = node.return; + while (parent !== null) { + detectUpdateOnUnmountedFiber(sourceFiber, node); + node = parent; + parent = node.return; + } + return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null; +} + +function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) { + if (__DEV__) { + const alternate = parent.alternate; + if ( + alternate === null && + (parent.flags & (Placement | Hydrating)) !== NoFlags + ) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); + } } } diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index fc25083fe4253..c83dd1aa102a3 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -44,6 +44,7 @@ import { import { NoLane, SyncLane, + OffscreenLane, NoLanes, isSubsetOfLanes, includesBlockingLane, @@ -83,6 +84,7 @@ import { } from './ReactHookEffectTags'; import { getWorkInProgressRoot, + getWorkInProgressRootRenderLanes, scheduleUpdateOnFiber, requestUpdateLane, requestEventTime, @@ -131,7 +133,6 @@ export type Update = {| export type UpdateQueue = {| pending: Update | null, - interleaved: Update | null, lanes: Lanes, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, @@ -741,7 +742,6 @@ function mountReducer( hook.memoizedState = hook.baseState = initialState; const queue: UpdateQueue = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: reducer, @@ -813,8 +813,20 @@ function updateReducer( let newBaseQueueLast = null; let update = first; do { - const updateLane = update.lane; - if (!isSubsetOfLanes(renderLanes, updateLane)) { + // An extra OffscreenLane bit is added to updates that were made to + // a hidden tree, so that we can distinguish them from updates that were + // already there when the tree was hidden. + const updateLane = removeLanes(update.lane, OffscreenLane); + const isHiddenUpdate = updateLane !== update.lane; + + // Check if this update was made while the tree was hidden. If so, then + // it's not a "base" update and we should disregard the extra base lanes + // that were added to renderLanes when we entered the Offscreen tree. + const shouldSkipUpdate = isHiddenUpdate + ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane) + : !isSubsetOfLanes(renderLanes, updateLane); + + if (shouldSkipUpdate) { // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. @@ -888,22 +900,7 @@ function updateReducer( queue.lastRenderedState = newState; } - // Interleaved updates are stored on a separate queue. We aren't going to - // process them during this render, but we do need to track which lanes - // are remaining. - const lastInterleaved = queue.interleaved; - if (lastInterleaved !== null) { - let interleaved = lastInterleaved; - do { - const interleavedLane = interleaved.lane; - currentlyRenderingFiber.lanes = mergeLanes( - currentlyRenderingFiber.lanes, - interleavedLane, - ); - markSkippedUpdateLanes(interleavedLane); - interleaved = ((interleaved: any).next: Update); - } while (interleaved !== lastInterleaved); - } else if (baseQueue === null) { + if (baseQueue === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. queue.lanes = NoLanes; @@ -1211,7 +1208,6 @@ function useMutableSource( // including any interleaving updates that occur. const newQueue: UpdateQueue> = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, @@ -1517,7 +1513,6 @@ function mountState( hook.memoizedState = hook.baseState = initialState; const queue: UpdateQueue> = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, @@ -2288,12 +2283,7 @@ function dispatchSetState( // if the component re-renders for a different reason and by that // time the reducer has changed. // TODO: Do we still need to entangle transitions in this case? - enqueueConcurrentHookUpdateAndEagerlyBailout( - fiber, - queue, - update, - lane, - ); + enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return; } } catch (error) { diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js index e71aa5575abb6..3128186af193e 100644 --- a/packages/react-reconciler/src/ReactFiberLane.old.js +++ b/packages/react-reconciler/src/ReactFiberLane.old.js @@ -9,6 +9,7 @@ import type {FiberRoot} from './ReactInternalTypes'; import type {Transition} from './ReactFiberTracingMarkerComponent.old'; +import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.old'; // TODO: Ideally these types would be opaque but that doesn't work well with // our reconciler fork infra, since these leak into non-reconciler packages. @@ -648,6 +649,7 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { const entanglements = root.entanglements; const eventTimes = root.eventTimes; const expirationTimes = root.expirationTimes; + const hiddenUpdates = root.hiddenUpdates; // Clear the lanes that no longer have pending work let lanes = noLongerPendingLanes; @@ -659,6 +661,21 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { eventTimes[index] = NoTimestamp; expirationTimes[index] = NoTimestamp; + const hiddenUpdatesForLane = hiddenUpdates[index]; + if (hiddenUpdatesForLane !== null) { + hiddenUpdates[index] = null; + // "Hidden" updates are updates that were made to a hidden component. They + // have special logic associated with them because they may be entangled + // with updates that occur outside that tree. But once the outer tree + // commits, they behave like regular updates. + for (let i = 0; i < hiddenUpdatesForLane.length; i++) { + const update = hiddenUpdatesForLane[i]; + if (update !== null) { + update.lane &= ~OffscreenLane; + } + } + } + lanes &= ~lane; } } @@ -694,6 +711,22 @@ export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) { } } +export function markHiddenUpdate( + root: FiberRoot, + update: ConcurrentUpdate, + lane: Lane, +) { + const index = laneToIndex(lane); + const hiddenUpdates = root.hiddenUpdates; + const hiddenUpdatesForLane = hiddenUpdates[index]; + if (hiddenUpdatesForLane === null) { + hiddenUpdates[index] = [update]; + } else { + hiddenUpdatesForLane.push(update); + } + update.lane = lane | OffscreenLane; +} + export function getBumpedLaneForHydration( root: FiberRoot, renderLanes: Lanes, diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index f9c9e8091c8db..b3dad6fc7aef8 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -80,6 +80,8 @@ function FiberRootNode( this.entangledLanes = NoLanes; this.entanglements = createLaneMap(NoLanes); + this.hiddenUpdates = createLaneMap(null); + this.identifierPrefix = identifierPrefix; this.onRecoverableError = onRecoverableError; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 4f0af4387098e..c052727bfb4c1 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -199,6 +199,7 @@ import { import { enqueueConcurrentRenderForLane, finishQueueingConcurrentUpdates, + getConcurrentlyUpdatedLanes, } from './ReactFiberConcurrentUpdates.old'; import { @@ -425,6 +426,10 @@ export function getWorkInProgressRoot(): FiberRoot | null { return workInProgressRoot; } +export function getWorkInProgressRootRenderLanes(): Lanes { + return workInProgressRootRenderLanes; +} + export function requestEventTime() { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // We're inside React, so it's fine to read the actual time. @@ -2051,9 +2056,15 @@ function commitRootImpl( root.callbackNode = null; root.callbackPriority = NoLane; - // Update the first and last pending times on this root. The new first - // pending time is whatever is left on the root fiber. + // Check which lanes no longer have any work scheduled on them, and mark + // those as finished. let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); + + // Make sure to account for lanes that were updated by a concurrent event + // during the render phase; don't mark them as finished. + const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes(); + remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); + markRootFinished(root, remainingLanes); if (root === workInProgressRoot) { @@ -2772,7 +2783,9 @@ function jnd(timeElapsed: number) { export function throwIfInfiniteUpdateLoopDetected() { if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { nestedUpdateCount = 0; + nestedPassiveUpdateCount = 0; rootWithNestedUpdates = null; + rootWithPassiveNestedUpdates = null; throw new Error( 'Maximum update depth exceeded. This can happen when a component ' + diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index 42b462100f316..f5812e948f291 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -476,8 +476,7 @@ describe('ReactOffscreen', () => { expect(root).toMatchRenderedOutput(Hi); }); - // Only works in new reconciler - // @gate variant + // @gate experimental || www it('revealing a hidden tree at high priority does not cause tearing', async () => { // When revealing an offscreen tree, we need to include updates that were // previously deferred because the tree was hidden, even if they are lower diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js index 3e5c25ce0271d..5bd523b0203d9 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js @@ -86,8 +86,7 @@ describe('ReactOffscreen', () => { return text; } - // Only works in new reconciler - // @gate variant + // @gate experimental || www test('detect updates to a hidden tree during a concurrent event', async () => { // This is a pretty complex test case. It relates to how we detect if an // update is made to a hidden tree: when scheduling the update, we walk up @@ -168,7 +167,7 @@ describe('ReactOffscreen', () => { // In the same render, also hide the offscreen tree. root.render(); - expect(Scheduler).toFlushAndYieldThrough([ + expect(Scheduler).toFlushUntilNextPaint([ // The outer update will commit, but the inner update is deferred until // a later render. 'Outer: 1', diff --git a/scripts/merge-fork/forked-revisions b/scripts/merge-fork/forked-revisions index c4bdf3a85b0e2..e69de29bb2d1d 100644 --- a/scripts/merge-fork/forked-revisions +++ b/scripts/merge-fork/forked-revisions @@ -1,3 +0,0 @@ -58bb11764bf0bb6db47527a64f693f67cdd3b0bb [FORKED] Check for infinite update loops even if unmounted -31882b5dd66f34f70d341ea2781cacbe802bf4d5 [FORKED] Bugfix: Revealing a hidden update -17691acc071d56261d43c3cf183f287d983baa9b [FORKED] Don't update childLanes until after current render