Skip to content

Commit

Permalink
Down-prioritize children of hidden host components
Browse files Browse the repository at this point in the history
To make sure we don't reset the priority of a down-prioritized fiber,
we compare the priority we're currently rendering at to the fiber's
work priority. If the work priority is lower, then we know not to reset
the work priority.
  • Loading branch information
acdlite committed Jul 1, 2017
1 parent 5e7f5e9 commit 4b9775e
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 121 deletions.
5 changes: 5 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
* updates a previous render
* can cancel partially rendered work and restart
* should call callbacks even if updates are aborted
* can deprioritize unfinished work and resume it later
* can deprioritize a tree from without dropping work
* memoizes work even if shouldComponentUpdate returns false
* can update in the middle of a tree using setState
* can queue multiple state updates
Expand All @@ -2035,6 +2037,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
* can handle if setState callback throws
* merges and masks context
* does not leak own context into context provider
* provides context when reusing work
* reads context when setState is below the provider
* reads context when setState is above the provider
* maintains the correct context when providers bail out due to low priority
Expand Down Expand Up @@ -2078,6 +2081,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js
* warns on cascading renders from top-level render
* does not treat setState from cWM or cWRP as cascading
* captures all lifecycles
* measures deprioritized work
* measures deferred work in chunks
* recovers from fatal errors
* recovers from caught errors
Expand Down Expand Up @@ -2117,6 +2121,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* can reuse side-effects after being preempted, if shouldComponentUpdate is false
* can update a completed tree before it has a chance to commit
* updates a child even though the old props is empty
* deprioritizes setStates that happens within a deprioritized tree
* calls callback after update is flushed
* calls setState callback even if component bails out
* calls componentWillUnmount after a deletion, even if nested
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,7 @@ exports.cloneChildFibers = function(
let currentChild = workInProgress.child;
let newChild = createWorkInProgress(currentChild, renderPriority);
// TODO: Pass this as an argument, since it's easy to forget.
newChild.pendingProps = null;
newChild.pendingProps = currentChild.pendingProps;
workInProgress.child = newChild;

newChild.return = workInProgress;
Expand All @@ -1473,7 +1473,7 @@ exports.cloneChildFibers = function(
currentChild,
renderPriority,
);
newChild.pendingProps = null;
newChild.pendingProps = currentChild.pendingProps;
newChild.return = workInProgress;
}
newChild.sibling = null;
Expand Down
7 changes: 7 additions & 0 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,10 @@ exports.createFiberFromPortal = function(
};
return fiber;
};

exports.largerPriority = function(
p1: PriorityLevel,
p2: PriorityLevel,
): PriorityLevel {
return p1 !== NoWork && (p2 === NoWork || p2 > p1) ? p1 : p2;
};
56 changes: 38 additions & 18 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var {
YieldComponent,
Fragment,
} = ReactTypeOfWork;
var {NoWork} = require('ReactPriorityLevel');
var {NoWork, OffscreenPriority} = require('ReactPriorityLevel');
var {
PerformedWork,
Placement,
Expand All @@ -76,7 +76,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
) {
const {shouldSetTextContent} = config;
const {
shouldSetTextContent,
useSyncScheduling,
shouldDeprioritizeSubtree,
} = config;

const {pushHostContext, pushHostContainer} = hostContext;

Expand Down Expand Up @@ -375,28 +379,29 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}

