Skip to content

Commit 19eca82

Browse files
committed
Down-prioritize children of hidden host components
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.
1 parent 483762f commit 19eca82

File tree

9 files changed

+171
-121
lines changed

9 files changed

+171
-121
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
20232023
* updates a previous render
20242024
* can cancel partially rendered work and restart
20252025
* should call callbacks even if updates are aborted
2026+
* can deprioritize unfinished work and resume it later
2027+
* can deprioritize a tree from without dropping work
20262028
* memoizes work even if shouldComponentUpdate returns false
20272029
* can update in the middle of a tree using setState
20282030
* can queue multiple state updates
@@ -2035,6 +2037,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
20352037
* can handle if setState callback throws
20362038
* merges and masks context
20372039
* does not leak own context into context provider
2040+
* provides context when reusing work
20382041
* reads context when setState is below the provider
20392042
* reads context when setState is above the provider
20402043
* maintains the correct context when providers bail out due to low priority
@@ -2078,6 +2081,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js
20782081
* warns on cascading renders from top-level render
20792082
* does not treat setState from cWM or cWRP as cascading
20802083
* captures all lifecycles
2084+
* measures deprioritized work
20812085
* measures deferred work in chunks
20822086
* recovers from fatal errors
20832087
* recovers from caught errors
@@ -2116,6 +2120,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
21162120
* can reuse side-effects after being preempted, if shouldComponentUpdate is false
21172121
* can update a completed tree before it has a chance to commit
21182122
* updates a child even though the old props is empty
2123+
* deprioritizes setStates that happens within a deprioritized tree
21192124
* calls callback after update is flushed
21202125
* calls setState callback even if component bails out
21212126
* calls componentWillUnmount after a deletion, even if nested

src/renderers/shared/fiber/ReactChildFiber.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ exports.cloneChildFibers = function(
14631463
let currentChild = workInProgress.child;
14641464
let newChild = createWorkInProgress(currentChild, renderPriority);
14651465
// TODO: Pass this as an argument, since it's easy to forget.
1466-
newChild.pendingProps = null;
1466+
newChild.pendingProps = currentChild.pendingProps;
14671467
workInProgress.child = newChild;
14681468

14691469
newChild.return = workInProgress;
@@ -1473,7 +1473,7 @@ exports.cloneChildFibers = function(
14731473
currentChild,
14741474
renderPriority,
14751475
);
1476-
newChild.pendingProps = null;
1476+
newChild.pendingProps = currentChild.pendingProps;
14771477
newChild.return = workInProgress;
14781478
}
14791479
newChild.sibling = null;

