react-reconciler 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /** @license React v16.1.1
1
+ /** @license React v16.2.0
2
2
  * react-reconciler.development.js
3
3
  *
4
4
  * Copyright (c) 2013-present, Facebook, Inc.
@@ -35,8 +35,6 @@ var shallowEqual = require('fbjs/lib/shallowEqual');
35
35
 
36
36
  var enableAsyncSubtreeAPI = true;
37
37
 
38
- // Exports React.Fragment
39
- var enableReactFragment = false;
40
38
  // Exports ReactDOM.createRoot
41
39
 
42
40
  var enableUserTimingAPI = true;
@@ -48,6 +46,9 @@ var enableNoopReconciler = false;
48
46
  // Experimental persistent mode (CS):
49
47
  var enablePersistentReconciler = false;
50
48
 
49
+ // Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
50
+ var debugRenderPhaseSideEffects = false;
51
+
51
52
  // Only used in www builds.
52
53
 
53
54
  /**
@@ -1135,6 +1136,10 @@ function msToExpirationTime(ms) {
1135
1136
  return (ms / UNIT_SIZE | 0) + MAGIC_NUMBER_OFFSET;
1136
1137
  }
1137
1138
 
1139
+ function expirationTimeToMs(expirationTime) {
1140
+ return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
1141
+ }
1142
+
1138
1143
  function ceiling(num, precision) {
1139
1144
  return ((num / precision | 0) + 1) * precision;
1140
1145
  }
@@ -1151,8 +1156,7 @@ var AsyncUpdates = 1;
1151
1156
  try {
1152
1157
  var nonExtensibleObject = Object.preventExtensions({});
1153
1158
  /* eslint-disable no-new */
1154
- new Map([[nonExtensibleObject, null]]);
1155
- new Set([nonExtensibleObject]);
1159
+
1156
1160
  /* eslint-enable no-new */
