1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFrameScheduling
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+// This a built-in polyfill for requestIdleCallback. It works by scheduling
16
+// a requestAnimationFrame, store the time for the start of the frame, then
17
+// schedule a postMessage which gets scheduled after paint. Within the
18
+// postMessage handler do as much work as possible until time + frame rate.
19
+// By separating the idle call into a separate event tick we ensure that
20
+// layout, paint and other browser work is counted against the available time.
21
+// The frame rate is dynamically adjusted.
22
+
23
+import type { Deadline } from 'ReactFiberReconciler';
24
+
25
+var invariant = require('invariant');
26
+
27
+// TODO: There's no way to cancel these, because Fiber doesn't atm.
28
+let rAF : (callback : (time : number) => void) => number;
29
+let rIC : (callback : (deadline : Deadline) => void) => number;
30
+if (typeof requestAnimationFrame !== 'function') {
31
+ invariant(
32
+ false,
33
+ 'React depends on requestAnimationFrame. Make sure that you load a ' +
34
+ 'polyfill in older browsers.'
35
+ );
36
+} else if (typeof requestIdleCallback !== 'function') {
37
+ // Wrap requestAnimationFrame and polyfill requestIdleCallback.
38
+
39
+ var scheduledRAFCallback = null;
40
+ var scheduledRICCallback = null;
41
+
42
+ var isIdleScheduled = false;
43
+ var isAnimationFrameScheduled = false;
44
+
45
+ var frameDeadline = 0;
46
+ // We start out assuming that we run at 30fps but then the heuristic tracking
47
+ // will adjust this value to a faster fps if we get more frequent animation
48
+ // frames.
49
+ var previousFrameTime = 33;
50
+ var activeFrameTime = 33;
51
+
52
+ var frameDeadlineObject = {
53
+ timeRemaining: (
54
+ typeof performance === 'object' &&
55
+ typeof performance.now === 'function' ? function() {
56
+ // We assume that if we have a performance timer that the rAF callback
57
+ // gets a performance timer value. Not sure if this is always true.
58
+ return frameDeadline - performance.now();
59
+ } : function() {
60
+ // As a fallback we use Date.now.
61
+ return frameDeadline - Date.now();
62
+ }
63
+ ),
64
+ };
65
+
66
+ // We use the postMessage trick to defer idle work until after the repaint.
67
+ var messageKey =
68
+ '__reactIdleCallback$' + Math.random().toString(36).slice(2);
69
+ var idleTick = function(event) {
70
+ if (event.source !== window || event.data !== messageKey) {
71
+ return;
72
+ }
73
+ isIdleScheduled = false;
74
+ var callback = scheduledRICCallback;
75
+ scheduledRICCallback = null;
76
+ if (callback) {
77
+ callback(frameDeadlineObject);
78
+ }
79
+ };
80
+ // Assumes that we have addEventListener in this environment. Might need
81
+ // something better for old IE.
82
+ window.addEventListener('message', idleTick, false);
83
+
84
+ var animationTick = function(rafTime) {
85
+ isAnimationFrameScheduled = false;
86
+ var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
87
+ if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
88
+ if (nextFrameTime < 8) {
89
+ // Defensive coding. We don't support higher frame rates than 120hz.
90
+ // If we get lower than that, it is probably a bug.
91
+ nextFrameTime = 8;
92
+ }
93
+ // If one frame goes long, then the next one can be short to catch up.
94
+ // If two frames are short in a row, then that's an indication that we
95
+ // actually have a higher frame rate than what we're currently optimizing.
96
+ // We adjust our heuristic dynamically accordingly. For example, if we're
97
+ // running on 120hz display or 90hz VR display.
98
+ // Take the max of the two in case one of them was an anomaly due to
99
+ // missed frame deadlines.
100
+ activeFrameTime = nextFrameTime < previousFrameTime ?
101
+ previousFrameTime : nextFrameTime;
102
+ } else {
103
+ previousFrameTime = nextFrameTime;
104
+ }
105
+ frameDeadline = rafTime + activeFrameTime;
106
+ if (!isIdleScheduled) {
107
+ isIdleScheduled = true;
108
+ window.postMessage(messageKey, '*');
109
+ }
110
+ var callback = scheduledRAFCallback;
111
+ scheduledRAFCallback = null;
112
+ if (callback) {
113
+ callback(rafTime);
114
+ }
115
+ };
116
+
117
+ rAF = function(callback : (time : number) => void) : number {
118
+ // This assumes that we only schedule one callback at a time because that's
119
+ // how Fiber uses it.
120
+ scheduledRAFCallback = callback;
121
+ if (!isAnimationFrameScheduled) {
122
+ // If rIC didn't already schedule one, we need to schedule a frame.
123
+ isAnimationFrameScheduled = true;
124
+ requestAnimationFrame(animationTick);
125
+ }
126
+ return 0;
127
+ };
128
+
129
+ rIC = function(callback : (deadline : Deadline) => void) : number {
130
+ // This assumes that we only schedule one callback at a time because that's
131
+ // how Fiber uses it.
132
+ scheduledRICCallback = callback;
133
+ if (!isAnimationFrameScheduled) {
134
+ // If rAF didn't already schedule one, we need to schedule a frame.
135
+ // TODO: If this rAF doesn't materialize because the browser throttles, we
136
+ // might want to still have setTimeout trigger rIC as a backup to ensure
137
+ // that we keep performing work.
138
+ isAnimationFrameScheduled = true;
139
+ requestAnimationFrame(animationTick);
140
+ }
141
+ return 0;
142
+ };
143
+} else {
144
+ rAF = requestAnimationFrame;
145
+ rIC = requestIdleCallback;
146
+}
147
+
148
+exports.rAF = rAF;
149
+exports.rIC = rIC;