UNPKG

30.4 kBJavaScriptView Raw
1/** @license React v0.17.0
2 * scheduler.development.js
3 *
4 * Copyright (c) Facebook, Inc. and its affiliates.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE file in the root directory of this source tree.
8 */
9
10'use strict';
11
12
13
14if (process.env.NODE_ENV !== "production") {
15 (function() {
16'use strict';
17
18Object.defineProperty(exports, '__esModule', { value: true });
19
20var enableSchedulerDebugging = false;
21var enableIsInputPending = false;
22var enableMessageLoopImplementation = true;
23var enableProfiling = true;
24
25// works by scheduling a requestAnimationFrame, storing the time for the start
26// of the frame, then scheduling a postMessage which gets scheduled after paint.
27// Within the postMessage handler do as much work as possible until time + frame
28// rate. By separating the idle call into a separate event tick we ensure that
29// layout, paint and other browser work is counted against the available time.
30// The frame rate is dynamically adjusted.
31
32var requestHostCallback;
33
34var requestHostTimeout;
35var cancelHostTimeout;
36var shouldYieldToHost;
37var requestPaint;
38
39
40
41if ( // If Scheduler runs in a non-DOM environment, it falls back to a naive
42// implementation using setTimeout.
43typeof window === 'undefined' || // Check if MessageChannel is supported, too.
44typeof MessageChannel !== 'function') {
45 // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore,
46 // fallback to a naive implementation.
47 var _callback = null;
48 var _timeoutID = null;
49
50 var _flushCallback = function () {
51 if (_callback !== null) {
52 try {
53 var currentTime = exports.unstable_now();
54 var hasRemainingTime = true;
55
56 _callback(hasRemainingTime, currentTime);
57
58 _callback = null;
59 } catch (e) {
60 setTimeout(_flushCallback, 0);
61 throw e;
62 }
63 }
64 };
65
66 var initialTime = Date.now();
67
68 exports.unstable_now = function () {
69 return Date.now() - initialTime;
70 };
71
72 requestHostCallback = function (cb) {
73 if (_callback !== null) {
74 // Protect against re-entrancy.
75 setTimeout(requestHostCallback, 0, cb);
76 } else {
77 _callback = cb;
78 setTimeout(_flushCallback, 0);
79 }
80 };
81
82 requestHostTimeout = function (cb, ms) {
83 _timeoutID = setTimeout(cb, ms);
84 };
85
86 cancelHostTimeout = function () {
87 clearTimeout(_timeoutID);
88 };
89
90 shouldYieldToHost = function () {
91 return false;
92 };
93
94 requestPaint = exports.unstable_forceFrameRate = function () {};
95} else {
96 // Capture local references to native APIs, in case a polyfill overrides them.
97 var performance = window.performance;
98 var _Date = window.Date;
99 var _setTimeout = window.setTimeout;
100 var _clearTimeout = window.clearTimeout;
101 var requestAnimationFrame = window.requestAnimationFrame;
102 var cancelAnimationFrame = window.cancelAnimationFrame;
103
104 if (typeof console !== 'undefined') {
105 // TODO: Remove fb.me link
106 if (typeof requestAnimationFrame !== 'function') {
107 console.error("This browser doesn't support requestAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills');
108 }
109
110 if (typeof cancelAnimationFrame !== 'function') {
111 console.error("This browser doesn't support cancelAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills');
112 }
113 }
114
115 if (typeof performance === 'object' && typeof performance.now === 'function') {
116 exports.unstable_now = function () {
117 return performance.now();
118 };
119 } else {
120 var _initialTime = _Date.now();
121
122 exports.unstable_now = function () {
123 return _Date.now() - _initialTime;
124 };
125 }
126
127 var isRAFLoopRunning = false;
128 var isMessageLoopRunning = false;
129 var scheduledHostCallback = null;
130 var rAFTimeoutID = -1;
131 var taskTimeoutID = -1;
132 var frameLength = enableMessageLoopImplementation ? // We won't attempt to align with the vsync. Instead we'll yield multiple
133 // times per frame, often enough to keep it responsive even at really
134 // high frame rates > 120.
135 5 : // Use a heuristic to measure the frame rate and yield at the end of the
136 // frame. We start out assuming that we run at 30fps but then the
137 // heuristic tracking will adjust this value to a faster fps if we get
138 // more frequent animation frames.
139 33.33;
140 var prevRAFTime = -1;
141 var prevRAFInterval = -1;
142 var frameDeadline = 0;
143 var fpsLocked = false; // TODO: Make this configurable
144 // TODO: Adjust this based on priority?
145
146 var maxFrameLength = 300;
147 var needsPaint = false;
148
149 if (enableIsInputPending && navigator !== undefined && navigator.scheduling !== undefined && navigator.scheduling.isInputPending !== undefined) {
150 var scheduling = navigator.scheduling;
151
152 shouldYieldToHost = function () {
153 var currentTime = exports.unstable_now();
154
155 if (currentTime >= frameDeadline) {
156 // There's no time left in the frame. We may want to yield control of
157 // the main thread, so the browser can perform high priority tasks. The
158 // main ones are painting and user input. If there's a pending paint or
159 // a pending input, then we should yield. But if there's neither, then
160 // we can yield less often while remaining responsive. We'll eventually
161 // yield regardless, since there could be a pending paint that wasn't
162 // accompanied by a call to `requestPaint`, or other main thread tasks
163 // like network events.
164 if (needsPaint || scheduling.isInputPending()) {
165 // There is either a pending paint or a pending input.
166 return true;
167 } // There's no pending input. Only yield if we've reached the max
168 // frame length.
169
170
171 return currentTime >= frameDeadline + maxFrameLength;
172 } else {
173 // There's still time left in the frame.
174 return false;
175 }
176 };
177
178 requestPaint = function () {
179 needsPaint = true;
180 };
181 } else {
182 // `isInputPending` is not available. Since we have no way of knowing if
183 // there's pending input, always yield at the end of the frame.
184 shouldYieldToHost = function () {
185 return exports.unstable_now() >= frameDeadline;
186 }; // Since we yield every frame regardless, `requestPaint` has no effect.
187
188
189 requestPaint = function () {};
190 }
191
192 exports.unstable_forceFrameRate = function (fps) {
193 if (fps < 0 || fps > 125) {
194 console.error('forceFrameRate takes a positive int between 0 and 125, ' + 'forcing framerates higher than 125 fps is not unsupported');
195 return;
196 }
197
198 if (fps > 0) {
199 frameLength = Math.floor(1000 / fps);
200 fpsLocked = true;
201 } else {
202 // reset the framerate
203 frameLength = 33.33;
204 fpsLocked = false;
205 }
206 };
207
208 var performWorkUntilDeadline = function () {
209 if (enableMessageLoopImplementation) {
210 if (scheduledHostCallback !== null) {
211 var currentTime = exports.unstable_now(); // Yield after `frameLength` ms, regardless of where we are in the vsync
212 // cycle. This means there's always time remaining at the beginning of
213 // the message event.
214
215 frameDeadline = currentTime + frameLength;
216 var hasTimeRemaining = true;
217
218 try {
219 var hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
220
221 if (!hasMoreWork) {
222 isMessageLoopRunning = false;
223 scheduledHostCallback = null;
224 } else {
225 // If there's more work, schedule the next message event at the end
226 // of the preceding one.
227 port.postMessage(null);
228 }
229 } catch (error) {
230 // If a scheduler task throws, exit the current browser task so the
231 // error can be observed.
232 port.postMessage(null);
233 throw error;
234 }
235 } else {
236 isMessageLoopRunning = false;
237 } // Yielding to the browser will give it a chance to paint, so we can
238 // reset this.
239
240
241 needsPaint = false;
242 } else {
243 if (scheduledHostCallback !== null) {
244 var _currentTime = exports.unstable_now();
245
246 var _hasTimeRemaining = frameDeadline - _currentTime > 0;
247
248 try {
249 var _hasMoreWork = scheduledHostCallback(_hasTimeRemaining, _currentTime);
250
251 if (!_hasMoreWork) {
252 scheduledHostCallback = null;
253 }
254 } catch (error) {
255 // If a scheduler task throws, exit the current browser task so the
256 // error can be observed, and post a new task as soon as possible
257 // so we can continue where we left off.
258 port.postMessage(null);
259 throw error;
260 }
261 } // Yielding to the browser will give it a chance to paint, so we can
262 // reset this.
263
264
265 needsPaint = false;
266 }
267 };
268
269 var channel = new MessageChannel();
270 var port = channel.port2;
271 channel.port1.onmessage = performWorkUntilDeadline;
272
273 var onAnimationFrame = function (rAFTime) {
274 if (scheduledHostCallback === null) {
275 // No scheduled work. Exit.
276 prevRAFTime = -1;
277 prevRAFInterval = -1;
278 isRAFLoopRunning = false;
279 return;
280 } // Eagerly schedule the next animation callback at the beginning of the
281 // frame. If the scheduler queue is not empty at the end of the frame, it
282 // will continue flushing inside that callback. If the queue *is* empty,
283 // then it will exit immediately. Posting the callback at the start of the
284 // frame ensures it's fired within the earliest possible frame. If we
285 // waited until the end of the frame to post the callback, we risk the
286 // browser skipping a frame and not firing the callback until the frame
287 // after that.
288
289
290 isRAFLoopRunning = true;
291 requestAnimationFrame(function (nextRAFTime) {
292 _clearTimeout(rAFTimeoutID);
293
294 onAnimationFrame(nextRAFTime);
295 }); // requestAnimationFrame is throttled when the tab is backgrounded. We
296 // don't want to stop working entirely. So we'll fallback to a timeout loop.
297 // TODO: Need a better heuristic for backgrounded work.
298
299 var onTimeout = function () {
300 frameDeadline = exports.unstable_now() + frameLength / 2;
301 performWorkUntilDeadline();
302 rAFTimeoutID = _setTimeout(onTimeout, frameLength * 3);
303 };
304
305 rAFTimeoutID = _setTimeout(onTimeout, frameLength * 3);
306
307 if (prevRAFTime !== -1 && // Make sure this rAF time is different from the previous one. This check
308 // could fail if two rAFs fire in the same frame.
309 rAFTime - prevRAFTime > 0.1) {
310 var rAFInterval = rAFTime - prevRAFTime;
311
312 if (!fpsLocked && prevRAFInterval !== -1) {
313 // We've observed two consecutive frame intervals. We'll use this to
314 // dynamically adjust the frame rate.
315 //
316 // If one frame goes long, then the next one can be short to catch up.
317 // If two frames are short in a row, then that's an indication that we
318 // actually have a higher frame rate than what we're currently
319 // optimizing. For example, if we're running on 120hz display or 90hz VR
320 // display. Take the max of the two in case one of them was an anomaly
321 // due to missed frame deadlines.
322 if (rAFInterval < frameLength && prevRAFInterval < frameLength) {
323 frameLength = rAFInterval < prevRAFInterval ? prevRAFInterval : rAFInterval;
324
325 if (frameLength < 8.33) {
326 // Defensive coding. We don't support higher frame rates than 120hz.
327 // If the calculated frame length gets lower than 8, it is probably
328 // a bug.
329 frameLength = 8.33;
330 }
331 }
332 }
333
334 prevRAFInterval = rAFInterval;
335 }
336
337 prevRAFTime = rAFTime;
338 frameDeadline = rAFTime + frameLength; // We use the postMessage trick to defer idle work until after the repaint.
339
340 port.postMessage(null);
341 };
342
343 requestHostCallback = function (callback) {
344 scheduledHostCallback = callback;
345
346 if (enableMessageLoopImplementation) {
347 if (!isMessageLoopRunning) {
348 isMessageLoopRunning = true;
349 port.postMessage(null);
350 }
351 } else {
352 if (!isRAFLoopRunning) {
353 // Start a rAF loop.
354 isRAFLoopRunning = true;
355 requestAnimationFrame(function (rAFTime) {
356 onAnimationFrame(rAFTime);
357 });
358 }
359 }
360 };
361
362 requestHostTimeout = function (callback, ms) {
363 taskTimeoutID = _setTimeout(function () {
364 callback(exports.unstable_now());
365 }, ms);
366 };
367
368 cancelHostTimeout = function () {
369 _clearTimeout(taskTimeoutID);
370
371 taskTimeoutID = -1;
372 };
373}
374
375function push(heap, node) {
376 var index = heap.length;
377 heap.push(node);
378 siftUp(heap, node, index);
379}
380function peek(heap) {
381 var first = heap[0];
382 return first === undefined ? null : first;
383}
384function pop(heap) {
385 var first = heap[0];
386
387 if (first !== undefined) {
388 var last = heap.pop();
389
390 if (last !== first) {
391 heap[0] = last;
392 siftDown(heap, last, 0);
393 }
394
395 return first;
396 } else {
397 return null;
398 }
399}
400
401function siftUp(heap, node, i) {
402 var index = i;
403
404 while (true) {
405 var parentIndex = Math.floor((index - 1) / 2);
406 var parent = heap[parentIndex];
407
408 if (parent !== undefined && compare(parent, node) > 0) {
409 // The parent is larger. Swap positions.
410 heap[parentIndex] = node;
411 heap[index] = parent;
412 index = parentIndex;
413 } else {
414 // The parent is smaller. Exit.
415 return;
416 }
417 }
418}
419
420function siftDown(heap, node, i) {
421 var index = i;
422 var length = heap.length;
423
424 while (index < length) {
425 var leftIndex = (index + 1) * 2 - 1;
426 var left = heap[leftIndex];
427 var rightIndex = leftIndex + 1;
428 var right = heap[rightIndex]; // If the left or right node is smaller, swap with the smaller of those.
429
430 if (left !== undefined && compare(left, node) < 0) {
431 if (right !== undefined && compare(right, left) < 0) {
432 heap[index] = right;
433 heap[rightIndex] = node;
434 index = rightIndex;
435 } else {
436 heap[index] = left;
437 heap[leftIndex] = node;
438 index = leftIndex;
439 }
440 } else if (right !== undefined && compare(right, node) < 0) {
441 heap[index] = right;
442 heap[rightIndex] = node;
443 index = rightIndex;
444 } else {
445 // Neither child is smaller. Exit.
446 return;
447 }
448 }
449}
450
451function compare(a, b) {
452 // Compare sort index first, then task id.
453 var diff = a.sortIndex - b.sortIndex;
454 return diff !== 0 ? diff : a.id - b.id;
455}
456
457// TODO: Use symbols?
458var NoPriority = 0;
459var ImmediatePriority = 1;
460var UserBlockingPriority = 2;
461var NormalPriority = 3;
462var LowPriority = 4;
463var IdlePriority = 5;
464
465var runIdCounter = 0;
466var mainThreadIdCounter = 0;
467var profilingStateSize = 4;
468var sharedProfilingBuffer = enableProfiling ? // $FlowFixMe Flow doesn't know about SharedArrayBuffer
469typeof SharedArrayBuffer === 'function' ? new SharedArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT) : // $FlowFixMe Flow doesn't know about ArrayBuffer
470typeof ArrayBuffer === 'function' ? new ArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT) : null // Don't crash the init path on IE9
471: null;
472var profilingState = enableProfiling && sharedProfilingBuffer !== null ? new Int32Array(sharedProfilingBuffer) : []; // We can't read this but it helps save bytes for null checks
473
474var PRIORITY = 0;
475var CURRENT_TASK_ID = 1;
476var CURRENT_RUN_ID = 2;
477var QUEUE_SIZE = 3;
478
479if (enableProfiling) {
480 profilingState[PRIORITY] = NoPriority; // This is maintained with a counter, because the size of the priority queue
481 // array might include canceled tasks.
482
483 profilingState[QUEUE_SIZE] = 0;
484 profilingState[CURRENT_TASK_ID] = 0;
485} // Bytes per element is 4
486
487
488var INITIAL_EVENT_LOG_SIZE = 131072;
489var MAX_EVENT_LOG_SIZE = 524288; // Equivalent to 2 megabytes
490
491var eventLogSize = 0;
492var eventLogBuffer = null;
493var eventLog = null;
494var eventLogIndex = 0;
495var TaskStartEvent = 1;
496var TaskCompleteEvent = 2;
497var TaskErrorEvent = 3;
498var TaskCancelEvent = 4;
499var TaskRunEvent = 5;
500var TaskYieldEvent = 6;
501var SchedulerSuspendEvent = 7;
502var SchedulerResumeEvent = 8;
503
504function logEvent(entries) {
505 if (eventLog !== null) {
506 var offset = eventLogIndex;
507 eventLogIndex += entries.length;
508
509 if (eventLogIndex + 1 > eventLogSize) {
510 eventLogSize *= 2;
511
512 if (eventLogSize > MAX_EVENT_LOG_SIZE) {
513 console.error("Scheduler Profiling: Event log exceeded maximum size. Don't " + 'forget to call `stopLoggingProfilingEvents()`.');
514 stopLoggingProfilingEvents();
515 return;
516 }
517
518 var newEventLog = new Int32Array(eventLogSize * 4);
519 newEventLog.set(eventLog);
520 eventLogBuffer = newEventLog.buffer;
521 eventLog = newEventLog;
522 }
523
524 eventLog.set(entries, offset);
525 }
526}
527
528function startLoggingProfilingEvents() {
529 eventLogSize = INITIAL_EVENT_LOG_SIZE;
530 eventLogBuffer = new ArrayBuffer(eventLogSize * 4);
531 eventLog = new Int32Array(eventLogBuffer);
532 eventLogIndex = 0;
533}
534function stopLoggingProfilingEvents() {
535 var buffer = eventLogBuffer;
536 eventLogSize = 0;
537 eventLogBuffer = null;
538 eventLog = null;
539 eventLogIndex = 0;
540 return buffer;
541}
542function markTaskStart(task, ms) {
543 if (enableProfiling) {
544 profilingState[QUEUE_SIZE]++;
545
546 if (eventLog !== null) {
547 // performance.now returns a float, representing milliseconds. When the
548 // event is logged, it's coerced to an int. Convert to microseconds to
549 // maintain extra degrees of precision.
550 logEvent([TaskStartEvent, ms * 1000, task.id, task.priorityLevel]);
551 }
552 }
553}
554function markTaskCompleted(task, ms) {
555 if (enableProfiling) {
556 profilingState[PRIORITY] = NoPriority;
557 profilingState[CURRENT_TASK_ID] = 0;
558 profilingState[QUEUE_SIZE]--;
559
560 if (eventLog !== null) {
561 logEvent([TaskCompleteEvent, ms * 1000, task.id]);
562 }
563 }
564}
565function markTaskCanceled(task, ms) {
566 if (enableProfiling) {
567 profilingState[QUEUE_SIZE]--;
568
569 if (eventLog !== null) {
570 logEvent([TaskCancelEvent, ms * 1000, task.id]);
571 }
572 }
573}
574function markTaskErrored(task, ms) {
575 if (enableProfiling) {
576 profilingState[PRIORITY] = NoPriority;
577 profilingState[CURRENT_TASK_ID] = 0;
578 profilingState[QUEUE_SIZE]--;
579
580 if (eventLog !== null) {
581 logEvent([TaskErrorEvent, ms * 1000, task.id]);
582 }
583 }
584}
585function markTaskRun(task, ms) {
586 if (enableProfiling) {
587 runIdCounter++;
588 profilingState[PRIORITY] = task.priorityLevel;
589 profilingState[CURRENT_TASK_ID] = task.id;
590 profilingState[CURRENT_RUN_ID] = runIdCounter;
591
592 if (eventLog !== null) {
593 logEvent([TaskRunEvent, ms * 1000, task.id, runIdCounter]);
594 }
595 }
596}
597function markTaskYield(task, ms) {
598 if (enableProfiling) {
599 profilingState[PRIORITY] = NoPriority;
600 profilingState[CURRENT_TASK_ID] = 0;
601 profilingState[CURRENT_RUN_ID] = 0;
602
603 if (eventLog !== null) {
604 logEvent([TaskYieldEvent, ms * 1000, task.id, runIdCounter]);
605 }
606 }
607}
608function markSchedulerSuspended(ms) {
609 if (enableProfiling) {
610 mainThreadIdCounter++;
611
612 if (eventLog !== null) {
613 logEvent([SchedulerSuspendEvent, ms * 1000, mainThreadIdCounter]);
614 }
615 }
616}
617function markSchedulerUnsuspended(ms) {
618 if (enableProfiling) {
619 if (eventLog !== null) {
620 logEvent([SchedulerResumeEvent, ms * 1000, mainThreadIdCounter]);
621 }
622 }
623}
624
625/* eslint-disable no-var */
626// Math.pow(2, 30) - 1
627// 0b111111111111111111111111111111
628
629var maxSigned31BitInt = 1073741823; // Times out immediately
630
631var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out
632
633var USER_BLOCKING_PRIORITY = 250;
634var NORMAL_PRIORITY_TIMEOUT = 5000;
635var LOW_PRIORITY_TIMEOUT = 10000; // Never times out
636
637var IDLE_PRIORITY = maxSigned31BitInt; // Tasks are stored on a min heap
638
639var taskQueue = [];
640var timerQueue = []; // Incrementing id counter. Used to maintain insertion order.
641
642var taskIdCounter = 1; // Pausing the scheduler is useful for debugging.
643
644var isSchedulerPaused = false;
645var currentTask = null;
646var currentPriorityLevel = NormalPriority; // This is set while performing work, to prevent re-entrancy.
647
648var isPerformingWork = false;
649var isHostCallbackScheduled = false;
650var isHostTimeoutScheduled = false;
651
652function advanceTimers(currentTime) {
653 // Check for tasks that are no longer delayed and add them to the queue.
654 var timer = peek(timerQueue);
655
656 while (timer !== null) {
657 if (timer.callback === null) {
658 // Timer was cancelled.
659 pop(timerQueue);
660 } else if (timer.startTime <= currentTime) {
661 // Timer fired. Transfer to the task queue.
662 pop(timerQueue);
663 timer.sortIndex = timer.expirationTime;
664 push(taskQueue, timer);
665
666 if (enableProfiling) {
667 markTaskStart(timer, currentTime);
668 timer.isQueued = true;
669 }
670 } else {
671 // Remaining timers are pending.
672 return;
673 }
674
675 timer = peek(timerQueue);
676 }
677}
678
679function handleTimeout(currentTime) {
680 isHostTimeoutScheduled = false;
681 advanceTimers(currentTime);
682
683 if (!isHostCallbackScheduled) {
684 if (peek(taskQueue) !== null) {
685 isHostCallbackScheduled = true;
686 requestHostCallback(flushWork);
687 } else {
688 var firstTimer = peek(timerQueue);
689
690 if (firstTimer !== null) {
691 requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
692 }
693 }
694 }
695}
696
697function flushWork(hasTimeRemaining, initialTime) {
698 if (enableProfiling) {
699 markSchedulerUnsuspended(initialTime);
700 } // We'll need a host callback the next time work is scheduled.
701
702
703 isHostCallbackScheduled = false;
704
705 if (isHostTimeoutScheduled) {
706 // We scheduled a timeout but it's no longer needed. Cancel it.
707 isHostTimeoutScheduled = false;
708 cancelHostTimeout();
709 }
710
711 isPerformingWork = true;
712 var previousPriorityLevel = currentPriorityLevel;
713
714 try {
715 if (enableProfiling) {
716 try {
717 return workLoop(hasTimeRemaining, initialTime);
718 } catch (error) {
719 if (currentTask !== null) {
720 var currentTime = exports.unstable_now();
721 markTaskErrored(currentTask, currentTime);
722 currentTask.isQueued = false;
723 }
724
725 throw error;
726 }
727 } else {
728 // No catch in prod codepath.
729 return workLoop(hasTimeRemaining, initialTime);
730 }
731 } finally {
732 currentTask = null;
733 currentPriorityLevel = previousPriorityLevel;
734 isPerformingWork = false;
735
736 if (enableProfiling) {
737 var _currentTime = exports.unstable_now();
738
739 markSchedulerSuspended(_currentTime);
740 }
741 }
742}
743
744function workLoop(hasTimeRemaining, initialTime) {
745 var currentTime = initialTime;
746 advanceTimers(currentTime);
747 currentTask = peek(taskQueue);
748
749 while (currentTask !== null && !(enableSchedulerDebugging && isSchedulerPaused)) {
750 if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) {
751 // This currentTask hasn't expired, and we've reached the deadline.
752 break;
753 }
754
755 var callback = currentTask.callback;
756
757 if (callback !== null) {
758 currentTask.callback = null;
759 currentPriorityLevel = currentTask.priorityLevel;
760 var didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
761 markTaskRun(currentTask, currentTime);
762 var continuationCallback = callback(didUserCallbackTimeout);
763 currentTime = exports.unstable_now();
764
765 if (typeof continuationCallback === 'function') {
766 currentTask.callback = continuationCallback;
767 markTaskYield(currentTask, currentTime);
768 } else {
769 if (enableProfiling) {
770 markTaskCompleted(currentTask, currentTime);
771 currentTask.isQueued = false;
772 }
773
774 if (currentTask === peek(taskQueue)) {
775 pop(taskQueue);
776 }
777 }
778
779 advanceTimers(currentTime);
780 } else {
781 pop(taskQueue);
782 }
783
784 currentTask = peek(taskQueue);
785 } // Return whether there's additional work
786
787
788 if (currentTask !== null) {
789 return true;
790 } else {
791 var firstTimer = peek(timerQueue);
792
793 if (firstTimer !== null) {
794 requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
795 }
796
797 return false;
798 }
799}
800
801function unstable_runWithPriority(priorityLevel, eventHandler) {
802 switch (priorityLevel) {
803 case ImmediatePriority:
804 case UserBlockingPriority:
805 case NormalPriority:
806 case LowPriority:
807 case IdlePriority:
808 break;
809
810 default:
811 priorityLevel = NormalPriority;
812 }
813
814 var previousPriorityLevel = currentPriorityLevel;
815 currentPriorityLevel = priorityLevel;
816
817 try {
818 return eventHandler();
819 } finally {
820 currentPriorityLevel = previousPriorityLevel;
821 }
822}
823
824function unstable_next(eventHandler) {
825 var priorityLevel;
826
827 switch (currentPriorityLevel) {
828 case ImmediatePriority:
829 case UserBlockingPriority:
830 case NormalPriority:
831 // Shift down to normal priority
832 priorityLevel = NormalPriority;
833 break;
834
835 default:
836 // Anything lower than normal priority should remain at the current level.
837 priorityLevel = currentPriorityLevel;
838 break;
839 }
840
841 var previousPriorityLevel = currentPriorityLevel;
842 currentPriorityLevel = priorityLevel;
843
844 try {
845 return eventHandler();
846 } finally {
847 currentPriorityLevel = previousPriorityLevel;
848 }
849}
850
851function unstable_wrapCallback(callback) {
852 var parentPriorityLevel = currentPriorityLevel;
853 return function () {
854 // This is a fork of runWithPriority, inlined for performance.
855 var previousPriorityLevel = currentPriorityLevel;
856 currentPriorityLevel = parentPriorityLevel;
857
858 try {
859 return callback.apply(this, arguments);
860 } finally {
861 currentPriorityLevel = previousPriorityLevel;
862 }
863 };
864}
865
866function timeoutForPriorityLevel(priorityLevel) {
867 switch (priorityLevel) {
868 case ImmediatePriority:
869 return IMMEDIATE_PRIORITY_TIMEOUT;
870
871 case UserBlockingPriority:
872 return USER_BLOCKING_PRIORITY;
873
874 case IdlePriority:
875 return IDLE_PRIORITY;
876
877 case LowPriority:
878 return LOW_PRIORITY_TIMEOUT;
879
880 case NormalPriority:
881 default:
882 return NORMAL_PRIORITY_TIMEOUT;
883 }
884}
885
886function unstable_scheduleCallback(priorityLevel, callback, options) {
887 var currentTime = exports.unstable_now();
888 var startTime;
889 var timeout;
890
891 if (typeof options === 'object' && options !== null) {
892 var delay = options.delay;
893
894 if (typeof delay === 'number' && delay > 0) {
895 startTime = currentTime + delay;
896 } else {
897 startTime = currentTime;
898 }
899
900 timeout = typeof options.timeout === 'number' ? options.timeout : timeoutForPriorityLevel(priorityLevel);
901 } else {
902 timeout = timeoutForPriorityLevel(priorityLevel);
903 startTime = currentTime;
904 }
905
906 var expirationTime = startTime + timeout;
907 var newTask = {
908 id: taskIdCounter++,
909 callback: callback,
910 priorityLevel: priorityLevel,
911 startTime: startTime,
912 expirationTime: expirationTime,
913 sortIndex: -1
914 };
915
916 if (enableProfiling) {
917 newTask.isQueued = false;
918 }
919
920 if (startTime > currentTime) {
921 // This is a delayed task.
922 newTask.sortIndex = startTime;
923 push(timerQueue, newTask);
924
925 if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
926 // All tasks are delayed, and this is the task with the earliest delay.
927 if (isHostTimeoutScheduled) {
928 // Cancel an existing timeout.
929 cancelHostTimeout();
930 } else {
931 isHostTimeoutScheduled = true;
932 } // Schedule a timeout.
933
934
935 requestHostTimeout(handleTimeout, startTime - currentTime);
936 }
937 } else {
938 newTask.sortIndex = expirationTime;
939 push(taskQueue, newTask);
940
941 if (enableProfiling) {
942 markTaskStart(newTask, currentTime);
943 newTask.isQueued = true;
944 } // Schedule a host callback, if needed. If we're already performing work,
945 // wait until the next time we yield.
946
947
948 if (!isHostCallbackScheduled && !isPerformingWork) {
949 isHostCallbackScheduled = true;
950 requestHostCallback(flushWork);
951 }
952 }
953
954 return newTask;
955}
956
957function unstable_pauseExecution() {
958 isSchedulerPaused = true;
959}
960
961function unstable_continueExecution() {
962 isSchedulerPaused = false;
963
964 if (!isHostCallbackScheduled && !isPerformingWork) {
965 isHostCallbackScheduled = true;
966 requestHostCallback(flushWork);
967 }
968}
969
970function unstable_getFirstCallbackNode() {
971 return peek(taskQueue);
972}
973
974function unstable_cancelCallback(task) {
975 if (enableProfiling) {
976 if (task.isQueued) {
977 var currentTime = exports.unstable_now();
978 markTaskCanceled(task, currentTime);
979 task.isQueued = false;
980 }
981 } // Null out the callback to indicate the task has been canceled. (Can't
982 // remove from the queue because you can't remove arbitrary nodes from an
983 // array based heap, only the first one.)
984
985
986 task.callback = null;
987}
988
989function unstable_getCurrentPriorityLevel() {
990 return currentPriorityLevel;
991}
992
993function unstable_shouldYield() {
994 var currentTime = exports.unstable_now();
995 advanceTimers(currentTime);
996 var firstTask = peek(taskQueue);
997 return firstTask !== currentTask && currentTask !== null && firstTask !== null && firstTask.callback !== null && firstTask.startTime <= currentTime && firstTask.expirationTime < currentTask.expirationTime || shouldYieldToHost();
998}
999
1000var unstable_requestPaint = requestPaint;
1001var unstable_Profiling = enableProfiling ? {
1002 startLoggingProfilingEvents: startLoggingProfilingEvents,
1003 stopLoggingProfilingEvents: stopLoggingProfilingEvents,
1004 sharedProfilingBuffer: sharedProfilingBuffer
1005} : null;
1006
1007exports.unstable_ImmediatePriority = ImmediatePriority;
1008exports.unstable_UserBlockingPriority = UserBlockingPriority;
1009exports.unstable_NormalPriority = NormalPriority;
1010exports.unstable_IdlePriority = IdlePriority;
1011exports.unstable_LowPriority = LowPriority;
1012exports.unstable_runWithPriority = unstable_runWithPriority;
1013exports.unstable_next = unstable_next;
1014exports.unstable_scheduleCallback = unstable_scheduleCallback;
1015exports.unstable_cancelCallback = unstable_cancelCallback;
1016exports.unstable_wrapCallback = unstable_wrapCallback;
1017exports.unstable_getCurrentPriorityLevel = unstable_getCurrentPriorityLevel;
1018exports.unstable_shouldYield = unstable_shouldYield;
1019exports.unstable_requestPaint = unstable_requestPaint;
1020exports.unstable_continueExecution = unstable_continueExecution;
1021exports.unstable_pauseExecution = unstable_pauseExecution;
1022exports.unstable_getFirstCallbackNode = unstable_getFirstCallbackNode;
1023exports.unstable_Profiling = unstable_Profiling;
1024 })();
1025}