Skip to content

Commit 19092ac

Browse files
authored
Re-add old Fabric Offscreen impl behind flag (#22018)
* Re-add old Fabric Offscreen impl behind flag There's a chance that #21960 will affect layout in a way that we don't expect, so I'm adding back the old implementation so we can toggle the feature with a flag. The flag should read from the ReactNativeFeatureFlags shim so that we can change it at runtime. I'll do that separately. * Import dynamic RN flags from external module Internal feature flags that we wish to control with a GK can now be imported from an external module, which I've called "ReactNativeInternalFeatureFlags". We'll need to add this module to the downstream repo. We can't yet use this in our tests, because we don't have a test configuration that runs against the React Native feature flags fork. We should set up that up the same way we did for www.
1 parent 215db46 commit 19092ac

25 files changed

+324
-44
lines changed

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,32 @@ export function getOffscreenContainerProps(
460460
}
461461
}
462462

463+
export function cloneHiddenInstance(
464+
instance: Instance,
465+
type: string,
466+
props: Props,
467+
internalInstanceHandle: Object,
468+
): Instance {
469+
const viewConfig = instance.canonical.viewConfig;
470+
const node = instance.node;
471+
const updatePayload = create(
472+
{style: {display: 'none'}},
473+
viewConfig.validAttributes,
474+
);
475+
return {
476+
node: cloneNodeWithNewProps(node, updatePayload),
477+
canonical: instance.canonical,
478+
};
479+
}
480+
481+
export function cloneHiddenTextInstance(
482+
instance: Instance,
483+
text: string,
484+
internalInstanceHandle: Object,
485+
): TextInstance {
486+
throw new Error('Not yet implemented.');
487+
}
488+
463489
export function createContainerChildSet(container: Container): ChildSet {
464490
return createChildNodeSet(container);
465491
}

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,54 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
582582
children,
583583
};
584584
},
585+
586+
cloneHiddenInstance(
587+
instance: Instance,
588+
type: string,
589+
props: Props,
590+
internalInstanceHandle: Object,
591+
): Instance {
592+
const clone = cloneInstance(
593+
instance,
594+
null,
595+
type,
596+
props,
597+
props,
598+
internalInstanceHandle,
599+
true,
600+
null,
601+
);
602+
clone.hidden = true;
603+
return clone;
604+
},
605+
606+
cloneHiddenTextInstance(
607+
instance: TextInstance,
608+
text: string,
609+
internalInstanceHandle: Object,
610+
): TextInstance {
611+
const clone = {
612+
text: instance.text,
613+
id: instance.id,
614+
parent: instance.parent,
615+
hidden: true,
616+
context: instance.context,
617+
};
618+
// Hide from unit tests
619+
Object.defineProperty(clone, 'id', {
620+
value: clone.id,
621+
enumerable: false,
622+
});
623+
Object.defineProperty(clone, 'parent', {
624+
value: clone.parent,
625+
enumerable: false,
626+
});
627+
Object.defineProperty(clone, 'context', {
628+
value: clone.context,
629+
enumerable: false,
630+
});
631+
return clone;
632+
},
585633
};
586634

587635
const NoopRenderer = reconciler(hostConfig);

packages/react-reconciler/src/ReactFiberBeginWork.new.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
enableLazyContextPropagation,
9090
enableSuspenseLayoutEffectSemantics,
9191
enableSchedulingProfiler,
92+
enablePersistentOffscreenHostContainer,
9293
} from 'shared/ReactFeatureFlags';
9394
import invariant from 'shared/invariant';
9495
import isArray from 'shared/isArray';
@@ -146,7 +147,6 @@ import {
146147
registerSuspenseInstanceRetry,
147148
supportsHydration,
148149
isPrimaryRenderer,
149-
supportsMutation,
150150
supportsPersistence,
151151
getOffscreenContainerProps,
152152
} from './ReactFiberHostConfig';
@@ -744,7 +744,7 @@ function updateOffscreenComponent(
744744
workInProgress.updateQueue = spawnedCachePool;
745745
}
746746

747-
if (supportsPersistence) {
747+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
748748
// In persistent mode, the offscreen children are wrapped in a host node.
749749
// TODO: Optimize this to use the OffscreenComponent fiber instead of
750750
// an extra HostComponent fiber. Need to make sure this doesn't break Fabric
@@ -760,12 +760,10 @@ function updateOffscreenComponent(
760760
renderLanes,
761761
);
762762
return offscreenContainer;
763-
}
764-
if (supportsMutation) {
763+
} else {
765764
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
766765
return workInProgress.child;
767766
}
768-
return null;
769767
}
770768

