@@ -14,22 +14,36 @@ const DEFERRED_TIMEOUT = 5000;
1414let firstCallbackNode = null ;
1515
1616let priorityContext = Deferred ;
17- let isPerformingWork = false ;
17+ let currentlyFlushingTime = - 1 ;
1818
1919let isHostCallbackScheduled = false ;
2020
2121let timeRemaining ;
2222if ( hasNativePerformanceNow ) {
2323 timeRemaining = ( ) => {
24+ if (
25+ firstCallbackNode !== null &&
26+ firstCallbackNode . timesOutAt < currentlyFlushingTime
27+ ) {
28+ // A higher priority callback was scheduled. Yield so we can switch to
29+ // working on that.
30+ return 0 ;
31+ }
2432 // We assume that if we have a performance timer that the rAF callback
2533 // gets a performance timer value. Not sure if this is always true.
26- const remaining = getTimeRemaining ( ) - performance . now ( ) ;
34+ const remaining = getFrameDeadline ( ) - performance . now ( ) ;
2735 return remaining > 0 ? remaining : 0 ;
2836 } ;
2937} else {
38+ // Same thing, but with Date.now()
3039 timeRemaining = ( ) => {
31- // Fallback to Date.now()
32- const remaining = getTimeRemaining ( ) - Date . now ( ) ;
40+ if (
41+ firstCallbackNode !== null &&
42+ firstCallbackNode . timesOutAt < currentlyFlushingTime
43+ ) {
44+ return 0 ;
45+ }
46+ const remaining = getFrameDeadline ( ) - Date . now ( ) ;
3347 return remaining > 0 ? remaining : 0 ;
3448 } ;
3549}
@@ -40,7 +54,7 @@ const deadlineObject = {
4054} ;
4155
4256function ensureHostCallbackIsScheduled ( highestPriorityNode ) {
43- if ( isPerformingWork ) {
57+ if ( currentlyFlushingTime !== - 1 ) {
4458 // Don't schedule work yet; wait until the next time we yield.
4559 return ;
4660 }
@@ -62,54 +76,96 @@ function computeAbsoluteTimeoutForPriority(currentTime, priority) {
6276 throw new Error ( 'Not yet implemented.' ) ;
6377}
6478
65- function flushCallback ( node ) {
66- // This is already true; only assigning to appease Flow.
67- firstCallbackNode = node ;
79+ function flushFirstCallback ( ) {
80+ const flushedNode = firstCallbackNode ;
6881
69- // Remove the node from the list before calling the callback. That way the
82+ // Remove the flushedNode from the list before calling the callback. That way the
7083 // list is in a consistent state even if the callback throws.
71- const next = firstCallbackNode . next ;
84+ let next = firstCallbackNode . next ;
7285 if ( firstCallbackNode === next ) {
7386 // This is the last callback in the list.
7487 firstCallbackNode = null ;
88+ next = null ;
7589 } else {
7690 const previous = firstCallbackNode . previous ;
7791 firstCallbackNode = previous . next = next ;
7892 next . previous = previous ;
7993 }
8094
81- node . next = node . previous = null ;
95+ flushedNode . next = flushedNode . previous = null ;
8296
8397 // Now it's safe to call the callback.
84- const callback = node . callback ;
85- callback ( deadlineObject ) ;
98+ currentlyFlushingTime = flushedNode . timesOutAt ;
99+ const callback = flushedNode . callback ;
100+ const continuationCallback = callback ( deadlineObject ) ;
101+
102+ if ( typeof continuationCallback === 'function' ) {
103+ const timesOutAt = flushedNode . timesOutAt ;
104+ const continuationNode : CallbackNode = {
105+ callback : continuationCallback ,
106+ timesOutAt,
107+ next : null ,
108+ previous : null ,
109+ } ;
110+
111+ // Insert the new callback into the list, sorted by its timeout.
112+ if ( firstCallbackNode === null ) {
113+ // This is the first callback in the list.
114+ firstCallbackNode = continuationNode . next = continuationNode . previous = continuationNode ;
115+ } else {
116+ let nextAfterContinuation = null ;
117+ let node = firstCallbackNode ;
118+ do {
119+ if ( node . timesOutAt >= timesOutAt ) {
120+ // This callback is equal or lower priority than the new one.
121+ nextAfterContinuation = node ;
122+ break ;
123+ }
124+ node = node . next ;
125+ } while ( node !== firstCallbackNode ) ;
126+
127+ if ( nextAfterContinuation === null ) {
128+ // No equal or lower priority callback was found, which means the new
129+ // callback is the lowest priority callback in the list.
130+ nextAfterContinuation = firstCallbackNode ;
131+ } else if ( nextAfterContinuation === firstCallbackNode ) {
132+ // The new callback is the highest priority callback in the list.
133+ firstCallbackNode = continuationNode ;
134+ ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
135+ }
136+
137+ const previous = nextAfterContinuation . previous ;
138+ previous . next = nextAfterContinuation . previous = continuationNode ;
139+ continuationNode . next = nextAfterContinuation ;
140+ continuationNode . previous = previous ;
141+ }
142+ }
86143}
87144
88145function flushWork ( didTimeout ) {
89- isPerformingWork = true ;
90146 deadlineObject . didTimeout = didTimeout ;
91147 try {
92- if ( firstCallbackNode !== null ) {
93- if ( didTimeout ) {
94- // Flush all the timed out callbacks without yielding.
148+ if ( didTimeout ) {
149+ // Flush all the timed out callbacks without yielding.
150+ while (
151+ firstCallbackNode !== null &&
152+ firstCallbackNode . timesOutAt <= getCurrentTime ( )
153+ ) {
154+ flushFirstCallback ( ) ;
155+ }
156+ } else {
157+ // Keep flushing callbacks until we run out of time in the frame.
158+ if ( firstCallbackNode !== null ) {
95159 do {
96- flushCallback ( firstCallbackNode ) ;
160+ flushFirstCallback ( ) ;
97161 } while (
98162 firstCallbackNode !== null &&
99- firstCallbackNode . timesOutAt <= getCurrentTime ( )
163+ getFrameDeadline ( ) - getCurrentTime ( ) > 0
100164 ) ;
101- } else {
102- // Keep flushing callbacks until we run out of time in the frame.
103- while (
104- firstCallbackNode !== null &&
105- getTimeRemaining ( ) - getCurrentTime ( ) > 0
106- ) {
107- flushCallback ( firstCallbackNode ) ;
108- }
109165 }
110166 }
111167 } finally {
112- isPerformingWork = false ;
168+ currentlyFlushingTime = - 1 ;
113169 if ( firstCallbackNode !== null ) {
114170 // There's still work remaining. Request another callback.
115171 ensureHostCallbackIsScheduled ( firstCallbackNode ) ;
@@ -123,19 +179,16 @@ function unstable_scheduleWork(callback, options) {
123179 const currentTime = getCurrentTime ( ) ;
124180
125181 let timesOutAt ;
126- if ( options !== undefined && options !== null ) {
127- const timeoutOption = options . timeout ;
128- if ( timeoutOption !== null && timeoutOption !== undefined ) {
129- // If an explicit timeout is provided, it takes precedence over the
130- // priority context.
131- timesOutAt = currentTime + timeoutOption ;
132- } else {
133- // Compute an absolute timeout using the current priority context.
134- timesOutAt = computeAbsoluteTimeoutForPriority (
135- currentTime ,
136- priorityContext ,
137- ) ;
138- }
182+ if (
183+ options !== undefined &&
184+ options !== null &&
185+ options . timeout !== null &&
186+ options . timeout !== undefined
187+ ) {
188+ // Check for an explicit timeout.
189+ timesOutAt = currentTime + options . timeout ;
190+ } else if ( currentlyFlushingTime !== - 1 ) {
191+ timesOutAt = currentlyFlushingTime ;
139192 } else {
140193 timesOutAt = computeAbsoluteTimeoutForPriority (
141194 currentTime ,
@@ -280,7 +333,7 @@ if (hasNativePerformanceNow) {
280333
281334let requestCallback ;
282335let cancelCallback ;
283- let getTimeRemaining ;
336+ let getFrameDeadline ;
284337
285338if ( typeof window === 'undefined' ) {
286339 // If this accidentally gets imported in a non-browser environment, fallback
@@ -292,13 +345,13 @@ if (typeof window === 'undefined') {
292345 cancelCallback = ( ) => {
293346 clearTimeout ( timeoutID ) ;
294347 } ;
295- getTimeRemaining = ( ) => 0 ;
348+ getFrameDeadline = ( ) => 0 ;
296349} else if ( window . _sched ) {
297350 // Dynamic injection, only for testing purposes.
298351 const impl = window . _sched ;
299352 requestCallback = impl [ 0 ] ;
300353 cancelCallback = impl [ 1 ] ;
301- getTimeRemaining = impl [ 2 ] ;
354+ getFrameDeadline = impl [ 2 ] ;
302355} else {
303356 if ( typeof console !== 'undefined' ) {
304357 if ( typeof localRequestAnimationFrame !== 'function' ) {
@@ -332,7 +385,7 @@ if (typeof window === 'undefined') {
332385 let previousFrameTime = 33 ;
333386 let activeFrameTime = 33 ;
334387
335- getTimeRemaining = ( ) => frameDeadline ;
388+ getFrameDeadline = ( ) => frameDeadline ;
336389
337390 // We use the postMessage trick to defer idle work until after the repaint.
338391 const messageKey =
0 commit comments