diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 6f438e9e23..f277e317d9 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1150,6 +1150,8 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js * handles interleaved deferred and animation work * schedules sync updates when inside componentDidMount/Update * can opt-in to deferred/animation scheduling inside componentDidMount/Update +* performs Task work even after time runs out +* does not perform animation work after time runs out src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js * can update child nodes of a host instance diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 2dd69a621d..c6ee0d5dc0 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -548,7 +548,9 @@ module.exports = function(config : HostConfig) { nextUnitOfWork = findNextUnitOfWork(); } - if (deadline) { + // If there's a deadline, and we're not performing Task work, perform work + // using this loop that checks the deadline on every iteration. + if (deadline && priorityLevel > TaskPriority) { // The deferred work loop will run until there's no time left in // the current frame. while (nextUnitOfWork && !deadlineHasExpired) { @@ -575,13 +577,9 @@ module.exports = function(config : HostConfig) { } } } else { - // The non-deferred work loop will run until there's no more work - // at the given priority level - if (priorityLevel >= HighPriority) { - throw new Error( - 'Deferred work should only be performed using deferredWorkLoop' - ); - } + // If there's no deadline, or if we're performing Task work, use this loop + // that doesn't check how much time is remaining. It will keep running + // until we run out of work at this priority level. while (nextUnitOfWork && nextPriorityLevel !== NoWork && nextPriorityLevel <= priorityLevel) { @@ -610,6 +608,12 @@ module.exports = function(config : HostConfig) { // catching an error. It also lets us flush Task work at the end of a // deferred batch. while (priorityLevel !== NoWork) { + if (priorityLevel >= HighPriority && !deadline) { + throw new Error( + 'Cannot perform deferred work without a deadline.' + ); + } + // Before starting any work, check to see if there are any pending // commits from the previous frame. An exception is if we're flushing // Task work in a deferred batch and the pending commit does not diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index f69a013d49..9bf334b208 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -410,7 +410,7 @@ describe('ReactIncremental', () => { // Make a quick update which will create a low pri tree on top of the // already low pri tree. ReactNoop.render(); - ReactNoop.flushDeferredPri(15 + 5); + ReactNoop.flushDeferredPri(15); expect(ops).toEqual(['Foo']); diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js index 155c2b7eaf..3ee13eaa63 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js @@ -93,7 +93,7 @@ describe('ReactIncrementalErrorHandling', () => { expect(ReactNoop.getChildren()).toEqual([]); ops.length = 0; - ReactNoop.flushDeferredPri(25 + 10); + ReactNoop.flushDeferredPri(30); expect(ops).toEqual([ 'BrokenRender', 'ErrorBoundary unstable_handleError', diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js index 4ef5c36997..f1bc165d2f 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js @@ -341,7 +341,7 @@ describe('ReactIncrementalScheduling', () => { expect(ReactNoop.getChildren('b')).toEqual([]); expect(ReactNoop.getChildren('c')).toEqual(null); // Then the second one gets processed - ReactNoop.flushDeferredPri(15 + 5 + 5); + ReactNoop.flushDeferredPri(15 + 5); expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]); expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]); expect(ReactNoop.getChildren('c')).toEqual(null); @@ -483,7 +483,7 @@ describe('ReactIncrementalScheduling', () => { ReactNoop.render(); - ReactNoop.flushDeferredPri(5 + 20 + 20); + ReactNoop.flushDeferredPri(20 + 5); expect(ops).toEqual([ 'render: 0', 'componentDidMount (before setState): 0', @@ -496,7 +496,7 @@ describe('ReactIncrementalScheduling', () => { ops = []; instance.setState({ tick: 2 }); - ReactNoop.flushDeferredPri(5 + 20 + 20); + ReactNoop.flushDeferredPri(20 + 5); expect(ops).toEqual([ 'render: 2', @@ -587,4 +587,63 @@ describe('ReactIncrementalScheduling', () => { 'componentDidUpdate: 3', ]); }); + + it('performs Task work even after time runs out', () => { + class Foo extends React.Component { + state = { step: 1 }; + componentDidMount() { + this.setState({ step: 2 }, () => { + this.setState({ step: 3 }, () => { + this.setState({ step: 4 }, () => { + this.setState({ step: 5 }); + }); + }); + }); + } + render() { + return ; + } + } + ReactNoop.render(); + // This should be just enough to complete all the work, but not enough to + // commit it. + ReactNoop.flushDeferredPri(20); + expect(ReactNoop.getChildren()).toEqual([]); + + // Do one more unit of work. + ReactNoop.flushDeferredPri(10); + // The updates should all be flushed with Task priority + expect(ReactNoop.getChildren()).toEqual([span(5)]); + }); + + it('does not perform animation work after time runs out', () => { + class Foo extends React.Component { + state = { step: 1 }; + componentDidMount() { + ReactNoop.performAnimationWork(() => { + this.setState({ step: 2 }, () => { + this.setState({ step: 3 }, () => { + this.setState({ step: 4 }, () => { + this.setState({ step: 5 }); + }); + }); + }); + }); + } + render() { + return ; + } + } + ReactNoop.render(); + // This should be just enough to complete all the work, but not enough to + // commit it. + ReactNoop.flushDeferredPri(20); + expect(ReactNoop.getChildren()).toEqual([]); + + // Do one more unit of work. + ReactNoop.flushDeferredPri(10); + // None of the updates should be flushed because they only have + // animation priority. + expect(ReactNoop.getChildren()).toEqual([span(1)]); + }); });