Skip to content

Commit

Permalink
Merge pull request facebook#8611 from bvaughn/shared-context-stack
Browse files Browse the repository at this point in the history
Added ReactFiberStack shared by ReactFiberContext and ReactFiberHostContext
  • Loading branch information
bvaughn authored Dec 22, 2016
2 parents cb66f5c + d1e6040 commit f1e193b
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 135 deletions.
13 changes: 9 additions & 4 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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?
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
55 changes: 23 additions & 32 deletions src/renderers/shared/fiber/ReactFiberContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'use strict';

import type { Fiber } from 'ReactFiber';
import type { StackCursor } from 'ReactFiberStack';

var emptyObject = require('emptyObject');
var invariant = require('invariant');
Expand All @@ -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) {
Expand All @@ -49,6 +51,7 @@ exports.getMaskedContext = function(workInProgress : Fiber) {

const unmaskedContext = getUnmaskedContext();
const context = {};

for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
}
};
148 changes: 57 additions & 91 deletions src/renderers/shared/fiber/ReactFiberHostContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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,
getRootHostContainer,
popHostContainer,
popHostContext,

pushHostContainer,
popHostContainer,
pushHostContext,
resetHostContainer,
};
};
Loading

0 comments on commit f1e193b

Please sign in to comment.