Skip to content

Commit 7087d18

Browse files
committed
Ensure that priority is not dropped from a progressed subtree
Must bubble up priority from a progressed subtree that was preempted by a high priority update. We know this is the case if the progressed priority is lower than the priority at which we're reconciling.
1 parent 2384a66 commit 7087d18

File tree

4 files changed

+115
-11
lines changed

4 files changed

+115
-11
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
12361236
* updates a child even though the old props is empty
12371237
* can defer side-effects and resume them later on
12381238
* can defer side-effects and reuse them later - complex
1239+
* does not drop priority from a progressed subtree
12391240
* deprioritizes setStates that happens within a deprioritized tree
12401241
* calls callback after update is flushed
12411242
* calls setState callback even if component bails out

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
629629
if (workInProgress.progressedPriority === priorityLevel) {
630630
// If we have progressed work on this priority level already, we can
631631
// proceed with that as the child.
632+
workInProgress.pendingWorkPriority = priorityLevel;
632633
workInProgress.child = workInProgress.progressedChild;
633634
}
634635

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,22 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
7272
popHostContainer,
7373
} = hostContext;
7474

75-
function resetWorkPriority(workInProgress : Fiber) {
76-
let newPriority = NoWork;
75+
function resetWorkPriority(workInProgress : Fiber, priorityLevel : PriorityLevel) {
76+
// priorityLevel is the level we're currently reconciling at. It's called
77+
// nextPriorityLevel in the scheduler. Can't think of a name that's
78+
// not confusing.
79+
80+
// If the progressedPriority is less than the priority we're currently
81+
// reconciling at, this was a bailout. Set the work priority to the
82+
// progressed priority, otherwise reset it to NoWork.
83+
let newPriority;
84+
if (workInProgress.progressedPriority === NoWork ||
85+
(workInProgress.progressedPriority > priorityLevel) && priorityLevel !== NoWork) {
86+
newPriority = workInProgress.progressedPriority;
87+
} else {
88+
newPriority = NoWork;
89+
}
90+
7791
// progressedChild is going to be the child set with the highest priority.
7892
// Either it is the same as child, or it just bailed out because it choose
7993
// not to do the work.
@@ -196,12 +210,12 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
196210

197211
switch (workInProgress.tag) {
198212
case FunctionalComponent:
199-
resetWorkPriority(workInProgress);
213+
resetWorkPriority(workInProgress, priorityLevel);
200214
return null;
201215
case ClassComponent: {
202216
// We are leaving this subtree, so pop context if any.
203217
popContextProvider(workInProgress);
204-
resetWorkPriority(workInProgress);
218+
resetWorkPriority(workInProgress, priorityLevel);
205219
return null;
206220
}
207221
case HostRoot: {
@@ -211,7 +225,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
211225
fiberRoot.context = fiberRoot.pendingContext;
212226
fiberRoot.pendingContext = null;
213227
}
214-
resetWorkPriority(workInProgress);
228+
resetWorkPriority(workInProgress, priorityLevel);
215229
return null;
216230
}
217231
case HostComponent:
@@ -278,7 +292,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
278292
// offscreen priority, there's remaining work in the subtree.
279293
workInProgress.pendingWorkPriority = OffscreenPriority;
280294
} else {
281-
resetWorkPriority(workInProgress);
295+
resetWorkPriority(workInProgress, priorityLevel);
282296
}
283297
return null;
284298
case HostText:
@@ -304,29 +318,29 @@ module.exports = function<T, P, I, TI, PI, C, CX>(
304318
const textInstance = createTextInstance(newText, rootContainerInstance, currentHostContext, workInProgress);
305319
workInProgress.stateNode = textInstance;
306320
}
307-
resetWorkPriority(workInProgress);
321+
resetWorkPriority(workInProgress, priorityLevel);
308322
return null;
309323
case CoroutineComponent: {
310324
const next = moveCoroutineToHandlerPhase(current, workInProgress);
311-
resetWorkPriority(workInProgress);
325+
resetWorkPriority(workInProgress, priorityLevel);
312326
return next;
313327
}
314328
case CoroutineHandlerPhase:
315329
// Reset the tag to now be a first phase coroutine.
316330
workInProgress.tag = CoroutineComponent;
317-
resetWorkPriority(workInProgress);
331+
resetWorkPriority(workInProgress, priorityLevel);
318332
return null;
319333
case YieldComponent:
320334
// Does nothing.
321335
return null;
322336
case Fragment:
323-
resetWorkPriority(workInProgress);
337+
resetWorkPriority(workInProgress, priorityLevel);
324338
return null;
325339
case HostPortal:
326340
// TODO: Only mark this as an update if we have any pending callbacks.
327341
markUpdate(workInProgress);
328342
popHostContainer(workInProgress);
329-
resetWorkPriority(workInProgress);
343+
resetWorkPriority(workInProgress, priorityLevel);
330344
return null;
331345

332346
// Error cases

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,94 @@ describe('ReactIncrementalSideEffects', () => {
756756
expect(ops).toEqual(['Bar', 'Baz', 'Bar', 'Bar']);
757757
});
758758

759+
it('does not drop priority from a progressed subtree', () => {
760+
let ops = [];
761+
let lowPri;
762+
let highPri;
763+
764+
function LowPriDidComplete() {
765+
ops.push('LowPriDidComplete');
766+
// Because this is terminal, beginning work on LowPriDidComplete implies
767+
// that LowPri will be completed before the scheduler yields.
768+
return null;
769+
}
770+
771+
class LowPri extends React.Component {
772+
state = { step: 0 };
773+
render() {
774+
ops.push('LowPri');
775+
lowPri = this;
776+
return [
777+
<span prop={this.state.step} />,
778+
<LowPriDidComplete />,
779+
];
780+
}
781+
}
782+
783+
function LowPriSibling() {
784+
ops.push('LowPriSibling');
785+
return null;
786+
}
787+
788+
class HighPri extends React.Component {
789+
state = { step: 0 };
790+
render() {
791+
ops.push('HighPri');
792+
highPri = this;
793+
return <span prop={this.state.step} />;
794+
}
795+
}
796+
797+
function App() {
798+
ops.push('App');
799+
return [
800+
<div>
801+
<LowPri />
802+
<LowPriSibling />
803+
</div>,
804+
<div><HighPri /></div>,
805+
];
806+
}
807+
808+
ReactNoop.render(<App />);
809+
ReactNoop.flush();
810+
expect(ReactNoop.getChildren()).toEqual([
811+
div(span(0)),
812+
div(span(0)),
813+
]);
814+
ops = [];
815+
816+
lowPri.setState({ step: 1 });
817+
// Do just enough work to begin LowPri
818+
ReactNoop.flushDeferredPri(30);
819+
expect(ops).toEqual(['LowPri']);
820+
// Now we'll do one more tick of work to complete LowPri. Because LowPri
821+
// has a sibling, the parent div of LowPri has not yet completed.
822+
ReactNoop.flushDeferredPri(10);
823+
expect(ops).toEqual(['LowPri', 'LowPriDidComplete']);
824+
expect(ReactNoop.getChildren()).toEqual([
825+
div(span(0)), // Complete, but not yet updated
826+
div(span(0)),
827+
]);
828+
ops = [];
829+
830+
// Interrupt with high pri update
831+
ReactNoop.performAnimationWork(() => highPri.setState({ step: 1 }));
832+
ReactNoop.flushAnimationPri();
833+
expect(ops).toEqual(['HighPri']);
834+
expect(ReactNoop.getChildren()).toEqual([
835+
div(span(0)), // Completed, but not yet updated
836+
div(span(1)),
837+
]);
838+
ops = [];
839+
840+
ReactNoop.flush();
841+
expect(ReactNoop.getChildren()).toEqual([
842+
div(span(1)),
843+
div(span(1)),
844+
]);
845+
});
846+
759847
it('deprioritizes setStates that happens within a deprioritized tree', () => {
760848
var ops = [];
761849

0 commit comments

Comments
 (0)