diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 26bc381c0677..ce503be9ec94 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -270,14 +270,19 @@ module.exports = function( 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( } 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( } break; case HostPortal: - pushHostContainer(workInProgress.stateNode.containerInfo); + pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo); break; } // TODO: What if this is currently in progress? diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 214d471d7efa..f58a689d1441 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -176,7 +176,7 @@ module.exports = function( 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( // 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 diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index 76e8ed568f62..013a083f8cb8 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -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 = []; -const didPerformWorkStack : Array = []; +let contextStackCursor : StackCursor = createCursor((null: ?Object)); +let didPerformWorkStackCursor : StackCursor = 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; - } -}; diff --git a/src/renderers/shared/fiber/ReactFiberHostContext.js b/src/renderers/shared/fiber/ReactFiberHostContext.js index 574ac240af0b..529271de6ad3 100644 --- a/src/renderers/shared/fiber/ReactFiberHostContext.js +++ b/src/renderers/shared/fiber/ReactFiberHostContext.js @@ -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 = { - 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( getRootHostContext, } = config; - // Context stack is reused across the subtrees. - // We use a null sentinel on the fiber stack to separate them. - let contextFibers : Array = []; - let contextValues : Array = []; - 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 = []; - let portalDepth : number = -1; + let contextStackCursor : StackCursor = createCursor((null: ?CX)); + let contextFiberStackCursor : StackCursor = createCursor((null: ?Fiber)); + let rootInstanceStackCursor : StackCursor = 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, }; }; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index defece414bdd..2e7635cfe764 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -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(config : HostConfig(config : HostConfig(config : HostConfig = { + current: T +}; + +const warning = require('warning'); + +const valueStack : Array = []; + +if (__DEV__) { + var fiberStack : Array = []; +} + +let index = -1; + +exports.createCursor = function(defaultValue : T) : StackCursor { + return { + current: defaultValue, + }; +}; + +exports.isEmpty = function() : boolean { + return index === -1; +}; + +exports.pop = function( + cursor : StackCursor, + 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( + cursor : StackCursor, + 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--; + } +};