Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/dom/animationframe/animationframe.js
2868 views
1
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview goog.dom.animationFrame permits work to be done in-sync with
17
* the render refresh rate of the browser and to divide work up globally based
18
* on whether the intent is to measure or to mutate the DOM. The latter avoids
19
* repeated style recalculation which can be really slow.
20
*
21
* Goals of the API:
22
* <ul>
23
* <li>Make it easy to schedule work for the next animation frame.
24
* <li>Make it easy to only do work once per animation frame, even if two
25
* events fire that trigger the same work.
26
* <li>Make it easy to do all work in two phases to avoid repeated style
27
* recalculation caused by interleaved reads and writes.
28
* <li>Avoid creating closures per schedule operation.
29
* </ul>
30
*
31
*
32
* Programmatic:
33
* <pre>
34
* var animationTask = goog.dom.animationFrame.createTask({
35
* measure: function(state) {
36
* state.width = goog.style.getSize(elem).width;
37
* this.animationTask();
38
* },
39
* mutate: function(state) {
40
* goog.style.setWidth(elem, Math.floor(state.width / 2));
41
* }
42
* }, this);
43
* });
44
* </pre>
45
*
46
* See also
47
* https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
48
*/
49
50
goog.provide('goog.dom.animationFrame');
51
goog.provide('goog.dom.animationFrame.Spec');
52
goog.provide('goog.dom.animationFrame.State');
53
54
goog.require('goog.dom.animationFrame.polyfill');
55
56
// Install the polyfill.
57
goog.dom.animationFrame.polyfill.install();
58
59
60
/**
61
* @typedef {{
62
* id: number,
63
* fn: !Function,
64
* context: (!Object|undefined)
65
* }}
66
* @private
67
*/
68
goog.dom.animationFrame.Task_;
69
70
71
/**
72
* @typedef {{
73
* measureTask: goog.dom.animationFrame.Task_,
74
* mutateTask: goog.dom.animationFrame.Task_,
75
* state: (!Object|undefined),
76
* args: (!Array|undefined),
77
* isScheduled: boolean
78
* }}
79
* @private
80
*/
81
goog.dom.animationFrame.TaskSet_;
82
83
84
/**
85
* @typedef {{
86
* measure: (!Function|undefined),
87
* mutate: (!Function|undefined)
88
* }}
89
*/
90
goog.dom.animationFrame.Spec;
91
92
93
94
/**
95
* A type to represent state. Users may add properties as desired.
96
* @constructor
97
* @final
98
*/
99
goog.dom.animationFrame.State = function() {};
100
101
102
/**
103
* Saves a set of tasks to be executed in the next requestAnimationFrame phase.
104
* This list is initialized once before any event firing occurs. It is not
105
* affected by the fired events or the requestAnimationFrame processing (unless
106
* a new event is created during the processing).
107
* @private {!Array<!Array<goog.dom.animationFrame.TaskSet_>>}
108
*/
109
goog.dom.animationFrame.tasks_ = [[], []];
110
111
112
/**
113
* Values are 0 or 1, for whether the first or second array should be used to
114
* lookup or add tasks.
115
* @private {number}
116
*/
117
goog.dom.animationFrame.doubleBufferIndex_ = 0;
118
119
120
/**
121
* Whether we have already requested an animation frame that hasn't happened
122
* yet.
123
* @private {boolean}
124
*/
125
goog.dom.animationFrame.requestedFrame_ = false;
126
127
128
/**
129
* Counter to generate IDs for tasks.
130
* @private {number}
131
*/
132
goog.dom.animationFrame.taskId_ = 0;
133
134
135
/**
136
* Whether the animationframe runTasks_ loop is currently running.
137
* @private {boolean}
138
*/
139
goog.dom.animationFrame.running_ = false;
140
141
142
/**
143
* Returns a function that schedules the two passed-in functions to be run upon
144
* the next animation frame. Calling the function again during the same
145
* animation frame does nothing.
146
*
147
* The function under the "measure" key will run first and together with all
148
* other functions scheduled under this key and the function under "mutate" will
149
* run after that.
150
*
151
* @param {{
152
* measure: (function(this:THIS, !goog.dom.animationFrame.State)|undefined),
153
* mutate: (function(this:THIS, !goog.dom.animationFrame.State)|undefined)
154
* }} spec
155
* @param {THIS=} opt_context Context in which to run the function.
156
* @return {function(...?)}
157
* @template THIS
158
*/
159
goog.dom.animationFrame.createTask = function(spec, opt_context) {
160
var id = goog.dom.animationFrame.taskId_++;
161
var measureTask = {id: id, fn: spec.measure, context: opt_context};
162
var mutateTask = {id: id, fn: spec.mutate, context: opt_context};
163
164
var taskSet = {
165
measureTask: measureTask,
166
mutateTask: mutateTask,
167
state: {},
168
args: undefined,
169
isScheduled: false
170
};
171
172
return function() {
173
// Save args and state.
174
if (arguments.length > 0) {
175
// The state argument goes last. That is kinda horrible but compatible
176
// with {@see wiz.async.method}.
177
if (!taskSet.args) {
178
taskSet.args = [];
179
}
180
taskSet.args.length = 0;
181
taskSet.args.push.apply(taskSet.args, arguments);
182
taskSet.args.push(taskSet.state);
183
} else {
184
if (!taskSet.args || taskSet.args.length == 0) {
185
taskSet.args = [taskSet.state];
186
} else {
187
taskSet.args[0] = taskSet.state;
188
taskSet.args.length = 1;
189
}
190
}
191
if (!taskSet.isScheduled) {
192
taskSet.isScheduled = true;
193
var tasksArray = goog.dom.animationFrame
194
.tasks_[goog.dom.animationFrame.doubleBufferIndex_];
195
tasksArray.push(
196
/** @type {goog.dom.animationFrame.TaskSet_} */ (taskSet));
197
}
198
goog.dom.animationFrame.requestAnimationFrame_();
199
};
200
};
201
202
203
/**
204
* Run scheduled tasks.
205
* @private
206
*/
207
goog.dom.animationFrame.runTasks_ = function() {
208
goog.dom.animationFrame.running_ = true;
209
goog.dom.animationFrame.requestedFrame_ = false;
210
var tasksArray = goog.dom.animationFrame
211
.tasks_[goog.dom.animationFrame.doubleBufferIndex_];
212
var taskLength = tasksArray.length;
213
214
// During the runTasks_, if there is a recursive call to queue up more
215
// task(s) for the next frame, we use double-buffering for that.
216
goog.dom.animationFrame.doubleBufferIndex_ =
217
(goog.dom.animationFrame.doubleBufferIndex_ + 1) % 2;
218
219
var task;
220
221
// Run all the measure tasks first.
222
for (var i = 0; i < taskLength; ++i) {
223
task = tasksArray[i];
224
var measureTask = task.measureTask;
225
task.isScheduled = false;
226
if (measureTask.fn) {
227
// TODO (perumaal): Handle any exceptions thrown by the lambda.
228
measureTask.fn.apply(measureTask.context, task.args);
229
}
230
}
231
232
// Run the mutate tasks next.
233
for (var i = 0; i < taskLength; ++i) {
234
task = tasksArray[i];
235
var mutateTask = task.mutateTask;
236
task.isScheduled = false;
237
if (mutateTask.fn) {
238
// TODO (perumaal): Handle any exceptions thrown by the lambda.
239
mutateTask.fn.apply(mutateTask.context, task.args);
240
}
241
242
// Clear state for next vsync.
243
task.state = {};
244
}
245
246
// Clear the tasks array as we have finished processing all the tasks.
247
tasksArray.length = 0;
248
goog.dom.animationFrame.running_ = false;
249
};
250
251
252
/**
253
* @return {boolean} Whether the animationframe is currently running. For use
254
* by callers who need not to delay tasks scheduled during runTasks_ for an
255
* additional frame.
256
*/
257
goog.dom.animationFrame.isRunning = function() {
258
return goog.dom.animationFrame.running_;
259
};
260
261
262
/**
263
* Request {@see goog.dom.animationFrame.runTasks_} to be called upon the
264
* next animation frame if we haven't done so already.
265
* @private
266
*/
267
goog.dom.animationFrame.requestAnimationFrame_ = function() {
268
if (goog.dom.animationFrame.requestedFrame_) {
269
return;
270
}
271
goog.dom.animationFrame.requestedFrame_ = true;
272
window.requestAnimationFrame(goog.dom.animationFrame.runTasks_);
273
};
274
275