56
var {onCommitRoot} = require('./ReactFiberDevToolsHook');
57
var {
58
NoWork,
59
- Task,
59
Sync,
60
Never,
61
msToExpirationTime,
1171
expirationTime = computeAsyncExpiration();
1172
}
1173
}
1175
-
1176
- if (
1177
- expirationTime === Sync &&
1178
- isBatchingUpdates &&
1179
- (!isUnbatchingUpdates || isCommitting)
1180
- ) {
1181
- // If we're in a batch, downgrade sync to task.
1182
- expirationTime = Task;
1183
- }
1174
return expirationTime;
1175
}
1176
1240
}
1241
1242
function scheduleErrorRecovery(fiber: Fiber) {
1253
- scheduleWorkImpl(fiber, Task, true);
1243
+ scheduleWorkImpl(fiber, Sync, true);
1244
}
1245
1246
function recalculateCurrentTime(): ExpirationTime {
1308
);
1309
}
1310
1311
+ // Add the root to the schedule.
1312
// Check if this root is already part of the schedule.
1322
- if (root.remainingExpirationTime === NoWork) {
1313
+ if (root.nextScheduledRoot === null) {
1314
// This root is not already scheduled. Add it.
1315
root.remainingExpirationTime = expirationTime;
1316
if (lastScheduledRoot === null) {
1317
firstScheduledRoot = lastScheduledRoot = root;
1318
+ root.nextScheduledRoot = root;
1319
} else {
1320
lastScheduledRoot.nextScheduledRoot = root;
1321
lastScheduledRoot = root;
1322
+ lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1323
}
1324
} else {
1325
// This root is already scheduled, but its priority may have increased.
1333
}
1334
}
1335
1343
- // If we're not already rendering, schedule work to flush now (if it's
1344
- // sync) or later (if it's async).
1345
- if (!isRendering) {
1346
- // TODO: Remove distinction between sync and task. Maybe we can remove
1347
- // these magic numbers entirely by always comparing to the current time?
1348
- if (expirationTime === Sync) {
1349
- if (isUnbatchingUpdates) {
1350
- performWork(Sync, null);
1351
- } else {
1352
- performWork(Task, null);
1353
- }
1354
- } else if (!isCallbackScheduled) {
1355
- isCallbackScheduled = true;
1356
- scheduleDeferredCallback(flushAsyncWork);
1336
+ if (isRendering) {
1337
+ // Prevent reentrancy. Remaining work will be scheduled at the end of
1338
+ // the currently rendering batch.
1339
+ return;
1340
+ }
1341
+
1342
+ if (isBatchingUpdates) {
1343
+ // Flush work at the end of the batch.
1344
+ if (isUnbatchingUpdates) {
1345
+ // ...unless we're inside unbatchedUpdates, in which case we should
1346
+ // flush it now.
1347
+ performWorkOnRoot(root, Sync);
1348
}
1349
+ return;
1350
+ }
1351
+
1352
+ // TODO: Get rid of Sync and use current time?
1353
+ if (expirationTime === Sync) {
1354
+ performWork(Sync, null);
1355
+ } else if (!isCallbackScheduled) {
1356
+ isCallbackScheduled = true;
1357
+ scheduleDeferredCallback(performAsyncWork);
1358
}
1359
}
1360
1362
let highestPriorityWork = NoWork;
1363
let highestPriorityRoot = null;
1364
1365
- let previousScheduledRoot = null;
1366
- let root = firstScheduledRoot;
1367
- while (root !== null) {
1368
- const remainingExpirationTime = root.remainingExpirationTime;
1369
- if (remainingExpirationTime === NoWork) {
1370
- // If this root no longer has work, remove it from the scheduler.
1371
- let next = root.nextScheduledRoot;
1372
- root.nextScheduledRoot = null;
1373
- if (previousScheduledRoot === null) {
1374
- firstScheduledRoot = next;
1365
+ if (lastScheduledRoot !== null) {
1366
+ let previousScheduledRoot = lastScheduledRoot;
1367
+ let root = firstScheduledRoot;
1368
+ while (root !== null) {
1369
+ const remainingExpirationTime = root.remainingExpirationTime;
1370
+ if (remainingExpirationTime === NoWork) {
1371
+ // This root no longer has work. Remove it from the scheduler.
1372
+
1373
+ // TODO: This check is redudant, but Flow is confused by the branch
1374
+ // below where we set lastScheduledRoot to null, even though we break
1375
+ // from the loop right after.
1376
+ invariant(
1377
+ previousScheduledRoot !== null && lastScheduledRoot !== null,
1378
+ 'Should have a previous and last root. This error is likely ' +
1379
+ 'caused by a bug in React. Please file an issue.',
1380
+ );
1381
+ if (root === root.nextScheduledRoot) {
1382
+ // This is the only root in the list.
1383
+ root.nextScheduledRoot = null;
1384
+ firstScheduledRoot = lastScheduledRoot = null;
1385
+ break;
1386
+ } else if (root === firstScheduledRoot) {
1387
+ // This is the first root in the list.
1388
+ const next = root.nextScheduledRoot;
1389
+ firstScheduledRoot = next;
1390
+ lastScheduledRoot.nextScheduledRoot = next;
1391
+ root.nextScheduledRoot = null;
1392
+ } else if (root === lastScheduledRoot) {
1393
+ // This is the last root in the list.
1394
+ lastScheduledRoot = previousScheduledRoot;
1395
+ lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1396
+ root.nextScheduledRoot = null;
1397
+ break;
1398
+ } else {
1399
+ previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1400
+ root.nextScheduledRoot = null;
1401
+ }
1402
+ root = previousScheduledRoot.nextScheduledRoot;
1403
} else {
1376
- previousScheduledRoot.nextScheduledRoot = next;
1377
- }
1378
- if (next === null) {
1379
- lastScheduledRoot = null;
1404
+ if (
1405
+ highestPriorityWork === NoWork ||
1406
+ remainingExpirationTime < highestPriorityWork
1407
+ ) {
1408
+ // Update the priority, if it's higher
1409
+ highestPriorityWork = remainingExpirationTime;
1410
+ highestPriorityRoot = root;
1411
+ }
1412
+ if (root === lastScheduledRoot) {
1413
+ break;
1414
+ }
1415
+ previousScheduledRoot = root;
1416
+ root = root.nextScheduledRoot;
1417
}
1381
- root = next;
1382
- continue;
1383
- } else if (
1384
- highestPriorityWork === NoWork ||
1385
- remainingExpirationTime < highestPriorityWork
1386
- ) {
1387
- // Update the priority, if it's higher
1388
- highestPriorityWork = remainingExpirationTime;
1389
- highestPriorityRoot = root;
1418
}
1391
- previousScheduledRoot = root;
1392
- root = root.nextScheduledRoot;
1419
}
1420
1421
// If the next root is the same as the previous root, this is a nested
1434
nextFlushedExpirationTime = highestPriorityWork;
1435
}
1436
1411
- function flushAsyncWork(dl) {
1437
+ function performAsyncWork(dl) {
1438
performWork(NoWork, dl);
1439
}
1440
1441
function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
1416
- invariant(
1417
- !isRendering,
1418
- 'performWork was called recursively. This error is likely caused ' +
1419
- 'by a bug in React. Please file an issue.',
1420
- );
1421
-
1422
- isRendering = true;
1442
deadline = dl;
1443
1444
// Keep working on roots until there's no more work, or until the we reach
1451
nextFlushedExpirationTime <= minExpirationTime) &&
1452
!deadlineDidExpire
1453
) {
1435
- // Check if this is async work or sync/expired work.
1436
- // TODO: Pass current time as argument to renderRoot, commitRoot
1437
- if (nextFlushedExpirationTime <= recalculateCurrentTime()) {
1438
- // Flush sync work.
1439
- let finishedWork = nextFlushedRoot.finishedWork;
1440
- if (finishedWork !== null) {
1441
- // This root is already complete. We can commit it.
1442
- nextFlushedRoot.finishedWork = null;
1443
- nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1444
- } else {
1445
- nextFlushedRoot.finishedWork = null;
1446
- finishedWork = renderRoot(nextFlushedRoot, nextFlushedExpirationTime);
1447
- if (finishedWork !== null) {
1448
- // We've completed the root. Commit it.
1449
- nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1450
- }
1451
- }
1452
- } else {
1453
- // Flush async work.
1454
- let finishedWork = nextFlushedRoot.finishedWork;
1455
- if (finishedWork !== null) {
1456
- // This root is already complete. We can commit it.
1457
- nextFlushedRoot.finishedWork = null;
1458
- nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1459
- } else {
1460
- nextFlushedRoot.finishedWork = null;
1461
- finishedWork = renderRoot(nextFlushedRoot, nextFlushedExpirationTime);
1462
- if (finishedWork !== null) {
1463
- // We've completed the root. Check the deadline one more time
1464
- // before committing.
1465
- if (!shouldYield()) {
1466
- // Still time left. Commit the root.
1467
- nextFlushedRoot.remainingExpirationTime = commitRoot(
1468
- finishedWork,
1469
- );
1470
- } else {
1471
- // There's no time left. Mark this root as complete. We'll come
1472
- // back and commit it later.
1473
- nextFlushedRoot.finishedWork = finishedWork;
1474
- }
1475
- }
1476
- }
1477
- }
1454
+ performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
1455
// Find the next highest priority work.
1456
findHighestPriorityRoot();
1457
}
1466
// If there's work left over, schedule a new callback.
1467
if (nextFlushedRoot !== null && !isCallbackScheduled) {
1468
isCallbackScheduled = true;
1492
- scheduleDeferredCallback(flushAsyncWork);
1469
+ scheduleDeferredCallback(performAsyncWork);
1470
}
1471
1472
// Clean-up.
1473
deadline = null;
1474
deadlineDidExpire = false;
1498
- isRendering = false;
1475
nestedUpdateCount = 0;
1476
1477
if (hasUnhandledError) {
1482
}
1483
}
1484
1485
+ function performWorkOnRoot(root, expirationTime) {
1486
+ invariant(
1487
+ !isRendering,
1488
+ 'performWorkOnRoot was called recursively. This error is likely caused ' +
1489
+ 'by a bug in React. Please file an issue.',
1490
+ );
1491
+
1492
+ isRendering = true;
1493
+
1494
+ // Check if this is async work or sync/expired work.
1495
+ // TODO: Pass current time as argument to renderRoot, commitRoot
1496
+ if (expirationTime <= recalculateCurrentTime()) {
1497
+ // Flush sync work.
1498
+ let finishedWork = root.finishedWork;
1499
+ if (finishedWork !== null) {
1500
+ // This root is already complete. We can commit it.
1501
+ root.finishedWork = null;
1502
+ root.remainingExpirationTime = commitRoot(finishedWork);
1503
+ } else {
1504
+ root.finishedWork = null;
1505
+ finishedWork = renderRoot(root, expirationTime);
1506
+ if (finishedWork !== null) {
1507
+ // We've completed the root. Commit it.
1508
+ root.remainingExpirationTime = commitRoot(finishedWork);
1509
+ }
1510
+ }
1511
+ } else {
1512
+ // Flush async work.
1513
+ let finishedWork = root.finishedWork;
1514
+ if (finishedWork !== null) {
1515
+ // This root is already complete. We can commit it.
1516
+ root.finishedWork = null;
1517
+ root.remainingExpirationTime = commitRoot(finishedWork);
1518
+ } else {
1519
+ root.finishedWork = null;
1520
+ finishedWork = renderRoot(root, expirationTime);
1521
+ if (finishedWork !== null) {
1522
+ // We've completed the root. Check the deadline one more time
1523
+ // before committing.
1524
+ if (!shouldYield()) {
1525
+ // Still time left. Commit the root.
1526
+ root.remainingExpirationTime = commitRoot(finishedWork);
1527
+ } else {
1528
+ // There's no time left. Mark this root as complete. We'll come
1529
+ // back and commit it later.
1530
+ root.finishedWork = finishedWork;
1531
+ }
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ isRendering = false;
1537
+ }
1538
+
1539
// When working on async work, the reconciler asks the renderer if it should
1540
// yield execution. For DOM, we implement this with requestIdleCallback.
1541
function shouldYield() {
1576
} finally {
1577
isBatchingUpdates = previousIsBatchingUpdates;
1578
if (!isBatchingUpdates && !isRendering) {
1549
- performWork(Task, null);
1579
+ performWork(Sync, null);
1580
}
1581
}
1582
}
1609
'flushSync was called from inside a lifecycle method. It cannot be ' +
1610
'called when React is already rendering.',
1611
);
1582
- performWork(Task, null);
1612
+ performWork(Sync, null);
1613
}
1614
}
1615