771769
function reconcileOffscreenHostContainer(
@@ -2383,7 +2381,7 @@ function updateSuspenseFallbackChildren(
23832381
currentPrimaryChildFragment.treeBaseDuration;
23842382
}
23852383

2386-
if (supportsPersistence) {
2384+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
23872385
// In persistent mode, the offscreen children are wrapped in a host node.
23882386
// We need to complete it now, because we're going to skip over its normal
23892387
// complete phase and go straight to rendering the fallback.
@@ -2411,7 +2409,7 @@ function updateSuspenseFallbackChildren(
24112409
primaryChildProps,
24122410
);
24132411

2414-
if (supportsPersistence) {
2412+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
24152413
// In persistent mode, the offscreen children are wrapped in a host node.
24162414
// We need to complete it now, because we're going to skip over its normal
24172415
// complete phase and go straight to rendering the fallback.

packages/react-reconciler/src/ReactFiberBeginWork.old.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
enableLazyContextPropagation,
9090
enableSuspenseLayoutEffectSemantics,
9191
enableSchedulingProfiler,
92+
enablePersistentOffscreenHostContainer,
9293
} from 'shared/ReactFeatureFlags';
9394
import invariant from 'shared/invariant';
9495
import isArray from 'shared/isArray';
@@ -146,7 +147,6 @@ import {
146147
registerSuspenseInstanceRetry,
147148
supportsHydration,
148149
isPrimaryRenderer,
149-
supportsMutation,
150150
supportsPersistence,
151151
getOffscreenContainerProps,
152152
} from './ReactFiberHostConfig';
@@ -744,7 +744,7 @@ function updateOffscreenComponent(
744744
workInProgress.updateQueue = spawnedCachePool;
745745
}
746746

747-
if (supportsPersistence) {
747+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
748748
// In persistent mode, the offscreen children are wrapped in a host node.
749749
// TODO: Optimize this to use the OffscreenComponent fiber instead of
750750
// an extra HostComponent fiber. Need to make sure this doesn't break Fabric
@@ -760,12 +760,10 @@ function updateOffscreenComponent(
760760
renderLanes,
761761
);
762762
return offscreenContainer;
763-
}
764-
if (supportsMutation) {
763+
} else {
765764
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
766765
return workInProgress.child;
767766
}
768-
return null;
769767
}
770768

771769
function reconcileOffscreenHostContainer(
@@ -2383,7 +2381,7 @@ function updateSuspenseFallbackChildren(
23832381
currentPrimaryChildFragment.treeBaseDuration;
23842382
}
23852383

2386-
if (supportsPersistence) {
2384+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
23872385
// In persistent mode, the offscreen children are wrapped in a host node.
23882386
// We need to complete it now, because we're going to skip over its normal
23892387
// complete phase and go straight to rendering the fallback.
@@ -2411,7 +2409,7 @@ function updateSuspenseFallbackChildren(
24112409
primaryChildProps,
24122410
);
24132411

2414-
if (supportsPersistence) {
2412+
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
24152413
// In persistent mode, the offscreen children are wrapped in a host node.
24162414
// We need to complete it now, because we're going to skip over its normal
24172415
// complete phase and go straight to rendering the fallback.

packages/react-reconciler/src/ReactFiberCompleteWork.new.js

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ import {
8484
supportsMutation,
8585
supportsPersistence,
8686
cloneInstance,
87+
cloneHiddenInstance,
88+
cloneHiddenTextInstance,
8789
createContainerChildSet,
8890
appendChildToContainerChildSet,
8991
finalizeContainerChildren,
@@ -128,6 +130,7 @@ import {
128130
enableProfilerTimer,
129131
enableCache,
130132
enableSuspenseLayoutEffectSemantics,
133+
enablePersistentOffscreenHostContainer,
131134
} from 'shared/ReactFeatureFlags';
132135
import {
133136
renderDidSuspend,
@@ -198,7 +201,12 @@ let updateHostText;
198201
if (supportsMutation) {
199202
// Mutation mode
200203

201-
appendAllChildren = function(parent: Instance, workInProgress: Fiber) {
204+
appendAllChildren = function(
205+
parent: Instance,
206+
workInProgress: Fiber,
207+
needsVisibilityToggle: boolean,
208+
isHidden: boolean,
209+
) {
202210
// We only have the top Fiber that was created but we need recurse down its
203211
// children to find all the terminal nodes.
204212
let node = workInProgress.child;
@@ -286,22 +294,53 @@ if (supportsMutation) {
286294
} else if (supportsPersistence) {
287295
// Persistent host tree mode
288296

289-
appendAllChildren = function(parent: Instance, workInProgress: Fiber) {
297+
appendAllChildren = function(
298+
parent: Instance,
299+
workInProgress: Fiber,
300+
needsVisibilityToggle: boolean,
301+
isHidden: boolean,
302+
) {
290303
// We only have the top Fiber that was created but we need recurse down its
291304
// children to find all the terminal nodes.
292305
let node = workInProgress.child;
293306
while (node !== null) {
294307
// eslint-disable-next-line no-labels
295308
branches: if (node.tag === HostComponent) {
296-
const instance = node.stateNode;
309+
let instance = node.stateNode;
310+
if (needsVisibilityToggle && isHidden) {
311+
// This child is inside a timed out tree. Hide it.
312+
const props = node.memoizedProps;
313+
const type = node.type;
314+
instance = cloneHiddenInstance(instance, type, props, node);
315+
}
297316
appendInitialChild(parent, instance);
298317
} else if (node.tag === HostText) {
299-
const instance = node.stateNode;
318+
let instance = node.stateNode;
319+
if (needsVisibilityToggle && isHidden) {
320+
// This child is inside a timed out tree. Hide it.
321+
const text = node.memoizedProps;
322+
instance = cloneHiddenTextInstance(instance, text, node);
323+
}
300324
appendInitialChild(parent, instance);
301325
} else if (node.tag === HostPortal) {
302326
// If we have a portal child, then we don't want to traverse
303327
// down its children. Instead, we'll get insertions from each child in
304328
// the portal directly.
329+
} else if (
330+
node.tag === OffscreenComponent &&
331+
node.memoizedState !== null
332+
) {
333+
// The children in this boundary are hidden. Toggle their visibility
334+
// before appending.
335+
const child = node.child;
336+
if (child !== null) {
337+
child.return = node;
338+
}
339+
if (enablePersistentOffscreenHostContainer) {
340+
appendAllChildren(parent, node, false, false);
341+
} else {
342+
appendAllChildren(parent, node, true, true);
343+
}
305344
} else if (node.child !== null) {
306345
node.child.return = node;
307346
node = node.child;
@@ -327,22 +366,50 @@ if (supportsMutation) {
327366
const appendAllChildrenToContainer = function(
328367
containerChildSet: ChildSet,
329368
workInProgress: Fiber,
369+
needsVisibilityToggle: boolean,
370+
isHidden: boolean,
330371
) {
331372
// We only have the top Fiber that was created but we need recurse down its
332373
// children to find all the terminal nodes.
333374
let node = workInProgress.child;
334375
while (node !== null) {
335376
// eslint-disable-next-line no-labels
336377
branches: if (node.tag === HostComponent) {
337-
const instance = node.stateNode;
378+
let instance = node.stateNode;
379+
if (needsVisibilityToggle && isHidden) {
380+
// This child is inside a timed out tree. Hide it.
381+
const props = node.memoizedProps;
382+
const type = node.type;
383+
instance = cloneHiddenInstance(instance, type, props, node);
384+
}
338385
appendChildToContainerChildSet(containerChildSet, instance);
339386
} else if (node.tag === HostText) {
340-
const instance = node.stateNode;
387+
let instance = node.stateNode;
388+
if (needsVisibilityToggle && isHidden) {
389+
// This child is inside a timed out tree. Hide it.
390+
const text = node.memoizedProps;
391+
instance = cloneHiddenTextInstance(instance, text, node);
392+
}
341393
appendChildToContainerChildSet(containerChildSet, instance);
342394
} else if (node.tag === HostPortal) {
343395
// If we have a portal child, then we don't want to traverse
344396
// down its children. Instead, we'll get insertions from each child in
345397
// the portal directly.
398+
} else if (
399+
node.tag === OffscreenComponent &&
400+
node.memoizedState !== null
401+
) {
402+
// The children in this boundary are hidden. Toggle their visibility
403+
// before appending.
404+
const child = node.child;
405+
if (child !== null) {
406+
child.return = node;
407+
}
408+
if (enablePersistentOffscreenHostContainer) {
409+
appendAllChildrenToContainer(containerChildSet, node, false, false);
410+
} else {
411+
appendAllChildrenToContainer(containerChildSet, node, true, true);
412+
}
346413
} else if (node.child !== null) {
347414
node.child.return = node;
348415
node = node.child;
@@ -376,7 +443,7 @@ if (supportsMutation) {
376443
const container = portalOrRoot.containerInfo;
377444
const newChildSet = createContainerChildSet(container);
378445
// If children might have changed, we have to add them all to the set.
379-
appendAllChildrenToContainer(newChildSet, workInProgress);
446+
appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
380447
portalOrRoot.pendingChildren = newChildSet;
381448
// Schedule an update on the container to swap out the container.
382449
markUpdate(workInProgress);
@@ -449,7 +516,7 @@ if (supportsMutation) {
449516
markUpdate(workInProgress);
450517
} else {
451518
// If children might have changed, we have to add them all to the set.
452-
appendAllChildren(newInstance, workInProgress);
519+
appendAllChildren(newInstance, workInProgress, false, false);
453520
}
454521
};
455522
updateHostText = function(
@@ -722,7 +789,7 @@ export function completeSuspendedOffscreenHostContainer(
722789
workInProgress,
723790
);
724791

725-
appendAllChildren(instance, workInProgress);
792+
appendAllChildren(instance, workInProgress, false, false);
726793

727794
workInProgress.stateNode = instance;
728795

@@ -869,7 +936,7 @@ function completeWork(
869936
workInProgress,
870937
);
871938

872-
appendAllChildren(instance, workInProgress);
939+
appendAllChildren(instance, workInProgress, false, false);
873940

874941
workInProgress.stateNode = instance;
875942

0 commit comments

Comments
 (0)