Skip to content

Commit

Permalink
Unify use of _pendingVisibility in attach/detach
Browse files Browse the repository at this point in the history
  • Loading branch information
sammy-SC committed Nov 21, 2022
1 parent cb3fdd7 commit f8118ec
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 69 deletions.
6 changes: 6 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ function updateOffscreenComponent(
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;

// Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
// to support batching of `attach` and `detach` calls.
workInProgress.stateNode._visibility &= ~OffscreenDetached;
workInProgress.stateNode._visibility |=
workInProgress.stateNode._pendingVisibility & OffscreenDetached;

markRef(current, workInProgress);

if (
Expand Down
6 changes: 6 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ function updateOffscreenComponent(
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;

// Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
// to support batching of `attach` and `detach` calls.
workInProgress.stateNode._visibility &= ~OffscreenDetached;
workInProgress.stateNode._visibility |=
workInProgress.stateNode._pendingVisibility & OffscreenDetached;

markRef(current, workInProgress);

if (
Expand Down
29 changes: 8 additions & 21 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ import {
clearSingleton,
acquireSingletonInstance,
releaseSingletonInstance,
scheduleMicrotask,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand Down Expand Up @@ -2423,26 +2422,16 @@ export function detachOffscreenInstance(instance: OffscreenInstance): void {
);
}

if ((instance._visibility & OffscreenDetached) !== NoFlags) {
if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
// The instance is already detached, this is a noop.
return;
}

instance._pendingVisibility |= OffscreenDetached;

// Detaching needs to be postoned in case attach is called before next update.
scheduleMicrotask(() => {
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
// Attach was called. Offscreen does not need to be detached.
return;
}

instance._visibility |= OffscreenDetached;
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
});
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._pendingVisibility |= OffscreenDetached;
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}

export function attachOffscreenInstance(instance: OffscreenInstance): void {
Expand All @@ -2453,16 +2442,14 @@ export function attachOffscreenInstance(instance: OffscreenInstance): void {
);
}

instance._pendingVisibility &= ~OffscreenDetached;

if ((instance._visibility & OffscreenDetached) === NoFlags) {
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
// The instance is already attached, this is a noop.
return;
}

const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._visibility &= ~OffscreenDetached;
instance._pendingVisibility &= ~OffscreenDetached;
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
Expand Down
29 changes: 8 additions & 21 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ import {
clearSingleton,
acquireSingletonInstance,
releaseSingletonInstance,
scheduleMicrotask,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand Down Expand Up @@ -2423,26 +2422,16 @@ export function detachOffscreenInstance(instance: OffscreenInstance): void {
);
}

if ((instance._visibility & OffscreenDetached) !== NoFlags) {
if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
// The instance is already detached, this is a noop.
return;
}

instance._pendingVisibility |= OffscreenDetached;

// Detaching needs to be postoned in case attach is called before next update.
scheduleMicrotask(() => {
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
// Attach was called. Offscreen does not need to be detached.
return;
}

instance._visibility |= OffscreenDetached;
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
});
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._pendingVisibility |= OffscreenDetached;
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}

export function attachOffscreenInstance(instance: OffscreenInstance): void {
Expand All @@ -2453,16 +2442,14 @@ export function attachOffscreenInstance(instance: OffscreenInstance): void {
);
}

instance._pendingVisibility &= ~OffscreenDetached;

if ((instance._visibility & OffscreenDetached) === NoFlags) {
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
// The instance is already attached, this is a noop.
return;
}

const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
instance._visibility &= ~OffscreenDetached;
instance._pendingVisibility &= ~OffscreenDetached;
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
Expand Down
40 changes: 13 additions & 27 deletions packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1663,43 +1663,29 @@ describe('ReactOffscreen', () => {

nextRenderTriggerDetach = true;

// Offscreen is attached. State updates from offscreen are **not defered**.
// Offscreen is detached inside useLayoutEffect;
// Offscreen is attached and gets detached inside useLayoutEffect.
// State updates from offscreen are **defered**.
await act(async () => {
updateChildState(1);
updateHighPriorityComponentState(1);
expect(Scheduler).toFlushUntilNextPaint([
'HighPriorityComponent 1',
'Child 1',
'HighPriorityComponent 2',
'Child 2',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="HighPriorityComponent 2" />
<span prop="Child 2" />
</>,
);
});

// Offscreen is detached. State updates from offscreen are **defered**.
await act(async () => {
updateChildState(3);
updateHighPriorityComponentState(3);
expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 3']);
expect(root).toMatchRenderedOutput(
<>
<span prop="HighPriorityComponent 3" />
<span prop="Child 2" />
<span prop="Child 1" />
</>,
);
});

expect(Scheduler).toHaveYielded(['Child 3']);
expect(Scheduler).toHaveYielded(['Child 2']);
expect(root).toMatchRenderedOutput(
<>
<span prop="HighPriorityComponent 3" />
<span prop="Child 3" />
<span prop="HighPriorityComponent 2" />
<span prop="Child 2" />
</>,
);

Expand All @@ -1708,16 +1694,16 @@ describe('ReactOffscreen', () => {
// Offscreen is detached. State updates from offscreen are **defered**.
// Offscreen is attached inside useLayoutEffect;
await act(async () => {
updateChildState(4);
updateHighPriorityComponentState(4);
updateChildState(3);
updateHighPriorityComponentState(3);
expect(Scheduler).toFlushUntilNextPaint([
'HighPriorityComponent 4',
'Child 4',
'HighPriorityComponent 3',
'Child 3',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="HighPriorityComponent 4" />
<span prop="Child 4" />
<span prop="HighPriorityComponent 3" />
<span prop="Child 3" />
</>,
);
});
Expand Down Expand Up @@ -2043,7 +2029,7 @@ describe('ReactOffscreen', () => {
instance.detach();
});

expect(Scheduler).toHaveYielded(['attach child', 'detach child']);
expect(Scheduler).toHaveYielded([]);
});

// @gate enableOffscreen
Expand Down

0 comments on commit f8118ec

Please sign in to comment.