Skip to content

Commit

Permalink
Replaced new root attribute with ClearContainer effect
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed Apr 25, 2020
1 parent fad4529 commit 88d6d34
Show file tree
Hide file tree
Showing 17 changed files with 94 additions and 52 deletions.
8 changes: 8 additions & 0 deletions packages/react-art/src/ReactARTHostConfig.js
Expand Up @@ -427,6 +427,14 @@ export function unhideTextInstance(textInstance, text): void {
// Noop
}

export function clearContainer(container: Container): void {
// TODO This doesn't work for anything other than SVG.
// Is that okay?
while (container.lastChild != null) {
container.lastChild.eject();
}
}

export function DEPRECATED_mountResponderInstance(
responder: ReactEventResponder<any, any>,
responderInstance: ReactEventResponderInstance<any, any>,
Expand Down
20 changes: 6 additions & 14 deletions packages/react-dom/src/client/ReactDOMRoot.js
Expand Up @@ -123,20 +123,12 @@ function createRootImpl(
(options != null && options.hydrationOptions) || null;
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
if (hydrate) {
if (tag !== LegacyRoot) {
const doc =
container.nodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
eagerlyTrapReplayableEvents(container, doc);
}
} else if (container.lastChild != null) {
// If the container has children already and we aren't hydrating-
// schedule them to be cleared before we mount new, React-managed children.
// This mimics legacy render into subtree behavior in a way that is safe for concurrent mode.
// (It doesn't result in multiple obsevable mutations.)
root.clearContainerBeforeMount = true;
if (hydrate && tag !== LegacyRoot) {
const doc =
container.nodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
eagerlyTrapReplayableEvents(container, doc);
}
return root;
}
Expand Down
Expand Up @@ -74,7 +74,8 @@ describe('when Trusted Types are available in global object', () => {
container,
);
expect(container.innerHTML).toBe('<div><b>Hi</b></div>');
expect(innerHTMLCalls.length).toBe(1);
// Second call to innerHTML is the ClearContainer check.
expect(innerHTMLCalls.length).toBe(2);
// Ensure it didn't get stringified when passed to a DOM sink:
expect(innerHTMLCalls[0]).toBe(ttObject1);

Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-renderer/src/ReactNativeHostConfig.js
Expand Up @@ -482,6 +482,11 @@ export function unhideInstance(instance: Instance, props: Props): void {
);
}

export function clearContainer(container: Container): void {
// TODO Implement this for React Native
// UIManager does not expose a "remove all" type method.
}

export function unhideTextInstance(
textInstance: TextInstance,
text: string,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-noop-renderer/src/createReactNoop.js
Expand Up @@ -155,6 +155,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
insertInContainerOrInstanceBefore(parentInstance, child, beforeChild);
}

function clearContainer(container: Container): void {
container.children.splice(0);
}

function removeChildFromContainerOrInstance(
parentInstance: Container | Instance,
child: Instance | TextInstance,
Expand Down Expand Up @@ -502,6 +506,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
insertInContainerBefore,
removeChild,
removeChildFromContainer,
clearContainer,

hideInstance(instance: Instance): void {
instance.hidden = true;
Expand Down Expand Up @@ -531,6 +536,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
supportsPersistence: true,

cloneInstance,
clearContainer,

createContainerChildSet(
container: Container,
Expand Down
12 changes: 11 additions & 1 deletion packages/react-reconciler/src/ReactFiberCommitWork.new.js
Expand Up @@ -68,6 +68,7 @@ import {
Snapshot,
Update,
Passive,
ClearContainer,
} from './ReactSideEffectTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
Expand Down Expand Up @@ -111,6 +112,7 @@ import {
commitHydratedContainer,
commitHydratedSuspenseInstance,
beforeRemoveInstance,
clearContainer,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand Down Expand Up @@ -293,7 +295,15 @@ function commitBeforeMutationLifeCycles(
}
return;
}
case HostRoot:
case HostRoot: {
if (supportsMutation) {
if (finishedWork.effectTag & ClearContainer) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
}
return;
}
case HostComponent:
case HostText:
case HostPortal:
Expand Down
12 changes: 11 additions & 1 deletion packages/react-reconciler/src/ReactFiberCommitWork.old.js
Expand Up @@ -68,6 +68,7 @@ import {
Snapshot,
Update,
Passive,
ClearContainer,
} from './ReactSideEffectTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
Expand Down Expand Up @@ -111,6 +112,7 @@ import {
commitHydratedContainer,
commitHydratedSuspenseInstance,
beforeRemoveInstance,
clearContainer,
} from './ReactFiberHostConfig';
import {
captureCommitPhaseError,
Expand Down Expand Up @@ -293,7 +295,15 @@ function commitBeforeMutationLifeCycles(
}
return;
}
case HostRoot:
case HostRoot: {
if (supportsMutation) {
if (finishedWork.effectTag & ClearContainer) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
}
return;
}
case HostComponent:
case HostText:
case HostPortal:
Expand Down
7 changes: 7 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.new.js
Expand Up @@ -61,6 +61,7 @@ import {
NoEffect,
DidCapture,
Deletion,
ClearContainer,
} from './ReactSideEffectTags';
import invariant from 'shared/invariant';

Expand Down Expand Up @@ -678,6 +679,12 @@ function completeWork(
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else {
// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the the container would already be empty).
workInProgress.effectTag |= ClearContainer;
}
}
updateHostContainer(workInProgress);
Expand Down
7 changes: 7 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Expand Up @@ -61,6 +61,7 @@ import {
NoEffect,
DidCapture,
Deletion,
ClearContainer,
} from './ReactSideEffectTags';
import invariant from 'shared/invariant';

Expand Down Expand Up @@ -678,6 +679,12 @@ function completeWork(
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else {
// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the the container would already be empty).
workInProgress.effectTag |= ClearContainer;
}
}
updateHostContainer(workInProgress);
Expand Down
Expand Up @@ -37,3 +37,4 @@ export const hideInstance = shim;
export const hideTextInstance = shim;
export const unhideInstance = shim;
export const unhideTextInstance = shim;
export const clearContainer = shim;
1 change: 0 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.old.js
Expand Up @@ -45,7 +45,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
this.mutableSourceLastPendingUpdateTime = NoWork;
this.clearContainerBeforeMount = false;

if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Expand Up @@ -112,6 +112,7 @@ import {
HostEffectMask,
Hydrating,
HydratingAndUpdate,
ClearContainer,
} from './ReactSideEffectTags';
import {
NoWork,
Expand Down Expand Up @@ -2063,7 +2064,7 @@ function commitBeforeMutationEffects() {
beforeActiveInstanceBlur();
}
const effectTag = nextEffect.effectTag;
if ((effectTag & Snapshot) !== NoEffect) {
if ((effectTag & (Snapshot | ClearContainer)) !== NoEffect) {
setCurrentDebugFiberInDEV(nextEffect);

const current = nextEffect.alternate;
Expand Down
13 changes: 2 additions & 11 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Expand Up @@ -74,7 +74,6 @@ import {
warnsIfNotActing,
beforeActiveInstanceBlur,
afterActiveInstanceBlur,
clearContainer,
} from './ReactFiberHostConfig';

import {
Expand Down Expand Up @@ -124,6 +123,7 @@ import {
HostEffectMask,
Hydrating,
HydratingAndUpdate,
ClearContainer,
} from './ReactSideEffectTags';
import {
NoWork,
Expand Down Expand Up @@ -1913,15 +1913,6 @@ function commitRootImpl(root, renderPriorityLevel) {
firstEffect = finishedWork.firstEffect;
}

if (root.clearContainerBeforeMount) {
// We are about to mount into a container that previous contained non-React elements.
// We should clear the previous contents before beginning.
// This mimics legacy render into subtree behavior in a way that is safe for concurrent mode.
// (It doesn't result in multiple obsevable mutations.)
root.clearContainerBeforeMount = false;
clearContainer(root.containerInfo);
}

if (firstEffect !== null) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
Expand Down Expand Up @@ -2177,7 +2168,7 @@ function commitBeforeMutationEffects() {
beforeActiveInstanceBlur();
}
const effectTag = nextEffect.effectTag;
if ((effectTag & Snapshot) !== NoEffect) {
if ((effectTag & (Snapshot | ClearContainer)) !== NoEffect) {
setCurrentDebugFiberInDEV(nextEffect);

const current = nextEffect.alternate;
Expand Down
3 changes: 0 additions & 3 deletions packages/react-reconciler/src/ReactInternalTypes.js
Expand Up @@ -233,9 +233,6 @@ type BaseFiberRootProperties = {|
// render again
lastPingedTime: ExpirationTime,
lastExpiredTime: ExpirationTime,
// The container had non-React managed children before rendering;
// it should be cleared during the commit phase, before new children are appended.
clearContainerBeforeMount: boolean,
// Used by useMutableSource hook to avoid tearing within this root
// when external, mutable sources are read from during render.
mutableSourceLastPendingUpdateTime: ExpirationTime,
Expand Down
39 changes: 20 additions & 19 deletions packages/react-reconciler/src/ReactSideEffectTags.js
Expand Up @@ -10,29 +10,30 @@
export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000000;
export const PerformedWork = /* */ 0b00000000000001;
export const NoEffect = /* */ 0b000000000000000;
export const PerformedWork = /* */ 0b000000000000001;

// You can change the rest (and add more).
export const Placement = /* */ 0b00000000000010;
export const Update = /* */ 0b00000000000100;
export const PlacementAndUpdate = /* */ 0b00000000000110;
export const Deletion = /* */ 0b00000000001000;
export const ContentReset = /* */ 0b00000000010000;
export const Callback = /* */ 0b00000000100000;
export const DidCapture = /* */ 0b00000001000000;
export const Ref = /* */ 0b00000010000000;
export const Snapshot = /* */ 0b00000100000000;
export const Passive = /* */ 0b00001000000000;
export const PassiveUnmountPendingDev = /* */ 0b10000000000000;
export const Hydrating = /* */ 0b00010000000000;
export const HydratingAndUpdate = /* */ 0b00010000000100;
export const Placement = /* */ 0b000000000000010;
export const Update = /* */ 0b000000000000100;
export const PlacementAndUpdate = /* */ 0b000000000000110;
export const Deletion = /* */ 0b000000000001000;
export const ContentReset = /* */ 0b000000000010000;
export const Callback = /* */ 0b000000000100000;
export const DidCapture = /* */ 0b000000001000000;
export const Ref = /* */ 0b000000010000000;
export const Snapshot = /* */ 0b000000100000000;
export const Passive = /* */ 0b000001000000000;
export const PassiveUnmountPendingDev = /* */ 0b010000000000000;
export const Hydrating = /* */ 0b000010000000000;
export const HydratingAndUpdate = /* */ 0b000010000000100;
export const ClearContainer = /* */ 0b100000000000000;

// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00001110100100;
export const LifecycleEffectMask = /* */ 0b000001110100100;

// Union of all host effects
export const HostEffectMask = /* */ 0b00011111111111;
export const HostEffectMask = /* */ 0b000011111111111;

export const Incomplete = /* */ 0b00100000000000;
export const ShouldCapture = /* */ 0b01000000000000;
export const Incomplete = /* */ 0b000100000000000;
export const ShouldCapture = /* */ 0b001000000000000;
Expand Up @@ -53,6 +53,7 @@ describe('ReactFiberHostContext', () => {
appendChildToContainer: function() {
return null;
},
clearContainer: function() {},
supportsMutation: true,
});

Expand Down Expand Up @@ -107,6 +108,7 @@ describe('ReactFiberHostContext', () => {
appendChildToContainer: function() {
return null;
},
clearContainer: function() {},
supportsMutation: true,
});

Expand Down
4 changes: 4 additions & 0 deletions packages/react-test-renderer/src/ReactTestHostConfig.js
Expand Up @@ -126,6 +126,10 @@ export function removeChild(
parentInstance.children.splice(index, 1);
}

export function clearContainer(container: Container): void {
container.children.splice(0);
}

export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
Expand Down

0 comments on commit 88d6d34

Please sign in to comment.