// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. (function(shared, scope, testing) { var originalRequestAnimationFrame = window.requestAnimationFrame; var rafCallbacks = []; var rafId = 0; window.requestAnimationFrame = function(f) { var id = rafId++; if (rafCallbacks.length == 0 && !WEB_ANIMATIONS_TESTING) { originalRequestAnimationFrame(processRafCallbacks); } rafCallbacks.push([id, f]); return id; }; window.cancelAnimationFrame = function(id) { rafCallbacks.forEach(function(entry) { if (entry[0] == id) { entry[1] = function() {}; } }); }; function processRafCallbacks(t) { var processing = rafCallbacks; rafCallbacks = []; if (t < timeline.currentTime) t = timeline.currentTime; timeline._animations.sort(compareAnimations); timeline._animations = tick(t, true, timeline._animations)[0]; processing.forEach(function(entry) { entry[1](t); }); applyPendingEffects(); _now = undefined; } function compareAnimations(leftAnimation, rightAnimation) { return leftAnimation._sequenceNumber - rightAnimation._sequenceNumber; } function InternalTimeline() { this._animations = []; // Android 4.3 browser has window.performance, but not window.performance.now this.currentTime = window.performance && performance.now ? performance.now() : 0; }; InternalTimeline.prototype = { _play: function(effect) { effect._timing = shared.normalizeTimingInput(effect.timing); var animation = new scope.Animation(effect); animation._idle = false; animation._timeline = this; this._animations.push(animation); scope.restart(); scope.applyDirtiedAnimation(animation); return animation; } }; var _now = undefined; if (WEB_ANIMATIONS_TESTING) { var now = function() { return timeline.currentTime; }; } else { var now = function() { if (_now == undefined) _now = window.performance && performance.now ? performance.now() : Date.now(); return _now; }; } var ticking = false; var hasRestartedThisFrame = false; scope.restart = function() { if (!ticking) { ticking = true; requestAnimationFrame(function() {}); hasRestartedThisFrame = true; } return hasRestartedThisFrame; }; // RAF is supposed to be the last script to occur before frame rendering but not // all browsers behave like this. This function is for synchonously updating an // animation's effects whenever its state is mutated by script to work around // incorrect script execution ordering by the browser. scope.applyDirtiedAnimation = function(animation) { if (inTick) { return; } animation._markTarget(); var animations = animation._targetAnimations(); animations.sort(compareAnimations); var inactiveAnimations = tick(scope.timeline.currentTime, false, animations.slice())[1]; inactiveAnimations.forEach(function(animation) { var index = timeline._animations.indexOf(animation); if (index !== -1) { timeline._animations.splice(index, 1); } }); applyPendingEffects(); }; var pendingEffects = []; function applyPendingEffects() { pendingEffects.forEach(function(f) { f(); }); pendingEffects.length = 0; } var t60hz = 1000 / 60; var inTick = false; function tick(t, isAnimationFrame, updatingAnimations) { inTick = true; hasRestartedThisFrame = false; var timeline = scope.timeline; timeline.currentTime = t; ticking = false; var newPendingClears = []; var newPendingEffects = []; var activeAnimations = []; var inactiveAnimations = []; updatingAnimations.forEach(function(animation) { animation._tick(t, isAnimationFrame); if (!animation._inEffect) { newPendingClears.push(animation._effect); animation._unmarkTarget(); } else { newPendingEffects.push(animation._effect); animation._markTarget(); } if (animation._needsTick) ticking = true; var alive = animation._inEffect || animation._needsTick; animation._inTimeline = alive; if (alive) { activeAnimations.push(animation); } else { inactiveAnimations.push(animation); } }); // FIXME: Should remove dupliactes from pendingEffects. pendingEffects.push.apply(pendingEffects, newPendingClears); pendingEffects.push.apply(pendingEffects, newPendingEffects); if (ticking) requestAnimationFrame(function() {}); inTick = false; return [activeAnimations, inactiveAnimations]; }; if (WEB_ANIMATIONS_TESTING) { testing.tick = function(t) { timeline.currentTime = t; processRafCallbacks(t); }; testing.isTicking = function() { return ticking; }; testing.setTicking = function(newVal) { ticking = newVal; }; } var timeline = new InternalTimeline(); scope.timeline = timeline; })(webAnimationsShared, webAnimations1, webAnimationsTesting);