function updateHostComponent(current, workInProgress) {
function updateHostComponent(current, workInProgress, renderPriority) {
pushHostContext(workInProgress);

if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}

let nextProps = workInProgress.pendingProps;
const type = workInProgress.type;
const prevProps = current !== null ? current.memoizedProps : null;
const memoizedProps = workInProgress.memoizedProps;
let nextProps = workInProgress.pendingProps;
if (nextProps === null) {
nextProps = memoizedProps;
invariant(
nextProps !== null,
'We should always have pending or current props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
const prevProps = current !== null ? current.memoizedProps : null;

if (hasContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
if (nextProps === null) {
nextProps = memoizedProps;
invariant(
nextProps !== null,
'We should always have pending or current props. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
} else if (nextProps === null || memoizedProps === nextProps) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
Expand All @@ -418,6 +423,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(

markRef(current, workInProgress);

// Check the host config to see if the children are offscreen/hidden.
if (
renderPriority !== OffscreenPriority &&
!useSyncScheduling &&
shouldDeprioritizeSubtree(type, nextProps)
) {
// Down-prioritize the children.
workInProgress.pendingWorkPriority = OffscreenPriority;
// Bailout and come back to this fiber later at OffscreenPriority.
return null;
}

reconcileChildren(current, workInProgress, nextChildren);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
Expand Down Expand Up @@ -686,10 +703,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return null;
}

// TODO: Delete memoizeProps/State and move to reconcile/bailout instead
function memoizeProps(workInProgress: Fiber, nextProps: any) {
workInProgress.memoizedProps = nextProps;
// Reset the pending props
workInProgress.pendingProps = null;
}

function memoizeState(workInProgress: Fiber, nextState: any) {
Expand Down Expand Up @@ -728,7 +744,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
case HostRoot:
return updateHostRoot(current, workInProgress, priorityLevel);
case HostComponent:
return updateHostComponent(current, workInProgress);
return updateHostComponent(current, workInProgress, priorityLevel);
case HostText:
return updateHostText(current, workInProgress);
case CoroutineHandlerPhase:
Expand Down Expand Up @@ -793,13 +809,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(

// 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;
workInProgress.memoizedProps = instance.props;
workInProgress.memoizedState = instance.state;
workInProgress.pendingProps = null;
}

return workInProgress.child;
Expand Down
19 changes: 17 additions & 2 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import type {ReactCoroutine} from 'ReactTypes';
import type {Fiber} from 'ReactFiber';
import type {PriorityLevel} from 'ReactPriorityLevel';
import type {HostContext} from 'ReactFiberHostContext';
import type {HydrationContext} from 'ReactFiberHydrationContext';
import type {FiberRoot} from 'ReactFiberRoot';
Expand All @@ -23,6 +24,7 @@ var {reconcileChildFibers} = require('ReactChildFiber');
var {popContextProvider} = require('ReactFiberContext');
var ReactTypeOfWork = require('ReactTypeOfWork');
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
var ReactPriorityLevel = require('ReactPriorityLevel');
var {
IndeterminateComponent,
FunctionalComponent,
Expand All @@ -37,6 +39,7 @@ var {
Fragment,
} = ReactTypeOfWork;
var {Placement, Ref, Update} = ReactTypeOfSideEffect;
var {OffscreenPriority} = ReactPriorityLevel;

if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
Expand Down Expand Up @@ -181,11 +184,24 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderPriority: PriorityLevel,
): Fiber | null {
if (__DEV__) {
ReactDebugCurrentFiber.current = workInProgress;
}

// Get the latest props.
let newProps = workInProgress.pendingProps;
if (newProps === null) {
newProps = workInProgress.memoizedProps;
} else if (
workInProgress.pendingWorkPriority !== OffscreenPriority ||
renderPriority === OffscreenPriority
) {
// Reset the pending props, unless this was a down-prioritization.
workInProgress.pendingProps = null;
}

switch (workInProgress.tag) {
case FunctionalComponent:
return null;
Expand Down Expand Up @@ -216,7 +232,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
const newProps = workInProgress.memoizedProps;
if (current !== null && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
Expand Down Expand Up @@ -311,7 +326,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return null;
}
case HostText: {
let newText = workInProgress.memoizedProps;
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
Expand Down
26 changes: 16 additions & 10 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var ReactFiberHydrationContext = require('ReactFiberHydrationContext');
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
var getComponentName = require('getComponentName');

var {createWorkInProgress} = require('ReactFiber');
var {createWorkInProgress, largerPriority} = require('ReactFiber');
var {onCommitRoot} = require('ReactFiberDevToolsHook');

var {
Expand Down Expand Up @@ -551,7 +551,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
priorityContext = previousPriorityContext;
}

function resetWorkPriority(workInProgress: Fiber) {
function resetWorkPriority(
workInProgress: Fiber,
renderPriority: PriorityLevel,
) {
if (
workInProgress.pendingWorkPriority !== NoWork &&
workInProgress.pendingWorkPriority > renderPriority
) {
// This was a down-prioritization. Don't bubble priority from children.
return;
}

// Check for pending update priority.
let newPriority = getUpdatePriority(workInProgress);

Expand All @@ -560,12 +571,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
let child = workInProgress.child;
while (child !== null) {
// Ensure that remaining work priority bubbles up.
if (
child.pendingWorkPriority !== NoWork &&
(newPriority === NoWork || newPriority > child.pendingWorkPriority)
) {
newPriority = child.pendingWorkPriority;
}
newPriority = largerPriority(newPriority, child.pendingWorkPriority);
child = child.sibling;
}
workInProgress.pendingWorkPriority = newPriority;
Expand All @@ -578,12 +584,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
const next = completeWork(current, workInProgress);
const next = completeWork(current, workInProgress, nextPriorityLevel);

const returnFiber = workInProgress.return;
const siblingFiber = workInProgress.sibling;

resetWorkPriority(workInProgress);
resetWorkPriority(workInProgress, nextPriorityLevel);

if (next !== null) {
if (__DEV__) {
Expand Down
6 changes: 3 additions & 3 deletions src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ describe('ReactIncremental', () => {
expect(inst.state).toEqual({text: 'bar', text2: 'baz'});
});

xit('can deprioritize unfinished work and resume it later', () => {
it('can deprioritize unfinished work and resume it later', () => {
var ops = [];

function Bar(props) {
Expand Down Expand Up @@ -296,7 +296,7 @@ describe('ReactIncremental', () => {
expect(ops).toEqual(['Middle', 'Middle']);
});

xit('can deprioritize a tree from without dropping work', () => {
it('can deprioritize a tree from without dropping work', () => {
var ops = [];

function Bar(props) {
Expand Down Expand Up @@ -1962,7 +1962,7 @@ describe('ReactIncremental', () => {
]);
});

xit('provides context when reusing work', () => {
it('provides context when reusing work', () => {
var ops = [];

class Intl extends React.Component {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ describe('ReactDebugFiberPerf', () => {
expect(getFlameChart()).toMatchSnapshot();
});

xit('measures deprioritized work', () => {
it('measures deprioritized work', () => {
addComment('Flush the parent');
ReactNoop.syncUpdates(() => {
ReactNoop.render(
Expand Down
Loading

0 comments on commit 4b9775e

Please sign in to comment.