4
* This source code is licensed under the MIT license found in the
5
* LICENSE file in the root directory of this source tree.
6
*
7
- * @flow
7
*/
8
10
-'use strict';
9
+/* eslint-disable no-var */
10
12
-/**
13
- * A scheduling library to allow scheduling work with more granular priority and
14
- * control than requestAnimationFrame and requestIdleCallback.
15
- * Current TODO items:
16
- * X- Pull out the scheduleWork polyfill built into React
17
- * X- Initial test coverage
18
- * X- Support for multiple callbacks
19
- * - Support for two priorities; serial and deferred
20
- * - Better test coverage
21
- * - Better docblock
22
- * - Polish documentation, API
23
- */
24
-
25
-// This is a built-in polyfill for requestIdleCallback. It works by scheduling
26
-// a requestAnimationFrame, storing the time for the start of the frame, then
27
-// scheduling a postMessage which gets scheduled after paint. Within the
28
-// postMessage handler do as much work as possible until time + frame rate.
29
-// By separating the idle call into a separate event tick we ensure that
30
-// layout, paint and other browser work is counted against the available time.
31
-// The frame rate is dynamically adjusted.
32
-
33
-import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
34
-type FrameCallbackType = Deadline => void;
35
-type CallbackConfigType = {|
36
- scheduledCallback: FrameCallbackType,
37
- timeoutTime: number,
38
- next: CallbackConfigType | null, // creating a linked list
39
- prev: CallbackConfigType | null, // creating a linked list
40
-|};
11
+// TODO: Currently there's only a single priority level, Deferred. Will add
12
+// additional priorities.
13
+var DEFERRED_TIMEOUT = 5000;
14
42
-export type CallbackIdType = CallbackConfigType;
15
+// Callbacks are stored as a circular, doubly linked list.
16
+var firstCallbackNode = null;
17
44
-import {canUseDOM} from 'shared/ExecutionEnvironment';
18
+var isPerformingWork = false;
19
46
-// We capture a local reference to any global, in case it gets polyfilled after
47
-// this module is initially evaluated.
48
-// We want to be using a consistent implementation.
49
-const localDate = Date;
50
-
51
-// This initialization code may run even on server environments
52
-// if a component just imports ReactDOM (e.g. for findDOMNode).
53
-// Some environments might not have setTimeout or clearTimeout.
54
-// However, we always expect them to be defined on the client.
55
-// https://github.com/facebook/react/pull/13088
56
-const localSetTimeout =
57
- typeof setTimeout === 'function' ? setTimeout : (undefined: any);
58
-const localClearTimeout =
59
- typeof clearTimeout === 'function' ? clearTimeout : (undefined: any);
60
-
61
-// We don't expect either of these to necessarily be defined,
62
-// but we will error later if they are missing on the client.
63
-const localRequestAnimationFrame =
64
- typeof requestAnimationFrame === 'function'
65
- ? requestAnimationFrame
66
- : (undefined: any);
67
-const localCancelAnimationFrame =
68
- typeof cancelAnimationFrame === 'function'
69
- ? cancelAnimationFrame
70
- : (undefined: any);
20
+var isHostCallbackScheduled = false;
21
72
-const hasNativePerformanceNow =
22
+var hasNativePerformanceNow =
23
typeof performance === 'object' && typeof performance.now === 'function';
24
75
-let now;
25
+var timeRemaining;
26
if (hasNativePerformanceNow) {
77
- const Performance = performance;
78
- now = function() {
79
- return Performance.now();
27
+ timeRemaining = function() {
28
+ // We assume that if we have a performance timer that the rAF callback
29
+ // gets a performance timer value. Not sure if this is always true.
30
+ var remaining = getFrameDeadline() - performance.now();
31
+ return remaining > 0 ? remaining : 0;
32
};
33
} else {
82
- now = function() {
83
- return localDate.now();
34
+ timeRemaining = function() {
35
+ // Fallback to Date.now()
36
+ var remaining = getFrameDeadline() - Date.now();
37
+ return remaining > 0 ? remaining : 0;
38
};
39
}
40
87
-let scheduleWork: (
88
- callback: FrameCallbackType,
89
- options?: {timeout: number},
90
-) => CallbackIdType;
91
-let cancelScheduledWork: (callbackId: CallbackIdType) => void;
92
-
93
-if (!canUseDOM) {
94
- const timeoutIds = new Map();
95
-
96
- scheduleWork = function(
97
- callback: FrameCallbackType,
98
- options?: {timeout: number},
99
- ): CallbackIdType {
100
- // keeping return type consistent
101
- const callbackConfig = {
102
- scheduledCallback: callback,
103
- timeoutTime: 0,
104
- next: null,
105
- prev: null,
106
- };
107
- const timeoutId = localSetTimeout(() => {
108
- callback({
109
- timeRemaining() {
110
- return Infinity;
111
- },
112
- didTimeout: false,
113
- });
114
- });
115
- timeoutIds.set(callback, timeoutId);
116
- return callbackConfig;
117
- };
118
- cancelScheduledWork = function(callbackId: CallbackIdType) {
119
- const callback = callbackId.scheduledCallback;
120
- const timeoutId = timeoutIds.get(callback);
121
- timeoutIds.delete(callbackId);
122
- localClearTimeout(timeoutId);
123
- };
124
-} else {
125
- if (__DEV__) {
126
- if (typeof console !== 'undefined') {
127
- if (typeof localRequestAnimationFrame !== 'function') {
128
- console.error(
129
- "This browser doesn't support requestAnimationFrame. " +
130
- 'Make sure that you load a ' +
131
- 'polyfill in older browsers. https://fb.me/react-polyfills',
132
- );
41
+var deadlineObject = {
42
+ timeRemaining,
43
+ didTimeout: false,
44
+};
45
+
46
+function ensureHostCallbackIsScheduled() {
47
+ if (isPerformingWork) {
48
+ // Don't schedule work yet; wait until the next time we yield.
49
+ return;
50
+ }
51
+ // Schedule the host callback using the earliest timeout in the list.
52
+ var timesOutAt = firstCallbackNode.timesOutAt;
53
+ if (!isHostCallbackScheduled) {
54
+ isHostCallbackScheduled = true;
55
+ } else {
56
+ // Cancel the existing host callback.
57
+ cancelCallback();
58
+ }
59
+ requestCallback(flushWork, timesOutAt);
60
+}
61
+
62
+function flushFirstCallback(node) {
63
+ var flushedNode = firstCallbackNode;
64
+
65
+ // Remove the node from the list before calling the callback. That way the
66
+ // list is in a consistent state even if the callback throws.
67
+ var next = firstCallbackNode.next;
68
+ if (firstCallbackNode === next) {
69
+ // This is the last callback in the list.
70
+ firstCallbackNode = null;
71
+ next = null;
72
+ } else {
73
+ var previous = firstCallbackNode.previous;
74
+ firstCallbackNode = previous.next = next;
75
+ next.previous = previous;
76
+ }
77
+
78
+ flushedNode.next = flushedNode.previous = null;
79
+
80
+ // Now it's safe to call the callback.
81
+ var callback = flushedNode.callback;
82
+ callback(deadlineObject);
83
+}
84
+
85
+function flushWork(didTimeout) {
86
+ isPerformingWork = true;
87
+ deadlineObject.didTimeout = didTimeout;
88
+ try {
89
+ if (didTimeout) {
90
+ // Flush all the timed out callbacks without yielding.
91
+ while (firstCallbackNode !== null) {
92
+ // Read the current time. Flush all the callbacks that expire at or
93
+ // earlier than that time. Then read the current time again and repeat.
94
+ // This optimizes for as few performance.now calls as possible.
95
+ var currentTime = getCurrentTime();
96
+ if (firstCallbackNode.timesOutAt <= currentTime) {
97
+ do {
98
+ flushFirstCallback();
99
+ } while (
100
+ firstCallbackNode !== null &&
101
+ firstCallbackNode.timesOutAt <= currentTime
102
+ );
103
+ continue;
104
+ }
105
+ break;
106
}
134
- if (typeof localCancelAnimationFrame !== 'function') {
135
- console.error(
136
- "This browser doesn't support cancelAnimationFrame. " +
137
- 'Make sure that you load a ' +
138
- 'polyfill in older browsers. https://fb.me/react-polyfills',
107
+ } else {
108
+ // Keep flushing callbacks until we run out of time in the frame.
109
+ if (firstCallbackNode !== null) {
110
+ do {
111
+ flushFirstCallback();
112
+ } while (
113
+ firstCallbackNode !== null &&
114
+ getFrameDeadline() - getCurrentTime() > 0
115
);
116
}
117
}
118
+ } finally {
119
+ isPerformingWork = false;
120
+ if (firstCallbackNode !== null) {
121
+ // There's still work remaining. Request another callback.
122
+ ensureHostCallbackIsScheduled(firstCallbackNode);
123
+ } else {
124
+ isHostCallbackScheduled = false;
125
+ }
126
}
127
+}
128
144
- let headOfPendingCallbacksLinkedList: CallbackConfigType | null = null;
145
- let tailOfPendingCallbacksLinkedList: CallbackConfigType | null = null;
146
-
147
- // We track what the next soonest timeoutTime is, to be able to quickly tell
148
- // if none of the scheduled callbacks have timed out.
149
- let nextSoonestTimeoutTime = -1;
150
-
151
- let isIdleScheduled = false;
152
- let isAnimationFrameScheduled = false;
153
-
154
- // requestAnimationFrame does not run when the tab is in the background.
155
- // if we're backgrounded we prefer for that work to happen so that the page
156
- // continues to load in the background.
157
- // so we also schedule a 'setTimeout' as a fallback.
158
- const animationFrameTimeout = 100;
159
- let rafID;
160
- let timeoutID;
161
- const scheduleAnimationFrameWithFallbackSupport = function(callback) {
162
- // schedule rAF and also a setTimeout
163
- rafID = localRequestAnimationFrame(function(timestamp) {
164
- // cancel the setTimeout
165
- localClearTimeout(timeoutID);
166
- callback(timestamp);
167
- });
168
- timeoutID = localSetTimeout(function() {
169
- // cancel the requestAnimationFrame
170
- localCancelAnimationFrame(rafID);
171
- callback(now());
172
- }, animationFrameTimeout);
173
- };
129
+function unstable_scheduleWork(callback, options) {
130
+ var currentTime = getCurrentTime();
131
175
- let frameDeadline = 0;
176
- // We start out assuming that we run at 30fps but then the heuristic tracking
177
- // will adjust this value to a faster fps if we get more frequent animation
178
- // frames.
179
- let previousFrameTime = 33;
180
- let activeFrameTime = 33;
181
-
182
- const frameDeadlineObject: Deadline = {
183
- didTimeout: false,
184
- timeRemaining() {
185
- const remaining = frameDeadline - now();
186
- return remaining > 0 ? remaining : 0;
187
- },
132
+ var timesOutAt;
133
+ if (
134
+ options !== undefined &&
135
+ options !== null &&
136
+ options.timeout !== null &&
137
+ options.timeout !== undefined
138
+ ) {
139
+ // Check for an explicit timeout
140
+ timesOutAt = currentTime + options.timeout;
141
+ } else {
142
+ // Compute an absolute timeout using the default constant.
143
+ timesOutAt = currentTime + DEFERRED_TIMEOUT;
144
+ }
145
+
146
+ var newNode = {
147
+ callback,
148
+ timesOutAt,
149
+ next: null,
150
+ previous: null,
151
};
152
190
- /**
191
- * Handles the case where a callback errors:
192
- * - don't catch the error, because this changes debugging behavior
193
- * - do start a new postMessage callback, to call any remaining callbacks,
194
- * - but only if there is an error, so there is not extra overhead.
195
- */
196
- const callUnsafely = function(
197
- callbackConfig: CallbackConfigType,
198
- arg: Deadline,
199
- ) {
200
- const callback = callbackConfig.scheduledCallback;
201
- let finishedCalling = false;
202
- try {
203
- callback(arg);
204
- finishedCalling = true;
205
- } finally {
206
- // always remove it from linked list
207
- cancelScheduledWork(callbackConfig);
208
-
209
- if (!finishedCalling) {
210
- // an error must have been thrown
211
- isIdleScheduled = true;
212
- window.postMessage(messageKey, '*');
153
+ // Insert the new callback into the list, sorted by its timeout.
154
+ if (firstCallbackNode === null) {
155
+ // This is the first callback in the list.
156
+ firstCallbackNode = newNode.next = newNode.previous = newNode;
157
+ ensureHostCallbackIsScheduled(firstCallbackNode);
158
+ } else {
159
+ var next = null;
160
+ var node = firstCallbackNode;
161
+ do {
162
+ if (node.timesOutAt > timesOutAt) {
163
+ // The new callback times out before this one.
164
+ next = node;
165
+ break;
166
}
167
+ node = node.next;
168
+ } while (node !== firstCallbackNode);
169
+
170
+ 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.
173
+ next = firstCallbackNode;
174
+ } else if (next === firstCallbackNode) {
175
+ // The new callback has the earliest timeout in the entire list.
176
+ firstCallbackNode = newNode;
177
+ ensureHostCallbackIsScheduled(firstCallbackNode);
178
}
215
- };
179
217
- /**
218
- * Checks for timed out callbacks, runs them, and then checks again to see if
219
- * any more have timed out.
220
- * Keeps doing this until there are none which have currently timed out.
221
- */
222
- const callTimedOutCallbacks = function() {
223
- if (headOfPendingCallbacksLinkedList === null) {
224
- return;
180
+ var previous = next.previous;
181
+ previous.next = next.previous = newNode;
182
+ newNode.next = next;
183
+ newNode.previous = previous;
184
+ }
185
+
186
+ return newNode;
187
+}
188
+
189
+function unstable_cancelScheduledWork(callbackNode: CallbackNode): void {
190
+ var next = callbackNode.next;
191
+ if (next === null) {
192
+ // Already cancelled.
193
+ return;
194
+ }
195
+
196
+ if (next === callbackNode) {
197
+ // This is the only scheduled callback. Clear the list.
198
+ firstCallbackNode = null;
199
+ } else {
200
+ // Remove the callback from its position in the list.
201
+ if (callbackNode === firstCallbackNode) {
202
+ firstCallbackNode = next;
203
}
204
+ var previous = callbackNode.previous;
205
+ previous.next = next;
206
+ next.previous = previous;
207
+ }
208
227
- const currentTime = now();
228
- // TODO: this would be more efficient if deferred callbacks are stored in
229
- // min heap.
230
- // Or in a linked list with links for both timeoutTime order and insertion
231
- // order.
232
- // For now an easy compromise is the current approach:
233
- // Keep a pointer to the soonest timeoutTime, and check that first.
234
- // If it has not expired, we can skip traversing the whole list.
235
- // If it has expired, then we step through all the callbacks.
236
- if (nextSoonestTimeoutTime === -1 || nextSoonestTimeoutTime > currentTime) {
237
- // We know that none of them have timed out yet.
238
- return;
209
+ callbackNode.next = callbackNode.previous = null;
210
+}
211
+
212
+// The remaining code is essentially a polyfill for requestIdleCallback. It
213
+// works by scheduling a requestAnimationFrame, storing the time for the start
214
+// of the frame, then scheduling a postMessage which gets scheduled after paint.
215
+// Within the postMessage handler do as much work as possible until time + frame
216
+// rate. By separating the idle call into a separate event tick we ensure that
217
+// layout, paint and other browser work is counted against the available time.
218
+// The frame rate is dynamically adjusted.
219
+
220
+// We capture a local reference to any global, in case it gets polyfilled after
221
+// this module is initially evaluated. We want to be using a
222
+// consistent implementation.
223
+var localDate = Date;
224
+
225
+// This initialization code may run even on server environments if a component
226
+// just imports ReactDOM (e.g. for findDOMNode). Some environments might not
227
+// have setTimeout or clearTimeout. However, we always expect them to be defined
228
+// on the client. https://github.com/facebook/react/pull/13088
229
+var localSetTimeout = typeof setTimeout === 'function' ? setTimeout : undefined;
230
+var localClearTimeout =
231
+ typeof clearTimeout === 'function' ? clearTimeout : undefined;
232
+
233
+// We don't expect either of these to necessarily be defined, but we will error
234
+// later if they are missing on the client.
235
+var localRequestAnimationFrame =
236
+ typeof requestAnimationFrame === 'function'
237
+ ? requestAnimationFrame
238
+ : undefined;
239
+var localCancelAnimationFrame =
240
+ typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : undefined;
241
+
242
+var getCurrentTime;
243
+
244
+// requestAnimationFrame does not run when the tab is in the background. If
245
+// we're backgrounded we prefer for that work to happen so that the page
246
+// continues to load in the background. So we also schedule a 'setTimeout' as
247
+// a fallback.
248
+// TODO: Need a better heuristic for backgrounded work.
249
+var ANIMATION_FRAME_TIMEOUT = 100;
250
+var rAFID;
251
+var rAFTimeoutID;
252
+var requestAnimationFrameWithTimeout = function(callback) {
253
+ // schedule rAF and also a setTimeout
254
+ rAFID = localRequestAnimationFrame(function(timestamp) {
255
+ // cancel the setTimeout
256
+ localClearTimeout(rAFTimeoutID);
257
+ callback(timestamp);
258
+ });
259
+ rAFTimeoutID = localSetTimeout(function() {
260
+ // cancel the requestAnimationFrame
261
+ localCancelAnimationFrame(rAFID);
262
+ callback(getCurrentTime());
263
+ }, ANIMATION_FRAME_TIMEOUT);
264
+};
265
+
266
+if (hasNativePerformanceNow) {
267
+ var Performance = performance;
268
+ getCurrentTime = function() {
269
+ return Performance.now();
270
+ };
271
+} else {
272
+ getCurrentTime = function() {
273
+ return localDate.now();
274
+ };
275
+}
276
+
277
+var requestCallback;
278
+var cancelCallback;
279
+var getFrameDeadline;
280
+
281
+if (typeof window === 'undefined') {
282
+ // If this accidentally gets imported in a non-browser environment, fallback
283
+ // to a naive implementation.
284
+ var timeoutID = -1;
285
+ requestCallback = function(callback, absoluteTimeout) {
286
+ timeoutID = setTimeout(callback, 0, true);
287
+ };
288
+ cancelCallback = function() {
289
+ clearTimeout(timeoutID);
290
+ };
291
+ getFrameDeadline = function() {
292
+ return 0;
293
+ };
294
+} else if (window._schedMock) {
295
+ // Dynamic injection, only for testing purposes.
296
+ var impl = window._schedMock;
297
+ requestCallback = impl[0];
298
+ cancelCallback = impl[1];
299
+ getFrameDeadline = impl[2];
300
+} else {
301
+ if (typeof console !== 'undefined') {
302
+ if (typeof localRequestAnimationFrame !== 'function') {
303
+ console.error(
304
+ "This browser doesn't support requestAnimationFrame. " +
305
+ 'Make sure that you load a ' +
306
+ 'polyfill in older browsers. https://fb.me/react-polyfills',
307
+ );
308
}
240
- // NOTE: we intentionally wait to update the nextSoonestTimeoutTime until
241
- // after successfully calling any timed out callbacks.
242
- // If a timed out callback throws an error, we could get stuck in a state
243
- // where the nextSoonestTimeoutTime was set wrong.
244
- let updatedNextSoonestTimeoutTime = -1; // we will update nextSoonestTimeoutTime below
245
- const timedOutCallbacks = [];
246
-
247
- // iterate once to find timed out callbacks and find nextSoonestTimeoutTime
248
- let currentCallbackConfig = headOfPendingCallbacksLinkedList;
249
- while (currentCallbackConfig !== null) {
250
- const timeoutTime = currentCallbackConfig.timeoutTime;
251
- if (timeoutTime !== -1 && timeoutTime <= currentTime) {
252
- // it has timed out!
253
- timedOutCallbacks.push(currentCallbackConfig);
254
- } else {
255
- if (
256
- timeoutTime !== -1 &&
257
- (updatedNextSoonestTimeoutTime === -1 ||
258
- timeoutTime < updatedNextSoonestTimeoutTime)
259
- ) {
260
- updatedNextSoonestTimeoutTime = timeoutTime;
261
- }
262
- }
263
- currentCallbackConfig = currentCallbackConfig.next;
309
+ if (typeof localCancelAnimationFrame !== 'function') {
310
+ console.error(
311
+ "This browser doesn't support cancelAnimationFrame. " +
312
+ 'Make sure that you load a ' +
313
+ 'polyfill in older browsers. https://fb.me/react-polyfills',
314
+ );
315
}
316
+ }
317
266
- if (timedOutCallbacks.length > 0) {
267
- frameDeadlineObject.didTimeout = true;
268
- for (let i = 0, len = timedOutCallbacks.length; i < len; i++) {
269
- callUnsafely(timedOutCallbacks[i], frameDeadlineObject);
270
- }
271
- }
318
+ var scheduledCallback = null;
319
+ var isIdleScheduled = false;
320
+ var timeoutTime = -1;
321
273
- // NOTE: we intentionally wait to update the nextSoonestTimeoutTime until
274
- // after successfully calling any timed out callbacks.
275
- nextSoonestTimeoutTime = updatedNextSoonestTimeoutTime;
322
+ var isAnimationFrameScheduled = false;
323
+
324
+ var isPerformingIdleWork = false;
325
+
326
+ var frameDeadline = 0;
327
+ // We start out assuming that we run at 30fps but then the heuristic tracking
328
+ // will adjust this value to a faster fps if we get more frequent animation
329
+ // frames.
330
+ var previousFrameTime = 33;
331
+ var activeFrameTime = 33;
332
+
333
+ getFrameDeadline = function() {
334
+ return frameDeadline;
335
};
336
337
// We use the postMessage trick to defer idle work until after the repaint.
279
- const messageKey =
338
+ var messageKey =
339
'__reactIdleCallback$' +
340
Math.random()
341
.toString(36)
342
.slice(2);
284
- const idleTick = function(event) {
343
+ var idleTick = function(event) {
344
if (event.source !== window || event.data !== messageKey) {
345
return;
346
}
288
- isIdleScheduled = false;
347
290
- if (headOfPendingCallbacksLinkedList === null) {
291
- return;
292
- }
348
+ isIdleScheduled = false;
349
294
- // First call anything which has timed out, until we have caught up.
295
- callTimedOutCallbacks();
350
+ var currentTime = getCurrentTime();
351
297
- let currentTime = now();
298
- // Next, as long as we have idle time, try calling more callbacks.
299
- while (
300
- frameDeadline - currentTime > 0 &&
301
- headOfPendingCallbacksLinkedList !== null
302
- ) {
303
- const latestCallbackConfig = headOfPendingCallbacksLinkedList;
304
- frameDeadlineObject.didTimeout = false;
305
- // callUnsafely will remove it from the head of the linked list
306
- callUnsafely(latestCallbackConfig, frameDeadlineObject);
307
- currentTime = now();
352
+ var didTimeout = false;
353
+ if (frameDeadline - currentTime <= 0) {
354
+ // There's no time left in this idle period. Check if the callback has
355
+ // a timeout and whether it's been exceeded.
356
+ if (timeoutTime !== -1 && timeoutTime <= currentTime) {
357
+ // Exceeded the timeout. Invoke the callback even though there's no
358
+ // time left.
359
+ didTimeout = true;
360
+ } else {
361
+ // No timeout.
362
+ if (!isAnimationFrameScheduled) {
363
+ // Schedule another animation callback so we retry later.
364
+ isAnimationFrameScheduled = true;
365
+ requestAnimationFrameWithTimeout(animationTick);
366
+ }
367
+ // Exit without invoking the callback.
368
+ return;
369
+ }
370
}
309
- if (headOfPendingCallbacksLinkedList !== null) {
310
- if (!isAnimationFrameScheduled) {
311
- // Schedule another animation callback so we retry later.
312
- isAnimationFrameScheduled = true;
313
- scheduleAnimationFrameWithFallbackSupport(animationTick);
371
+
372
+ timeoutTime = -1;
373
+ var callback = scheduledCallback;
374
+ scheduledCallback = null;
375
+ if (callback !== null) {
376
+ isPerformingIdleWork = true;
377
+ try {
378
+ callback(didTimeout);
379
+ } finally {
380
+ isPerformingIdleWork = false;
381
}
382
}
383
};
385
// something better for old IE.
386
window.addEventListener('message', idleTick, false);
387
321
- const animationTick = function(rafTime) {
388
+ var animationTick = function(rafTime) {
389
isAnimationFrameScheduled = false;
323
- let nextFrameTime = rafTime - frameDeadline + activeFrameTime;
390
+ var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
391
if (
392
nextFrameTime < activeFrameTime &&
393
previousFrameTime < activeFrameTime
416
}
417
};
418
352
- scheduleWork = function(
353
- callback: FrameCallbackType,
354
- options?: {timeout: number},
355
- ): CallbackIdType /* CallbackConfigType */ {
356
- let timeoutTime = -1;
357
- if (options != null && typeof options.timeout === 'number') {
358
- timeoutTime = now() + options.timeout;
359
- }
360
- if (
361
- nextSoonestTimeoutTime === -1 ||
362
- (timeoutTime !== -1 && timeoutTime < nextSoonestTimeoutTime)
363
- ) {
364
- nextSoonestTimeoutTime = timeoutTime;
365
- }
366
-
367
- const scheduledCallbackConfig: CallbackConfigType = {
368
- scheduledCallback: callback,
369
- timeoutTime,
370
- prev: null,
371
- next: null,
372
- };
373
- if (headOfPendingCallbacksLinkedList === null) {
374
- // Make this callback the head and tail of our list
375
- headOfPendingCallbacksLinkedList = scheduledCallbackConfig;
376
- tailOfPendingCallbacksLinkedList = scheduledCallbackConfig;
377
- } else {
378
- // Add latest callback as the new tail of the list
379
- scheduledCallbackConfig.prev = tailOfPendingCallbacksLinkedList;
380
- // renaming for clarity
381
- const oldTailOfPendingCallbacksLinkedList = tailOfPendingCallbacksLinkedList;
382
- if (oldTailOfPendingCallbacksLinkedList !== null) {
383
- oldTailOfPendingCallbacksLinkedList.next = scheduledCallbackConfig;
384
- }
385
- tailOfPendingCallbacksLinkedList = scheduledCallbackConfig;
386
- }
387
-
388
- if (!isAnimationFrameScheduled) {
419
+ requestCallback = function(callback, absoluteTimeout) {
420
+ scheduledCallback = callback;
421
+ timeoutTime = absoluteTimeout;
422
+ if (isPerformingIdleWork) {
423
+ // If we're already performing idle work, an error must have been thrown.
424
+ // Don't wait for the next frame. Continue working ASAP, in a new event.
425
+ window.postMessage(messageKey, '*');
426
+ } else if (!isAnimationFrameScheduled) {
427
// If rAF didn't already schedule one, we need to schedule a frame.
428
// TODO: If this rAF doesn't materialize because the browser throttles, we
391
- // might want to still have setTimeout trigger scheduleWork as a backup to ensure
429
+ // might want to still have setTimeout trigger rIC as a backup to ensure
430
// that we keep performing work.
431
isAnimationFrameScheduled = true;
394
- scheduleAnimationFrameWithFallbackSupport(animationTick);
432
+ requestAnimationFrameWithTimeout(animationTick);
433
}
396
- return scheduledCallbackConfig;
434
};
435
399
- cancelScheduledWork = function(
400
- callbackConfig: CallbackIdType /* CallbackConfigType */,
401
- ) {
402
- if (
403
- callbackConfig.prev === null &&
404
- headOfPendingCallbacksLinkedList !== callbackConfig
405
- ) {
406
- // this callbackConfig has already been cancelled.
407
- // cancelScheduledWork should be idempotent, a no-op after first call.
408
- return;
409
- }
410
-
411
- /**
412
- * There are four possible cases:
413
- * - Head/nodeToRemove/Tail -> null
414
- * In this case we set Head and Tail to null.
415
- * - Head -> ... middle nodes... -> Tail/nodeToRemove
416
- * In this case we point the middle.next to null and put middle as the new
417
- * Tail.
418
- * - Head/nodeToRemove -> ...middle nodes... -> Tail
419
- * In this case we point the middle.prev at null and move the Head to
420
- * middle.
421
- * - Head -> ... ?some nodes ... -> nodeToRemove -> ... ?some nodes ... -> Tail
422
- * In this case we point the Head.next to the Tail and the Tail.prev to
423
- * the Head.
424
- */
425
- const next = callbackConfig.next;
426
- const prev = callbackConfig.prev;
427
- callbackConfig.next = null;
428
- callbackConfig.prev = null;
429
- if (next !== null) {
430
- // we have a next
431
-
432
- if (prev !== null) {
433
- // we have a prev
434
-
435
- // callbackConfig is somewhere in the middle of a list of 3 or more nodes.
436
- prev.next = next;
437
- next.prev = prev;
438
- return;
439
- } else {
440
- // there is a next but not a previous one;
441
- // callbackConfig is the head of a list of 2 or more other nodes.
442
- next.prev = null;
443
- headOfPendingCallbacksLinkedList = next;
444
- return;
445
- }
446
- } else {
447
- // there is no next callback config; this must the tail of the list
448
-
449
- if (prev !== null) {
450
- // we have a prev
451
-
452
- // callbackConfig is the tail of a list of 2 or more other nodes.
453
- prev.next = null;
454
- tailOfPendingCallbacksLinkedList = prev;
455
- return;
456
- } else {
457
- // there is no previous callback config;
458
- // callbackConfig is the only thing in the linked list,
459
- // so both head and tail point to it.
460
- headOfPendingCallbacksLinkedList = null;
461
- tailOfPendingCallbacksLinkedList = null;
462
- return;
463
- }
464
- }
436
+ cancelCallback = function() {
437
+ scheduledCallback = null;
438
+ isIdleScheduled = false;
439
+ timeoutTime = -1;
440
};
441
}
442
443
export {
469
- now as unstable_now,
470
- scheduleWork as unstable_scheduleWork,
471
- cancelScheduledWork as unstable_cancelScheduledWork,
444
+ unstable_scheduleWork,
445
+ unstable_cancelScheduledWork,
446
+ getCurrentTime as unstable_now,
447
};