495 Object.seal(refObject); |
485 Object.seal(refObject); |
496 } |
486 } |
497 return refObject; |
487 return refObject; |
498 } |
488 } |
499 |
489 |
500 var enableSchedulerDebugging = false; |
|
501 |
|
502 /* eslint-disable no-var */ |
|
503 |
|
504 // TODO: Use symbols? |
|
505 var ImmediatePriority = 1; |
|
506 var UserBlockingPriority = 2; |
|
507 var NormalPriority = 3; |
|
508 var LowPriority = 4; |
|
509 var IdlePriority = 5; |
|
510 |
|
511 // Max 31 bit integer. The max integer size in V8 for 32-bit systems. |
|
512 // Math.pow(2, 30) - 1 |
|
513 // 0b111111111111111111111111111111 |
|
514 var maxSigned31BitInt = 1073741823; |
|
515 |
|
516 // Times out immediately |
|
517 var IMMEDIATE_PRIORITY_TIMEOUT = -1; |
|
518 // Eventually times out |
|
519 var USER_BLOCKING_PRIORITY = 250; |
|
520 var NORMAL_PRIORITY_TIMEOUT = 5000; |
|
521 var LOW_PRIORITY_TIMEOUT = 10000; |
|
522 // Never times out |
|
523 var IDLE_PRIORITY = maxSigned31BitInt; |
|
524 |
|
525 // Callbacks are stored as a circular, doubly linked list. |
|
526 var firstCallbackNode = null; |
|
527 |
|
528 var currentDidTimeout = false; |
|
529 // Pausing the scheduler is useful for debugging. |
|
530 var isSchedulerPaused = false; |
|
531 |
|
532 var currentPriorityLevel = NormalPriority; |
|
533 var currentEventStartTime = -1; |
|
534 var currentExpirationTime = -1; |
|
535 |
|
536 // This is set when a callback is being executed, to prevent re-entrancy. |
|
537 var isExecutingCallback = false; |
|
538 |
|
539 var isHostCallbackScheduled = false; |
|
540 |
|
541 var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function'; |
|
542 |
|
543 function ensureHostCallbackIsScheduled() { |
|
544 if (isExecutingCallback) { |
|
545 // Don't schedule work yet; wait until the next time we yield. |
|
546 return; |
|
547 } |
|
548 // Schedule the host callback using the earliest expiration in the list. |
|
549 var expirationTime = firstCallbackNode.expirationTime; |
|
550 if (!isHostCallbackScheduled) { |
|
551 isHostCallbackScheduled = true; |
|
552 } else { |
|
553 // Cancel the existing host callback. |
|
554 cancelHostCallback(); |
|
555 } |
|
556 requestHostCallback(flushWork, expirationTime); |
|
557 } |
|
558 |
|
559 function flushFirstCallback() { |
|
560 var flushedNode = firstCallbackNode; |
|
561 |
|
562 // Remove the node from the list before calling the callback. That way the |
|
563 // list is in a consistent state even if the callback throws. |
|
564 var next = firstCallbackNode.next; |
|
565 if (firstCallbackNode === next) { |
|
566 // This is the last callback in the list. |
|
567 firstCallbackNode = null; |
|
568 next = null; |
|
569 } else { |
|
570 var lastCallbackNode = firstCallbackNode.previous; |
|
571 firstCallbackNode = lastCallbackNode.next = next; |
|
572 next.previous = lastCallbackNode; |
|
573 } |
|
574 |
|
575 flushedNode.next = flushedNode.previous = null; |
|
576 |
|
577 // Now it's safe to call the callback. |
|
578 var callback = flushedNode.callback; |
|
579 var expirationTime = flushedNode.expirationTime; |
|
580 var priorityLevel = flushedNode.priorityLevel; |
|
581 var previousPriorityLevel = currentPriorityLevel; |
|
582 var previousExpirationTime = currentExpirationTime; |
|
583 currentPriorityLevel = priorityLevel; |
|
584 currentExpirationTime = expirationTime; |
|
585 var continuationCallback; |
|
586 try { |
|
587 continuationCallback = callback(); |
|
588 } finally { |
|
589 currentPriorityLevel = previousPriorityLevel; |
|
590 currentExpirationTime = previousExpirationTime; |
|
591 } |
|
592 |
|
593 // A callback may return a continuation. The continuation should be scheduled |
|
594 // with the same priority and expiration as the just-finished callback. |
|
595 if (typeof continuationCallback === 'function') { |
|
596 var continuationNode = { |
|
597 callback: continuationCallback, |
|
598 priorityLevel: priorityLevel, |
|
599 expirationTime: expirationTime, |
|
600 next: null, |
|
601 previous: null |
|
602 }; |
|
603 |
|
604 // Insert the new callback into the list, sorted by its expiration. This is |
|
605 // almost the same as the code in `scheduleCallback`, except the callback |
|
606 // is inserted into the list *before* callbacks of equal expiration instead |
|
607 // of after. |
|
608 if (firstCallbackNode === null) { |
|
609 // This is the first callback in the list. |
|
610 firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode; |
|
611 } else { |
|
612 var nextAfterContinuation = null; |
|
613 var node = firstCallbackNode; |
|
614 do { |
|
615 if (node.expirationTime >= expirationTime) { |
|
616 // This callback expires at or after the continuation. We will insert |
|
617 // the continuation *before* this callback. |
|
618 nextAfterContinuation = node; |
|
619 break; |
|
620 } |
|
621 node = node.next; |
|
622 } while (node !== firstCallbackNode); |
|
623 |
|
624 if (nextAfterContinuation === null) { |
|
625 // No equal or lower priority callback was found, which means the new |
|
626 // callback is the lowest priority callback in the list. |
|
627 nextAfterContinuation = firstCallbackNode; |
|
628 } else if (nextAfterContinuation === firstCallbackNode) { |
|
629 // The new callback is the highest priority callback in the list. |
|
630 firstCallbackNode = continuationNode; |
|
631 ensureHostCallbackIsScheduled(); |
|
632 } |
|
633 |
|
634 var previous = nextAfterContinuation.previous; |
|
635 previous.next = nextAfterContinuation.previous = continuationNode; |
|
636 continuationNode.next = nextAfterContinuation; |
|
637 continuationNode.previous = previous; |
|
638 } |
|
639 } |
|
640 } |
|
641 |
|
642 function flushImmediateWork() { |
|
643 if ( |
|
644 // Confirm we've exited the outer most event handler |
|
645 currentEventStartTime === -1 && firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority) { |
|
646 isExecutingCallback = true; |
|
647 try { |
|
648 do { |
|
649 flushFirstCallback(); |
|
650 } while ( |
|
651 // Keep flushing until there are no more immediate callbacks |
|
652 firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority); |
|
653 } finally { |
|
654 isExecutingCallback = false; |
|
655 if (firstCallbackNode !== null) { |
|
656 // There's still work remaining. Request another callback. |
|
657 ensureHostCallbackIsScheduled(); |
|
658 } else { |
|
659 isHostCallbackScheduled = false; |
|
660 } |
|
661 } |
|
662 } |
|
663 } |
|
664 |
|
665 function flushWork(didTimeout) { |
|
666 // Exit right away if we're currently paused |
|
667 |
|
668 if (enableSchedulerDebugging && isSchedulerPaused) { |
|
669 return; |
|
670 } |
|
671 |
|
672 isExecutingCallback = true; |
|
673 var previousDidTimeout = currentDidTimeout; |
|
674 currentDidTimeout = didTimeout; |
|
675 try { |
|
676 if (didTimeout) { |
|
677 // Flush all the expired callbacks without yielding. |
|
678 while (firstCallbackNode !== null && !(enableSchedulerDebugging && isSchedulerPaused)) { |
|
679 // TODO Wrap in feature flag |
|
680 // Read the current time. Flush all the callbacks that expire at or |
|
681 // earlier than that time. Then read the current time again and repeat. |
|
682 // This optimizes for as few performance.now calls as possible. |
|
683 var currentTime = getCurrentTime(); |
|
684 if (firstCallbackNode.expirationTime <= currentTime) { |
|
685 do { |
|
686 flushFirstCallback(); |
|
687 } while (firstCallbackNode !== null && firstCallbackNode.expirationTime <= currentTime && !(enableSchedulerDebugging && isSchedulerPaused)); |
|
688 continue; |
|
689 } |
|
690 break; |
|
691 } |
|
692 } else { |
|
693 // Keep flushing callbacks until we run out of time in the frame. |
|
694 if (firstCallbackNode !== null) { |
|
695 do { |
|
696 if (enableSchedulerDebugging && isSchedulerPaused) { |
|
697 break; |
|
698 } |
|
699 flushFirstCallback(); |
|
700 } while (firstCallbackNode !== null && !shouldYieldToHost()); |
|
701 } |
|
702 } |
|
703 } finally { |
|
704 isExecutingCallback = false; |
|
705 currentDidTimeout = previousDidTimeout; |
|
706 if (firstCallbackNode !== null) { |
|
707 // There's still work remaining. Request another callback. |
|
708 ensureHostCallbackIsScheduled(); |
|
709 } else { |
|
710 isHostCallbackScheduled = false; |
|
711 } |
|
712 // Before exiting, flush all the immediate work that was scheduled. |
|
713 flushImmediateWork(); |
|
714 } |
|
715 } |
|
716 |
|
717 function unstable_runWithPriority(priorityLevel, eventHandler) { |
|
718 switch (priorityLevel) { |
|
719 case ImmediatePriority: |
|
720 case UserBlockingPriority: |
|
721 case NormalPriority: |
|
722 case LowPriority: |
|
723 case IdlePriority: |
|
724 break; |
|
725 default: |
|
726 priorityLevel = NormalPriority; |
|
727 } |
|
728 |
|
729 var previousPriorityLevel = currentPriorityLevel; |
|
730 var previousEventStartTime = currentEventStartTime; |
|
731 currentPriorityLevel = priorityLevel; |
|
732 currentEventStartTime = getCurrentTime(); |
|
733 |
|
734 try { |
|
735 return eventHandler(); |
|
736 } finally { |
|
737 currentPriorityLevel = previousPriorityLevel; |
|
738 currentEventStartTime = previousEventStartTime; |
|
739 |
|
740 // Before exiting, flush all the immediate work that was scheduled. |
|
741 flushImmediateWork(); |
|
742 } |
|
743 } |
|
744 |
|
745 function unstable_next(eventHandler) { |
|
746 var priorityLevel = void 0; |
|
747 switch (currentPriorityLevel) { |
|
748 case ImmediatePriority: |
|
749 case UserBlockingPriority: |
|
750 case NormalPriority: |
|
751 // Shift down to normal priority |
|
752 priorityLevel = NormalPriority; |
|
753 break; |
|
754 default: |
|
755 // Anything lower than normal priority should remain at the current level. |
|
756 priorityLevel = currentPriorityLevel; |
|
757 break; |
|
758 } |
|
759 |
|
760 var previousPriorityLevel = currentPriorityLevel; |
|
761 var previousEventStartTime = currentEventStartTime; |
|
762 currentPriorityLevel = priorityLevel; |
|
763 currentEventStartTime = getCurrentTime(); |
|
764 |
|
765 try { |
|
766 return eventHandler(); |
|
767 } finally { |
|
768 currentPriorityLevel = previousPriorityLevel; |
|
769 currentEventStartTime = previousEventStartTime; |
|
770 |
|
771 // Before exiting, flush all the immediate work that was scheduled. |
|
772 flushImmediateWork(); |
|
773 } |
|
774 } |
|
775 |
|
776 function unstable_wrapCallback(callback) { |
|
777 var parentPriorityLevel = currentPriorityLevel; |
|
778 return function () { |
|
779 // This is a fork of runWithPriority, inlined for performance. |
|
780 var previousPriorityLevel = currentPriorityLevel; |
|
781 var previousEventStartTime = currentEventStartTime; |
|
782 currentPriorityLevel = parentPriorityLevel; |
|
783 currentEventStartTime = getCurrentTime(); |
|
784 |
|
785 try { |
|
786 return callback.apply(this, arguments); |
|
787 } finally { |
|
788 currentPriorityLevel = previousPriorityLevel; |
|
789 currentEventStartTime = previousEventStartTime; |
|
790 flushImmediateWork(); |
|
791 } |
|
792 }; |
|
793 } |
|
794 |
|
795 function unstable_scheduleCallback(callback, deprecated_options) { |
|
796 var startTime = currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime(); |
|
797 |
|
798 var expirationTime; |
|
799 if (typeof deprecated_options === 'object' && deprecated_options !== null && typeof deprecated_options.timeout === 'number') { |
|
800 // FIXME: Remove this branch once we lift expiration times out of React. |
|
801 expirationTime = startTime + deprecated_options.timeout; |
|
802 } else { |
|
803 switch (currentPriorityLevel) { |
|
804 case ImmediatePriority: |
|
805 expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT; |
|
806 break; |
|
807 case UserBlockingPriority: |
|
808 expirationTime = startTime + USER_BLOCKING_PRIORITY; |
|
809 break; |
|
810 case IdlePriority: |
|
811 expirationTime = startTime + IDLE_PRIORITY; |
|
812 break; |
|
813 case LowPriority: |
|
814 expirationTime = startTime + LOW_PRIORITY_TIMEOUT; |
|
815 break; |
|
816 case NormalPriority: |
|
817 default: |
|
818 expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT; |
|
819 } |
|
820 } |
|
821 |
|
822 var newNode = { |
|
823 callback: callback, |
|
824 priorityLevel: currentPriorityLevel, |
|
825 expirationTime: expirationTime, |
|
826 next: null, |
|
827 previous: null |
|
828 }; |
|
829 |
|
830 // Insert the new callback into the list, ordered first by expiration, then |
|
831 // by insertion. So the new callback is inserted any other callback with |
|
832 // equal expiration. |
|
833 if (firstCallbackNode === null) { |
|
834 // This is the first callback in the list. |
|
835 firstCallbackNode = newNode.next = newNode.previous = newNode; |
|
836 ensureHostCallbackIsScheduled(); |
|
837 } else { |
|
838 var next = null; |
|
839 var node = firstCallbackNode; |
|
840 do { |
|
841 if (node.expirationTime > expirationTime) { |
|
842 // The new callback expires before this one. |
|
843 next = node; |
|
844 break; |
|
845 } |
|
846 node = node.next; |
|
847 } while (node !== firstCallbackNode); |
|
848 |
|
849 if (next === null) { |
|
850 // No callback with a later expiration was found, which means the new |
|
851 // callback has the latest expiration in the list. |
|
852 next = firstCallbackNode; |
|
853 } else if (next === firstCallbackNode) { |
|
854 // The new callback has the earliest expiration in the entire list. |
|
855 firstCallbackNode = newNode; |
|
856 ensureHostCallbackIsScheduled(); |
|
857 } |
|
858 |
|
859 var previous = next.previous; |
|
860 previous.next = next.previous = newNode; |
|
861 newNode.next = next; |
|
862 newNode.previous = previous; |
|
863 } |
|
864 |
|
865 return newNode; |
|
866 } |
|
867 |
|
868 function unstable_pauseExecution() { |
|
869 isSchedulerPaused = true; |
|
870 } |
|
871 |
|
872 function unstable_continueExecution() { |
|
873 isSchedulerPaused = false; |
|
874 if (firstCallbackNode !== null) { |
|
875 ensureHostCallbackIsScheduled(); |
|
876 } |
|
877 } |
|
878 |
|
879 function unstable_getFirstCallbackNode() { |
|
880 return firstCallbackNode; |
|
881 } |
|
882 |
|
883 function unstable_cancelCallback(callbackNode) { |
|
884 var next = callbackNode.next; |
|
885 if (next === null) { |
|
886 // Already cancelled. |
|
887 return; |
|
888 } |
|
889 |
|
890 if (next === callbackNode) { |
|
891 // This is the only scheduled callback. Clear the list. |
|
892 firstCallbackNode = null; |
|
893 } else { |
|
894 // Remove the callback from its position in the list. |
|
895 if (callbackNode === firstCallbackNode) { |
|
896 firstCallbackNode = next; |
|
897 } |
|
898 var previous = callbackNode.previous; |
|
899 previous.next = next; |
|
900 next.previous = previous; |
|
901 } |
|
902 |
|
903 callbackNode.next = callbackNode.previous = null; |
|
904 } |
|
905 |
|
906 function unstable_getCurrentPriorityLevel() { |
|
907 return currentPriorityLevel; |
|
908 } |
|
909 |
|
910 function unstable_shouldYield() { |
|
911 return !currentDidTimeout && (firstCallbackNode !== null && firstCallbackNode.expirationTime < currentExpirationTime || shouldYieldToHost()); |
|
912 } |
|
913 |
|
914 // The remaining code is essentially a polyfill for requestIdleCallback. It |
|
915 // works by scheduling a requestAnimationFrame, storing the time for the start |
|
916 // of the frame, then scheduling a postMessage which gets scheduled after paint. |
|
917 // Within the postMessage handler do as much work as possible until time + frame |
|
918 // rate. By separating the idle call into a separate event tick we ensure that |
|
919 // layout, paint and other browser work is counted against the available time. |
|
920 // The frame rate is dynamically adjusted. |
|
921 |
|
922 // We capture a local reference to any global, in case it gets polyfilled after |
|
923 // this module is initially evaluated. We want to be using a |
|
924 // consistent implementation. |
|
925 var localDate = Date; |
|
926 |
|
927 // This initialization code may run even on server environments if a component |
|
928 // just imports ReactDOM (e.g. for findDOMNode). Some environments might not |
|
929 // have setTimeout or clearTimeout. However, we always expect them to be defined |
|
930 // on the client. https://github.com/facebook/react/pull/13088 |
|
931 var localSetTimeout = typeof setTimeout === 'function' ? setTimeout : undefined; |
|
932 var localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : undefined; |
|
933 |
|
934 // We don't expect either of these to necessarily be defined, but we will error |
|
935 // later if they are missing on the client. |
|
936 var localRequestAnimationFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : undefined; |
|
937 var localCancelAnimationFrame = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : undefined; |
|
938 |
|
939 var getCurrentTime; |
|
940 |
|
941 // requestAnimationFrame does not run when the tab is in the background. If |
|
942 // we're backgrounded we prefer for that work to happen so that the page |
|
943 // continues to load in the background. So we also schedule a 'setTimeout' as |
|
944 // a fallback. |
|
945 // TODO: Need a better heuristic for backgrounded work. |
|
946 var ANIMATION_FRAME_TIMEOUT = 100; |
|
947 var rAFID; |
|
948 var rAFTimeoutID; |
|
949 var requestAnimationFrameWithTimeout = function (callback) { |
|
950 // schedule rAF and also a setTimeout |
|
951 rAFID = localRequestAnimationFrame(function (timestamp) { |
|
952 // cancel the setTimeout |
|
953 localClearTimeout(rAFTimeoutID); |
|
954 callback(timestamp); |
|
955 }); |
|
956 rAFTimeoutID = localSetTimeout(function () { |
|
957 // cancel the requestAnimationFrame |
|
958 localCancelAnimationFrame(rAFID); |
|
959 callback(getCurrentTime()); |
|
960 }, ANIMATION_FRAME_TIMEOUT); |
|
961 }; |
|
962 |
|
963 if (hasNativePerformanceNow) { |
|
964 var Performance = performance; |
|
965 getCurrentTime = function () { |
|
966 return Performance.now(); |
|
967 }; |
|
968 } else { |
|
969 getCurrentTime = function () { |
|
970 return localDate.now(); |
|
971 }; |
|
972 } |
|
973 |
|
974 var requestHostCallback; |
|
975 var cancelHostCallback; |
|
976 var shouldYieldToHost; |
|
977 |
|
978 var globalValue = null; |
|
979 if (typeof window !== 'undefined') { |
|
980 globalValue = window; |
|
981 } else if (typeof global !== 'undefined') { |
|
982 globalValue = global; |
|
983 } |
|
984 |
|
985 if (globalValue && globalValue._schedMock) { |
|
986 // Dynamic injection, only for testing purposes. |
|
987 var globalImpl = globalValue._schedMock; |
|
988 requestHostCallback = globalImpl[0]; |
|
989 cancelHostCallback = globalImpl[1]; |
|
990 shouldYieldToHost = globalImpl[2]; |
|
991 getCurrentTime = globalImpl[3]; |
|
992 } else if ( |
|
993 // If Scheduler runs in a non-DOM environment, it falls back to a naive |
|
994 // implementation using setTimeout. |
|
995 typeof window === 'undefined' || |
|
996 // Check if MessageChannel is supported, too. |
|
997 typeof MessageChannel !== 'function') { |
|
998 // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, |
|
999 // fallback to a naive implementation. |
|
1000 var _callback = null; |
|
1001 var _flushCallback = function (didTimeout) { |
|
1002 if (_callback !== null) { |
|
1003 try { |
|
1004 _callback(didTimeout); |
|
1005 } finally { |
|
1006 _callback = null; |
|
1007 } |
|
1008 } |
|
1009 }; |
|
1010 requestHostCallback = function (cb, ms) { |
|
1011 if (_callback !== null) { |
|
1012 // Protect against re-entrancy. |
|
1013 setTimeout(requestHostCallback, 0, cb); |
|
1014 } else { |
|
1015 _callback = cb; |
|
1016 setTimeout(_flushCallback, 0, false); |
|
1017 } |
|
1018 }; |
|
1019 cancelHostCallback = function () { |
|
1020 _callback = null; |
|
1021 }; |
|
1022 shouldYieldToHost = function () { |
|
1023 return false; |
|
1024 }; |
|
1025 } else { |
|
1026 if (typeof console !== 'undefined') { |
|
1027 // TODO: Remove fb.me link |
|
1028 if (typeof localRequestAnimationFrame !== 'function') { |
|
1029 console.error("This browser doesn't support requestAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); |
|
1030 } |
|
1031 if (typeof localCancelAnimationFrame !== 'function') { |
|
1032 console.error("This browser doesn't support cancelAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); |
|
1033 } |
|
1034 } |
|
1035 |
|
1036 var scheduledHostCallback = null; |
|
1037 var isMessageEventScheduled = false; |
|
1038 var timeoutTime = -1; |
|
1039 |
|
1040 var isAnimationFrameScheduled = false; |
|
1041 |
|
1042 var isFlushingHostCallback = false; |
|
1043 |
|
1044 var frameDeadline = 0; |
|
1045 // We start out assuming that we run at 30fps but then the heuristic tracking |
|
1046 // will adjust this value to a faster fps if we get more frequent animation |
|
1047 // frames. |
|
1048 var previousFrameTime = 33; |
|
1049 var activeFrameTime = 33; |
|
1050 |
|
1051 shouldYieldToHost = function () { |
|
1052 return frameDeadline <= getCurrentTime(); |
|
1053 }; |
|
1054 |
|
1055 // We use the postMessage trick to defer idle work until after the repaint. |
|
1056 var channel = new MessageChannel(); |
|
1057 var port = channel.port2; |
|
1058 channel.port1.onmessage = function (event) { |
|
1059 isMessageEventScheduled = false; |
|
1060 |
|
1061 var prevScheduledCallback = scheduledHostCallback; |
|
1062 var prevTimeoutTime = timeoutTime; |
|
1063 scheduledHostCallback = null; |
|
1064 timeoutTime = -1; |
|
1065 |
|
1066 var currentTime = getCurrentTime(); |
|
1067 |
|
1068 var didTimeout = false; |
|
1069 if (frameDeadline - currentTime <= 0) { |
|
1070 // There's no time left in this idle period. Check if the callback has |
|
1071 // a timeout and whether it's been exceeded. |
|
1072 if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) { |
|
1073 // Exceeded the timeout. Invoke the callback even though there's no |
|
1074 // time left. |
|
1075 didTimeout = true; |
|
1076 } else { |
|
1077 // No timeout. |
|
1078 if (!isAnimationFrameScheduled) { |
|
1079 // Schedule another animation callback so we retry later. |
|
1080 isAnimationFrameScheduled = true; |
|
1081 requestAnimationFrameWithTimeout(animationTick); |
|
1082 } |
|
1083 // Exit without invoking the callback. |
|
1084 scheduledHostCallback = prevScheduledCallback; |
|
1085 timeoutTime = prevTimeoutTime; |
|
1086 return; |
|
1087 } |
|
1088 } |
|
1089 |
|
1090 if (prevScheduledCallback !== null) { |
|
1091 isFlushingHostCallback = true; |
|
1092 try { |
|
1093 prevScheduledCallback(didTimeout); |
|
1094 } finally { |
|
1095 isFlushingHostCallback = false; |
|
1096 } |
|
1097 } |
|
1098 }; |
|
1099 |
|
1100 var animationTick = function (rafTime) { |
|
1101 if (scheduledHostCallback !== null) { |
|
1102 // Eagerly schedule the next animation callback at the beginning of the |
|
1103 // frame. If the scheduler queue is not empty at the end of the frame, it |
|
1104 // will continue flushing inside that callback. If the queue *is* empty, |
|
1105 // then it will exit immediately. Posting the callback at the start of the |
|
1106 // frame ensures it's fired within the earliest possible frame. If we |
|
1107 // waited until the end of the frame to post the callback, we risk the |
|
1108 // browser skipping a frame and not firing the callback until the frame |
|
1109 // after that. |
|
1110 requestAnimationFrameWithTimeout(animationTick); |
|
1111 } else { |
|
1112 // No pending work. Exit. |
|
1113 isAnimationFrameScheduled = false; |
|
1114 return; |
|
1115 } |
|
1116 |
|
1117 var nextFrameTime = rafTime - frameDeadline + activeFrameTime; |
|
1118 if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) { |
|
1119 if (nextFrameTime < 8) { |
|
1120 // Defensive coding. We don't support higher frame rates than 120hz. |
|
1121 // If the calculated frame time gets lower than 8, it is probably a bug. |
|
1122 nextFrameTime = 8; |
|
1123 } |
|
1124 // If one frame goes long, then the next one can be short to catch up. |
|
1125 // If two frames are short in a row, then that's an indication that we |
|
1126 // actually have a higher frame rate than what we're currently optimizing. |
|
1127 // We adjust our heuristic dynamically accordingly. For example, if we're |
|
1128 // running on 120hz display or 90hz VR display. |
|
1129 // Take the max of the two in case one of them was an anomaly due to |
|
1130 // missed frame deadlines. |
|
1131 activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime; |
|
1132 } else { |
|
1133 previousFrameTime = nextFrameTime; |
|
1134 } |
|
1135 frameDeadline = rafTime + activeFrameTime; |
|
1136 if (!isMessageEventScheduled) { |
|
1137 isMessageEventScheduled = true; |
|
1138 port.postMessage(undefined); |
|
1139 } |
|
1140 }; |
|
1141 |
|
1142 requestHostCallback = function (callback, absoluteTimeout) { |
|
1143 scheduledHostCallback = callback; |
|
1144 timeoutTime = absoluteTimeout; |
|
1145 if (isFlushingHostCallback || absoluteTimeout < 0) { |
|
1146 // Don't wait for the next frame. Continue working ASAP, in a new event. |
|
1147 port.postMessage(undefined); |
|
1148 } else if (!isAnimationFrameScheduled) { |
|
1149 // If rAF didn't already schedule one, we need to schedule a frame. |
|
1150 // TODO: If this rAF doesn't materialize because the browser throttles, we |
|
1151 // might want to still have setTimeout trigger rIC as a backup to ensure |
|
1152 // that we keep performing work. |
|
1153 isAnimationFrameScheduled = true; |
|
1154 requestAnimationFrameWithTimeout(animationTick); |
|
1155 } |
|
1156 }; |
|
1157 |
|
1158 cancelHostCallback = function () { |
|
1159 scheduledHostCallback = null; |
|
1160 isMessageEventScheduled = false; |
|
1161 timeoutTime = -1; |
|
1162 }; |
|
1163 } |
|
1164 |
|
1165 // Helps identify side effects in begin-phase lifecycle hooks and setState reducers: |
|
1166 |
|
1167 |
|
1168 // In some cases, StrictMode should also double-render lifecycles. |
|
1169 // This can be confusing for tests though, |
|
1170 // And it can be bad for performance in production. |
|
1171 // This feature flag can be used to control the behavior: |
|
1172 |
|
1173 |
|
1174 // To preserve the "Pause on caught exceptions" behavior of the debugger, we |
|
1175 // replay the begin phase of a failed component inside invokeGuardedCallback. |
|
1176 |
|
1177 |
|
1178 // Warn about deprecated, async-unsafe lifecycles; relates to RFC #6: |
|
1179 |
|
1180 |
|
1181 // Gather advanced timing metrics for Profiler subtrees. |
|
1182 |
|
1183 |
|
1184 // Trace which interactions trigger each commit. |
|
1185 var enableSchedulerTracing = true; |
|
1186 |
|
1187 // Only used in www builds. |
|
1188 // TODO: true? Here it might just be false. |
|
1189 |
|
1190 // Only used in www builds. |
|
1191 |
|
1192 |
|
1193 // Only used in www builds. |
|
1194 |
|
1195 |
|
1196 // React Fire: prevent the value and checked attributes from syncing |
|
1197 // with their related DOM properties |
|
1198 |
|
1199 |
|
1200 // These APIs will no longer be "unstable" in the upcoming 16.7 release, |
|
1201 // Control this behavior with a flag to support 16.6 minor releases in the meanwhile. |
|
1202 var enableStableConcurrentModeAPIs = false; |
|
1203 |
|
1204 var DEFAULT_THREAD_ID = 0; |
|
1205 |
|
1206 // Counters used to generate unique IDs. |
|
1207 var interactionIDCounter = 0; |
|
1208 var threadIDCounter = 0; |
|
1209 |
|
1210 // Set of currently traced interactions. |
|
1211 // Interactions "stack"– |
|
1212 // Meaning that newly traced interactions are appended to the previously active set. |
|
1213 // When an interaction goes out of scope, the previous set (if any) is restored. |
|
1214 var interactionsRef = null; |
|
1215 |
|
1216 // Listener(s) to notify when interactions begin and end. |
|
1217 var subscriberRef = null; |
|
1218 |
|
1219 if (enableSchedulerTracing) { |
|
1220 interactionsRef = { |
|
1221 current: new Set() |
|
1222 }; |
|
1223 subscriberRef = { |
|
1224 current: null |
|
1225 }; |
|
1226 } |
|
1227 |
|
1228 function unstable_clear(callback) { |
|
1229 if (!enableSchedulerTracing) { |
|
1230 return callback(); |
|
1231 } |
|
1232 |
|
1233 var prevInteractions = interactionsRef.current; |
|
1234 interactionsRef.current = new Set(); |
|
1235 |
|
1236 try { |
|
1237 return callback(); |
|
1238 } finally { |
|
1239 interactionsRef.current = prevInteractions; |
|
1240 } |
|
1241 } |
|
1242 |
|
1243 function unstable_getCurrent() { |
|
1244 if (!enableSchedulerTracing) { |
|
1245 return null; |
|
1246 } else { |
|
1247 return interactionsRef.current; |
|
1248 } |
|
1249 } |
|
1250 |
|
1251 function unstable_getThreadID() { |
|
1252 return ++threadIDCounter; |
|
1253 } |
|
1254 |
|
1255 function unstable_trace(name, timestamp, callback) { |
|
1256 var threadID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DEFAULT_THREAD_ID; |
|
1257 |
|
1258 if (!enableSchedulerTracing) { |
|
1259 return callback(); |
|
1260 } |
|
1261 |
|
1262 var interaction = { |
|
1263 __count: 1, |
|
1264 id: interactionIDCounter++, |
|
1265 name: name, |
|
1266 timestamp: timestamp |
|
1267 }; |
|
1268 |
|
1269 var prevInteractions = interactionsRef.current; |
|
1270 |
|
1271 // Traced interactions should stack/accumulate. |
|
1272 // To do that, clone the current interactions. |
|
1273 // The previous set will be restored upon completion. |
|
1274 var interactions = new Set(prevInteractions); |
|
1275 interactions.add(interaction); |
|
1276 interactionsRef.current = interactions; |
|
1277 |
|
1278 var subscriber = subscriberRef.current; |
|
1279 var returnValue = void 0; |
|
1280 |
|
1281 try { |
|
1282 if (subscriber !== null) { |
|
1283 subscriber.onInteractionTraced(interaction); |
|
1284 } |
|
1285 } finally { |
|
1286 try { |
|
1287 if (subscriber !== null) { |
|
1288 subscriber.onWorkStarted(interactions, threadID); |
|
1289 } |
|
1290 } finally { |
|
1291 try { |
|
1292 returnValue = callback(); |
|
1293 } finally { |
|
1294 interactionsRef.current = prevInteractions; |
|
1295 |
|
1296 try { |
|
1297 if (subscriber !== null) { |
|
1298 subscriber.onWorkStopped(interactions, threadID); |
|
1299 } |
|
1300 } finally { |
|
1301 interaction.__count--; |
|
1302 |
|
1303 // If no async work was scheduled for this interaction, |
|
1304 // Notify subscribers that it's completed. |
|
1305 if (subscriber !== null && interaction.__count === 0) { |
|
1306 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
1307 } |
|
1308 } |
|
1309 } |
|
1310 } |
|
1311 } |
|
1312 |
|
1313 return returnValue; |
|
1314 } |
|
1315 |
|
1316 function unstable_wrap(callback) { |
|
1317 var threadID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_THREAD_ID; |
|
1318 |
|
1319 if (!enableSchedulerTracing) { |
|
1320 return callback; |
|
1321 } |
|
1322 |
|
1323 var wrappedInteractions = interactionsRef.current; |
|
1324 |
|
1325 var subscriber = subscriberRef.current; |
|
1326 if (subscriber !== null) { |
|
1327 subscriber.onWorkScheduled(wrappedInteractions, threadID); |
|
1328 } |
|
1329 |
|
1330 // Update the pending async work count for the current interactions. |
|
1331 // Update after calling subscribers in case of error. |
|
1332 wrappedInteractions.forEach(function (interaction) { |
|
1333 interaction.__count++; |
|
1334 }); |
|
1335 |
|
1336 var hasRun = false; |
|
1337 |
|
1338 function wrapped() { |
|
1339 var prevInteractions = interactionsRef.current; |
|
1340 interactionsRef.current = wrappedInteractions; |
|
1341 |
|
1342 subscriber = subscriberRef.current; |
|
1343 |
|
1344 try { |
|
1345 var returnValue = void 0; |
|
1346 |
|
1347 try { |
|
1348 if (subscriber !== null) { |
|
1349 subscriber.onWorkStarted(wrappedInteractions, threadID); |
|
1350 } |
|
1351 } finally { |
|
1352 try { |
|
1353 returnValue = callback.apply(undefined, arguments); |
|
1354 } finally { |
|
1355 interactionsRef.current = prevInteractions; |
|
1356 |
|
1357 if (subscriber !== null) { |
|
1358 subscriber.onWorkStopped(wrappedInteractions, threadID); |
|
1359 } |
|
1360 } |
|
1361 } |
|
1362 |
|
1363 return returnValue; |
|
1364 } finally { |
|
1365 if (!hasRun) { |
|
1366 // We only expect a wrapped function to be executed once, |
|
1367 // But in the event that it's executed more than once– |
|
1368 // Only decrement the outstanding interaction counts once. |
|
1369 hasRun = true; |
|
1370 |
|
1371 // Update pending async counts for all wrapped interactions. |
|
1372 // If this was the last scheduled async work for any of them, |
|
1373 // Mark them as completed. |
|
1374 wrappedInteractions.forEach(function (interaction) { |
|
1375 interaction.__count--; |
|
1376 |
|
1377 if (subscriber !== null && interaction.__count === 0) { |
|
1378 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
1379 } |
|
1380 }); |
|
1381 } |
|
1382 } |
|
1383 } |
|
1384 |
|
1385 wrapped.cancel = function cancel() { |
|
1386 subscriber = subscriberRef.current; |
|
1387 |
|
1388 try { |
|
1389 if (subscriber !== null) { |
|
1390 subscriber.onWorkCanceled(wrappedInteractions, threadID); |
|
1391 } |
|
1392 } finally { |
|
1393 // Update pending async counts for all wrapped interactions. |
|
1394 // If this was the last scheduled async work for any of them, |
|
1395 // Mark them as completed. |
|
1396 wrappedInteractions.forEach(function (interaction) { |
|
1397 interaction.__count--; |
|
1398 |
|
1399 if (subscriber && interaction.__count === 0) { |
|
1400 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
1401 } |
|
1402 }); |
|
1403 } |
|
1404 }; |
|
1405 |
|
1406 return wrapped; |
|
1407 } |
|
1408 |
|
1409 var subscribers = null; |
|
1410 if (enableSchedulerTracing) { |
|
1411 subscribers = new Set(); |
|
1412 } |
|
1413 |
|
1414 function unstable_subscribe(subscriber) { |
|
1415 if (enableSchedulerTracing) { |
|
1416 subscribers.add(subscriber); |
|
1417 |
|
1418 if (subscribers.size === 1) { |
|
1419 subscriberRef.current = { |
|
1420 onInteractionScheduledWorkCompleted: onInteractionScheduledWorkCompleted, |
|
1421 onInteractionTraced: onInteractionTraced, |
|
1422 onWorkCanceled: onWorkCanceled, |
|
1423 onWorkScheduled: onWorkScheduled, |
|
1424 onWorkStarted: onWorkStarted, |
|
1425 onWorkStopped: onWorkStopped |
|
1426 }; |
|
1427 } |
|
1428 } |
|
1429 } |
|
1430 |
|
1431 function unstable_unsubscribe(subscriber) { |
|
1432 if (enableSchedulerTracing) { |
|
1433 subscribers.delete(subscriber); |
|
1434 |
|
1435 if (subscribers.size === 0) { |
|
1436 subscriberRef.current = null; |
|
1437 } |
|
1438 } |
|
1439 } |
|
1440 |
|
1441 function onInteractionTraced(interaction) { |
|
1442 var didCatchError = false; |
|
1443 var caughtError = null; |
|
1444 |
|
1445 subscribers.forEach(function (subscriber) { |
|
1446 try { |
|
1447 subscriber.onInteractionTraced(interaction); |
|
1448 } catch (error) { |
|
1449 if (!didCatchError) { |
|
1450 didCatchError = true; |
|
1451 caughtError = error; |
|
1452 } |
|
1453 } |
|
1454 }); |
|
1455 |
|
1456 if (didCatchError) { |
|
1457 throw caughtError; |
|
1458 } |
|
1459 } |
|
1460 |
|
1461 function onInteractionScheduledWorkCompleted(interaction) { |
|
1462 var didCatchError = false; |
|
1463 var caughtError = null; |
|
1464 |
|
1465 subscribers.forEach(function (subscriber) { |
|
1466 try { |
|
1467 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
1468 } catch (error) { |
|
1469 if (!didCatchError) { |
|
1470 didCatchError = true; |
|
1471 caughtError = error; |
|
1472 } |
|
1473 } |
|
1474 }); |
|
1475 |
|
1476 if (didCatchError) { |
|
1477 throw caughtError; |
|
1478 } |
|
1479 } |
|
1480 |
|
1481 function onWorkScheduled(interactions, threadID) { |
|
1482 var didCatchError = false; |
|
1483 var caughtError = null; |
|
1484 |
|
1485 subscribers.forEach(function (subscriber) { |
|
1486 try { |
|
1487 subscriber.onWorkScheduled(interactions, threadID); |
|
1488 } catch (error) { |
|
1489 if (!didCatchError) { |
|
1490 didCatchError = true; |
|
1491 caughtError = error; |
|
1492 } |
|
1493 } |
|
1494 }); |
|
1495 |
|
1496 if (didCatchError) { |
|
1497 throw caughtError; |
|
1498 } |
|
1499 } |
|
1500 |
|
1501 function onWorkStarted(interactions, threadID) { |
|
1502 var didCatchError = false; |
|
1503 var caughtError = null; |
|
1504 |
|
1505 subscribers.forEach(function (subscriber) { |
|
1506 try { |
|
1507 subscriber.onWorkStarted(interactions, threadID); |
|
1508 } catch (error) { |
|
1509 if (!didCatchError) { |
|
1510 didCatchError = true; |
|
1511 caughtError = error; |
|
1512 } |
|
1513 } |
|
1514 }); |
|
1515 |
|
1516 if (didCatchError) { |
|
1517 throw caughtError; |
|
1518 } |
|
1519 } |
|
1520 |
|
1521 function onWorkStopped(interactions, threadID) { |
|
1522 var didCatchError = false; |
|
1523 var caughtError = null; |
|
1524 |
|
1525 subscribers.forEach(function (subscriber) { |
|
1526 try { |
|
1527 subscriber.onWorkStopped(interactions, threadID); |
|
1528 } catch (error) { |
|
1529 if (!didCatchError) { |
|
1530 didCatchError = true; |
|
1531 caughtError = error; |
|
1532 } |
|
1533 } |
|
1534 }); |
|
1535 |
|
1536 if (didCatchError) { |
|
1537 throw caughtError; |
|
1538 } |
|
1539 } |
|
1540 |
|
1541 function onWorkCanceled(interactions, threadID) { |
|
1542 var didCatchError = false; |
|
1543 var caughtError = null; |
|
1544 |
|
1545 subscribers.forEach(function (subscriber) { |
|
1546 try { |
|
1547 subscriber.onWorkCanceled(interactions, threadID); |
|
1548 } catch (error) { |
|
1549 if (!didCatchError) { |
|
1550 didCatchError = true; |
|
1551 caughtError = error; |
|
1552 } |
|
1553 } |
|
1554 }); |
|
1555 |
|
1556 if (didCatchError) { |
|
1557 throw caughtError; |
|
1558 } |
|
1559 } |
|
1560 |
|
1561 /** |
490 /** |
1562 * Keeps track of the current dispatcher. |
491 * Keeps track of the current dispatcher. |
1563 */ |
492 */ |
1564 var ReactCurrentDispatcher = { |
493 var ReactCurrentDispatcher = { |
1565 /** |
494 /** |
1566 * @internal |
495 * @internal |
1567 * @type {ReactComponent} |
496 * @type {ReactComponent} |
1568 */ |
497 */ |
1569 current: null |
498 current: null |
|
499 }; |
|
500 |
|
501 /** |
|
502 * Keeps track of the current batch's configuration such as how long an update |
|
503 * should suspend for if it needs to. |
|
504 */ |
|
505 var ReactCurrentBatchConfig = { |
|
506 suspense: null |
1570 }; |
507 }; |
1571 |
508 |
1572 /** |
509 /** |
1573 * Keeps track of the current owner. |
510 * Keeps track of the current owner. |
1574 * |
511 * |
3078 } |
2192 } |
3079 validatePropTypes(newElement); |
2193 validatePropTypes(newElement); |
3080 return newElement; |
2194 return newElement; |
3081 } |
2195 } |
3082 |
2196 |
|
2197 var enableSchedulerDebugging = false; |
|
2198 var enableIsInputPending = false; |
|
2199 var requestIdleCallbackBeforeFirstFrame = false; |
|
2200 var requestTimerEventBeforeFirstFrame = false; |
|
2201 var enableMessageLoopImplementation = false; |
|
2202 |
|
2203 // The DOM Scheduler implementation is similar to requestIdleCallback. It |
|
2204 // works by scheduling a requestAnimationFrame, storing the time for the start |
|
2205 // of the frame, then scheduling a postMessage which gets scheduled after paint. |
|
2206 // Within the postMessage handler do as much work as possible until time + frame |
|
2207 // rate. By separating the idle call into a separate event tick we ensure that |
|
2208 // layout, paint and other browser work is counted against the available time. |
|
2209 // The frame rate is dynamically adjusted. |
|
2210 |
|
2211 var requestHostCallback = void 0; |
|
2212 |
|
2213 var requestHostTimeout = void 0; |
|
2214 var cancelHostTimeout = void 0; |
|
2215 var shouldYieldToHost = void 0; |
|
2216 var requestPaint = void 0; |
|
2217 var getCurrentTime = void 0; |
|
2218 var forceFrameRate = void 0; |
|
2219 |
|
2220 if ( |
|
2221 // If Scheduler runs in a non-DOM environment, it falls back to a naive |
|
2222 // implementation using setTimeout. |
|
2223 typeof window === 'undefined' || |
|
2224 // Check if MessageChannel is supported, too. |
|
2225 typeof MessageChannel !== 'function') { |
|
2226 // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, |
|
2227 // fallback to a naive implementation. |
|
2228 var _callback = null; |
|
2229 var _timeoutID = null; |
|
2230 var _flushCallback = function () { |
|
2231 if (_callback !== null) { |
|
2232 try { |
|
2233 var currentTime = getCurrentTime(); |
|
2234 var hasRemainingTime = true; |
|
2235 _callback(hasRemainingTime, currentTime); |
|
2236 _callback = null; |
|
2237 } catch (e) { |
|
2238 setTimeout(_flushCallback, 0); |
|
2239 throw e; |
|
2240 } |
|
2241 } |
|
2242 }; |
|
2243 getCurrentTime = function () { |
|
2244 return Date.now(); |
|
2245 }; |
|
2246 requestHostCallback = function (cb) { |
|
2247 if (_callback !== null) { |
|
2248 // Protect against re-entrancy. |
|
2249 setTimeout(requestHostCallback, 0, cb); |
|
2250 } else { |
|
2251 _callback = cb; |
|
2252 setTimeout(_flushCallback, 0); |
|
2253 } |
|
2254 }; |
|
2255 requestHostTimeout = function (cb, ms) { |
|
2256 _timeoutID = setTimeout(cb, ms); |
|
2257 }; |
|
2258 cancelHostTimeout = function () { |
|
2259 clearTimeout(_timeoutID); |
|
2260 }; |
|
2261 shouldYieldToHost = function () { |
|
2262 return false; |
|
2263 }; |
|
2264 requestPaint = forceFrameRate = function () {}; |
|
2265 } else { |
|
2266 // Capture local references to native APIs, in case a polyfill overrides them. |
|
2267 var performance = window.performance; |
|
2268 var _Date = window.Date; |
|
2269 var _setTimeout = window.setTimeout; |
|
2270 var _clearTimeout = window.clearTimeout; |
|
2271 var requestAnimationFrame = window.requestAnimationFrame; |
|
2272 var cancelAnimationFrame = window.cancelAnimationFrame; |
|
2273 var requestIdleCallback = window.requestIdleCallback; |
|
2274 |
|
2275 if (typeof console !== 'undefined') { |
|
2276 // TODO: Remove fb.me link |
|
2277 if (typeof requestAnimationFrame !== 'function') { |
|
2278 console.error("This browser doesn't support requestAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); |
|
2279 } |
|
2280 if (typeof cancelAnimationFrame !== 'function') { |
|
2281 console.error("This browser doesn't support cancelAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); |
|
2282 } |
|
2283 } |
|
2284 |
|
2285 var requestIdleCallbackBeforeFirstFrame$1 = requestIdleCallbackBeforeFirstFrame && typeof requestIdleCallback === 'function' && typeof cancelIdleCallback === 'function'; |
|
2286 |
|
2287 getCurrentTime = typeof performance === 'object' && typeof performance.now === 'function' ? function () { |
|
2288 return performance.now(); |
|
2289 } : function () { |
|
2290 return _Date.now(); |
|
2291 }; |
|
2292 |
|
2293 var isRAFLoopRunning = false; |
|
2294 var isMessageLoopRunning = false; |
|
2295 var scheduledHostCallback = null; |
|
2296 var rAFTimeoutID = -1; |
|
2297 var taskTimeoutID = -1; |
|
2298 |
|
2299 var frameLength = enableMessageLoopImplementation ? // We won't attempt to align with the vsync. Instead we'll yield multiple |
|
2300 // times per frame, often enough to keep it responsive even at really |
|
2301 // high frame rates > 120. |
|
2302 5 : // Use a heuristic to measure the frame rate and yield at the end of the |
|
2303 // frame. We start out assuming that we run at 30fps but then the |
|
2304 // heuristic tracking will adjust this value to a faster fps if we get |
|
2305 // more frequent animation frames. |
|
2306 33.33; |
|
2307 |
|
2308 var prevRAFTime = -1; |
|
2309 var prevRAFInterval = -1; |
|
2310 var frameDeadline = 0; |
|
2311 |
|
2312 var fpsLocked = false; |
|
2313 |
|
2314 // TODO: Make this configurable |
|
2315 // TODO: Adjust this based on priority? |
|
2316 var maxFrameLength = 300; |
|
2317 var needsPaint = false; |
|
2318 |
|
2319 if (enableIsInputPending && navigator !== undefined && navigator.scheduling !== undefined && navigator.scheduling.isInputPending !== undefined) { |
|
2320 var scheduling = navigator.scheduling; |
|
2321 shouldYieldToHost = function () { |
|
2322 var currentTime = getCurrentTime(); |
|
2323 if (currentTime >= frameDeadline) { |
|
2324 // There's no time left in the frame. We may want to yield control of |
|
2325 // the main thread, so the browser can perform high priority tasks. The |
|
2326 // main ones are painting and user input. If there's a pending paint or |
|
2327 // a pending input, then we should yield. But if there's neither, then |
|
2328 // we can yield less often while remaining responsive. We'll eventually |
|
2329 // yield regardless, since there could be a pending paint that wasn't |
|
2330 // accompanied by a call to `requestPaint`, or other main thread tasks |
|
2331 // like network events. |
|
2332 if (needsPaint || scheduling.isInputPending()) { |
|
2333 // There is either a pending paint or a pending input. |
|
2334 return true; |
|
2335 } |
|
2336 // There's no pending input. Only yield if we've reached the max |
|
2337 // frame length. |
|
2338 return currentTime >= frameDeadline + maxFrameLength; |
|
2339 } else { |
|
2340 // There's still time left in the frame. |
|
2341 return false; |
|
2342 } |
|
2343 }; |
|
2344 |
|
2345 requestPaint = function () { |
|
2346 needsPaint = true; |
|
2347 }; |
|
2348 } else { |
|
2349 // `isInputPending` is not available. Since we have no way of knowing if |
|
2350 // there's pending input, always yield at the end of the frame. |
|
2351 shouldYieldToHost = function () { |
|
2352 return getCurrentTime() >= frameDeadline; |
|
2353 }; |
|
2354 |
|
2355 // Since we yield every frame regardless, `requestPaint` has no effect. |
|
2356 requestPaint = function () {}; |
|
2357 } |
|
2358 |
|
2359 forceFrameRate = function (fps) { |
|
2360 if (fps < 0 || fps > 125) { |
|
2361 console.error('forceFrameRate takes a positive int between 0 and 125, ' + 'forcing framerates higher than 125 fps is not unsupported'); |
|
2362 return; |
|
2363 } |
|
2364 if (fps > 0) { |
|
2365 frameLength = Math.floor(1000 / fps); |
|
2366 fpsLocked = true; |
|
2367 } else { |
|
2368 // reset the framerate |
|
2369 frameLength = 33.33; |
|
2370 fpsLocked = false; |
|
2371 } |
|
2372 }; |
|
2373 |
|
2374 var performWorkUntilDeadline = function () { |
|
2375 if (enableMessageLoopImplementation) { |
|
2376 if (scheduledHostCallback !== null) { |
|
2377 var currentTime = getCurrentTime(); |
|
2378 // Yield after `frameLength` ms, regardless of where we are in the vsync |
|
2379 // cycle. This means there's always time remaining at the beginning of |
|
2380 // the message event. |
|
2381 frameDeadline = currentTime + frameLength; |
|
2382 var hasTimeRemaining = true; |
|
2383 try { |
|
2384 var hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); |
|
2385 if (!hasMoreWork) { |
|
2386 isMessageLoopRunning = false; |
|
2387 scheduledHostCallback = null; |
|
2388 } else { |
|
2389 // If there's more work, schedule the next message event at the end |
|
2390 // of the preceding one. |
|
2391 port.postMessage(null); |
|
2392 } |
|
2393 } catch (error) { |
|
2394 // If a scheduler task throws, exit the current browser task so the |
|
2395 // error can be observed. |
|
2396 port.postMessage(null); |
|
2397 throw error; |
|
2398 } |
|
2399 } |
|
2400 // Yielding to the browser will give it a chance to paint, so we can |
|
2401 // reset this. |
|
2402 needsPaint = false; |
|
2403 } else { |
|
2404 if (scheduledHostCallback !== null) { |
|
2405 var _currentTime = getCurrentTime(); |
|
2406 var _hasTimeRemaining = frameDeadline - _currentTime > 0; |
|
2407 try { |
|
2408 var _hasMoreWork = scheduledHostCallback(_hasTimeRemaining, _currentTime); |
|
2409 if (!_hasMoreWork) { |
|
2410 scheduledHostCallback = null; |
|
2411 } |
|
2412 } catch (error) { |
|
2413 // If a scheduler task throws, exit the current browser task so the |
|
2414 // error can be observed, and post a new task as soon as possible |
|
2415 // so we can continue where we left off. |
|
2416 port.postMessage(null); |
|
2417 throw error; |
|
2418 } |
|
2419 } |
|
2420 // Yielding to the browser will give it a chance to paint, so we can |
|
2421 // reset this. |
|
2422 needsPaint = false; |
|
2423 } |
|
2424 }; |
|
2425 |
|
2426 var channel = new MessageChannel(); |
|
2427 var port = channel.port2; |
|
2428 channel.port1.onmessage = performWorkUntilDeadline; |
|
2429 |
|
2430 var onAnimationFrame = function (rAFTime) { |
|
2431 if (scheduledHostCallback === null) { |
|
2432 // No scheduled work. Exit. |
|
2433 prevRAFTime = -1; |
|
2434 prevRAFInterval = -1; |
|
2435 isRAFLoopRunning = false; |
|
2436 return; |
|
2437 } |
|
2438 |
|
2439 // Eagerly schedule the next animation callback at the beginning of the |
|
2440 // frame. If the scheduler queue is not empty at the end of the frame, it |
|
2441 // will continue flushing inside that callback. If the queue *is* empty, |
|
2442 // then it will exit immediately. Posting the callback at the start of the |
|
2443 // frame ensures it's fired within the earliest possible frame. If we |
|
2444 // waited until the end of the frame to post the callback, we risk the |
|
2445 // browser skipping a frame and not firing the callback until the frame |
|
2446 // after that. |
|
2447 isRAFLoopRunning = true; |
|
2448 requestAnimationFrame(function (nextRAFTime) { |
|
2449 _clearTimeout(rAFTimeoutID); |
|
2450 onAnimationFrame(nextRAFTime); |
|
2451 }); |
|
2452 |
|
2453 // requestAnimationFrame is throttled when the tab is backgrounded. We |
|
2454 // don't want to stop working entirely. So we'll fallback to a timeout loop. |
|
2455 // TODO: Need a better heuristic for backgrounded work. |
|
2456 var onTimeout = function () { |
|
2457 frameDeadline = getCurrentTime() + frameLength / 2; |
|
2458 performWorkUntilDeadline(); |
|
2459 rAFTimeoutID = _setTimeout(onTimeout, frameLength * 3); |
|
2460 }; |
|
2461 rAFTimeoutID = _setTimeout(onTimeout, frameLength * 3); |
|
2462 |
|
2463 if (prevRAFTime !== -1 && |
|
2464 // Make sure this rAF time is different from the previous one. This check |
|
2465 // could fail if two rAFs fire in the same frame. |
|
2466 rAFTime - prevRAFTime > 0.1) { |
|
2467 var rAFInterval = rAFTime - prevRAFTime; |
|
2468 if (!fpsLocked && prevRAFInterval !== -1) { |
|
2469 // We've observed two consecutive frame intervals. We'll use this to |
|
2470 // dynamically adjust the frame rate. |
|
2471 // |
|
2472 // If one frame goes long, then the next one can be short to catch up. |
|
2473 // If two frames are short in a row, then that's an indication that we |
|
2474 // actually have a higher frame rate than what we're currently |
|
2475 // optimizing. For example, if we're running on 120hz display or 90hz VR |
|
2476 // display. Take the max of the two in case one of them was an anomaly |
|
2477 // due to missed frame deadlines. |
|
2478 if (rAFInterval < frameLength && prevRAFInterval < frameLength) { |
|
2479 frameLength = rAFInterval < prevRAFInterval ? prevRAFInterval : rAFInterval; |
|
2480 if (frameLength < 8.33) { |
|
2481 // Defensive coding. We don't support higher frame rates than 120hz. |
|
2482 // If the calculated frame length gets lower than 8, it is probably |
|
2483 // a bug. |
|
2484 frameLength = 8.33; |
|
2485 } |
|
2486 } |
|
2487 } |
|
2488 prevRAFInterval = rAFInterval; |
|
2489 } |
|
2490 prevRAFTime = rAFTime; |
|
2491 frameDeadline = rAFTime + frameLength; |
|
2492 |
|
2493 // We use the postMessage trick to defer idle work until after the repaint. |
|
2494 port.postMessage(null); |
|
2495 }; |
|
2496 |
|
2497 requestHostCallback = function (callback) { |
|
2498 scheduledHostCallback = callback; |
|
2499 if (enableMessageLoopImplementation) { |
|
2500 if (!isMessageLoopRunning) { |
|
2501 isMessageLoopRunning = true; |
|
2502 port.postMessage(null); |
|
2503 } |
|
2504 } else { |
|
2505 if (!isRAFLoopRunning) { |
|
2506 // Start a rAF loop. |
|
2507 isRAFLoopRunning = true; |
|
2508 requestAnimationFrame(function (rAFTime) { |
|
2509 if (requestIdleCallbackBeforeFirstFrame$1) { |
|
2510 cancelIdleCallback(idleCallbackID); |
|
2511 } |
|
2512 if (requestTimerEventBeforeFirstFrame) { |
|
2513 _clearTimeout(idleTimeoutID); |
|
2514 } |
|
2515 onAnimationFrame(rAFTime); |
|
2516 }); |
|
2517 |
|
2518 // If we just missed the last vsync, the next rAF might not happen for |
|
2519 // another frame. To claim as much idle time as possible, post a |
|
2520 // callback with `requestIdleCallback`, which should fire if there's |
|
2521 // idle time left in the frame. |
|
2522 // |
|
2523 // This should only be an issue for the first rAF in the loop; |
|
2524 // subsequent rAFs are scheduled at the beginning of the |
|
2525 // preceding frame. |
|
2526 var idleCallbackID = void 0; |
|
2527 if (requestIdleCallbackBeforeFirstFrame$1) { |
|
2528 idleCallbackID = requestIdleCallback(function onIdleCallbackBeforeFirstFrame() { |
|
2529 if (requestTimerEventBeforeFirstFrame) { |
|
2530 _clearTimeout(idleTimeoutID); |
|
2531 } |
|
2532 frameDeadline = getCurrentTime() + frameLength; |
|
2533 performWorkUntilDeadline(); |
|
2534 }); |
|
2535 } |
|
2536 // Alternate strategy to address the same problem. Scheduler a timer |
|
2537 // with no delay. If this fires before the rAF, that likely indicates |
|
2538 // that there's idle time before the next vsync. This isn't always the |
|
2539 // case, but we'll be aggressive and assume it is, as a trade off to |
|
2540 // prevent idle periods. |
|
2541 var idleTimeoutID = void 0; |
|
2542 if (requestTimerEventBeforeFirstFrame) { |
|
2543 idleTimeoutID = _setTimeout(function onTimerEventBeforeFirstFrame() { |
|
2544 if (requestIdleCallbackBeforeFirstFrame$1) { |
|
2545 cancelIdleCallback(idleCallbackID); |
|
2546 } |
|
2547 frameDeadline = getCurrentTime() + frameLength; |
|
2548 performWorkUntilDeadline(); |
|
2549 }, 0); |
|
2550 } |
|
2551 } |
|
2552 } |
|
2553 }; |
|
2554 |
|
2555 requestHostTimeout = function (callback, ms) { |
|
2556 taskTimeoutID = _setTimeout(function () { |
|
2557 callback(getCurrentTime()); |
|
2558 }, ms); |
|
2559 }; |
|
2560 |
|
2561 cancelHostTimeout = function () { |
|
2562 _clearTimeout(taskTimeoutID); |
|
2563 taskTimeoutID = -1; |
|
2564 }; |
|
2565 } |
|
2566 |
|
2567 /* eslint-disable no-var */ |
|
2568 |
|
2569 // TODO: Use symbols? |
|
2570 var ImmediatePriority = 1; |
|
2571 var UserBlockingPriority = 2; |
|
2572 var NormalPriority = 3; |
|
2573 var LowPriority = 4; |
|
2574 var IdlePriority = 5; |
|
2575 |
|
2576 // Max 31 bit integer. The max integer size in V8 for 32-bit systems. |
|
2577 // Math.pow(2, 30) - 1 |
|
2578 // 0b111111111111111111111111111111 |
|
2579 var maxSigned31BitInt = 1073741823; |
|
2580 |
|
2581 // Times out immediately |
|
2582 var IMMEDIATE_PRIORITY_TIMEOUT = -1; |
|
2583 // Eventually times out |
|
2584 var USER_BLOCKING_PRIORITY = 250; |
|
2585 var NORMAL_PRIORITY_TIMEOUT = 5000; |
|
2586 var LOW_PRIORITY_TIMEOUT = 10000; |
|
2587 // Never times out |
|
2588 var IDLE_PRIORITY = maxSigned31BitInt; |
|
2589 |
|
2590 // Tasks are stored as a circular, doubly linked list. |
|
2591 var firstTask = null; |
|
2592 var firstDelayedTask = null; |
|
2593 |
|
2594 // Pausing the scheduler is useful for debugging. |
|
2595 var isSchedulerPaused = false; |
|
2596 |
|
2597 var currentTask = null; |
|
2598 var currentPriorityLevel = NormalPriority; |
|
2599 |
|
2600 // This is set while performing work, to prevent re-entrancy. |
|
2601 var isPerformingWork = false; |
|
2602 |
|
2603 var isHostCallbackScheduled = false; |
|
2604 var isHostTimeoutScheduled = false; |
|
2605 |
|
2606 function scheduler_flushTaskAtPriority_Immediate(callback, didTimeout) { |
|
2607 return callback(didTimeout); |
|
2608 } |
|
2609 function scheduler_flushTaskAtPriority_UserBlocking(callback, didTimeout) { |
|
2610 return callback(didTimeout); |
|
2611 } |
|
2612 function scheduler_flushTaskAtPriority_Normal(callback, didTimeout) { |
|
2613 return callback(didTimeout); |
|
2614 } |
|
2615 function scheduler_flushTaskAtPriority_Low(callback, didTimeout) { |
|
2616 return callback(didTimeout); |
|
2617 } |
|
2618 function scheduler_flushTaskAtPriority_Idle(callback, didTimeout) { |
|
2619 return callback(didTimeout); |
|
2620 } |
|
2621 |
|
2622 function flushTask(task, currentTime) { |
|
2623 // Remove the task from the list before calling the callback. That way the |
|
2624 // list is in a consistent state even if the callback throws. |
|
2625 var next = task.next; |
|
2626 if (next === task) { |
|
2627 // This is the only scheduled task. Clear the list. |
|
2628 firstTask = null; |
|
2629 } else { |
|
2630 // Remove the task from its position in the list. |
|
2631 if (task === firstTask) { |
|
2632 firstTask = next; |
|
2633 } |
|
2634 var previous = task.previous; |
|
2635 previous.next = next; |
|
2636 next.previous = previous; |
|
2637 } |
|
2638 task.next = task.previous = null; |
|
2639 |
|
2640 // Now it's safe to execute the task. |
|
2641 var callback = task.callback; |
|
2642 var previousPriorityLevel = currentPriorityLevel; |
|
2643 var previousTask = currentTask; |
|
2644 currentPriorityLevel = task.priorityLevel; |
|
2645 currentTask = task; |
|
2646 var continuationCallback; |
|
2647 try { |
|
2648 var didUserCallbackTimeout = task.expirationTime <= currentTime; |
|
2649 // Add an extra function to the callstack. Profiling tools can use this |
|
2650 // to infer the priority of work that appears higher in the stack. |
|
2651 switch (currentPriorityLevel) { |
|
2652 case ImmediatePriority: |
|
2653 continuationCallback = scheduler_flushTaskAtPriority_Immediate(callback, didUserCallbackTimeout); |
|
2654 break; |
|
2655 case UserBlockingPriority: |
|
2656 continuationCallback = scheduler_flushTaskAtPriority_UserBlocking(callback, didUserCallbackTimeout); |
|
2657 break; |
|
2658 case NormalPriority: |
|
2659 continuationCallback = scheduler_flushTaskAtPriority_Normal(callback, didUserCallbackTimeout); |
|
2660 break; |
|
2661 case LowPriority: |
|
2662 continuationCallback = scheduler_flushTaskAtPriority_Low(callback, didUserCallbackTimeout); |
|
2663 break; |
|
2664 case IdlePriority: |
|
2665 continuationCallback = scheduler_flushTaskAtPriority_Idle(callback, didUserCallbackTimeout); |
|
2666 break; |
|
2667 } |
|
2668 } catch (error) { |
|
2669 throw error; |
|
2670 } finally { |
|
2671 currentPriorityLevel = previousPriorityLevel; |
|
2672 currentTask = previousTask; |
|
2673 } |
|
2674 |
|
2675 // A callback may return a continuation. The continuation should be scheduled |
|
2676 // with the same priority and expiration as the just-finished callback. |
|
2677 if (typeof continuationCallback === 'function') { |
|
2678 var expirationTime = task.expirationTime; |
|
2679 var continuationTask = task; |
|
2680 continuationTask.callback = continuationCallback; |
|
2681 |
|
2682 // Insert the new callback into the list, sorted by its timeout. This is |
|
2683 // almost the same as the code in `scheduleCallback`, except the callback |
|
2684 // is inserted into the list *before* callbacks of equal timeout instead |
|
2685 // of after. |
|
2686 if (firstTask === null) { |
|
2687 // This is the first callback in the list. |
|
2688 firstTask = continuationTask.next = continuationTask.previous = continuationTask; |
|
2689 } else { |
|
2690 var nextAfterContinuation = null; |
|
2691 var t = firstTask; |
|
2692 do { |
|
2693 if (expirationTime <= t.expirationTime) { |
|
2694 // This task times out at or after the continuation. We will insert |
|
2695 // the continuation *before* this task. |
|
2696 nextAfterContinuation = t; |
|
2697 break; |
|
2698 } |
|
2699 t = t.next; |
|
2700 } while (t !== firstTask); |
|
2701 if (nextAfterContinuation === null) { |
|
2702 // No equal or lower priority task was found, which means the new task |
|
2703 // is the lowest priority task in the list. |
|
2704 nextAfterContinuation = firstTask; |
|
2705 } else if (nextAfterContinuation === firstTask) { |
|
2706 // The new task is the highest priority task in the list. |
|
2707 firstTask = continuationTask; |
|
2708 } |
|
2709 |
|
2710 var _previous = nextAfterContinuation.previous; |
|
2711 _previous.next = nextAfterContinuation.previous = continuationTask; |
|
2712 continuationTask.next = nextAfterContinuation; |
|
2713 continuationTask.previous = _previous; |
|
2714 } |
|
2715 } |
|
2716 } |
|
2717 |
|
2718 function advanceTimers(currentTime) { |
|
2719 // Check for tasks that are no longer delayed and add them to the queue. |
|
2720 if (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime) { |
|
2721 do { |
|
2722 var task = firstDelayedTask; |
|
2723 var next = task.next; |
|
2724 if (task === next) { |
|
2725 firstDelayedTask = null; |
|
2726 } else { |
|
2727 firstDelayedTask = next; |
|
2728 var previous = task.previous; |
|
2729 previous.next = next; |
|
2730 next.previous = previous; |
|
2731 } |
|
2732 task.next = task.previous = null; |
|
2733 insertScheduledTask(task, task.expirationTime); |
|
2734 } while (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime); |
|
2735 } |
|
2736 } |
|
2737 |
|
2738 function handleTimeout(currentTime) { |
|
2739 isHostTimeoutScheduled = false; |
|
2740 advanceTimers(currentTime); |
|
2741 |
|
2742 if (!isHostCallbackScheduled) { |
|
2743 if (firstTask !== null) { |
|
2744 isHostCallbackScheduled = true; |
|
2745 requestHostCallback(flushWork); |
|
2746 } else if (firstDelayedTask !== null) { |
|
2747 requestHostTimeout(handleTimeout, firstDelayedTask.startTime - currentTime); |
|
2748 } |
|
2749 } |
|
2750 } |
|
2751 |
|
2752 function flushWork(hasTimeRemaining, initialTime) { |
|
2753 // Exit right away if we're currently paused |
|
2754 if (enableSchedulerDebugging && isSchedulerPaused) { |
|
2755 return; |
|
2756 } |
|
2757 |
|
2758 // We'll need a host callback the next time work is scheduled. |
|
2759 isHostCallbackScheduled = false; |
|
2760 if (isHostTimeoutScheduled) { |
|
2761 // We scheduled a timeout but it's no longer needed. Cancel it. |
|
2762 isHostTimeoutScheduled = false; |
|
2763 cancelHostTimeout(); |
|
2764 } |
|
2765 |
|
2766 var currentTime = initialTime; |
|
2767 advanceTimers(currentTime); |
|
2768 |
|
2769 isPerformingWork = true; |
|
2770 try { |
|
2771 if (!hasTimeRemaining) { |
|
2772 // Flush all the expired callbacks without yielding. |
|
2773 // TODO: Split flushWork into two separate functions instead of using |
|
2774 // a boolean argument? |
|
2775 while (firstTask !== null && firstTask.expirationTime <= currentTime && !(enableSchedulerDebugging && isSchedulerPaused)) { |
|
2776 flushTask(firstTask, currentTime); |
|
2777 currentTime = getCurrentTime(); |
|
2778 advanceTimers(currentTime); |
|
2779 } |
|
2780 } else { |
|
2781 // Keep flushing callbacks until we run out of time in the frame. |
|
2782 if (firstTask !== null) { |
|
2783 do { |
|
2784 flushTask(firstTask, currentTime); |
|
2785 currentTime = getCurrentTime(); |
|
2786 advanceTimers(currentTime); |
|
2787 } while (firstTask !== null && !shouldYieldToHost() && !(enableSchedulerDebugging && isSchedulerPaused)); |
|
2788 } |
|
2789 } |
|
2790 // Return whether there's additional work |
|
2791 if (firstTask !== null) { |
|
2792 return true; |
|
2793 } else { |
|
2794 if (firstDelayedTask !== null) { |
|
2795 requestHostTimeout(handleTimeout, firstDelayedTask.startTime - currentTime); |
|
2796 } |
|
2797 return false; |
|
2798 } |
|
2799 } finally { |
|
2800 isPerformingWork = false; |
|
2801 } |
|
2802 } |
|
2803 |
|
2804 function unstable_runWithPriority(priorityLevel, eventHandler) { |
|
2805 switch (priorityLevel) { |
|
2806 case ImmediatePriority: |
|
2807 case UserBlockingPriority: |
|
2808 case NormalPriority: |
|
2809 case LowPriority: |
|
2810 case IdlePriority: |
|
2811 break; |
|
2812 default: |
|
2813 priorityLevel = NormalPriority; |
|
2814 } |
|
2815 |
|
2816 var previousPriorityLevel = currentPriorityLevel; |
|
2817 currentPriorityLevel = priorityLevel; |
|
2818 |
|
2819 try { |
|
2820 return eventHandler(); |
|
2821 } finally { |
|
2822 currentPriorityLevel = previousPriorityLevel; |
|
2823 } |
|
2824 } |
|
2825 |
|
2826 function unstable_next(eventHandler) { |
|
2827 var priorityLevel; |
|
2828 switch (currentPriorityLevel) { |
|
2829 case ImmediatePriority: |
|
2830 case UserBlockingPriority: |
|
2831 case NormalPriority: |
|
2832 // Shift down to normal priority |
|
2833 priorityLevel = NormalPriority; |
|
2834 break; |
|
2835 default: |
|
2836 // Anything lower than normal priority should remain at the current level. |
|
2837 priorityLevel = currentPriorityLevel; |
|
2838 break; |
|
2839 } |
|
2840 |
|
2841 var previousPriorityLevel = currentPriorityLevel; |
|
2842 currentPriorityLevel = priorityLevel; |
|
2843 |
|
2844 try { |
|
2845 return eventHandler(); |
|
2846 } finally { |
|
2847 currentPriorityLevel = previousPriorityLevel; |
|
2848 } |
|
2849 } |
|
2850 |
|
2851 function unstable_wrapCallback(callback) { |
|
2852 var parentPriorityLevel = currentPriorityLevel; |
|
2853 return function () { |
|
2854 // This is a fork of runWithPriority, inlined for performance. |
|
2855 var previousPriorityLevel = currentPriorityLevel; |
|
2856 currentPriorityLevel = parentPriorityLevel; |
|
2857 |
|
2858 try { |
|
2859 return callback.apply(this, arguments); |
|
2860 } finally { |
|
2861 currentPriorityLevel = previousPriorityLevel; |
|
2862 } |
|
2863 }; |
|
2864 } |
|
2865 |
|
2866 function timeoutForPriorityLevel(priorityLevel) { |
|
2867 switch (priorityLevel) { |
|
2868 case ImmediatePriority: |
|
2869 return IMMEDIATE_PRIORITY_TIMEOUT; |
|
2870 case UserBlockingPriority: |
|
2871 return USER_BLOCKING_PRIORITY; |
|
2872 case IdlePriority: |
|
2873 return IDLE_PRIORITY; |
|
2874 case LowPriority: |
|
2875 return LOW_PRIORITY_TIMEOUT; |
|
2876 case NormalPriority: |
|
2877 default: |
|
2878 return NORMAL_PRIORITY_TIMEOUT; |
|
2879 } |
|
2880 } |
|
2881 |
|
2882 function unstable_scheduleCallback(priorityLevel, callback, options) { |
|
2883 var currentTime = getCurrentTime(); |
|
2884 |
|
2885 var startTime; |
|
2886 var timeout; |
|
2887 if (typeof options === 'object' && options !== null) { |
|
2888 var delay = options.delay; |
|
2889 if (typeof delay === 'number' && delay > 0) { |
|
2890 startTime = currentTime + delay; |
|
2891 } else { |
|
2892 startTime = currentTime; |
|
2893 } |
|
2894 timeout = typeof options.timeout === 'number' ? options.timeout : timeoutForPriorityLevel(priorityLevel); |
|
2895 } else { |
|
2896 timeout = timeoutForPriorityLevel(priorityLevel); |
|
2897 startTime = currentTime; |
|
2898 } |
|
2899 |
|
2900 var expirationTime = startTime + timeout; |
|
2901 |
|
2902 var newTask = { |
|
2903 callback: callback, |
|
2904 priorityLevel: priorityLevel, |
|
2905 startTime: startTime, |
|
2906 expirationTime: expirationTime, |
|
2907 next: null, |
|
2908 previous: null |
|
2909 }; |
|
2910 |
|
2911 if (startTime > currentTime) { |
|
2912 // This is a delayed task. |
|
2913 insertDelayedTask(newTask, startTime); |
|
2914 if (firstTask === null && firstDelayedTask === newTask) { |
|
2915 // All tasks are delayed, and this is the task with the earliest delay. |
|
2916 if (isHostTimeoutScheduled) { |
|
2917 // Cancel an existing timeout. |
|
2918 cancelHostTimeout(); |
|
2919 } else { |
|
2920 isHostTimeoutScheduled = true; |
|
2921 } |
|
2922 // Schedule a timeout. |
|
2923 requestHostTimeout(handleTimeout, startTime - currentTime); |
|
2924 } |
|
2925 } else { |
|
2926 insertScheduledTask(newTask, expirationTime); |
|
2927 // Schedule a host callback, if needed. If we're already performing work, |
|
2928 // wait until the next time we yield. |
|
2929 if (!isHostCallbackScheduled && !isPerformingWork) { |
|
2930 isHostCallbackScheduled = true; |
|
2931 requestHostCallback(flushWork); |
|
2932 } |
|
2933 } |
|
2934 |
|
2935 return newTask; |
|
2936 } |
|
2937 |
|
2938 function insertScheduledTask(newTask, expirationTime) { |
|
2939 // Insert the new task into the list, ordered first by its timeout, then by |
|
2940 // insertion. So the new task is inserted after any other task the |
|
2941 // same timeout |
|
2942 if (firstTask === null) { |
|
2943 // This is the first task in the list. |
|
2944 firstTask = newTask.next = newTask.previous = newTask; |
|
2945 } else { |
|
2946 var next = null; |
|
2947 var task = firstTask; |
|
2948 do { |
|
2949 if (expirationTime < task.expirationTime) { |
|
2950 // The new task times out before this one. |
|
2951 next = task; |
|
2952 break; |
|
2953 } |
|
2954 task = task.next; |
|
2955 } while (task !== firstTask); |
|
2956 |
|
2957 if (next === null) { |
|
2958 // No task with a later timeout was found, which means the new task has |
|
2959 // the latest timeout in the list. |
|
2960 next = firstTask; |
|
2961 } else if (next === firstTask) { |
|
2962 // The new task has the earliest expiration in the entire list. |
|
2963 firstTask = newTask; |
|
2964 } |
|
2965 |
|
2966 var previous = next.previous; |
|
2967 previous.next = next.previous = newTask; |
|
2968 newTask.next = next; |
|
2969 newTask.previous = previous; |
|
2970 } |
|
2971 } |
|
2972 |
|
2973 function insertDelayedTask(newTask, startTime) { |
|
2974 // Insert the new task into the list, ordered by its start time. |
|
2975 if (firstDelayedTask === null) { |
|
2976 // This is the first task in the list. |
|
2977 firstDelayedTask = newTask.next = newTask.previous = newTask; |
|
2978 } else { |
|
2979 var next = null; |
|
2980 var task = firstDelayedTask; |
|
2981 do { |
|
2982 if (startTime < task.startTime) { |
|
2983 // The new task times out before this one. |
|
2984 next = task; |
|
2985 break; |
|
2986 } |
|
2987 task = task.next; |
|
2988 } while (task !== firstDelayedTask); |
|
2989 |
|
2990 if (next === null) { |
|
2991 // No task with a later timeout was found, which means the new task has |
|
2992 // the latest timeout in the list. |
|
2993 next = firstDelayedTask; |
|
2994 } else if (next === firstDelayedTask) { |
|
2995 // The new task has the earliest expiration in the entire list. |
|
2996 firstDelayedTask = newTask; |
|
2997 } |
|
2998 |
|
2999 var previous = next.previous; |
|
3000 previous.next = next.previous = newTask; |
|
3001 newTask.next = next; |
|
3002 newTask.previous = previous; |
|
3003 } |
|
3004 } |
|
3005 |
|
3006 function unstable_pauseExecution() { |
|
3007 isSchedulerPaused = true; |
|
3008 } |
|
3009 |
|
3010 function unstable_continueExecution() { |
|
3011 isSchedulerPaused = false; |
|
3012 if (!isHostCallbackScheduled && !isPerformingWork) { |
|
3013 isHostCallbackScheduled = true; |
|
3014 requestHostCallback(flushWork); |
|
3015 } |
|
3016 } |
|
3017 |
|
3018 function unstable_getFirstCallbackNode() { |
|
3019 return firstTask; |
|
3020 } |
|
3021 |
|
3022 function unstable_cancelCallback(task) { |
|
3023 var next = task.next; |
|
3024 if (next === null) { |
|
3025 // Already cancelled. |
|
3026 return; |
|
3027 } |
|
3028 |
|
3029 if (task === next) { |
|
3030 if (task === firstTask) { |
|
3031 firstTask = null; |
|
3032 } else if (task === firstDelayedTask) { |
|
3033 firstDelayedTask = null; |
|
3034 } |
|
3035 } else { |
|
3036 if (task === firstTask) { |
|
3037 firstTask = next; |
|
3038 } else if (task === firstDelayedTask) { |
|
3039 firstDelayedTask = next; |
|
3040 } |
|
3041 var previous = task.previous; |
|
3042 previous.next = next; |
|
3043 next.previous = previous; |
|
3044 } |
|
3045 |
|
3046 task.next = task.previous = null; |
|
3047 } |
|
3048 |
|
3049 function unstable_getCurrentPriorityLevel() { |
|
3050 return currentPriorityLevel; |
|
3051 } |
|
3052 |
|
3053 function unstable_shouldYield() { |
|
3054 var currentTime = getCurrentTime(); |
|
3055 advanceTimers(currentTime); |
|
3056 return currentTask !== null && firstTask !== null && firstTask.startTime <= currentTime && firstTask.expirationTime < currentTask.expirationTime || shouldYieldToHost(); |
|
3057 } |
|
3058 |
|
3059 var unstable_requestPaint = requestPaint; |
|
3060 |
|
3061 |
|
3062 |
|
3063 var Scheduler = Object.freeze({ |
|
3064 unstable_ImmediatePriority: ImmediatePriority, |
|
3065 unstable_UserBlockingPriority: UserBlockingPriority, |
|
3066 unstable_NormalPriority: NormalPriority, |
|
3067 unstable_IdlePriority: IdlePriority, |
|
3068 unstable_LowPriority: LowPriority, |
|
3069 unstable_runWithPriority: unstable_runWithPriority, |
|
3070 unstable_next: unstable_next, |
|
3071 unstable_scheduleCallback: unstable_scheduleCallback, |
|
3072 unstable_cancelCallback: unstable_cancelCallback, |
|
3073 unstable_wrapCallback: unstable_wrapCallback, |
|
3074 unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel, |
|
3075 unstable_shouldYield: unstable_shouldYield, |
|
3076 unstable_requestPaint: unstable_requestPaint, |
|
3077 unstable_continueExecution: unstable_continueExecution, |
|
3078 unstable_pauseExecution: unstable_pauseExecution, |
|
3079 unstable_getFirstCallbackNode: unstable_getFirstCallbackNode, |
|
3080 get unstable_now () { return getCurrentTime; }, |
|
3081 get unstable_forceFrameRate () { return forceFrameRate; } |
|
3082 }); |
|
3083 |
|
3084 // Helps identify side effects in begin-phase lifecycle hooks and setState reducers: |
|
3085 |
|
3086 |
|
3087 // In some cases, StrictMode should also double-render lifecycles. |
|
3088 // This can be confusing for tests though, |
|
3089 // And it can be bad for performance in production. |
|
3090 // This feature flag can be used to control the behavior: |
|
3091 |
|
3092 |
|
3093 // To preserve the "Pause on caught exceptions" behavior of the debugger, we |
|
3094 // replay the begin phase of a failed component inside invokeGuardedCallback. |
|
3095 |
|
3096 |
|
3097 // Warn about deprecated, async-unsafe lifecycles; relates to RFC #6: |
|
3098 |
|
3099 |
|
3100 // Gather advanced timing metrics for Profiler subtrees. |
|
3101 |
|
3102 |
|
3103 // Trace which interactions trigger each commit. |
|
3104 var enableSchedulerTracing = true; |
|
3105 |
|
3106 // Only used in www builds. |
|
3107 // TODO: true? Here it might just be false. |
|
3108 |
|
3109 // Only used in www builds. |
|
3110 |
|
3111 |
|
3112 // Only used in www builds. |
|
3113 |
|
3114 |
|
3115 // Disable javascript: URL strings in href for XSS protection. |
|
3116 |
|
3117 |
|
3118 // React Fire: prevent the value and checked attributes from syncing |
|
3119 // with their related DOM properties |
|
3120 |
|
3121 |
|
3122 // These APIs will no longer be "unstable" in the upcoming 16.7 release, |
|
3123 // Control this behavior with a flag to support 16.6 minor releases in the meanwhile. |
|
3124 |
|
3125 |
|
3126 |
|
3127 |
|
3128 // See https://github.com/react-native-community/discussions-and-proposals/issues/72 for more information |
|
3129 // This is a flag so we can fix warnings in RN core before turning it on |
|
3130 |
|
3131 |
|
3132 // Experimental React Flare event system and event components support. |
|
3133 var enableFlareAPI = false; |
|
3134 |
|
3135 // Experimental Host Component support. |
|
3136 var enableFundamentalAPI = false; |
|
3137 |
|
3138 // New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107 |
|
3139 var enableJSXTransformAPI = false; |
|
3140 |
|
3141 // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?) |
|
3142 // Till then, we warn about the missing mock, but still fallback to a sync mode compatible version |
|
3143 |
|
3144 // Temporary flag to revert the fix in #15650 |
|
3145 |
|
3146 |
|
3147 // For tests, we flush suspense fallbacks in an act scope; |
|
3148 // *except* in some of our own tests, where we test incremental loading states. |
|
3149 |
|
3150 |
|
3151 // Changes priority of some events like mousemove to user-blocking priority, |
|
3152 // but without making them discrete. The flag exists in case it causes |
|
3153 // starvation problems. |
|
3154 |
|
3155 |
|
3156 // Add a callback property to suspense to notify which promises are currently |
|
3157 // in the update queue. This allows reporting and tracing of what is causing |
|
3158 // the user to see a loading state. |
|
3159 |
|
3160 |
|
3161 // Part of the simplification of React.createElement so we can eventually move |
|
3162 // from React.createElement to React.jsx |
|
3163 // https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md |
|
3164 |
|
3165 var DEFAULT_THREAD_ID = 0; |
|
3166 |
|
3167 // Counters used to generate unique IDs. |
|
3168 var interactionIDCounter = 0; |
|
3169 var threadIDCounter = 0; |
|
3170 |
|
3171 // Set of currently traced interactions. |
|
3172 // Interactions "stack"– |
|
3173 // Meaning that newly traced interactions are appended to the previously active set. |
|
3174 // When an interaction goes out of scope, the previous set (if any) is restored. |
|
3175 var interactionsRef = null; |
|
3176 |
|
3177 // Listener(s) to notify when interactions begin and end. |
|
3178 var subscriberRef = null; |
|
3179 |
|
3180 if (enableSchedulerTracing) { |
|
3181 interactionsRef = { |
|
3182 current: new Set() |
|
3183 }; |
|
3184 subscriberRef = { |
|
3185 current: null |
|
3186 }; |
|
3187 } |
|
3188 |
|
3189 function unstable_clear(callback) { |
|
3190 if (!enableSchedulerTracing) { |
|
3191 return callback(); |
|
3192 } |
|
3193 |
|
3194 var prevInteractions = interactionsRef.current; |
|
3195 interactionsRef.current = new Set(); |
|
3196 |
|
3197 try { |
|
3198 return callback(); |
|
3199 } finally { |
|
3200 interactionsRef.current = prevInteractions; |
|
3201 } |
|
3202 } |
|
3203 |
|
3204 function unstable_getCurrent() { |
|
3205 if (!enableSchedulerTracing) { |
|
3206 return null; |
|
3207 } else { |
|
3208 return interactionsRef.current; |
|
3209 } |
|
3210 } |
|
3211 |
|
3212 function unstable_getThreadID() { |
|
3213 return ++threadIDCounter; |
|
3214 } |
|
3215 |
|
3216 function unstable_trace(name, timestamp, callback) { |
|
3217 var threadID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DEFAULT_THREAD_ID; |
|
3218 |
|
3219 if (!enableSchedulerTracing) { |
|
3220 return callback(); |
|
3221 } |
|
3222 |
|
3223 var interaction = { |
|
3224 __count: 1, |
|
3225 id: interactionIDCounter++, |
|
3226 name: name, |
|
3227 timestamp: timestamp |
|
3228 }; |
|
3229 |
|
3230 var prevInteractions = interactionsRef.current; |
|
3231 |
|
3232 // Traced interactions should stack/accumulate. |
|
3233 // To do that, clone the current interactions. |
|
3234 // The previous set will be restored upon completion. |
|
3235 var interactions = new Set(prevInteractions); |
|
3236 interactions.add(interaction); |
|
3237 interactionsRef.current = interactions; |
|
3238 |
|
3239 var subscriber = subscriberRef.current; |
|
3240 var returnValue = void 0; |
|
3241 |
|
3242 try { |
|
3243 if (subscriber !== null) { |
|
3244 subscriber.onInteractionTraced(interaction); |
|
3245 } |
|
3246 } finally { |
|
3247 try { |
|
3248 if (subscriber !== null) { |
|
3249 subscriber.onWorkStarted(interactions, threadID); |
|
3250 } |
|
3251 } finally { |
|
3252 try { |
|
3253 returnValue = callback(); |
|
3254 } finally { |
|
3255 interactionsRef.current = prevInteractions; |
|
3256 |
|
3257 try { |
|
3258 if (subscriber !== null) { |
|
3259 subscriber.onWorkStopped(interactions, threadID); |
|
3260 } |
|
3261 } finally { |
|
3262 interaction.__count--; |
|
3263 |
|
3264 // If no async work was scheduled for this interaction, |
|
3265 // Notify subscribers that it's completed. |
|
3266 if (subscriber !== null && interaction.__count === 0) { |
|
3267 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
3268 } |
|
3269 } |
|
3270 } |
|
3271 } |
|
3272 } |
|
3273 |
|
3274 return returnValue; |
|
3275 } |
|
3276 |
|
3277 function unstable_wrap(callback) { |
|
3278 var threadID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_THREAD_ID; |
|
3279 |
|
3280 if (!enableSchedulerTracing) { |
|
3281 return callback; |
|
3282 } |
|
3283 |
|
3284 var wrappedInteractions = interactionsRef.current; |
|
3285 |
|
3286 var subscriber = subscriberRef.current; |
|
3287 if (subscriber !== null) { |
|
3288 subscriber.onWorkScheduled(wrappedInteractions, threadID); |
|
3289 } |
|
3290 |
|
3291 // Update the pending async work count for the current interactions. |
|
3292 // Update after calling subscribers in case of error. |
|
3293 wrappedInteractions.forEach(function (interaction) { |
|
3294 interaction.__count++; |
|
3295 }); |
|
3296 |
|
3297 var hasRun = false; |
|
3298 |
|
3299 function wrapped() { |
|
3300 var prevInteractions = interactionsRef.current; |
|
3301 interactionsRef.current = wrappedInteractions; |
|
3302 |
|
3303 subscriber = subscriberRef.current; |
|
3304 |
|
3305 try { |
|
3306 var returnValue = void 0; |
|
3307 |
|
3308 try { |
|
3309 if (subscriber !== null) { |
|
3310 subscriber.onWorkStarted(wrappedInteractions, threadID); |
|
3311 } |
|
3312 } finally { |
|
3313 try { |
|
3314 returnValue = callback.apply(undefined, arguments); |
|
3315 } finally { |
|
3316 interactionsRef.current = prevInteractions; |
|
3317 |
|
3318 if (subscriber !== null) { |
|
3319 subscriber.onWorkStopped(wrappedInteractions, threadID); |
|
3320 } |
|
3321 } |
|
3322 } |
|
3323 |
|
3324 return returnValue; |
|
3325 } finally { |
|
3326 if (!hasRun) { |
|
3327 // We only expect a wrapped function to be executed once, |
|
3328 // But in the event that it's executed more than once– |
|
3329 // Only decrement the outstanding interaction counts once. |
|
3330 hasRun = true; |
|
3331 |
|
3332 // Update pending async counts for all wrapped interactions. |
|
3333 // If this was the last scheduled async work for any of them, |
|
3334 // Mark them as completed. |
|
3335 wrappedInteractions.forEach(function (interaction) { |
|
3336 interaction.__count--; |
|
3337 |
|
3338 if (subscriber !== null && interaction.__count === 0) { |
|
3339 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
3340 } |
|
3341 }); |
|
3342 } |
|
3343 } |
|
3344 } |
|
3345 |
|
3346 wrapped.cancel = function cancel() { |
|
3347 subscriber = subscriberRef.current; |
|
3348 |
|
3349 try { |
|
3350 if (subscriber !== null) { |
|
3351 subscriber.onWorkCanceled(wrappedInteractions, threadID); |
|
3352 } |
|
3353 } finally { |
|
3354 // Update pending async counts for all wrapped interactions. |
|
3355 // If this was the last scheduled async work for any of them, |
|
3356 // Mark them as completed. |
|
3357 wrappedInteractions.forEach(function (interaction) { |
|
3358 interaction.__count--; |
|
3359 |
|
3360 if (subscriber && interaction.__count === 0) { |
|
3361 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
3362 } |
|
3363 }); |
|
3364 } |
|
3365 }; |
|
3366 |
|
3367 return wrapped; |
|
3368 } |
|
3369 |
|
3370 var subscribers = null; |
|
3371 if (enableSchedulerTracing) { |
|
3372 subscribers = new Set(); |
|
3373 } |
|
3374 |
|
3375 function unstable_subscribe(subscriber) { |
|
3376 if (enableSchedulerTracing) { |
|
3377 subscribers.add(subscriber); |
|
3378 |
|
3379 if (subscribers.size === 1) { |
|
3380 subscriberRef.current = { |
|
3381 onInteractionScheduledWorkCompleted: onInteractionScheduledWorkCompleted, |
|
3382 onInteractionTraced: onInteractionTraced, |
|
3383 onWorkCanceled: onWorkCanceled, |
|
3384 onWorkScheduled: onWorkScheduled, |
|
3385 onWorkStarted: onWorkStarted, |
|
3386 onWorkStopped: onWorkStopped |
|
3387 }; |
|
3388 } |
|
3389 } |
|
3390 } |
|
3391 |
|
3392 function unstable_unsubscribe(subscriber) { |
|
3393 if (enableSchedulerTracing) { |
|
3394 subscribers.delete(subscriber); |
|
3395 |
|
3396 if (subscribers.size === 0) { |
|
3397 subscriberRef.current = null; |
|
3398 } |
|
3399 } |
|
3400 } |
|
3401 |
|
3402 function onInteractionTraced(interaction) { |
|
3403 var didCatchError = false; |
|
3404 var caughtError = null; |
|
3405 |
|
3406 subscribers.forEach(function (subscriber) { |
|
3407 try { |
|
3408 subscriber.onInteractionTraced(interaction); |
|
3409 } catch (error) { |
|
3410 if (!didCatchError) { |
|
3411 didCatchError = true; |
|
3412 caughtError = error; |
|
3413 } |
|
3414 } |
|
3415 }); |
|
3416 |
|
3417 if (didCatchError) { |
|
3418 throw caughtError; |
|
3419 } |
|
3420 } |
|
3421 |
|
3422 function onInteractionScheduledWorkCompleted(interaction) { |
|
3423 var didCatchError = false; |
|
3424 var caughtError = null; |
|
3425 |
|
3426 subscribers.forEach(function (subscriber) { |
|
3427 try { |
|
3428 subscriber.onInteractionScheduledWorkCompleted(interaction); |
|
3429 } catch (error) { |
|
3430 if (!didCatchError) { |
|
3431 didCatchError = true; |
|
3432 caughtError = error; |
|
3433 } |
|
3434 } |
|
3435 }); |
|
3436 |
|
3437 if (didCatchError) { |
|
3438 throw caughtError; |
|
3439 } |
|
3440 } |
|
3441 |
|
3442 function onWorkScheduled(interactions, threadID) { |
|
3443 var didCatchError = false; |
|
3444 var caughtError = null; |
|
3445 |
|
3446 subscribers.forEach(function (subscriber) { |
|
3447 try { |
|
3448 subscriber.onWorkScheduled(interactions, threadID); |
|
3449 } catch (error) { |
|
3450 if (!didCatchError) { |
|
3451 didCatchError = true; |
|
3452 caughtError = error; |
|
3453 } |
|
3454 } |
|
3455 }); |
|
3456 |
|
3457 if (didCatchError) { |
|
3458 throw caughtError; |
|
3459 } |
|
3460 } |
|
3461 |
|
3462 function onWorkStarted(interactions, threadID) { |
|
3463 var didCatchError = false; |
|
3464 var caughtError = null; |
|
3465 |
|
3466 subscribers.forEach(function (subscriber) { |
|
3467 try { |
|
3468 subscriber.onWorkStarted(interactions, threadID); |
|
3469 } catch (error) { |
|
3470 if (!didCatchError) { |
|
3471 didCatchError = true; |
|
3472 caughtError = error; |
|
3473 } |
|
3474 } |
|
3475 }); |
|
3476 |
|
3477 if (didCatchError) { |
|
3478 throw caughtError; |
|
3479 } |
|
3480 } |
|
3481 |
|
3482 function onWorkStopped(interactions, threadID) { |
|
3483 var didCatchError = false; |
|
3484 var caughtError = null; |
|
3485 |
|
3486 subscribers.forEach(function (subscriber) { |
|
3487 try { |
|
3488 subscriber.onWorkStopped(interactions, threadID); |
|
3489 } catch (error) { |
|
3490 if (!didCatchError) { |
|
3491 didCatchError = true; |
|
3492 caughtError = error; |
|
3493 } |
|
3494 } |
|
3495 }); |
|
3496 |
|
3497 if (didCatchError) { |
|
3498 throw caughtError; |
|
3499 } |
|
3500 } |
|
3501 |
|
3502 function onWorkCanceled(interactions, threadID) { |
|
3503 var didCatchError = false; |
|
3504 var caughtError = null; |
|
3505 |
|
3506 subscribers.forEach(function (subscriber) { |
|
3507 try { |
|
3508 subscriber.onWorkCanceled(interactions, threadID); |
|
3509 } catch (error) { |
|
3510 if (!didCatchError) { |
|
3511 didCatchError = true; |
|
3512 caughtError = error; |
|
3513 } |
|
3514 } |
|
3515 }); |
|
3516 |
|
3517 if (didCatchError) { |
|
3518 throw caughtError; |
|
3519 } |
|
3520 } |
|
3521 |
|
3522 |
|
3523 |
|
3524 var SchedulerTracing = Object.freeze({ |
|
3525 get __interactionsRef () { return interactionsRef; }, |
|
3526 get __subscriberRef () { return subscriberRef; }, |
|
3527 unstable_clear: unstable_clear, |
|
3528 unstable_getCurrent: unstable_getCurrent, |
|
3529 unstable_getThreadID: unstable_getThreadID, |
|
3530 unstable_trace: unstable_trace, |
|
3531 unstable_wrap: unstable_wrap, |
|
3532 unstable_subscribe: unstable_subscribe, |
|
3533 unstable_unsubscribe: unstable_unsubscribe |
|
3534 }); |
|
3535 |
|
3536 var ReactSharedInternals$2 = { |
|
3537 ReactCurrentDispatcher: ReactCurrentDispatcher, |
|
3538 ReactCurrentOwner: ReactCurrentOwner, |
|
3539 IsSomeRendererActing: IsSomeRendererActing, |
|
3540 // Used by renderers to avoid bundling object-assign twice in UMD bundles: |
|
3541 assign: objectAssign |
|
3542 }; |
|
3543 |
|
3544 { |
|
3545 objectAssign(ReactSharedInternals$2, { |
|
3546 // These should not be included in production. |
|
3547 ReactDebugCurrentFrame: ReactDebugCurrentFrame, |
|
3548 // Shim for React DOM 16.0.0 which still destructured (but not used) this. |
|
3549 // TODO: remove in React 17.0. |
|
3550 ReactComponentTreeHook: {} |
|
3551 }); |
|
3552 } |
|
3553 |
|
3554 // Re-export the schedule API(s) for UMD bundles. |
|
3555 // This avoids introducing a dependency on a new UMD global in a minor update, |
|
3556 // Since that would be a breaking change (e.g. for all existing CodeSandboxes). |
|
3557 // This re-export is only required for UMD bundles; |
|
3558 // CJS bundles use the shared NPM package. |
|
3559 objectAssign(ReactSharedInternals$2, { |
|
3560 Scheduler: Scheduler, |
|
3561 SchedulerTracing: SchedulerTracing |
|
3562 }); |
|
3563 |
|
3564 var hasBadMapPolyfill = void 0; |
|
3565 |
|
3566 { |
|
3567 hasBadMapPolyfill = false; |
|
3568 try { |
|
3569 var frozenObject = Object.freeze({}); |
|
3570 var testMap = new Map([[frozenObject, null]]); |
|
3571 var testSet = new Set([frozenObject]); |
|
3572 // This is necessary for Rollup to not consider these unused. |
|
3573 // https://github.com/rollup/rollup/issues/1771 |
|
3574 // TODO: we can remove these if Rollup fixes the bug. |
|
3575 testMap.set(0, 0); |
|
3576 testSet.add(0); |
|
3577 } catch (e) { |
|
3578 // TODO: Consider warning about bad polyfills |
|
3579 hasBadMapPolyfill = true; |
|
3580 } |
|
3581 } |
|
3582 |
|
3583 function createFundamentalComponent(impl) { |
|
3584 // We use responder as a Map key later on. When we have a bad |
|
3585 // polyfill, then we can't use it as a key as the polyfill tries |
|
3586 // to add a property to the object. |
|
3587 if (true && !hasBadMapPolyfill) { |
|
3588 Object.freeze(impl); |
|
3589 } |
|
3590 var fundamantalComponent = { |
|
3591 $$typeof: REACT_FUNDAMENTAL_TYPE, |
|
3592 impl: impl |
|
3593 }; |
|
3594 { |
|
3595 Object.freeze(fundamantalComponent); |
|
3596 } |
|
3597 return fundamantalComponent; |
|
3598 } |
|
3599 |
|
3600 function createEventResponder(displayName, responderConfig) { |
|
3601 var getInitialState = responderConfig.getInitialState, |
|
3602 onEvent = responderConfig.onEvent, |
|
3603 onMount = responderConfig.onMount, |
|
3604 onUnmount = responderConfig.onUnmount, |
|
3605 onOwnershipChange = responderConfig.onOwnershipChange, |
|
3606 onRootEvent = responderConfig.onRootEvent, |
|
3607 rootEventTypes = responderConfig.rootEventTypes, |
|
3608 targetEventTypes = responderConfig.targetEventTypes; |
|
3609 |
|
3610 var eventResponder = { |
|
3611 $$typeof: REACT_RESPONDER_TYPE, |
|
3612 displayName: displayName, |
|
3613 getInitialState: getInitialState || null, |
|
3614 onEvent: onEvent || null, |
|
3615 onMount: onMount || null, |
|
3616 onOwnershipChange: onOwnershipChange || null, |
|
3617 onRootEvent: onRootEvent || null, |
|
3618 onUnmount: onUnmount || null, |
|
3619 rootEventTypes: rootEventTypes || null, |
|
3620 targetEventTypes: targetEventTypes || null |
|
3621 }; |
|
3622 // We use responder as a Map key later on. When we have a bad |
|
3623 // polyfill, then we can't use it as a key as the polyfill tries |
|
3624 // to add a property to the object. |
|
3625 if (true && !hasBadMapPolyfill) { |
|
3626 Object.freeze(eventResponder); |
|
3627 } |
|
3628 return eventResponder; |
|
3629 } |
|
3630 |
3083 var React = { |
3631 var React = { |
3084 Children: { |
3632 Children: { |
3085 map: mapChildren, |
3633 map: mapChildren, |
3086 forEach: forEachChildren, |
3634 forEach: forEachChildren, |
3087 count: countChildren, |
3635 count: countChildren, |