src/renderers/shared/fiber/ReactFiber.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,10 @@ exports.createFiberFromPortal = function(
451451
};
452452
return fiber;
453453
};
454+
455+
exports.largerPriority = function(
456+
p1: PriorityLevel,
457+
p2: PriorityLevel,
458+
): PriorityLevel {
459+
return p1 !== NoWork && (p2 === NoWork || p2 > p1) ? p1 : p2;
460+
};

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ var {
4949
YieldComponent,
5050
Fragment,
5151
} = ReactTypeOfWork;
52-
var {NoWork} = require('ReactPriorityLevel');
52+
var {NoWork, OffscreenPriority} = require('ReactPriorityLevel');
5353
var {
5454
PerformedWork,
5555
Placement,
@@ -76,7 +76,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
7676
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
7777
getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
7878
) {
79-
const {shouldSetTextContent} = config;
79+
const {
80+
shouldSetTextContent,
81+
useSyncScheduling,
82+
shouldDeprioritizeSubtree,
83+
} = config;
8084

8185
const {pushHostContext, pushHostContainer} = hostContext;
8286

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

378-
function updateHostComponent(current, workInProgress) {
382+
function updateHostComponent(current, workInProgress, renderPriority) {
379383
pushHostContext(workInProgress);
380384

381385
if (current === null) {
382386
tryToClaimNextHydratableInstance(workInProgress);
383387
}
384388

385-
let nextProps = workInProgress.pendingProps;
386389
const type = workInProgress.type;
387-
const prevProps = current !== null ? current.memoizedProps : null;
388390
const memoizedProps = workInProgress.memoizedProps;
391+
let nextProps = workInProgress.pendingProps;
392+
if (nextProps === null) {
393+
nextProps = memoizedProps;
394+
invariant(
395+
nextProps !== null,
396+
'We should always have pending or current props. This error is ' +
397+
'likely caused by a bug in React. Please file an issue.',
398+
);
399+
}
400+
const prevProps = current !== null ? current.memoizedProps : null;
401+
389402
if (hasContextChanged()) {
390403
// Normally we can bail out on props equality but if context has changed
391404
// we don't do the bailout and we have to reuse existing props instead.
392-
if (nextProps === null) {
393-
nextProps = memoizedProps;
394-
invariant(
395-
nextProps !== null,
396-
'We should always have pending or current props. This error is ' +
397-
'likely caused by a bug in React. Please file an issue.',
398-
);
399-
}
400405
} else if (nextProps === null || memoizedProps === nextProps) {
401406
return bailoutOnAlreadyFinishedWork(current, workInProgress);
402407
}
@@ -418,6 +423,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
418423

419424
markRef(current, workInProgress);
420425

426+
// Check the host config to see if the children are offscreen/hidden.
427+
if (
428+
renderPriority !== OffscreenPriority &&
429+
!useSyncScheduling &&
430+
shouldDeprioritizeSubtree(type, nextProps)
431+
) {
432+
// Down-prioritize the children.
433+
workInProgress.pendingWorkPriority = OffscreenPriority;
434+
// Bailout and come back to this fiber later at OffscreenPriority.
435+
return null;
436+
}
437+
421438
reconcileChildren(current, workInProgress, nextChildren);
422439
memoizeProps(workInProgress, nextProps);
423440
return workInProgress.child;
@@ -686,10 +703,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
686703
return null;
687704
}
688705

706+
// TODO: Delete memoizeProps/State and move to reconcile/bailout instead
689707
function memoizeProps(workInProgress: Fiber, nextProps: any) {
690708
workInProgress.memoizedProps = nextProps;
691-
// Reset the pending props
692-
workInProgress.pendingProps = null;
693709
}
694710

695711
function memoizeState(workInProgress: Fiber, nextState: any) {
@@ -728,7 +744,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
728744
case HostRoot:
729745
return updateHostRoot(current, workInProgress, priorityLevel);
730746
case HostComponent:
731-
return updateHostComponent(current, workInProgress);
747+
return updateHostComponent(current, workInProgress, priorityLevel);
732748
case HostText:
733749
return updateHostText(current, workInProgress);
734750
case CoroutineHandlerPhase:
@@ -793,13 +809,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
793809

794810
// Unmount the current children as if the component rendered null
795811
const nextChildren = null;
796-
reconcileChildren(current, workInProgress, nextChildren);
812+
reconcileChildrenAtPriority(
813+
current,
814+
workInProgress,
815+
nextChildren,
816+
priorityLevel,
817+
);
797818

798819
if (workInProgress.tag === ClassComponent) {
799820
const instance = workInProgress.stateNode;
800821
workInProgress.memoizedProps = instance.props;
801822
workInProgress.memoizedState = instance.state;
802-
workInProgress.pendingProps = null;
803823
}
804824

805825
return workInProgress.child;

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import type {ReactCoroutine} from 'ReactTypes';
1616
import type {Fiber} from 'ReactFiber';
17+
import type {PriorityLevel} from 'ReactPriorityLevel';
1718
import type {HostContext} from 'ReactFiberHostContext';
1819
import type {HydrationContext} from 'ReactFiberHydrationContext';
1920
import type {FiberRoot} from 'ReactFiberRoot';
@@ -23,6 +24,7 @@ var {reconcileChildFibers} = require('ReactChildFiber');
2324
var {popContextProvider} = require('ReactFiberContext');
2425
var ReactTypeOfWork = require('ReactTypeOfWork');
2526
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
27+
var ReactPriorityLevel = require('ReactPriorityLevel');
2628
var {
2729
IndeterminateComponent,
2830
FunctionalComponent,
@@ -37,6 +39,7 @@ var {
3739
Fragment,
3840
} = ReactTypeOfWork;
3941
var {Placement, Ref, Update} = ReactTypeOfSideEffect;
42+
var {OffscreenPriority} = ReactPriorityLevel;
4043

4144
if (__DEV__) {
4245
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
@@ -181,11 +184,24 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
181184
function completeWork(
182185
current: Fiber | null,
183186
workInProgress: Fiber,
187+
renderPriority: PriorityLevel,
184188
): Fiber | null {
185189
if (__DEV__) {
186190
ReactDebugCurrentFiber.current = workInProgress;
187191
}
188192

193+
// Get the latest props.
194+
let newProps = workInProgress.pendingProps;
195+
if (newProps === null) {
196+
newProps = workInProgress.memoizedProps;
197+
} else if (
198+
workInProgress.pendingWorkPriority !== OffscreenPriority ||
199+
renderPriority === OffscreenPriority
200+
) {
201+
// Reset the pending props, unless this was a down-prioritization.
202+
workInProgress.pendingProps = null;
203+
}
204+
189205
switch (workInProgress.tag) {
190206
case FunctionalComponent:
191207
return null;
@@ -216,7 +232,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
216232
popHostContext(workInProgress);
217233
const rootContainerInstance = getRootHostContainer();
218234
const type = workInProgress.type;
219-
const newProps = workInProgress.memoizedProps;
220235
if (current !== null && workInProgress.stateNode != null) {
221236
// If we have an alternate, that means this is an update and we need to
222237
// schedule a side-effect to do the updates.
@@ -311,7 +326,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
311326
return null;
312327
}
313328
case HostText: {
314-
let newText = workInProgress.memoizedProps;
329+
let newText = newProps;
315330
if (current && workInProgress.stateNode != null) {
316331
const oldText = current.memoizedProps;
317332
// If we have an alternate, that means this is an update and we need

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var ReactFiberHydrationContext = require('ReactFiberHydrationContext');
4848
var {ReactCurrentOwner} = require('ReactGlobalSharedState');
4949
var getComponentName = require('getComponentName');
5050

51-
var {createWorkInProgress} = require('ReactFiber');
51+
var {createWorkInProgress, largerPriority} = require('ReactFiber');
5252
var {onCommitRoot} = require('ReactFiberDevToolsHook');
5353

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

554-
function resetWorkPriority(workInProgress: Fiber) {
554+
function resetWorkPriority(
555+
workInProgress: Fiber,
556+
renderPriority: PriorityLevel,
557+
) {
558+
if (
559+
workInProgress.pendingWorkPriority !== NoWork &&
560+
workInProgress.pendingWorkPriority > renderPriority
561+
) {
562+
// This was a down-prioritization. Don't bubble priority from children.
563+
return;
564+
}
565+
555566
// Check for pending update priority.
556567
let newPriority = getUpdatePriority(workInProgress);
557568

@@ -560,12 +571,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
560571
let child = workInProgress.child;
561572
while (child !== null) {
562573
// Ensure that remaining work priority bubbles up.
563-
if (
564-
child.pendingWorkPriority !== NoWork &&
565-
(newPriority === NoWork || newPriority > child.pendingWorkPriority)
566-
) {
567-
newPriority = child.pendingWorkPriority;
568-
}
574+
newPriority = largerPriority(newPriority, child.pendingWorkPriority);
569575
child = child.sibling;
570576
}
571577
workInProgress.pendingWorkPriority = newPriority;
@@ -578,12 +584,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
578584
// means that we don't need an additional field on the work in
579585
// progress.
580586
const current = workInProgress.alternate;
581-
const next = completeWork(current, workInProgress);
587+
const next = completeWork(current, workInProgress, nextPriorityLevel);
582588

583589
const returnFiber = workInProgress.return;
584590
const siblingFiber = workInProgress.sibling;
585591

586-
resetWorkPriority(workInProgress);
592+
resetWorkPriority(workInProgress, nextPriorityLevel);
587593

588594
if (next !== null) {
589595
if (__DEV__) {

src/renderers/shared/fiber/__tests__/ReactIncremental-test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ describe('ReactIncremental', () => {
244244
expect(inst.state).toEqual({text: 'bar', text2: 'baz'});
245245
});
246246

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

250250
function Bar(props) {
@@ -296,7 +296,7 @@ describe('ReactIncremental', () => {
296296
expect(ops).toEqual(['Middle', 'Middle']);
297297
});
298298

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

302302
function Bar(props) {
@@ -1962,7 +1962,7 @@ describe('ReactIncremental', () => {
19621962
]);
19631963
});
19641964

1965-
xit('provides context when reusing work', () => {
1965+
it('provides context when reusing work', () => {
19661966
var ops = [];
19671967

19681968
class Intl extends React.Component {

src/renderers/shared/fiber/__tests__/ReactIncrementalPerf-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ describe('ReactDebugFiberPerf', () => {
280280
expect(getFlameChart()).toMatchSnapshot();
281281
});
282282

283-
xit('measures deprioritized work', () => {
283+
it('measures deprioritized work', () => {
284284
addComment('Flush the parent');
285285
ReactNoop.syncUpdates(() => {
286286
ReactNoop.render(

0 commit comments

Comments
 (0)