Merge pull request #8611 from bvaughn/shared-context-stack

Added ReactFiberStack shared by ReactFiberContext and ReactFiberHostContext
This commit is contained in:
Brian Vaughn
2016-12-21 18:50:34 -08:00
committed by GitHub
6 changed files with 205 additions and 136 deletions
@@ -270,14 +270,19 @@ module.exports = function<T, P, I, TI, C, CX>(
const root = (workInProgress.stateNode : FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context
);
} else {
pushTopLevelContextObject(root.context, false);
pushTopLevelContextObject(
workInProgress,
root.context,
false
);
}
pushHostContainer(root.containerInfo);
pushHostContainer(workInProgress, root.containerInfo);
const updateQueue = workInProgress.updateQueue;
if (updateQueue) {
@@ -444,7 +449,7 @@ module.exports = function<T, P, I, TI, C, CX>(
}
function updatePortalComponent(current, workInProgress) {
pushHostContainer(workInProgress.stateNode.containerInfo);
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const priorityLevel = workInProgress.pendingWorkPriority;
let nextChildren = workInProgress.pendingProps;
if (hasContextChanged()) {
@@ -535,7 +540,7 @@ module.exports = function<T, P, I, TI, C, CX>(
}
break;
case HostPortal:
pushHostContainer(workInProgress.stateNode.containerInfo);
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
break;
}
// TODO: What if this is currently in progress?
@@ -176,7 +176,7 @@ module.exports = function<T, P, I, TI, C, CX>(
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
if (isContextProvider(workInProgress)) {
popContextProvider();
popContextProvider(workInProgress);
}
// Don't use the state queue to compute the memoized state. We already
// merged it and assigned it to the instance. Transfer it from there.
@@ -303,7 +303,7 @@ module.exports = function<T, P, I, TI, C, CX>(
// TODO: Only mark this as an update if we have any pending callbacks.
markUpdate(workInProgress);
workInProgress.memoizedProps = workInProgress.pendingProps;
popHostContainer();
popHostContainer(workInProgress);
return null;
// Error cases
+23 -32
View File
@@ -13,6 +13,7 @@
'use strict';
import type { Fiber } from 'ReactFiber';
import type { StackCursor } from 'ReactFiberStack';
var emptyObject = require('emptyObject');
var invariant = require('invariant');
@@ -24,20 +25,21 @@ var {
ClassComponent,
HostRoot,
} = require('ReactTypeOfWork');
const {
createCursor,
pop,
push,
} = require('ReactFiberStack');
if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec');
}
let index = -1;
const contextStack : Array<Object> = [];
const didPerformWorkStack : Array<boolean> = [];
let contextStackCursor : StackCursor<?Object> = createCursor((null: ?Object));
let didPerformWorkStackCursor : StackCursor<boolean> = createCursor(false);
function getUnmaskedContext() {
if (index === -1) {
return emptyObject;
}
return contextStack[index];
return contextStackCursor.current || emptyObject;
}
exports.getMaskedContext = function(workInProgress : Fiber) {
@@ -49,6 +51,7 @@ exports.getMaskedContext = function(workInProgress : Fiber) {
const unmaskedContext = getUnmaskedContext();
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
@@ -62,7 +65,7 @@ exports.getMaskedContext = function(workInProgress : Fiber) {
};
exports.hasContextChanged = function() : boolean {
return index > -1 && didPerformWorkStack[index];
return didPerformWorkStackCursor.current;
};
function isContextProvider(fiber : Fiber) : boolean {
@@ -75,19 +78,17 @@ function isContextProvider(fiber : Fiber) : boolean {
}
exports.isContextProvider = isContextProvider;
function popContextProvider() : void {
invariant(index > -1, 'Unexpected context pop');
contextStack[index] = emptyObject;
didPerformWorkStack[index] = false;
index--;
function popContextProvider(fiber : Fiber) : void {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
exports.popContextProvider = popContextProvider;
exports.pushTopLevelContextObject = function(context : Object, didChange : boolean) : void {
invariant(index === -1, 'Unexpected context found on stack');
index++;
contextStack[index] = context;
didPerformWorkStack[index] = didChange;
exports.pushTopLevelContextObject = function(fiber : Fiber, context : Object, didChange : boolean) : void {
invariant(contextStackCursor.cursor == null, 'Unexpected context found on stack');
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
};
function processChildContext(fiber : Fiber, parentContext : Object, isReconciling : boolean): Object {
@@ -129,13 +130,13 @@ exports.pushContextProvider = function(workInProgress : Fiber, didPerformWork :
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
}
index++;
contextStack[index] = mergedContext;
didPerformWorkStack[index] = didPerformWork;
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didPerformWork, workInProgress);
};
exports.resetContext = function() : void {
index = -1;
contextStackCursor.current = null;
didPerformWorkStackCursor.current = false;
};
exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
@@ -157,13 +158,3 @@ exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
}
return node.stateNode.context;
};
exports.unwindContext = function(from : Fiber, to: Fiber) {
let node = from;
while (node && (node !== to) && (node.alternate !== to)) {
if (isContextProvider(node)) {
popContextProvider();
}
node = node.return;
}
};
@@ -14,16 +14,23 @@
import type { Fiber } from 'ReactFiber';
import type { HostConfig } from 'ReactFiberReconciler';
import type { StackCursor } from 'ReactFiberStack';
const emptyObject = require('emptyObject');
const {
createCursor,
pop,
push,
} = require('ReactFiberStack');
export type HostContext<C, CX> = {
getRootHostContainer() : C,
getHostContext() : CX,
pushHostContext(fiber : Fiber) : void,
getRootHostContainer() : C,
popHostContainer(fiber : Fiber) : void,
popHostContext(fiber : Fiber) : void,
pushHostContainer(container : C) : void,
popHostContainer() : void,
pushHostContainer(fiber : Fiber, container : C) : void,
pushHostContext(fiber : Fiber) : void,
resetHostContainer() : void,
};
@@ -35,129 +42,88 @@ module.exports = function<T, P, I, TI, C, CX>(
getRootHostContext,
} = config;
// Context stack is reused across the subtrees.
// We use a null sentinel on the fiber stack to separate them.
let contextFibers : Array<Fiber | null> = [];
let contextValues : Array<CX | null> = [];
let contextDepth : number = -1;
// Current context for fast access.
let currentContextValue : CX | null = null;
// Current root instance for fast access.
let rootInstance : C | null = null;
// A stack of outer root instances if we're in a portal.
let portalStack : Array<C | null> = [];
let portalDepth : number = -1;
let contextStackCursor : StackCursor<?CX> = createCursor((null: ?CX));
let contextFiberStackCursor : StackCursor<?Fiber> = createCursor((null: ?Fiber));
let rootInstanceStackCursor : StackCursor<?C> = createCursor((null: ?C));
function getRootHostContainer() : C {
const rootInstance = rootInstanceStackCursor.current;
if (rootInstance == null) {
throw new Error('Expected root container to exist.');
}
return rootInstance;
}
function pushHostContainer(nextRootInstance : C) {
function pushHostContainer(fiber : Fiber, nextRootInstance : C) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
if (rootInstance == null) {
// We're entering a root.
rootInstance = nextRootInstance;
} else {
// We're entering a portal.
// Save the current root to the portal stack.
portalDepth++;
portalStack[portalDepth] = rootInstance;
rootInstance = nextRootInstance;
}
// Push the next root or portal context.
contextDepth++;
contextFibers[contextDepth] = null;
contextValues[contextDepth] = nextRootContext;
currentContextValue = nextRootContext;
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
function popHostContainer() {
if (portalDepth === -1) {
// We're popping the root.
rootInstance = null;
currentContextValue = null;
contextDepth = -1;
} else {
// We're popping a portal.
// Restore the root instance.
rootInstance = portalStack[portalDepth];
portalStack[portalDepth] = null;
portalDepth--;
// If we pushed any context while in a portal, we need to roll it back.
if (contextDepth > -1) {
contextDepth--;
if (contextDepth > -1) {
currentContextValue = contextValues[contextDepth];
} else {
currentContextValue = null;
}
}
}
function popHostContainer(fiber : Fiber) {
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
pop(rootInstanceStackCursor, fiber);
}
function getHostContext() : CX {
if (currentContextValue == null) {
const context = contextStackCursor.current;
if (context == null) {
throw new Error('Expected host context to exist.');
}
return currentContextValue;
return context;
}
function pushHostContext(fiber : Fiber) : void {
if (currentContextValue == null) {
const rootInstance = rootInstanceStackCursor.current;
if (rootInstance == null) {
throw new Error('Expected root host context to exist.');
}
const nextContextValue = getChildHostContext(currentContextValue, fiber.type, rootInstance);
if (currentContextValue === nextContextValue) {
const context = contextStackCursor.current || emptyObject;
const nextContext = getChildHostContext(context, fiber.type, rootInstance);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
contextDepth++;
contextFibers[contextDepth] = fiber;
contextValues[contextDepth] = nextContextValue;
currentContextValue = nextContextValue;
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function popHostContext(fiber : Fiber) : void {
if (contextDepth === -1) {
return;
}
if (contextFibers == null || contextValues == null) {
throw new Error('Expected host context stacks to exist when index is more than -1.');
}
if (fiber !== contextFibers[contextDepth]) {
// Do not pop unless this Fiber provided the current context.
// pushHostContext() only pushes Fibers that provide unique contexts.
if (contextFiberStackCursor.current !== fiber) {
return;
}
contextFibers[contextDepth] = null;
contextValues[contextDepth] = null;
contextDepth--;
if (contextDepth > -1) {
currentContextValue = contextValues[contextDepth];
} else {
currentContextValue = null;
}
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
}
function resetHostContainer() {
// Reset portal stack pointer because we're starting from the very top.
portalDepth = -1;
// Reset current container state.
rootInstance = null;
contextDepth = -1;
currentContextValue = null;
contextStackCursor.current = null;
rootInstanceStackCursor.current = null;
}
return {
getRootHostContainer,
getHostContext,
pushHostContext,
popHostContext,
pushHostContainer,
getRootHostContainer,
popHostContainer,
popHostContext,
pushHostContainer,
pushHostContext,
resetHostContainer,
};
};
@@ -17,6 +17,12 @@ import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig, Deadline } from 'ReactFiberReconciler';
import type { PriorityLevel } from 'ReactPriorityLevel';
var {
isContextProvider,
popContextProvider,
} = require('ReactFiberContext');
const { reset } = require('ReactFiberStack');
var ReactFiberBeginWork = require('ReactFiberBeginWork');
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
var ReactFiberCommitWork = require('ReactFiberCommitWork');
@@ -59,7 +65,6 @@ var {
var {
resetContext,
unwindContext,
} = require('ReactFiberContext');
if (__DEV__) {
@@ -155,6 +160,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
}
function resetContextStack() {
// Reset the stack
reset();
// Reset the cursors
resetContext();
resetHostContainer();
}
@@ -724,8 +732,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
// from the root. Can't do that until then because without memoized
// props, the nodes higher up in the tree will rerender unnecessarily.
if (failedWork) {
unwindContext(failedWork, boundary);
unwindHostContext(failedWork, boundary);
unwindContexts(failedWork, boundary);
}
nextUnitOfWork = completeUnitOfWork(boundary);
}
@@ -945,18 +952,23 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
}
}
function unwindHostContext(from : Fiber, to: Fiber) {
function unwindContexts(from : Fiber, to: Fiber) {
let node = from;
while (node && (node !== to) && (node.alternate !== to)) {
switch (node.tag) {
case ClassComponent:
if (isContextProvider(node)) {
popContextProvider(node);
}
break;
case HostComponent:
popHostContext(node);
break;
case HostRoot:
popHostContainer();
popHostContainer(node);
break;
case HostPortal:
popHostContainer();
popHostContainer(node);
break;
}
node = node.return;
@@ -0,0 +1,95 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactFiberStack
* @flow
*/
'use strict';
import type { Fiber } from 'ReactFiber';
export type StackCursor<T> = {
current: T
};
const warning = require('warning');
const valueStack : Array<any> = [];
if (__DEV__) {
var fiberStack : Array<Fiber | null> = [];
}
let index = -1;
exports.createCursor = function<T>(defaultValue : T) : StackCursor<T> {
return {
current: defaultValue,
};
};
exports.isEmpty = function() : boolean {
return index === -1;
};
exports.pop = function<T>(
cursor : StackCursor<T>,
fiber: Fiber,
) : void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
};
exports.push = function<T>(
cursor : StackCursor<T>,
value : T,
fiber: Fiber,
) : void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
};
exports.reset = function() : void {
while (index > -1) {
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
};