diff --git a/examples/fiber/index.html b/examples/fiber/index.html
index d824f2a6311..7a56ca0b9da 100644
--- a/examples/fiber/index.html
+++ b/examples/fiber/index.html
@@ -90,7 +90,7 @@
Fiber Example
return r;
}
var newSize = s / 2;
- var slowDown = false;
+ var slowDown = true;
if (slowDown) {
var e = performance.now() + 0.8;
while (performance.now() < e) {
diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index 21c98427165..dae770f119b 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -1245,6 +1245,8 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* updates a child even though the old props is empty
* can defer side-effects and resume them later on
* can defer side-effects and reuse them later - complex
+* does not drop priority from a progressed subtree
+* does not complete already completed work
* deprioritizes setStates that happens within a deprioritized tree
* calls callback after update is flushed
* calls setState callback even if component bails out
diff --git a/src/renderers/art/ReactARTFiber.js b/src/renderers/art/ReactARTFiber.js
index fcdcc957e0f..0b075a7679c 100644
--- a/src/renderers/art/ReactARTFiber.js
+++ b/src/renderers/art/ReactARTFiber.js
@@ -21,6 +21,7 @@ const invariant = require('fbjs/lib/invariant');
const emptyObject = require('emptyObject');
const React = require('React');
const ReactFiberReconciler = require('ReactFiberReconciler');
+const ReactDOMFrameScheduling = require('ReactDOMFrameScheduling');
const { Component } = React;
@@ -509,9 +510,9 @@ const ARTRenderer = ReactFiberReconciler({
return emptyObject;
},
- scheduleAnimationCallback: window.requestAnimationFrame,
+ scheduleAnimationCallback: ReactDOMFrameScheduling.rAF,
- scheduleDeferredCallback: window.requestIdleCallback,
+ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
shouldSetTextContent(props) {
return (
diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js
index 487013cbc19..c1ebaae2bde 100644
--- a/src/renderers/dom/fiber/ReactDOMFiber.js
+++ b/src/renderers/dom/fiber/ReactDOMFiber.js
@@ -20,6 +20,7 @@ var ReactControlledComponent = require('ReactControlledComponent');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
+var ReactDOMFrameScheduling = require('ReactDOMFrameScheduling');
var ReactDOMInjection = require('ReactDOMInjection');
var ReactGenericBatching = require('ReactGenericBatching');
var ReactFiberReconciler = require('ReactFiberReconciler');
@@ -302,9 +303,9 @@ var DOMRenderer = ReactFiberReconciler({
parentInstance.removeChild(child);
},
- scheduleAnimationCallback: window.requestAnimationFrame,
+ scheduleAnimationCallback: ReactDOMFrameScheduling.rAF,
- scheduleDeferredCallback: window.requestIdleCallback,
+ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
useSyncScheduling: true,
diff --git a/src/renderers/dom/fiber/ReactDOMFrameScheduling.js b/src/renderers/dom/fiber/ReactDOMFrameScheduling.js
new file mode 100644
index 00000000000..2ef1d331e30
--- /dev/null
+++ b/src/renderers/dom/fiber/ReactDOMFrameScheduling.js
@@ -0,0 +1,149 @@
+/**
+ * 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 ReactDOMFrameScheduling
+ * @flow
+ */
+
+'use strict';
+
+// This a built-in polyfill for requestIdleCallback. It works by scheduling
+// a requestAnimationFrame, store the time for the start of the frame, then
+// schedule a postMessage which gets scheduled after paint. Within the
+// postMessage handler do as much work as possible until time + frame rate.
+// By separating the idle call into a separate event tick we ensure that
+// layout, paint and other browser work is counted against the available time.
+// The frame rate is dynamically adjusted.
+
+import type { Deadline } from 'ReactFiberReconciler';
+
+var invariant = require('invariant');
+
+// TODO: There's no way to cancel these, because Fiber doesn't atm.
+let rAF : (callback : (time : number) => void) => number;
+let rIC : (callback : (deadline : Deadline) => void) => number;
+if (typeof requestAnimationFrame !== 'function') {
+ invariant(
+ false,
+ 'React depends on requestAnimationFrame. Make sure that you load a ' +
+ 'polyfill in older browsers.'
+ );
+} else if (typeof requestIdleCallback !== 'function') {
+ // Wrap requestAnimationFrame and polyfill requestIdleCallback.
+
+ var scheduledRAFCallback = null;
+ var scheduledRICCallback = null;
+
+ var isIdleScheduled = false;
+ var isAnimationFrameScheduled = false;
+
+ var frameDeadline = 0;
+ // We start out assuming that we run at 30fps but then the heuristic tracking
+ // will adjust this value to a faster fps if we get more frequent animation
+ // frames.
+ var previousFrameTime = 33;
+ var activeFrameTime = 33;
+
+ var frameDeadlineObject = {
+ timeRemaining: (
+ typeof performance === 'object' &&
+ typeof performance.now === 'function' ? function() {
+ // We assume that if we have a performance timer that the rAF callback
+ // gets a performance timer value. Not sure if this is always true.
+ return frameDeadline - performance.now();
+ } : function() {
+ // As a fallback we use Date.now.
+ return frameDeadline - Date.now();
+ }
+ ),
+ };
+
+ // We use the postMessage trick to defer idle work until after the repaint.
+ var messageKey =
+ '__reactIdleCallback$' + Math.random().toString(36).slice(2);
+ var idleTick = function(event) {
+ if (event.source !== window || event.data !== messageKey) {
+ return;
+ }
+ isIdleScheduled = false;
+ var callback = scheduledRICCallback;
+ scheduledRICCallback = null;
+ if (callback) {
+ callback(frameDeadlineObject);
+ }
+ };
+ // Assumes that we have addEventListener in this environment. Might need
+ // something better for old IE.
+ window.addEventListener('message', idleTick, false);
+
+ var animationTick = function(rafTime) {
+ isAnimationFrameScheduled = false;
+ var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
+ if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
+ if (nextFrameTime < 8) {
+ // Defensive coding. We don't support higher frame rates than 120hz.
+ // If we get lower than that, it is probably a bug.
+ nextFrameTime = 8;
+ }
+ // If one frame goes long, then the next one can be short to catch up.
+ // If two frames are short in a row, then that's an indication that we
+ // actually have a higher frame rate than what we're currently optimizing.
+ // We adjust our heuristic dynamically accordingly. For example, if we're
+ // running on 120hz display or 90hz VR display.
+ // Take the max of the two in case one of them was an anomaly due to
+ // missed frame deadlines.
+ activeFrameTime = nextFrameTime < previousFrameTime ?
+ previousFrameTime : nextFrameTime;
+ } else {
+ previousFrameTime = nextFrameTime;
+ }
+ frameDeadline = rafTime + activeFrameTime;
+ if (!isIdleScheduled) {
+ isIdleScheduled = true;
+ window.postMessage(messageKey, '*');
+ }
+ var callback = scheduledRAFCallback;
+ scheduledRAFCallback = null;
+ if (callback) {
+ callback(rafTime);
+ }
+ };
+
+ rAF = function(callback : (time : number) => void) : number {
+ // This assumes that we only schedule one callback at a time because that's
+ // how Fiber uses it.
+ scheduledRAFCallback = callback;
+ if (!isAnimationFrameScheduled) {
+ // If rIC didn't already schedule one, we need to schedule a frame.
+ isAnimationFrameScheduled = true;
+ requestAnimationFrame(animationTick);
+ }
+ return 0;
+ };
+
+ rIC = function(callback : (deadline : Deadline) => void) : number {
+ // This assumes that we only schedule one callback at a time because that's
+ // how Fiber uses it.
+ scheduledRICCallback = callback;
+ if (!isAnimationFrameScheduled) {
+ // If rAF didn't already schedule one, we need to schedule a frame.
+ // TODO: If this rAF doesn't materialize because the browser throttles, we
+ // might want to still have setTimeout trigger rIC as a backup to ensure
+ // that we keep performing work.
+ isAnimationFrameScheduled = true;
+ requestAnimationFrame(animationTick);
+ }
+ return 0;
+ };
+} else {
+ rAF = requestAnimationFrame;
+ rIC = requestIdleCallback;
+}
+
+exports.rAF = rAF;
+exports.rIC = rIC;
diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js
index c741d94bddf..f7f9c363327 100644
--- a/src/renderers/shared/fiber/ReactChildFiber.js
+++ b/src/renderers/shared/fiber/ReactChildFiber.js
@@ -192,19 +192,15 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
return existingChildren;
}
- function useFiber(fiber : Fiber, priority : PriorityLevel) : Fiber {
+ function useFiber(fiber : Fiber) : Fiber {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
if (shouldClone) {
- const clone = cloneFiber(fiber, priority);
+ const clone = cloneFiber(fiber);
clone.index = 0;
clone.sibling = null;
return clone;
} else {
- // We override the pending priority even if it is higher, because if
- // we're reconciling at a lower priority that means that this was
- // down-prioritized.
- fiber.pendingWorkPriority = priority;
fiber.effectTag = NoEffect;
fiber.index = 0;
fiber.sibling = null;
@@ -248,17 +244,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateTextNode(
returnFiber : Fiber,
current : ?Fiber,
- textContent : string,
- priority : PriorityLevel
+ textContent : string
) {
if (current == null || current.tag !== HostText) {
// Insert
- const created = createFiberFromText(textContent, priority);
+ const created = createFiberFromText(textContent);
created.return = returnFiber;
return created;
} else {
// Update
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.pendingProps = textContent;
existing.return = returnFiber;
return existing;
@@ -268,18 +263,17 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateElement(
returnFiber : Fiber,
current : ?Fiber,
- element : ReactElement,
- priority : PriorityLevel
+ element : ReactElement
) : Fiber {
if (current == null || current.type !== element.type) {
// Insert
- const created = createFiberFromElement(element, priority);
+ const created = createFiberFromElement(element);
created.ref = coerceRef(current, element);
created.return = returnFiber;
return created;
} else {
// Move based on index
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.ref = coerceRef(current, element);
existing.pendingProps = element.props;
existing.return = returnFiber;
@@ -294,18 +288,17 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateCoroutine(
returnFiber : Fiber,
current : ?Fiber,
- coroutine : ReactCoroutine,
- priority : PriorityLevel
+ coroutine : ReactCoroutine
) : Fiber {
// TODO: Should this also compare handler to determine whether to reuse?
if (current == null || current.tag !== CoroutineComponent) {
// Insert
- const created = createFiberFromCoroutine(coroutine, priority);
+ const created = createFiberFromCoroutine(coroutine);
created.return = returnFiber;
return created;
} else {
// Move based on index
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.pendingProps = coroutine;
existing.return = returnFiber;
return existing;
@@ -315,20 +308,19 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateYield(
returnFiber : Fiber,
current : ?Fiber,
- yieldNode : ReactYield,
- priority : PriorityLevel
+ yieldNode : ReactYield
) : Fiber {
// TODO: Should this also compare continuation to determine whether to reuse?
if (current == null || current.tag !== YieldComponent) {
// Insert
const reifiedYield = createReifiedYield(yieldNode);
- const created = createFiberFromYield(yieldNode, priority);
+ const created = createFiberFromYield(yieldNode);
created.type = reifiedYield;
created.return = returnFiber;
return created;
} else {
// Move based on index
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.type = createUpdatedReifiedYield(
current.type,
yieldNode
@@ -341,8 +333,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updatePortal(
returnFiber : Fiber,
current : ?Fiber,
- portal : ReactPortal,
- priority : PriorityLevel
+ portal : ReactPortal
) : Fiber {
if (
current == null ||
@@ -351,12 +342,12 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
current.stateNode.implementation !== portal.implementation
) {
// Insert
- const created = createFiberFromPortal(portal, priority);
+ const created = createFiberFromPortal(portal);
created.return = returnFiber;
return created;
} else {
// Update
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.pendingProps = portal.children || [];
existing.return = returnFiber;
return existing;
@@ -366,17 +357,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateFragment(
returnFiber : Fiber,
current : ?Fiber,
- fragment : Iterable<*>,
- priority : PriorityLevel
+ fragment : Iterable<*>
) : Fiber {
if (current == null || current.tag !== Fragment) {
// Insert
- const created = createFiberFromFragment(fragment, priority);
+ const created = createFiberFromFragment(fragment);
created.return = returnFiber;
return created;
} else {
// Update
- const existing = useFiber(current, priority);
+ const existing = useFiber(current);
existing.pendingProps = fragment;
existing.return = returnFiber;
return existing;
@@ -385,14 +375,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function createChild(
returnFiber : Fiber,
- newChild : any,
- priority : PriorityLevel
+ newChild : any
) : ?Fiber {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes doesn't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
- const created = createFiberFromText('' + newChild, priority);
+ const created = createFiberFromText('' + newChild);
created.return = returnFiber;
return created;
}
@@ -400,35 +389,35 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
- const created = createFiberFromElement(newChild, priority);
+ const created = createFiberFromElement(newChild);
created.ref = coerceRef(null, newChild);
created.return = returnFiber;
return created;
}
case REACT_COROUTINE_TYPE: {
- const created = createFiberFromCoroutine(newChild, priority);
+ const created = createFiberFromCoroutine(newChild);
created.return = returnFiber;
return created;
}
case REACT_YIELD_TYPE: {
const reifiedYield = createReifiedYield(newChild);
- const created = createFiberFromYield(newChild, priority);
+ const created = createFiberFromYield(newChild);
created.type = reifiedYield;
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
- const created = createFiberFromPortal(newChild, priority);
+ const created = createFiberFromPortal(newChild);
created.return = returnFiber;
return created;
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
- const created = createFiberFromFragment(newChild, priority);
+ const created = createFiberFromFragment(newChild);
created.return = returnFiber;
return created;
}
@@ -440,8 +429,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function updateSlot(
returnFiber : Fiber,
oldFiber : ?Fiber,
- newChild : any,
- priority : PriorityLevel
+ newChild : any
) : ?Fiber {
// Update the fiber if the keys match, otherwise return null.
@@ -454,14 +442,14 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (key !== null) {
return null;
}
- return updateTextNode(returnFiber, oldFiber, '' + newChild, priority);
+ return updateTextNode(returnFiber, oldFiber, '' + newChild);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
- return updateElement(returnFiber, oldFiber, newChild, priority);
+ return updateElement(returnFiber, oldFiber, newChild);
} else {
return null;
}
@@ -469,7 +457,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
case REACT_COROUTINE_TYPE: {
if (newChild.key === key) {
- return updateCoroutine(returnFiber, oldFiber, newChild, priority);
+ return updateCoroutine(returnFiber, oldFiber, newChild);
} else {
return null;
}
@@ -477,7 +465,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
case REACT_YIELD_TYPE: {
if (newChild.key === key) {
- return updateYield(returnFiber, oldFiber, newChild, priority);
+ return updateYield(returnFiber, oldFiber, newChild);
} else {
return null;
}
@@ -490,7 +478,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (key !== null) {
return null;
}
- return updateFragment(returnFiber, oldFiber, newChild, priority);
+ return updateFragment(returnFiber, oldFiber, newChild);
}
}
@@ -501,15 +489,14 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
existingChildren : Map,
returnFiber : Fiber,
newIdx : number,
- newChild : any,
- priority : PriorityLevel
+ newChild : any
) : ?Fiber {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes doesn't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
- return updateTextNode(returnFiber, matchedFiber, '' + newChild, priority);
+ return updateTextNode(returnFiber, matchedFiber, '' + newChild);
}
if (typeof newChild === 'object' && newChild !== null) {
@@ -518,34 +505,34 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
const matchedFiber = existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
- return updateElement(returnFiber, matchedFiber, newChild, priority);
+ return updateElement(returnFiber, matchedFiber, newChild);
}
case REACT_COROUTINE_TYPE: {
const matchedFiber = existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
- return updateCoroutine(returnFiber, matchedFiber, newChild, priority);
+ return updateCoroutine(returnFiber, matchedFiber, newChild);
}
case REACT_YIELD_TYPE: {
const matchedFiber = existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
- return updateYield(returnFiber, matchedFiber, newChild, priority);
+ return updateYield(returnFiber, matchedFiber, newChild);
}
case REACT_PORTAL_TYPE: {
const matchedFiber = existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
- return updatePortal(returnFiber, matchedFiber, newChild, priority);
+ return updatePortal(returnFiber, matchedFiber, newChild);
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
- return updateFragment(returnFiber, matchedFiber, newChild, priority);
+ return updateFragment(returnFiber, matchedFiber, newChild);
}
}
@@ -597,8 +584,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileChildrenArray(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- newChildren : Array<*>,
- priority : PriorityLevel) : ?Fiber {
+ newChildren : Array<*>) : ?Fiber {
// This algorithm can't optimize by searching from boths ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
@@ -647,8 +633,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
const newFiber = updateSlot(
returnFiber,
oldFiber,
- newChildren[newIdx],
- priority
+ newChildren[newIdx]
);
if (!newFiber) {
// TODO: This breaks on empty slots like null children. That's
@@ -694,8 +679,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
- newChildren[newIdx],
- priority
+ newChildren[newIdx]
);
if (!newFiber) {
continue;
@@ -721,8 +705,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
existingChildren,
returnFiber,
newIdx,
- newChildren[newIdx],
- priority
+ newChildren[newIdx]
);
if (newFiber) {
if (shouldTrackSideEffects) {
@@ -758,8 +741,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileChildrenIterator(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- newChildrenIterable : Iterable<*>,
- priority : PriorityLevel) : ?Fiber {
+ newChildrenIterable : Iterable<*>) : ?Fiber {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
@@ -810,8 +792,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
const newFiber = updateSlot(
returnFiber,
oldFiber,
- step.value,
- priority
+ step.value
);
if (!newFiber) {
// TODO: This breaks on empty slots like null children. That's
@@ -857,8 +838,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = createChild(
returnFiber,
- step.value,
- priority
+ step.value
);
if (!newFiber) {
continue;
@@ -884,8 +864,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
existingChildren,
returnFiber,
newIdx,
- step.value,
- priority
+ step.value
);
if (newFiber) {
if (shouldTrackSideEffects) {
@@ -921,8 +900,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileSingleTextNode(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- textContent : string,
- priority : PriorityLevel
+ textContent : string
) : Fiber {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
@@ -930,7 +908,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
- const existing = useFiber(currentFirstChild, priority);
+ const existing = useFiber(currentFirstChild);
existing.pendingProps = textContent;
existing.return = returnFiber;
return existing;
@@ -938,7 +916,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
deleteRemainingChildren(returnFiber, currentFirstChild);
- const created = createFiberFromText(textContent, priority);
+ const created = createFiberFromText(textContent);
created.return = returnFiber;
return created;
}
@@ -946,8 +924,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileSingleElement(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- element : ReactElement,
- priority : PriorityLevel
+ element : ReactElement
) : Fiber {
const key = element.key;
let child = currentFirstChild;
@@ -957,7 +934,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (child.key === key) {
if (child.type === element.type) {
deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, priority);
+ const existing = useFiber(child);
existing.ref = coerceRef(child, element);
existing.pendingProps = element.props;
existing.return = returnFiber;
@@ -976,7 +953,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
child = child.sibling;
}
- const created = createFiberFromElement(element, priority);
+ const created = createFiberFromElement(element);
created.ref = coerceRef(currentFirstChild, element);
created.return = returnFiber;
return created;
@@ -985,8 +962,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileSingleCoroutine(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- coroutine : ReactCoroutine,
- priority : PriorityLevel
+ coroutine : ReactCoroutine
) : Fiber {
const key = coroutine.key;
let child = currentFirstChild;
@@ -996,7 +972,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (child.key === key) {
if (child.tag === CoroutineComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, priority);
+ const existing = useFiber(child);
existing.pendingProps = coroutine;
existing.return = returnFiber;
return existing;
@@ -1010,7 +986,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
child = child.sibling;
}
- const created = createFiberFromCoroutine(coroutine, priority);
+ const created = createFiberFromCoroutine(coroutine);
created.return = returnFiber;
return created;
}
@@ -1018,8 +994,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileSingleYield(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- yieldNode : ReactYield,
- priority : PriorityLevel
+ yieldNode : ReactYield
) : Fiber {
const key = yieldNode.key;
let child = currentFirstChild;
@@ -1029,7 +1004,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (child.key === key) {
if (child.tag === YieldComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, priority);
+ const existing = useFiber(child);
existing.type = createUpdatedReifiedYield(
child.type,
yieldNode
@@ -1047,7 +1022,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
}
const reifiedYield = createReifiedYield(yieldNode);
- const created = createFiberFromYield(yieldNode, priority);
+ const created = createFiberFromYield(yieldNode);
created.type = reifiedYield;
created.return = returnFiber;
return created;
@@ -1056,8 +1031,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
function reconcileSinglePortal(
returnFiber : Fiber,
currentFirstChild : ?Fiber,
- portal : ReactPortal,
- priority : PriorityLevel
+ portal : ReactPortal
) : Fiber {
const key = portal.key;
let child = currentFirstChild;
@@ -1071,7 +1045,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
child.stateNode.implementation === portal.implementation
) {
deleteRemainingChildren(returnFiber, child.sibling);
- const existing = useFiber(child, priority);
+ const existing = useFiber(child);
existing.pendingProps = portal.children || [];
existing.return = returnFiber;
return existing;
@@ -1085,7 +1059,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
child = child.sibling;
}
- const created = createFiberFromPortal(portal, priority);
+ const created = createFiberFromPortal(portal);
created.return = returnFiber;
return created;
}
@@ -1097,8 +1071,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
returnFiber : Fiber,
currentFirstChild : ?Fiber,
newChild : any,
- priority : PriorityLevel
+ priorityLevel : PriorityLevel
) : ?Fiber {
+ returnFiber.pendingWorkPriority = priorityLevel;
+
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
@@ -1114,7 +1090,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- priority
));
case REACT_PORTAL_TYPE:
@@ -1122,7 +1097,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- priority
));
}
}
@@ -1170,8 +1144,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
return placeSingleChild(reconcileSingleTextNode(
returnFiber,
currentFirstChild,
- '' + newChild,
- priority
+ '' + newChild
));
}
@@ -1181,32 +1154,28 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
return placeSingleChild(reconcileSingleElement(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
));
case REACT_COROUTINE_TYPE:
return placeSingleChild(reconcileSingleCoroutine(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
));
case REACT_YIELD_TYPE:
return placeSingleChild(reconcileSingleYield(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
));
}
@@ -1214,8 +1183,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
);
}
@@ -1223,8 +1191,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
- newChild,
- priority
+ newChild
);
}
}
@@ -1276,6 +1243,10 @@ exports.cloneChildFibers = function(current : ?Fiber, workInProgress : Fiber) :
if (!workInProgress.child) {
return;
}
+
+ // cloneChildFibers is only called when bailing out on already finished work.
+ // So the pendingWorkPriority should remain at its existing value.
+
if (current && workInProgress.child === current.child) {
// We use workInProgress.child since that lets Flow know that it can't be
// null since we validated that already. However, as the line above suggests
@@ -1288,16 +1259,13 @@ exports.cloneChildFibers = function(current : ?Fiber, workInProgress : Fiber) :
// observable when the first sibling has lower priority work remaining
// than the next sibling. At that point we should add tests that catches
// this.
- let newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority);
+ let newChild = cloneFiber(currentChild);
workInProgress.child = newChild;
newChild.return = workInProgress;
while (currentChild.sibling) {
currentChild = currentChild.sibling;
- newChild = newChild.sibling = cloneFiber(
- currentChild,
- currentChild.pendingWorkPriority
- );
+ newChild = newChild.sibling = cloneFiber(currentChild);
newChild.return = workInProgress;
}
newChild.sibling = null;
diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js
index 3657d087bb8..3824df543d9 100644
--- a/src/renderers/shared/fiber/ReactFiber.js
+++ b/src/renderers/shared/fiber/ReactFiber.js
@@ -128,6 +128,8 @@ export type Fiber = {
lastEffect: ?Fiber,
// This will be used to quickly determine if a subtree has no pending changes.
+ // It represents the priority of the child subtree, not including the
+ // fiber itself.
pendingWorkPriority: PriorityLevel,
// This value represents the priority level that was last used to process this
@@ -234,7 +236,7 @@ function shouldConstruct(Component) {
// This is used to create an alternate fiber to do work on.
// TODO: Rename to createWorkInProgressFiber or something like that.
-exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fiber {
+exports.cloneFiber = function(fiber : Fiber) : Fiber {
// We clone to get a work in progress. That means that this fiber is the
// current. To make it safe to reuse that fiber later on as work in progress
// we need to reset its work in progress flag now. We don't have an
@@ -278,7 +280,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
// TODO: Pass in the new pendingProps as an argument maybe?
alt.pendingProps = fiber.pendingProps;
cloneUpdateQueue(fiber, alt);
- alt.pendingWorkPriority = priorityLevel;
+ alt.pendingWorkPriority = fiber.pendingWorkPriority;
alt.memoizedProps = fiber.memoizedProps;
alt.memoizedState = fiber.memoizedState;
@@ -297,15 +299,13 @@ exports.createHostRootFiber = function() : Fiber {
return fiber;
};
-exports.createFiberFromElement = function(element : ReactElement, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromElement = function(element : ReactElement) : Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
-
const fiber = createFiberFromElementType(element.type, element.key, owner);
fiber.pendingProps = element.props;
- fiber.pendingWorkPriority = priorityLevel;
if (__DEV__) {
fiber._debugSource = element._source;
@@ -315,19 +315,17 @@ exports.createFiberFromElement = function(element : ReactElement, priorityLevel
return fiber;
};
-exports.createFiberFromFragment = function(elements : ReactFragment, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromFragment = function(elements : ReactFragment) : Fiber {
// TODO: Consider supporting keyed fragments. Technically, we accidentally
// support that in the existing React.
const fiber = createFiber(Fragment, null);
fiber.pendingProps = elements;
- fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
-exports.createFiberFromText = function(content : string, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromText = function(content : string) : Fiber {
const fiber = createFiber(HostText, null);
fiber.pendingProps = content;
- fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
@@ -384,24 +382,22 @@ function createFiberFromElementType(type : mixed, key : null | string, debugOwne
exports.createFiberFromElementType = createFiberFromElementType;
-exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromCoroutine = function(coroutine : ReactCoroutine) : Fiber {
const fiber = createFiber(CoroutineComponent, coroutine.key);
fiber.type = coroutine.handler;
fiber.pendingProps = coroutine;
- fiber.pendingWorkPriority = priorityLevel;
return fiber;
};
-exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromYield = function(yieldNode : ReactYield) : Fiber {
const fiber = createFiber(YieldComponent, yieldNode.key);
fiber.pendingProps = {};
return fiber;
};
-exports.createFiberFromPortal = function(portal : ReactPortal, priorityLevel : PriorityLevel) : Fiber {
+exports.createFiberFromPortal = function(portal : ReactPortal) : Fiber {
const fiber = createFiber(HostPortal, portal.key);
fiber.pendingProps = portal.children || [];
- fiber.pendingWorkPriority = priorityLevel;
fiber.stateNode = {
containerInfo: portal.containerInfo,
implementation: portal.implementation,
diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js
index dbbb9146695..f0bec0b7471 100644
--- a/src/renderers/shared/fiber/ReactFiberBeginWork.js
+++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js
@@ -75,6 +75,7 @@ module.exports = function(
hostContext : HostContext,
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
getPriorityContext : () => PriorityLevel,
+ appendChildEffects : (returnFiber : Fiber, workInProgress : Fiber) => void,
) {
const { shouldSetTextContent } = config;
@@ -121,11 +122,6 @@ module.exports = function(
workInProgress.lastEffect = workInProgress.progressedLastDeletion;
}
- function reconcileChildren(current, workInProgress, nextChildren) {
- const priorityLevel = workInProgress.pendingWorkPriority;
- reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
- }
-
function reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel) {
// At this point any memoization is no longer valid since we'll have changed
// the children.
@@ -174,7 +170,7 @@ module.exports = function(
markChildAsProgressed(current, workInProgress, priorityLevel);
}
- function updateFragment(current, workInProgress) {
+ function updateFragment(current, workInProgress, priorityLevel) {
var nextChildren = workInProgress.pendingProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
@@ -183,9 +179,9 @@ module.exports = function(
nextChildren = workInProgress.memoizedProps;
}
} else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
}
@@ -198,7 +194,7 @@ module.exports = function(
}
}
- function updateFunctionalComponent(current, workInProgress) {
+ function updateFunctionalComponent(current, workInProgress, priorityLevel) {
var fn = workInProgress.type;
var nextProps = workInProgress.pendingProps;
@@ -211,7 +207,7 @@ module.exports = function(
}
} else {
if (nextProps == null || memoizedProps === nextProps) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
// TODO: Disable this before release, since it is not part of the public API
// I use this for testing to compare the relative overhead of classes.
@@ -219,7 +215,7 @@ module.exports = function(
!fn.shouldComponentUpdate(memoizedProps, nextProps)) {
// Memoize props even if shouldComponentUpdate returns false
memoizeProps(workInProgress, nextProps);
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
}
@@ -234,7 +230,7 @@ module.exports = function(
} else {
nextChildren = fn(nextProps, context);
}
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
@@ -259,7 +255,7 @@ module.exports = function(
} else {
shouldUpdate = updateClassInstance(current, workInProgress, priorityLevel);
}
- return finishClassComponent(current, workInProgress, shouldUpdate, hasContext);
+ return finishClassComponent(current, workInProgress, shouldUpdate, hasContext, priorityLevel);
}
function finishClassComponent(
@@ -267,12 +263,13 @@ module.exports = function(
workInProgress : Fiber,
shouldUpdate : boolean,
hasContext : boolean,
+ priorityLevel : PriorityLevel
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
if (!shouldUpdate) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
const instance = workInProgress.stateNode;
@@ -280,7 +277,7 @@ module.exports = function(
// Rerender
ReactCurrentOwner.current = workInProgress;
const nextChildren = instance.render();
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
// Memoize props and state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
memoizeState(workInProgress, instance.state);
@@ -319,18 +316,18 @@ module.exports = function(
if (prevState === state) {
// If the state is the same as before, that's a bailout because we had
// no work matching this priority.
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
const element = state.element;
- reconcileChildren(current, workInProgress, element);
+ reconcileChildrenAtPriority(current, workInProgress, element, priorityLevel);
memoizeState(workInProgress, state);
return workInProgress.child;
}
// If there is no update queue, that's a bailout because the root has no props.
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
- function updateHostComponent(current, workInProgress) {
+ function updateHostComponent(current, workInProgress, priorityLevel) {
pushHostContext(workInProgress);
let nextProps = workInProgress.pendingProps;
@@ -346,8 +343,7 @@ module.exports = function(
}
}
} else if (nextProps === null || memoizedProps === nextProps) {
- if (memoizedProps.hidden &&
- workInProgress.pendingWorkPriority !== OffscreenPriority) {
+ if (memoizedProps.hidden && priorityLevel !== OffscreenPriority) {
// This subtree still has work, but it should be deprioritized so we need
// to bail out and not do any work yet.
// TODO: It would be better if this tree got its correct priority set
@@ -355,16 +351,9 @@ module.exports = function(
// priority reconciliation first before we can get down here. However,
// that is a bit tricky since workInProgress and current can have
// different "hidden" settings.
- let child = workInProgress.progressedChild;
- while (child) {
- // To ensure that this subtree gets its priority reset, the children
- // need to be reset.
- child.pendingWorkPriority = OffscreenPriority;
- child = child.sibling;
- }
return null;
}
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
let nextChildren = nextProps.children;
@@ -387,8 +376,7 @@ module.exports = function(
markRef(current, workInProgress);
- if (nextProps.hidden &&
- workInProgress.pendingWorkPriority !== OffscreenPriority) {
+ if (nextProps.hidden && priorityLevel !== OffscreenPriority) {
// If this host component is hidden, we can bail out on the children.
// We'll rerender the children later at the lower priority.
@@ -424,7 +412,7 @@ module.exports = function(
// Abort and don't process children yet.
return null;
} else {
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
@@ -469,7 +457,7 @@ module.exports = function(
const hasContext = pushContextProvider(workInProgress);
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, priorityLevel);
- return finishClassComponent(current, workInProgress, true, hasContext);
+ return finishClassComponent(current, workInProgress, true, hasContext, priorityLevel);
} else {
// Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent;
@@ -498,13 +486,13 @@ module.exports = function(
}
}
}
- reconcileChildren(current, workInProgress, value);
+ reconcileChildrenAtPriority(current, workInProgress, value, priorityLevel);
memoizeProps(workInProgress, props);
return workInProgress.child;
}
}
- function updateCoroutineComponent(current, workInProgress) {
+ function updateCoroutineComponent(current, workInProgress, priorityLevel) {
var nextCoroutine = (workInProgress.pendingProps : null | ReactCoroutine);
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
@@ -516,18 +504,17 @@ module.exports = function(
}
}
} else if (nextCoroutine === null || workInProgress.memoizedProps === nextCoroutine) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
- reconcileChildren(current, workInProgress, nextCoroutine.children);
+ reconcileChildrenAtPriority(current, workInProgress, nextCoroutine.children, priorityLevel);
memoizeProps(workInProgress, nextCoroutine);
// This doesn't take arbitrary time so we could synchronously just begin
// eagerly do the work of workInProgress.child as an optimization.
return workInProgress.child;
}
- function updatePortalComponent(current, workInProgress) {
+ function updatePortalComponent(current, workInProgress, priorityLevel) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
- const priorityLevel = workInProgress.pendingWorkPriority;
let nextChildren = workInProgress.pendingProps;
if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
@@ -539,7 +526,7 @@ module.exports = function(
}
}
} else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, priorityLevel);
}
if (!current) {
@@ -557,72 +544,49 @@ module.exports = function(
memoizeProps(workInProgress, nextChildren);
markChildAsProgressed(current, workInProgress, priorityLevel);
} else {
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
memoizeProps(workInProgress, nextChildren);
}
return workInProgress.child;
}
- /*
function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
let child = firstChild;
do {
- // Ensure that the first and last effect of the parent corresponds
- // to the children's first and last effect.
- if (!returnFiber.firstEffect) {
- returnFiber.firstEffect = child.firstEffect;
- }
- if (child.lastEffect) {
- if (returnFiber.lastEffect) {
- returnFiber.lastEffect.nextEffect = child.firstEffect;
- }
- returnFiber.lastEffect = child.lastEffect;
- }
+ appendChildEffects(returnFiber, child);
} while (child = child.sibling);
}
- */
-
- function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : ?Fiber {
- const priorityLevel = workInProgress.pendingWorkPriority;
- // TODO: We should ideally be able to bail out early if the children have no
- // more work to do. However, since we don't have a separation of this
- // Fiber's priority and its children yet - we don't know without doing lots
- // of the same work we do anyway. Once we have that separation we can just
- // bail out here if the children has no more work at this priority level.
- // if (workInProgress.priorityOfChildren <= priorityLevel) {
- // // If there are side-effects in these children that have not yet been
- // // committed we need to ensure that they get properly transferred up.
- // if (current && current.child !== workInProgress.child) {
- // reuseChildrenEffects(workInProgress, child);
- // }
- // return null;
- // }
-
- if (current && workInProgress.child === current.child) {
+
+ function bailoutOnAlreadyFinishedWork(
+ current : ?Fiber,
+ workInProgress : Fiber,
+ priorityLevel : PriorityLevel
+ ) : ?Fiber {
+ const childIsClone = !current || workInProgress.child !== current.child;
+
+ if (!childIsClone) {
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
clearDeletions(workInProgress);
}
- cloneChildFibers(current, workInProgress);
- markChildAsProgressed(current, workInProgress, priorityLevel);
- return workInProgress.child;
- }
-
- function bailoutOnLowPriority(current, workInProgress) {
- // TODO: Handle HostComponent tags here as well and call pushHostContext()?
- // See PR 8590 discussion for context
- switch (workInProgress.tag) {
- case ClassComponent:
- pushContextProvider(workInProgress);
- break;
- case HostPortal:
- pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
- break;
+ if (workInProgress.pendingWorkPriority === NoWork ||
+ workInProgress.pendingWorkPriority > priorityLevel) {
+ // The work in the child's subtree does not have sufficient priority.
+ // Bail out.
+ if (childIsClone &&
+ workInProgress.progressedPriority <= priorityLevel &&
+ workInProgress.child) {
+ reuseChildrenEffects(workInProgress, workInProgress.child);
+ } else {
+ workInProgress.child = current ? current.child : null;
+ }
+ return null;
+ } else {
+ cloneChildFibers(current, workInProgress);
+ markChildAsProgressed(current, workInProgress, priorityLevel);
+ return workInProgress.child;
}
- // TODO: What if this is currently in progress?
- // How can that happen? How is this not being cloned?
- return null;
}
function memoizeProps(workInProgress : Fiber, nextProps : any) {
@@ -638,11 +602,6 @@ module.exports = function(
}
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
- if (workInProgress.pendingWorkPriority === NoWork ||
- workInProgress.pendingWorkPriority > priorityLevel) {
- return bailoutOnLowPriority(current, workInProgress);
- }
-
if (__DEV__) {
ReactDebugCurrentFiber.current = workInProgress;
}
@@ -654,7 +613,7 @@ module.exports = function(
if (workInProgress.progressedPriority === priorityLevel) {
// If we have progressed work on this priority level already, we can
- // proceed this that as the child.
+ // proceed with that as the child.
workInProgress.child = workInProgress.progressedChild;
}
@@ -662,29 +621,29 @@ module.exports = function(
case IndeterminateComponent:
return mountIndeterminateComponent(current, workInProgress, priorityLevel);
case FunctionalComponent:
- return updateFunctionalComponent(current, workInProgress);
+ return updateFunctionalComponent(current, workInProgress, priorityLevel);
case ClassComponent:
return updateClassComponent(current, workInProgress, priorityLevel);
case HostRoot:
return updateHostRoot(current, workInProgress, priorityLevel);
case HostComponent:
- return updateHostComponent(current, workInProgress);
+ return updateHostComponent(current, workInProgress, priorityLevel);
case HostText:
- return updateHostText(current, workInProgress);
+ return updateHostText(current, workInProgress, priorityLevel);
case CoroutineHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
workInProgress.tag = CoroutineComponent;
// Intentionally fall through since this is now the same.
case CoroutineComponent:
- return updateCoroutineComponent(current, workInProgress);
+ return updateCoroutineComponent(current, workInProgress, priorityLevel);
case YieldComponent:
// A yield component is just a placeholder, we can just run through the
// next one immediately.
return null;
case HostPortal:
- return updatePortalComponent(current, workInProgress);
+ return updatePortalComponent(current, workInProgress, priorityLevel);
case Fragment:
- return updateFragment(current, workInProgress);
+ return updateFragment(current, workInProgress, priorityLevel);
default:
throw new Error('Unknown unit of work tag');
}
@@ -699,11 +658,6 @@ module.exports = function(
// Add an error effect so we can handle the error during the commit phase
workInProgress.effectTag |= Err;
- if (workInProgress.pendingWorkPriority === NoWork ||
- workInProgress.pendingWorkPriority > priorityLevel) {
- return bailoutOnLowPriority(current, workInProgress);
- }
-
// If we don't bail out, we're going be recomputing our children so we need
// to drop our effect list.
workInProgress.firstEffect = null;
@@ -711,7 +665,7 @@ module.exports = function(
// Unmount the current children as if the component rendered null
const nextChildren = null;
- reconcileChildren(current, workInProgress, nextChildren);
+ reconcileChildrenAtPriority(current, workInProgress, nextChildren, priorityLevel);
if (workInProgress.tag === ClassComponent) {
const instance = workInProgress.stateNode;
diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
index 776db30ecc0..edfc849e795 100644
--- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js
+++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js
@@ -18,6 +18,7 @@ import type { HostContext } from 'ReactFiberHostContext';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';
+import type { PriorityLevel } from 'ReactPriorityLevel';
var { reconcileChildFibers } = require('ReactChildFiber');
var {
@@ -25,6 +26,7 @@ var {
} = require('ReactFiberContext');
var ReactTypeOfWork = require('ReactTypeOfWork');
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
+var ReactPriorityLevel = require('ReactPriorityLevel');
var {
IndeterminateComponent,
FunctionalComponent,
@@ -42,6 +44,10 @@ var {
Ref,
Update,
} = ReactTypeOfSideEffect;
+var {
+ NoWork,
+ OffscreenPriority,
+} = ReactPriorityLevel;
if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
@@ -50,6 +56,7 @@ if (__DEV__) {
module.exports = function(
config : HostConfig,
hostContext : HostContext,
+ getUpdateAndChildPriority : (fiber: Fiber) => PriorityLevel,
) {
const {
createInstance,
@@ -66,6 +73,39 @@ module.exports = function(
popHostContainer,
} = hostContext;
+ function resetWorkPriority(workInProgress : Fiber, priorityLevel : PriorityLevel) {
+ // priorityLevel is the level we're currently reconciling at. It's called
+ // nextPriorityLevel in the scheduler. Can't think of a name that's
+ // not confusing.
+
+ // If the progressedPriority is less than the priority we're currently
+ // reconciling at, this was a bailout. Set the work priority to the
+ // progressed priority, otherwise reset it to NoWork.
+ let newPriority;
+ if (workInProgress.progressedPriority === NoWork ||
+ (workInProgress.progressedPriority > priorityLevel) && priorityLevel !== NoWork) {
+ newPriority = workInProgress.progressedPriority;
+ } else {
+ newPriority = NoWork;
+ }
+
+ // progressedChild is going to be the child set with the highest priority.
+ // Either it is the same as child, or it just bailed out because it choose
+ // not to do the work.
+ let child = workInProgress.progressedChild;
+ while (child) {
+ const childPriority = getUpdateAndChildPriority(child);
+ // Ensure that remaining work priority bubbles up.
+ if (childPriority !== NoWork &&
+ (newPriority === NoWork ||
+ newPriority > childPriority)) {
+ newPriority = childPriority;
+ }
+ child = child.sibling;
+ }
+ workInProgress.pendingWorkPriority = newPriority;
+ }
+
function markUpdate(workInProgress : Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// an UpdateAndPlacement.
@@ -164,17 +204,19 @@ module.exports = function(
}
}
- function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
+ function completeWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
if (__DEV__) {
ReactDebugCurrentFiber.current = workInProgress;
}
switch (workInProgress.tag) {
case FunctionalComponent:
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
case ClassComponent: {
// We are leaving this subtree, so pop context if any.
popContextProvider(workInProgress);
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
}
case HostRoot: {
@@ -184,6 +226,7 @@ module.exports = function(
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
}
case HostComponent: {
@@ -247,6 +290,15 @@ module.exports = function(
workInProgress.effectTag |= Ref;
}
}
+ if (newProps.hidden &&
+ priorityLevel !== NoWork &&
+ priorityLevel < OffscreenPriority) {
+ // If this node is hidden, and we're reconciling at higher than
+ // offscreen priority, there's remaining work in the subtree.
+ workInProgress.pendingWorkPriority = OffscreenPriority;
+ } else {
+ resetWorkPriority(workInProgress, priorityLevel);
+ }
return null;
}
case HostText: {
@@ -272,23 +324,30 @@ module.exports = function(
const textInstance = createTextInstance(newText, rootContainerInstance, currentHostContext, workInProgress);
workInProgress.stateNode = textInstance;
}
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
}
- case CoroutineComponent:
- return moveCoroutineToHandlerPhase(current, workInProgress);
+ case CoroutineComponent: {
+ const next = moveCoroutineToHandlerPhase(current, workInProgress);
+ resetWorkPriority(workInProgress, priorityLevel);
+ return next;
+ }
case CoroutineHandlerPhase:
// Reset the tag to now be a first phase coroutine.
workInProgress.tag = CoroutineComponent;
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
case YieldComponent:
// Does nothing.
return null;
case Fragment:
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
case HostPortal:
// TODO: Only mark this as an update if we have any pending callbacks.
markUpdate(workInProgress);
popHostContainer(workInProgress);
+ resetWorkPriority(workInProgress, priorityLevel);
return null;
// Error cases
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index 94c843b4a1a..9117d57c17c 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -67,8 +67,8 @@ export type HostConfig = {
insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void,
removeChild(parentInstance : I | C, child : I | TI) : void,
- scheduleAnimationCallback(callback : () => void) : void,
- scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void,
+ scheduleAnimationCallback(callback : () => void) : number | void,
+ scheduleDeferredCallback(callback : (deadline : Deadline) => void) : number | void,
prepareForCommit() : void,
resetAfterCommit() : void,
diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js
index 773ac5c83a2..5cf0bdb7d29 100644
--- a/src/renderers/shared/fiber/ReactFiberScheduler.js
+++ b/src/renderers/shared/fiber/ReactFiberScheduler.js
@@ -96,8 +96,14 @@ module.exports = function(config : HostConfig(config : HostConfig updatePriority) && updatePriority !== NoWork) {
+ return updatePriority;
+ } else {
+ return childPriority;
+ }
+ }
+
// findNextUnitOfWork mutates the current priority context. It is reset after
// after the workLoop exits, so never call findNextUnitOfWork from outside
// the work loop.
function findNextUnitOfWork() {
// Clear out roots with no more work on them, or if they have uncaught errors
- while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
+ while (nextScheduledRoot && getUpdateAndChildPriority(nextScheduledRoot.current) === NoWork) {
// Unschedule this root.
nextScheduledRoot.isScheduled = false;
// Read the next pointer now.
@@ -212,10 +231,11 @@ module.exports = function(config : HostConfig root.current.pendingWorkPriority)) {
- highestPriorityLevel = root.current.pendingWorkPriority;
+ highestPriorityLevel > rootPriority)) {
+ highestPriorityLevel = rootPriority;
highestPriorityRoot = root;
}
// We didn't find anything to do in this root, so let's try the next one.
@@ -232,10 +252,7 @@ module.exports = function(config : HostConfig(config : HostConfig child.pendingWorkPriority)) {
- newPriority = child.pendingWorkPriority;
- }
- child = child.sibling;
- }
- workInProgress.pendingWorkPriority = newPriority;
- }
-
function completeUnitOfWork(workInProgress : Fiber) : ?Fiber {
while (true) {
// The current, flushed, state of this fiber is the alternate.
@@ -457,13 +448,11 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig(config : HostConfig {
// We're now rendering an update that will bail out on updating middle.
ReactNoop.render();
- ReactNoop.flushDeferredPri(45 + 5);
+ ReactNoop.flushDeferredPri(45 + 10);
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
@@ -428,7 +428,7 @@ describe('ReactIncremental', () => {
// Let us try this again without fully finishing the first time. This will
// create a hanging subtree that is reconciling at the normal priority.
ReactNoop.render();
- ReactNoop.flushDeferredPri(40);
+ ReactNoop.flushDeferredPri(35);
expect(ops).toEqual(['Foo', 'Bar']);
diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
index f8b46fa1866..d1e96cb88c1 100644
--- a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
@@ -756,6 +756,184 @@ describe('ReactIncrementalSideEffects', () => {
expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar']);
});
+ it('does not drop priority from a progressed subtree', () => {
+ let ops = [];
+ let lowPri;
+ let highPri;
+
+ function LowPriDidComplete() {
+ ops.push('LowPriDidComplete');
+ // Because this is terminal, beginning work on LowPriDidComplete implies
+ // that LowPri will be completed before the scheduler yields.
+ return null;
+ }
+
+ class LowPri extends React.Component {
+ state = { step: 0 };
+ render() {
+ ops.push('LowPri');
+ lowPri = this;
+ return [
+ ,
+ ,
+ ];
+ }
+ }
+
+ function LowPriSibling() {
+ ops.push('LowPriSibling');
+ return null;
+ }
+
+ class HighPri extends React.Component {
+ state = { step: 0 };
+ render() {
+ ops.push('HighPri');
+ highPri = this;
+ return ;
+ }
+ }
+
+ function App() {
+ ops.push('App');
+ return [
+
+
+
+
,
+
,
+ ];
+ }
+
+ ReactNoop.render();
+ ReactNoop.flush();
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)),
+ div(span(0)),
+ ]);
+ ops = [];
+
+ lowPri.setState({ step: 1 });
+ // Do just enough work to begin LowPri
+ ReactNoop.flushDeferredPri(30);
+ expect(ops).toEqual(['LowPri']);
+ // Now we'll do one more tick of work to complete LowPri. Because LowPri
+ // has a sibling, the parent div of LowPri has not yet completed.
+ ReactNoop.flushDeferredPri(10);
+ expect(ops).toEqual(['LowPri', 'LowPriDidComplete']);
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)), // Complete, but not yet updated
+ div(span(0)),
+ ]);
+ ops = [];
+
+ // Interrupt with high pri update
+ ReactNoop.performAnimationWork(() => highPri.setState({ step: 1 }));
+ ReactNoop.flushAnimationPri();
+ expect(ops).toEqual(['HighPri']);
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)), // Completed, but not yet updated
+ div(span(1)),
+ ]);
+ ops = [];
+
+ ReactNoop.flush();
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(1)),
+ div(span(1)),
+ ]);
+ });
+
+ it('does not complete already completed work', () => {
+ let ops = [];
+ let lowPri;
+ let highPri;
+
+ function LowPriDidComplete() {
+ ops.push('LowPriDidComplete');
+ // Because this is terminal, beginning work on LowPriDidComplete implies
+ // that LowPri will be completed before the scheduler yields.
+ return null;
+ }
+
+ class LowPri extends React.Component {
+ state = { step: 0 };
+ render() {
+ ops.push('LowPri');
+ lowPri = this;
+ return [
+ ,
+ ,
+ ];
+ }
+ }
+
+ function LowPriSibling() {
+ ops.push('LowPriSibling');
+ return null;
+ }
+
+ class HighPri extends React.Component {
+ state = { step: 0 };
+ render() {
+ ops.push('HighPri');
+ highPri = this;
+ return ;
+ }
+ }
+
+ function App() {
+ ops.push('App');
+ return [
+
+
+
+
,
+
,
+ ];
+ }
+
+ ReactNoop.render();
+ ReactNoop.flush();
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)),
+ div(span(0)),
+ ]);
+ ops = [];
+
+ lowPri.setState({ step: 1 });
+ // Do just enough work to begin LowPri
+ ReactNoop.flushDeferredPri(30);
+ expect(ops).toEqual(['LowPri']);
+ // Now we'll do one more tick of work to complete LowPri. Because LowPri
+ // has a sibling, the parent div of LowPri has not yet completed.
+ ReactNoop.flushDeferredPri(10);
+ expect(ops).toEqual(['LowPri', 'LowPriDidComplete']);
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)), // Complete, but not yet updated
+ div(span(0)),
+ ]);
+ ops = [];
+
+ // Interrupt with high pri update
+ ReactNoop.performAnimationWork(() => highPri.setState({ step: 1 }));
+ ReactNoop.flushAnimationPri();
+ expect(ops).toEqual(['HighPri']);
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(0)), // Completed, but not yet updated
+ div(span(1)),
+ ]);
+ ops = [];
+
+ // If this is not enough to commit the rest of the work, that means we're
+ // not bailing out on the already-completed LowPri tree.
+ ReactNoop.flushDeferredPri(45);
+ expect(ReactNoop.getChildren()).toEqual([
+ div(span(1)),
+ div(span(1)),
+ ]);
+ });
+
it('deprioritizes setStates that happens within a deprioritized tree', () => {
var ops = [];