1157
1161
  } catch (e) {
1158
1162
  // TODO: Consider warning about bad polyfills
@@ -1795,6 +1799,12 @@ function getStateFromUpdate(update, instance, prevState, props) {
1795
1799
  var partialState = update.partialState;
1796
1800
  if (typeof partialState === 'function') {
1797
1801
  var updateFn = partialState;
1802
+
1803
+ // Invoke setState callback an extra time to help detect side-effects.
1804
+ if (debugRenderPhaseSideEffects) {
1805
+ updateFn.call(instance, prevState, props);
1806
+ }
1807
+
1798
1808
  return updateFn.call(instance, prevState, props);
1799
1809
  } else {
1800
1810
  return partialState;
@@ -2040,6 +2050,11 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2040
2050
  var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
2041
2051
  stopPhaseTimer();
2042
2052
 
2053
+ // Simulate an async bailout/interruption by invoking lifecycle twice.
2054
+ if (debugRenderPhaseSideEffects) {
2055
+ instance.shouldComponentUpdate(newProps, newState, newContext);
2056
+ }
2057
+
2043
2058
  {
2044
2059
  warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentName(workInProgress) || 'Unknown');
2045
2060
  }
@@ -2096,10 +2111,10 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2096
2111
 
2097
2112
  var state = instance.state;
2098
2113
  if (state && (typeof state !== 'object' || isArray(state))) {
2099
- invariant(false, '%s.state: must be set to an object or null', getComponentName(workInProgress));
2114
+ warning(false, '%s.state: must be set to an object or null', getComponentName(workInProgress));
2100
2115
  }
2101
2116
  if (typeof instance.getChildContext === 'function') {
2102
- !(typeof workInProgress.type.childContextTypes === 'object') ? invariant(false, '%s.getChildContext(): childContextTypes must be defined in order to use getChildContext().', getComponentName(workInProgress)) : void 0;
2117
+ warning(typeof workInProgress.type.childContextTypes === 'object', '%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', getComponentName(workInProgress));
2103
2118
  }
2104
2119
  }
2105
2120
 
@@ -2139,9 +2154,13 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2139
2154
  startPhaseTimer(workInProgress, 'componentWillMount');
2140
2155
  var oldState = instance.state;
2141
2156
  instance.componentWillMount();
2142
-
2143
2157
  stopPhaseTimer();
2144
2158
 
2159
+ // Simulate an async bailout/interruption by invoking lifecycle twice.
2160
+ if (debugRenderPhaseSideEffects) {
2161
+ instance.componentWillMount();
2162
+ }
2163
+
2145
2164
  if (oldState !== instance.state) {
2146
2165
  {
2147
2166
  warning(false, '%s.componentWillMount(): Assigning directly to this.state is ' + "deprecated (except inside a component's " + 'constructor). Use setState instead.', getComponentName(workInProgress));
@@ -2156,6 +2175,11 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2156
2175
  instance.componentWillReceiveProps(newProps, newContext);
2157
2176
  stopPhaseTimer();
2158
2177
 
2178
+ // Simulate an async bailout/interruption by invoking lifecycle twice.
2179
+ if (debugRenderPhaseSideEffects) {
2180
+ instance.componentWillReceiveProps(newProps, newContext);
2181
+ }
2182
+
2159
2183
  if (instance.state !== oldState) {
2160
2184
  {
2161
2185
  var componentName = getComponentName(workInProgress) || 'Component';
@@ -2365,6 +2389,11 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2365
2389
  startPhaseTimer(workInProgress, 'componentWillUpdate');
2366
2390
  instance.componentWillUpdate(newProps, newState, newContext);
2367
2391
  stopPhaseTimer();
2392
+
2393
+ // Simulate an async bailout/interruption by invoking lifecycle twice.
2394
+ if (debugRenderPhaseSideEffects) {
2395
+ instance.componentWillUpdate(newProps, newState, newContext);
2396
+ }
2368
2397
  }
2369
2398
  if (typeof instance.componentDidUpdate === 'function') {
2370
2399
  workInProgress.effectTag |= Update;
@@ -2402,9 +2431,29 @@ var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber
2402
2431
  };
2403
2432
  };
2404
2433
 
2405
- // The Symbol used to tag the special React types. If there is no native Symbol
2434
+ // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
2406
2435
  // nor polyfill, then a plain number is used for performance.
2407
- var REACT_PORTAL_TYPE = typeof Symbol === 'function' && Symbol['for'] && Symbol['for']('react.portal') || 0xeaca;
2436
+ var hasSymbol = typeof Symbol === 'function' && Symbol['for'];
2437
+
2438
+ var REACT_ELEMENT_TYPE = hasSymbol ? Symbol['for']('react.element') : 0xeac7;
2439
+ var REACT_CALL_TYPE = hasSymbol ? Symbol['for']('react.call') : 0xeac8;
2440
+ var REACT_RETURN_TYPE = hasSymbol ? Symbol['for']('react.return') : 0xeac9;
2441
+ var REACT_PORTAL_TYPE = hasSymbol ? Symbol['for']('react.portal') : 0xeaca;
2442
+ var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol['for']('react.fragment') : 0xeacb;
2443
+
2444
+ var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
2445
+ var FAUX_ITERATOR_SYMBOL = '@@iterator';
2446
+
2447
+ function getIteratorFn(maybeIterable) {
2448
+ if (maybeIterable === null || typeof maybeIterable === 'undefined') {
2449
+ return null;
2450
+ }
2451
+ var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
2452
+ if (typeof maybeIterator === 'function') {
2453
+ return maybeIterator;
2454
+ }
2455
+ return null;
2456
+ }
2408
2457
 
2409
2458
  var getCurrentFiberStackAddendum$1 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
2410
2459
 
@@ -2441,38 +2490,6 @@ var getCurrentFiberStackAddendum$1 = ReactDebugCurrentFiber.getCurrentFiberStack
2441
2490
 
2442
2491
  var isArray$1 = Array.isArray;
2443
2492
 
2444
- var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
2445
- var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
2446
-
2447
- // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
2448
- // nor polyfill, then a plain number is used for performance.
2449
- var REACT_ELEMENT_TYPE;
2450
- var REACT_CALL_TYPE;
2451
- var REACT_RETURN_TYPE;
2452
- var REACT_FRAGMENT_TYPE;
2453
- if (typeof Symbol === 'function' && Symbol['for']) {
2454
- REACT_ELEMENT_TYPE = Symbol['for']('react.element');
2455
- REACT_CALL_TYPE = Symbol['for']('react.call');
2456
- REACT_RETURN_TYPE = Symbol['for']('react.return');
2457
- REACT_FRAGMENT_TYPE = Symbol['for']('react.fragment');
2458
- } else {
2459
- REACT_ELEMENT_TYPE = 0xeac7;
2460
- REACT_CALL_TYPE = 0xeac8;
2461
- REACT_RETURN_TYPE = 0xeac9;
2462
- REACT_FRAGMENT_TYPE = 0xeacb;
2463
- }
2464
-
2465
- function getIteratorFn(maybeIterable) {
2466
- if (maybeIterable === null || typeof maybeIterable === 'undefined') {
2467
- return null;
2468
- }
2469
- var iteratorFn = ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
2470
- if (typeof iteratorFn === 'function') {
2471
- return iteratorFn;
2472
- }
2473
- return null;
2474
- }
2475
-
2476
2493
  function coerceRef(current, element) {
2477
2494
  var mixedRef = element.ref;
2478
2495
  if (mixedRef !== null && typeof mixedRef !== 'function') {
@@ -2533,21 +2550,12 @@ function warnOnFunctionType() {
2533
2550
  // to be able to optimize each path individually by branching early. This needs
2534
2551
  // a compiler or we can do it manually. Helpers that don't need this branching
2535
2552
  // live outside of this function.
2536
- function ChildReconciler(shouldClone, shouldTrackSideEffects) {
2553
+ function ChildReconciler(shouldTrackSideEffects) {
2537
2554
  function deleteChild(returnFiber, childToDelete) {
2538
2555
  if (!shouldTrackSideEffects) {
2539
2556
  // Noop.
2540
2557
  return;
2541
2558
  }
2542
- if (!shouldClone) {
2543
- // When we're reconciling in place we have a work in progress copy. We
2544
- // actually want the current copy. If there is no current copy, then we
2545
- // don't need to track deletion side-effects.
2546
- if (childToDelete.alternate === null) {
2547
- return;
2548
- }
2549
- childToDelete = childToDelete.alternate;
2550
- }
2551
2559
  // Deletions are added in reversed order so we add it to the front.
2552
2560
  // At this point, the return fiber's effect list is empty except for
2553
2561
  // deletions, so we can just append the deletion to the list. The remaining
@@ -2600,22 +2608,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
2600
2608
  function useFiber(fiber, pendingProps, expirationTime) {
2601
2609
  // We currently set sibling to null and index to 0 here because it is easy
2602
2610
  // to forget to do before returning it. E.g. for the single child case.
2603
- if (shouldClone) {
2604
- var clone = createWorkInProgress(fiber, pendingProps, expirationTime);
2605
- clone.index = 0;
2606
- clone.sibling = null;
2607
- return clone;
2608
- } else {
2609
- // We override the expiration time even if it is earlier, because if
2610
- // we're reconciling at a later time that means that this was
2611
- // down-prioritized.
2612
- fiber.expirationTime = expirationTime;
2613
- fiber.effectTag = NoEffect;
2614
- fiber.index = 0;
2615
- fiber.sibling = null;
2616
- fiber.pendingProps = pendingProps;
2617
- return fiber;
2618
- }
2611
+ var clone = createWorkInProgress(fiber, pendingProps, expirationTime);
2612
+ clone.index = 0;
2613
+ clone.sibling = null;
2614
+ return clone;
2619
2615
  }
2620
2616
 
2621
2617
  function placeChild(newFiber, lastPlacedIndex, newIndex) {
@@ -3412,7 +3408,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
3412
3408
  // Handle top level unkeyed fragments as if they were arrays.
3413
3409
  // This leads to an ambiguity between <>{[...]}</> and <>...</>.
3414
3410
  // We treat the ambiguous cases above the same.
3415
- if (enableReactFragment && typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null) {
3411
+ if (typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null) {
3416
3412
  newChild = newChild.props.children;
3417
3413
  }
3418
3414
 
@@ -3487,11 +3483,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
3487
3483
  return reconcileChildFibers;
3488
3484
  }
3489
3485
 
3490
- var reconcileChildFibers = ChildReconciler(true, true);
3491
-
3492
- var reconcileChildFibersInPlace = ChildReconciler(false, true);
3493
-
3494
- var mountChildFibersInPlace = ChildReconciler(false, false);
3486
+ var reconcileChildFibers = ChildReconciler(true);
3487
+ var mountChildFibers = ChildReconciler(false);
3495
3488
 
3496
3489
  function cloneChildFibers(current, workInProgress) {
3497
3490
  !(current === null || workInProgress.child === current.child) ? invariant(false, 'Resuming work not yet implemented.') : void 0;
@@ -3546,20 +3539,15 @@ var ReactFiberBeginWork = function (config, hostContext, hydrationContext, sched
3546
3539
  // won't update its child set by applying minimal side-effects. Instead,
3547
3540
  // we will add them all to the child before it gets rendered. That means
3548
3541
  // we can optimize this reconciliation pass by not tracking side-effects.
3549
- workInProgress.child = mountChildFibersInPlace(workInProgress, workInProgress.child, nextChildren, renderExpirationTime);
3550
- } else if (current.child === workInProgress.child) {
3542
+ workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
3543
+ } else {
3551
3544
  // If the current child is the same as the work in progress, it means that
3552
3545
  // we haven't yet started any work on these children. Therefore, we use
3553
3546
  // the clone algorithm to create a copy of all the current children.
3554
3547
 
3555
3548
  // If we had any progressed work already, that is invalid at this point so
3556
3549
  // let's throw it out.
3557
- workInProgress.child = reconcileChildFibers(workInProgress, workInProgress.child, nextChildren, renderExpirationTime);
3558
- } else {
3559
- // If, on the other hand, it is already using a clone, that means we've
3560
- // already begun some work on this tree and we can continue where we left
3561
- // off by reconciling against the existing children.
3562
- workInProgress.child = reconcileChildFibersInPlace(workInProgress, workInProgress.child, nextChildren, renderExpirationTime);
3550
+ workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderExpirationTime);
3563
3551
  }
3564
3552
  }
3565
3553
 
@@ -3669,6 +3657,9 @@ var ReactFiberBeginWork = function (config, hostContext, hydrationContext, sched
3669
3657
  {
3670
3658
  ReactDebugCurrentFiber.setCurrentPhase('render');
3671
3659
  nextChildren = instance.render();
3660
+ if (debugRenderPhaseSideEffects) {
3661
+ instance.render();
3662
+ }
3672
3663
  ReactDebugCurrentFiber.setCurrentPhase(null);
3673
3664
  }
3674
3665
  // React DevTools reads this flag.
@@ -3727,7 +3718,7 @@ var ReactFiberBeginWork = function (config, hostContext, hydrationContext, sched
3727
3718
  // Ensure that children mount into this root without tracking
3728
3719
  // side-effects. This ensures that we don't store Placement effects on
3729
3720
  // nodes that will be hydrated.
3730
- workInProgress.child = mountChildFibersInPlace(workInProgress, workInProgress.child, element, renderExpirationTime);
3721
+ workInProgress.child = mountChildFibers(workInProgress, null, element, renderExpirationTime);
3731
3722
  } else {
3732
3723
  // Otherwise reset hydration state in case we aborted and resumed another
3733
3724
  // root.
@@ -3894,11 +3885,9 @@ var ReactFiberBeginWork = function (config, hostContext, hydrationContext, sched
3894
3885
  // The following is a fork of reconcileChildrenAtExpirationTime but using
3895
3886
  // stateNode to store the child.
3896
3887
  if (current === null) {
3897
- workInProgress.stateNode = mountChildFibersInPlace(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
3898
- } else if (current.child === workInProgress.child) {
3899
- workInProgress.stateNode = reconcileChildFibers(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
3888
+ workInProgress.stateNode = mountChildFibers(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
3900
3889
  } else {
3901
- workInProgress.stateNode = reconcileChildFibersInPlace(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
3890
+ workInProgress.stateNode = reconcileChildFibers(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
3902
3891
  }
3903
3892
 
3904
3893
  memoizeProps(workInProgress, nextCall);
@@ -3927,7 +3916,7 @@ var ReactFiberBeginWork = function (config, hostContext, hydrationContext, sched
3927
3916
  // flow doesn't do during mount. This doesn't happen at the root because
3928
3917
  // the root always starts with a "current" with a null child.
3929
3918
  // TODO: Consider unifying this with how the root works.
3930
- workInProgress.child = reconcileChildFibersInPlace(workInProgress, workInProgress.child, nextChildren, renderExpirationTime);
3919
+ workInProgress.child = reconcileChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
3931
3920
  memoizeProps(workInProgress, nextChildren);
3932
3921
  } else {
3933
3922
  reconcileChildren(current, workInProgress, nextChildren);
@@ -5508,6 +5497,12 @@ function logCapturedError(capturedError) {
5508
5497
  return;
5509
5498
  }
5510
5499
 
5500
+ var error = capturedError.error;
5501
+ var suppressLogging = error && error.suppressReactErrorLogging;
5502
+ if (suppressLogging) {
5503
+ return;
5504
+ }
5505
+
5511
5506
  {
5512
5507
  var componentName = capturedError.componentName,
5513
5508
  componentStack = capturedError.componentStack,
@@ -5603,6 +5598,7 @@ var ReactFiberScheduler = function (config) {
5603
5598
 
5604
5599
  var now = config.now,
5605
5600
  scheduleDeferredCallback = config.scheduleDeferredCallback,
5601
+ cancelDeferredCallback = config.cancelDeferredCallback,
5606
5602
  useSyncScheduling = config.useSyncScheduling,
5607
5603
  prepareForCommit = config.prepareForCommit,
5608
5604
  resetAfterCommit = config.resetAfterCommit;
@@ -6010,6 +6006,7 @@ var ReactFiberScheduler = function (config) {
6010
6006
  {
6011
6007
  ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
6012
6008
  }
6009
+
6013
6010
  var next = beginWork(current, workInProgress, nextRenderExpirationTime);
6014
6011
  {
6015
6012
  ReactDebugCurrentFiber.resetCurrentFiber();
@@ -6330,7 +6327,10 @@ var ReactFiberScheduler = function (config) {
6330
6327
  } catch (e) {
6331
6328
  // Prevent cycle if logCapturedError() throws.
6332
6329
  // A cycle may still occur if logCapturedError renders a component that throws.
6333
- console.error(e);
6330
+ var suppressLogging = e && e.suppressReactErrorLogging;
6331
+ if (!suppressLogging) {
6332
+ console.error(e);
6333
+ }
6334
6334
  }
6335
6335
 
6336
6336
  // If we're in the commit phase, defer scheduling an update on the
@@ -6475,6 +6475,19 @@ var ReactFiberScheduler = function (config) {
6475
6475
  return scheduleWorkImpl(fiber, expirationTime, false);
6476
6476
  }
6477
6477
 
6478
+ function checkRootNeedsClearing(root, fiber, expirationTime) {
6479
+ if (!isWorking && root === nextRoot && expirationTime < nextRenderExpirationTime) {
6480
+ // Restart the root from the top.
6481
+ if (nextUnitOfWork !== null) {
6482
+ // This is an interruption. (Used for performance tracking.)
6483
+ interruptedBy = fiber;
6484
+ }
6485
+ nextRoot = null;
6486
+ nextUnitOfWork = null;
6487
+ nextRenderExpirationTime = NoWork;
6488
+ }
6489
+ }
6490
+
6478
6491
  function scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) {
6479
6492
  recordScheduleUpdate();
6480
6493
 
@@ -6500,17 +6513,10 @@ var ReactFiberScheduler = function (config) {
6500
6513
  if (node['return'] === null) {
6501
6514
  if (node.tag === HostRoot) {
6502
6515
  var root = node.stateNode;
6503
- if (!isWorking && root === nextRoot && expirationTime <= nextRenderExpirationTime) {
6504
- // Restart the root from the top.
6505
- if (nextUnitOfWork !== null) {
6506
- // This is an interruption. (Used for performance tracking.)
6507
- interruptedBy = fiber;
6508
- }
6509
- nextRoot = null;
6510
- nextUnitOfWork = null;
6511
- nextRenderExpirationTime = NoWork;
6512
- }
6516
+
6517
+ checkRootNeedsClearing(root, fiber, expirationTime);
6513
6518
  requestWork(root, expirationTime);
6519
+ checkRootNeedsClearing(root, fiber, expirationTime);
6514
6520
  } else {
6515
6521
  {
6516
6522
  if (!isErrorRecovery && fiber.tag === ClassComponent) {
@@ -6562,7 +6568,8 @@ var ReactFiberScheduler = function (config) {
6562
6568
  var firstScheduledRoot = null;
6563
6569
  var lastScheduledRoot = null;
6564
6570
 
6565
- var isCallbackScheduled = false;
6571
+ var callbackExpirationTime = NoWork;
6572
+ var callbackID = -1;
6566
6573
  var isRendering = false;
6567
6574
  var nextFlushedRoot = null;
6568
6575
  var nextFlushedExpirationTime = NoWork;
@@ -6580,6 +6587,31 @@ var ReactFiberScheduler = function (config) {
6580
6587
 
6581
6588
  var timeHeuristicForUnitOfWork = 1;
6582
6589
 
6590
+ function scheduleCallbackWithExpiration(expirationTime) {
6591
+ if (callbackExpirationTime !== NoWork) {
6592
+ // A callback is already scheduled. Check its expiration time (timeout).
6593
+ if (expirationTime > callbackExpirationTime) {
6594
+ // Existing callback has sufficient timeout. Exit.
6595
+ return;
6596
+ } else {
6597
+ // Existing callback has insufficient timeout. Cancel and schedule a
6598
+ // new one.
6599
+ cancelDeferredCallback(callbackID);
6600
+ }
6601
+ // The request callback timer is already running. Don't start a new one.
6602
+ } else {
6603
+ startRequestCallbackTimer();
6604
+ }
6605
+
6606
+ // Compute a timeout for the given expiration time.
6607
+ var currentMs = now() - startTime;
6608
+ var expirationMs = expirationTimeToMs(expirationTime);
6609
+ var timeout = expirationMs - currentMs;
6610
+
6611
+ callbackExpirationTime = expirationTime;
6612
+ callbackID = scheduleDeferredCallback(performAsyncWork, { timeout: timeout });
6613
+ }
6614
+
6583
6615
  // requestWork is called by the scheduler whenever a root receives an update.
6584
6616
  // It's up to the renderer to call renderRoot at some point in the future.
6585
6617
  function requestWork(root, expirationTime) {
@@ -6620,7 +6652,9 @@ var ReactFiberScheduler = function (config) {
6620
6652
  if (isUnbatchingUpdates) {
6621
6653
  // ...unless we're inside unbatchedUpdates, in which case we should
6622
6654
  // flush it now.
6623
- performWorkOnRoot(root, Sync);
6655
+ nextFlushedRoot = root;
6656
+ nextFlushedExpirationTime = Sync;
6657
+ performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
6624
6658
  }
6625
6659
  return;
6626
6660
  }
@@ -6628,10 +6662,8 @@ var ReactFiberScheduler = function (config) {
6628
6662
  // TODO: Get rid of Sync and use current time?
6629
6663
  if (expirationTime === Sync) {
6630
6664
  performWork(Sync, null);
6631
- } else if (!isCallbackScheduled) {
6632
- isCallbackScheduled = true;
6633
- startRequestCallbackTimer();
6634
- scheduleDeferredCallback(performAsyncWork);
6665
+ } else {
6666
+ scheduleCallbackWithExpiration(expirationTime);
6635
6667
  }
6636
6668
  }
6637
6669
 
@@ -6728,13 +6760,12 @@ var ReactFiberScheduler = function (config) {
6728
6760
 
6729
6761
  // If we're inside a callback, set this to false since we just completed it.
6730
6762
  if (deadline !== null) {
6731
- isCallbackScheduled = false;
6763
+ callbackExpirationTime = NoWork;
6764
+ callbackID = -1;
6732
6765
  }
6733
6766
  // If there's work left over, schedule a new callback.
6734
- if (nextFlushedRoot !== null && !isCallbackScheduled) {
6735
- isCallbackScheduled = true;
6736
- startRequestCallbackTimer();
6737
- scheduleDeferredCallback(performAsyncWork);
6767
+ if (nextFlushedExpirationTime !== NoWork) {
6768
+ scheduleCallbackWithExpiration(nextFlushedExpirationTime);
6738
6769
  }
6739
6770
 
6740
6771
  // Clean-up.
@@ -6807,6 +6838,8 @@ var ReactFiberScheduler = function (config) {
6807
6838
  return false;
6808
6839
  }
6809
6840
  if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
6841
+ // Disregard deadline.didTimeout. Only expired work should be flushed
6842
+ // during a timeout. This path is only hit for non-expired work.
6810
6843
  return false;
6811
6844
  }
6812
6845
  deadlineDidExpire = true;