Skip to content

Commit

Permalink
Add detach to Offscreen component (#25265)
Browse files Browse the repository at this point in the history
  • Loading branch information
sammy-SC committed Oct 18, 2022
1 parent 3bb71df commit 14072ce
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 17 deletions.
6 changes: 5 additions & 1 deletion packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ import {
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {
resolveClassForHotReloading,
Expand Down Expand Up @@ -109,6 +108,7 @@ import {
REACT_TRACING_MARKER_TYPE,
} from 'shared/ReactSymbols';
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';

export type {Fiber};

Expand Down Expand Up @@ -755,6 +755,8 @@ export function createFiberFromOffscreen(
_pendingMarkers: null,
_retryCache: null,
_transitions: null,
_current: null,
detach: () => detachOffscreenInstance(primaryChildInstance),
};
fiber.stateNode = primaryChildInstance;
return fiber;
Expand All @@ -776,6 +778,8 @@ export function createFiberFromLegacyHidden(
_pendingMarkers: null,
_transitions: null,
_retryCache: null,
_current: null,
detach: () => detachOffscreenInstance(instance),
};
fiber.stateNode = instance;
return fiber;
Expand Down
6 changes: 5 additions & 1 deletion packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ import {
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {
resolveClassForHotReloading,
Expand Down Expand Up @@ -109,6 +108,7 @@ import {
REACT_TRACING_MARKER_TYPE,
} from 'shared/ReactSymbols';
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.old';
import {detachOffscreenInstance} from './ReactFiberCommitWork.old';

export type {Fiber};

Expand Down Expand Up @@ -755,6 +755,8 @@ export function createFiberFromOffscreen(
_pendingMarkers: null,
_retryCache: null,
_transitions: null,
_current: null,
detach: () => detachOffscreenInstance(primaryChildInstance),
};
fiber.stateNode = primaryChildInstance;
return fiber;
Expand All @@ -776,6 +778,8 @@ export function createFiberFromLegacyHidden(
_pendingMarkers: null,
_transitions: null,
_retryCache: null,
_current: null,
detach: () => detachOffscreenInstance(instance),
};
fiber.stateNode = instance;
return fiber;
Expand Down
7 changes: 5 additions & 2 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
OffscreenQueue,
OffscreenInstance,
} from './ReactFiberOffscreenComponent';
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
import type {
Cache,
CacheComponentState,
Expand All @@ -37,7 +38,6 @@ import type {
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
import type {RootState} from './ReactFiberRoot.new';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';

import checkPropTypes from 'shared/checkPropTypes';
import {
markComponentRenderStarted,
Expand Down Expand Up @@ -688,7 +688,10 @@ function updateOffscreenComponent(

if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
(enableLegacyHidden &&
nextProps.mode === 'unstable-defer-without-hiding') ||
// TODO: remove read from stateNode.
workInProgress.stateNode._visibility & OffscreenDetached
) {
// Rendering a hidden tree.

Expand Down
7 changes: 5 additions & 2 deletions packages/react-reconciler/src/ReactFiberBeginWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
OffscreenQueue,
OffscreenInstance,
} from './ReactFiberOffscreenComponent';
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
import type {
Cache,
CacheComponentState,
Expand All @@ -37,7 +38,6 @@ import type {
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
import type {RootState} from './ReactFiberRoot.old';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';

import checkPropTypes from 'shared/checkPropTypes';
import {
markComponentRenderStarted,
Expand Down Expand Up @@ -688,7 +688,10 @@ function updateOffscreenComponent(

if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
(enableLegacyHidden &&
nextProps.mode === 'unstable-defer-without-hiding') ||
// TODO: remove read from stateNode.
workInProgress.stateNode._visibility & OffscreenDetached
) {
// Rendering a hidden tree.

Expand Down
31 changes: 30 additions & 1 deletion packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
import type {Wakeable} from 'shared/ReactTypes';
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
import type {
OffscreenState,
OffscreenInstance,
Expand Down Expand Up @@ -156,6 +157,7 @@ import {
clearSingleton,
acquireSingletonInstance,
releaseSingletonInstance,
scheduleMicrotask,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand All @@ -172,6 +174,7 @@ import {
setIsRunningInsertionEffect,
getExecutionContext,
CommitContext,
RenderContext,
NoContext,
} from './ReactFiberWorkLoop.new';
import {
Expand Down Expand Up @@ -200,6 +203,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
import {clearTransitionsForLanes} from './ReactFiberLane.new';
import {
OffscreenVisible,
OffscreenDetached,
OffscreenPassiveEffectsConnected,
} from './ReactFiberOffscreenComponent';
import {
Expand Down Expand Up @@ -2416,6 +2420,28 @@ function getRetryCache(finishedWork) {
}
}

export function detachOffscreenInstance(instance: OffscreenInstance): void {
const currentOffscreenFiber = instance._current;
if (currentOffscreenFiber === null) {
throw new Error(
'Calling Offscreen.detach before instance handle has been set.',
);
}

const executionContext = getExecutionContext();
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
scheduleMicrotask(() => {
instance._visibility |= OffscreenDetached;
disappearLayoutEffects(currentOffscreenFiber);
disconnectPassiveEffect(currentOffscreenFiber);
});
} else {
instance._visibility |= OffscreenDetached;
disappearLayoutEffects(currentOffscreenFiber);
disconnectPassiveEffect(currentOffscreenFiber);
}
}

function attachSuspenseRetryListeners(
finishedWork: Fiber,
wakeables: Set<Wakeable>,
Expand Down Expand Up @@ -2845,6 +2871,8 @@ function commitMutationEffectsOnFiber(
}

commitReconciliationEffects(finishedWork);
// TODO: Add explicit effect flag to set _current.
finishedWork.stateNode._current = finishedWork;

if (flags & Visibility) {
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
Expand All @@ -2871,7 +2899,8 @@ function commitMutationEffectsOnFiber(
}
}

if (supportsMutation) {
// Offscreen with manual mode manages visibility manually.
if (supportsMutation && !isOffscreenManual(finishedWork)) {
// TODO: This needs to run whenever there's an insertion or update
// inside a hidden Offscreen tree.
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
Expand Down
31 changes: 30 additions & 1 deletion packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
import type {Wakeable} from 'shared/ReactTypes';
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
import type {
OffscreenState,
OffscreenInstance,
Expand Down Expand Up @@ -156,6 +157,7 @@ import {
clearSingleton,
acquireSingletonInstance,
releaseSingletonInstance,
scheduleMicrotask,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand All @@ -172,6 +174,7 @@ import {
setIsRunningInsertionEffect,
getExecutionContext,
CommitContext,
RenderContext,
NoContext,
} from './ReactFiberWorkLoop.old';
import {
Expand Down Expand Up @@ -200,6 +203,7 @@ import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
import {clearTransitionsForLanes} from './ReactFiberLane.old';
import {
OffscreenVisible,
OffscreenDetached,
OffscreenPassiveEffectsConnected,
} from './ReactFiberOffscreenComponent';
import {
Expand Down Expand Up @@ -2416,6 +2420,28 @@ function getRetryCache(finishedWork) {
}
}

export function detachOffscreenInstance(instance: OffscreenInstance): void {
const currentOffscreenFiber = instance._current;
if (currentOffscreenFiber === null) {
throw new Error(
'Calling Offscreen.detach before instance handle has been set.',
);
}

const executionContext = getExecutionContext();
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
scheduleMicrotask(() => {
instance._visibility |= OffscreenDetached;
disappearLayoutEffects(currentOffscreenFiber);
disconnectPassiveEffect(currentOffscreenFiber);
});
} else {
instance._visibility |= OffscreenDetached;
disappearLayoutEffects(currentOffscreenFiber);
disconnectPassiveEffect(currentOffscreenFiber);
}
}

function attachSuspenseRetryListeners(
finishedWork: Fiber,
wakeables: Set<Wakeable>,
Expand Down Expand Up @@ -2845,6 +2871,8 @@ function commitMutationEffectsOnFiber(
}

commitReconciliationEffects(finishedWork);
// TODO: Add explicit effect flag to set _current.
finishedWork.stateNode._current = finishedWork;

if (flags & Visibility) {
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
Expand All @@ -2871,7 +2899,8 @@ function commitMutationEffectsOnFiber(
}
}

if (supportsMutation) {
// Offscreen with manual mode manages visibility manually.
if (supportsMutation && !isOffscreenManual(finishedWork)) {
// TODO: This needs to run whenever there's an insertion or update
// inside a hidden Offscreen tree.
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
Expand Down
10 changes: 9 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
SuspenseState,
SuspenseListRenderState,
} from './ReactFiberSuspenseComponent.new';
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
import type {Cache} from './ReactFiberCacheComponent.new';
Expand Down Expand Up @@ -425,7 +426,14 @@ if (supportsMutation) {
if (child !== null) {
child.return = node;
}
appendAllChildrenToContainer(containerChildSet, node, true, true);
// If Offscreen is not in manual mode, detached tree is hidden from user space.
const _needsVisibilityToggle = !isOffscreenManual(node);
appendAllChildrenToContainer(
containerChildSet,
node,
_needsVisibilityToggle,
true,
);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
Expand Down
10 changes: 9 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
SuspenseState,
SuspenseListRenderState,
} from './ReactFiberSuspenseComponent.old';
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
import type {Cache} from './ReactFiberCacheComponent.old';
Expand Down Expand Up @@ -425,7 +426,14 @@ if (supportsMutation) {
if (child !== null) {
child.return = node;
}
appendAllChildrenToContainer(containerChildSet, node, true, true);
// If Offscreen is not in manual mode, detached tree is hidden from user space.
const _needsVisibilityToggle = !isOffscreenManual(node);
appendAllChildrenToContainer(
containerChildSet,
node,
_needsVisibilityToggle,
true,
);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
Expand Down
19 changes: 17 additions & 2 deletions packages/react-reconciler/src/ReactFiberOffscreenComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import type {ReactNodeList, OffscreenMode, Wakeable} from 'shared/ReactTypes';
import type {Lanes} from './ReactFiberLane.old';
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
import type {Fiber} from './ReactInternalTypes';
import type {
Transition,
TracingMarkerInstance,
Expand Down Expand Up @@ -44,13 +45,27 @@ export type OffscreenQueue = {

type OffscreenVisibility = number;

export const OffscreenVisible = /* */ 0b01;
export const OffscreenPassiveEffectsConnected = /* */ 0b10;
export const OffscreenVisible = /* */ 0b001;
export const OffscreenDetached = /* */ 0b010;
export const OffscreenPassiveEffectsConnected = /* */ 0b100;

export type OffscreenInstance = {
_visibility: OffscreenVisibility,
_pendingMarkers: Set<TracingMarkerInstance> | null,
_transitions: Set<Transition> | null,
// $FlowFixMe[incompatible-type-arg] found when upgrading Flow
_retryCache: WeakSet<Wakeable> | Set<Wakeable> | null,

// Represents the current Offscreen fiber
_current: Fiber | null,
detach: () => void,

// TODO: attach
};

export function isOffscreenManual(offscreenFiber: Fiber): boolean {
return (
offscreenFiber.memoizedProps !== null &&
offscreenFiber.memoizedProps.mode === 'manual'
);
}
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ type ExecutionContext = number;

export const NoContext = /* */ 0b000;
const BatchedContext = /* */ 0b001;
const RenderContext = /* */ 0b010;
export const RenderContext = /* */ 0b010;
export const CommitContext = /* */ 0b100;

type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ type ExecutionContext = number;

export const NoContext = /* */ 0b000;
const BatchedContext = /* */ 0b001;
const RenderContext = /* */ 0b010;
export const RenderContext = /* */ 0b010;
export const CommitContext = /* */ 0b100;

type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
Expand Down
Loading

0 comments on commit 14072ce

Please sign in to comment.