88
99/* eslint-disable no-var */
1010
11- // TODO: Currently there's only a single priority level, Deferred. Will add
12- // additional priorities.
13- var DEFERRED_TIMEOUT = 5000 ;
11+ // TODO: Use symbols?
12+ var ImmediatePriority = 1 ;
13+ var InteractivePriority = 2 ;
14+ var DefaultPriority = 3 ;
15+ var MaybePriority = 4 ;
16+
17+ // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
18+ // Math.pow(2, 30) - 1
19+ // 0b111111111111111111111111111111
20+ var maxSigned31BitInt = 1073741823 ;
21+
22+ // Times out immediately
23+ var IMMEDIATE_PRIORITY_TIMEOUT = - 1 ;
24+ // Eventually times out
25+ var INTERACTIVE_PRIORITY_TIMEOUT = 250 ;
26+ var DEFAULT_PRIORITY_TIMEOUT = 5000 ;
27+ // Never times out
28+ var MAYBE_PRIORITY_TIMEOUT = maxSigned31BitInt ;
1429
1530// Callbacks are stored as a circular, doubly linked list.
1631var firstCallbackNode = null ;
1732
18- var isPerformingWork = false ;
33+ var currentPriorityLevel = DefaultPriority ;
34+ var currentEventStartTime = - 1 ;
35+ var currentExpirationTime = - 1 ;
36+
37+ // This is set when a callback is being executed, to prevent re-entrancy.
38+ var isExecutingCallback = false ;
1939
2040var isHostCallbackScheduled = false ;
2141
@@ -25,6 +45,14 @@ var hasNativePerformanceNow =
2545var timeRemaining ;
2646if ( hasNativePerformanceNow ) {
2747 timeRemaining = function ( ) {
48+ if (
49+ firstCallbackNode !== null &&
50+ firstCallbackNode . expirationTime < currentExpirationTime
51+ ) {
52+ // A higher priority callback was scheduled. Yield so we can switch to
53+ // working on that.
54+ return 0 ;
55+ }
2856 // We assume that if we have a performance timer that the rAF callback
2957 // gets a performance timer value. Not sure if this is always true.
3058 var remaining = getFrameDeadline ( ) - performance . now ( ) ;
@@ -33,6 +61,12 @@ if (hasNativePerformanceNow) {
3361} else {
3462 timeRemaining = function ( ) {
3563 // Fallback to Date.now()
64+ if (
65+ firstCallbackNode !== null &&
66+ firstCallbackNode . expirationTime < currentExpirationTime
67+ ) {
68+ return 0 ;
69+ }
3670 var remaining = getFrameDeadline ( ) - Date . now ( ) ;
3771 return remaining > 0 ? remaining : 0 ;
3872 } ;
@@ -44,22 +78,22 @@ var deadlineObject = {
4478} ;
4579
4680function ensureHostCallbackIsScheduled ( ) {
47- if ( isPerformingWork ) {
81+ if ( isExecutingCallback ) {
4882 // Don't schedule work yet; wait until the next time we yield.
4983 return ;
5084 }
51- // Schedule the host callback using the earliest timeout in the list.
52- var timesOutAt = firstCallbackNode . timesOutAt ;
85+ // Schedule the host callback using the earliest expiration in the list.
86+ var expirationTime = firstCallbackNode . expirationTime ;
5387 if ( ! isHostCallbackScheduled ) {
5488 isHostCallbackScheduled = true ;
5589 } else {
5690 // Cancel the existing host callback.
5791 cancelCallback ( ) ;
5892 }
59- requestCallback ( flushWork , timesOutAt ) ;
93+ requestCallback ( flushWork , expirationTime ) ;
6094}
6195
62- function flushFirstCallback ( node ) {
96+ function flushFirstCallback ( ) {
6397 var flushedNode = firstCallbackNode ;
6498
6599 // Remove the node from the list before calling the callback. That way the
@@ -70,20 +104,101 @@ function flushFirstCallback(node) {
70104 firstCallbackNode = null ;
71105 next = null ;
72106 } else {
73- var previous = firstCallbackNode . previous ;
74- firstCallbackNode = previous . next = next ;
75- next . previous = previous ;
107+ var lastCallbackNode = firstCallbackNode . previous ;
108+ firstCallbackNode = lastCallbackNode . next = next ;
109+ next . previous = lastCallbackNode ;
76110 }
77111
78112 flushedNode . next = flushedNode . previous = null ;
79113
80114 // Now it's safe to call the callback.
81115 var callback = flushedNode . callback ;
82- callback ( deadlineObject ) ;
116+ var expirationTime = flushedNode . expirationTime ;
117+ var priorityLevel = flushedNode . priorityLevel ;
118+ var previousPriorityLevel = currentPriorityLevel ;
119+ var previousExpirationTime = currentExpirationTime ;
120+ currentPriorityLevel = priorityLevel ;
121+ currentExpirationTime = expirationTime ;
122+ var continuationCallback ;
123+ try {
124+ continuationCallback = callback ( deadlineObject ) ;
125+ } finally {
126+ currentPriorityLevel = previousPriorityLevel ;
127+ currentExpirationTime = previousExpirationTime ;
128+ }
129+
130+ if ( typeof continuationCallback === 'function' ) {
131+ var continuationNode : CallbackNode = {
132+ callback : continuationCallback ,
133+ priorityLevel,
134+ expirationTime,
135+ next : null ,
136+ previous : null ,
137+ } ;
138+
139+ // Insert the new callback into the list, sorted by its timeout.
140+ if ( firstCallbackNode === null ) {
141+ // This is the first callback in the list.
142+ firstCallbackNode = continuationNode . next = continuationNode . previous = continuationNode ;
143+ } else {
144+ var nextAfterContinuation = null ;
145+ var node = firstCallbackNode ;
146+ do {
147+ if ( node . expirationTime >= expirationTime ) {
148+ // This callback is equal or lower priority than the new one.
149+ nextAfterContinuation = node ;
150+ break ;
151+ }
152+ node = node . next ;
153+ } while ( node !== firstCallbackNode ) ;
154+
155+ if ( nextAfterContinuation === null ) {
156+ // No equal or lower priority callback was found, which means the new
157+ // callback is the lowest priority callback in the list.
158+ nextAfterContinuation = firstCallbackNode ;
159+ } else if ( nextAfterContinuation === firstCallbackNode ) {
160+ // The new callback is the highest priority callback in the list.
161+ firstCallbackNode = continuationNode ;
162+ ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
163+ }
164+
165+ var previous = nextAfterContinuation . previous ;
166+ previous . next = nextAfterContinuation . previous = continuationNode ;
167+ continuationNode . next = nextAfterContinuation ;
168+ continuationNode . previous = previous ;
169+ }
170+ }
171+ }
172+
173+ function flushImmediateWork ( ) {
174+ if (
175+ currentEventStartTime === - 1 &&
176+ firstCallbackNode !== null &&
177+ firstCallbackNode . priorityLevel === ImmediatePriority
178+ ) {
179+ isExecutingCallback = true ;
180+ deadlineObject . didTimeout = true ;
181+ try {
182+ do {
183+ flushFirstCallback ( ) ;
184+ } while (
185+ firstCallbackNode !== null &&
186+ firstCallbackNode . priorityLevel === ImmediatePriority
187+ ) ;
188+ } finally {
189+ isExecutingCallback = false ;
190+ if ( firstCallbackNode !== null ) {
191+ // There's still work remaining. Request another callback.
192+ ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
193+ } else {
194+ isHostCallbackScheduled = false ;
195+ }
196+ }
197+ }
83198}
84199
85200function flushWork ( didTimeout ) {
86- isPerformingWork = true ;
201+ isExecutingCallback = true ;
87202 deadlineObject . didTimeout = didTimeout ;
88203 try {
89204 if ( didTimeout ) {
@@ -93,12 +208,12 @@ function flushWork(didTimeout) {
93208 // earlier than that time. Then read the current time again and repeat.
94209 // This optimizes for as few performance.now calls as possible.
95210 var currentTime = getCurrentTime ( ) ;
96- if ( firstCallbackNode . timesOutAt <= currentTime ) {
211+ if ( firstCallbackNode . expirationTime <= currentTime ) {
97212 do {
98213 flushFirstCallback ( ) ;
99214 } while (
100215 firstCallbackNode !== null &&
101- firstCallbackNode . timesOutAt <= currentTime
216+ firstCallbackNode . expirationTime <= currentTime
102217 ) ;
103218 continue ;
104219 }
@@ -116,36 +231,93 @@ function flushWork(didTimeout) {
116231 }
117232 }
118233 } finally {
119- isPerformingWork = false ;
234+ isExecutingCallback = false ;
120235 if ( firstCallbackNode !== null ) {
121236 // There's still work remaining. Request another callback.
122237 ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
123238 } else {
124239 isHostCallbackScheduled = false ;
125240 }
241+ flushImmediateWork ( ) ;
242+ }
243+ }
244+
245+ function unstable_runWithPriority ( eventHandler , priorityLevel ) {
246+ switch ( priorityLevel ) {
247+ case ImmediatePriority :
248+ case InteractivePriority :
249+ case DefaultPriority :
250+ case MaybePriority :
251+ break ;
252+ default :
253+ priorityLevel = DefaultPriority ;
254+ }
255+
256+ var previousPriorityLevel = currentPriorityLevel ;
257+ var previousEventStartTime = currentEventStartTime ;
258+ currentPriorityLevel = priorityLevel ;
259+ currentEventStartTime = getCurrentTime ( ) ;
260+
261+ try {
262+ return eventHandler ( ) ;
263+ } finally {
264+ currentPriorityLevel = previousPriorityLevel ;
265+ currentEventStartTime = previousEventStartTime ;
266+ flushImmediateWork ( ) ;
126267 }
127268}
128269
129- function unstable_scheduleWork ( callback , options ) {
130- var currentTime = getCurrentTime ( ) ;
270+ function unstable_wrap ( callback ) {
271+ var parentPriorityLevel = currentPriorityLevel ;
272+ return function ( ) {
273+ var previousPriorityLevel = currentPriorityLevel ;
274+ var previousEventStartTime = currentEventStartTime ;
275+ currentPriorityLevel = parentPriorityLevel ;
276+ currentEventStartTime = getCurrentTime ( ) ;
277+
278+ try {
279+ return callback . apply ( this , arguments ) ;
280+ } finally {
281+ currentPriorityLevel = previousPriorityLevel ;
282+ currentEventStartTime = previousEventStartTime ;
283+ flushImmediateWork ( ) ;
284+ }
285+ } ;
286+ }
131287
132- var timesOutAt ;
288+ function unstable_scheduleWork ( callback , deprecated_options ) {
289+ var startTime =
290+ currentEventStartTime !== - 1 ? currentEventStartTime : getCurrentTime ( ) ;
291+
292+ var expirationTime ;
133293 if (
134- options !== undefined &&
135- options !== null &&
136- options . timeout !== null &&
137- options . timeout !== undefined
294+ typeof deprecated_options === 'object' &&
295+ deprecated_options !== null &&
296+ typeof deprecated_options . timeout === 'number'
138297 ) {
139- // Check for an explicit timeout
140- timesOutAt = currentTime + options . timeout ;
298+ // FIXME: Remove this branch once we lift expiration times out of React.
299+ expirationTime = startTime + deprecated_options . timeout ;
141300 } else {
142- // Compute an absolute timeout using the default constant.
143- timesOutAt = currentTime + DEFERRED_TIMEOUT ;
301+ switch ( currentPriorityLevel ) {
302+ case ImmediatePriority :
303+ expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT ;
304+ break ;
305+ case InteractivePriority :
306+ expirationTime = startTime + INTERACTIVE_PRIORITY_TIMEOUT ;
307+ break ;
308+ case MaybePriority :
309+ expirationTime = startTime + MAYBE_PRIORITY_TIMEOUT ;
310+ break ;
311+ case DefaultPriority :
312+ default :
313+ expirationTime = startTime + DEFAULT_PRIORITY_TIMEOUT ;
314+ }
144315 }
145316
146317 var newNode = {
147318 callback,
148- timesOutAt,
319+ priorityLevel : currentPriorityLevel ,
320+ expirationTime,
149321 next : null ,
150322 previous : null ,
151323 } ;
@@ -159,20 +331,20 @@ function unstable_scheduleWork(callback, options) {
159331 var next = null ;
160332 var node = firstCallbackNode ;
161333 do {
162- if ( node . timesOutAt > timesOutAt ) {
163- // The new callback times out before this one.
334+ if ( node . expirationTime > expirationTime ) {
335+ // The new callback expires before this one.
164336 next = node ;
165337 break ;
166338 }
167339 node = node . next ;
168340 } while ( node !== firstCallbackNode ) ;
169341
170342 if ( next === null ) {
171- // No callback with a later timeout was found, which means the new
172- // callback has the latest timeout in the list.
343+ // No callback with a later expiration was found, which means the new
344+ // callback has the latest expiration in the list.
173345 next = firstCallbackNode ;
174346 } else if ( next === firstCallbackNode ) {
175- // The new callback has the earliest timeout in the entire list.
347+ // The new callback has the earliest expiration in the entire list.
176348 firstCallbackNode = newNode ;
177349 ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
178350 }
@@ -299,6 +471,7 @@ if (typeof window === 'undefined') {
299471 getFrameDeadline = impl [ 2 ] ;
300472} else {
301473 if ( typeof console !== 'undefined' ) {
474+ // TODO: Remove fb.me link
302475 if ( typeof localRequestAnimationFrame !== 'function' ) {
303476 console . error (
304477 "This browser doesn't support requestAnimationFrame. " +
@@ -441,7 +614,12 @@ if (typeof window === 'undefined') {
441614}
442615
443616export {
617+ ImmediatePriority as unstable_ImmediatePriority ,
618+ InteractivePriority as unstable_InteractivePriority ,
619+ DefaultPriority as unstable_DefaultPriority ,
620+ unstable_runWithPriority ,
444621 unstable_scheduleWork ,
445622 unstable_cancelScheduledWork ,
623+ unstable_wrap ,
446624 getCurrentTime as unstable_now ,
447625} ;
0 commit comments