1// Copyright 2014 Google Inc. 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(function(shared, scope, testing) { 17 var originalRequestAnimationFrame = window.requestAnimationFrame; 18 var rafCallbacks = []; 19 var rafId = 0; 20 window.requestAnimationFrame = function(f) { 21 var id = rafId++; 22 if (rafCallbacks.length == 0 && !WEB_ANIMATIONS_TESTING) { 23 originalRequestAnimationFrame(processRafCallbacks); 24 } 25 rafCallbacks.push([id, f]); 26 return id; 27 }; 28 29 window.cancelAnimationFrame = function(id) { 30 rafCallbacks.forEach(function(entry) { 31 if (entry[0] == id) { 32 entry[1] = function() {}; 33 } 34 }); 35 }; 36 37 function processRafCallbacks(t) { 38 var processing = rafCallbacks; 39 rafCallbacks = []; 40 if (t < timeline.currentTime) 41 t = timeline.currentTime; 42 timeline._animations.sort(compareAnimations); 43 timeline._animations = tick(t, true, timeline._animations)[0]; 44 processing.forEach(function(entry) { entry[1](t); }); 45 applyPendingEffects(); 46 _now = undefined; 47 } 48 49 function compareAnimations(leftAnimation, rightAnimation) { 50 return leftAnimation._sequenceNumber - rightAnimation._sequenceNumber; 51 } 52 53 function InternalTimeline() { 54 this._animations = []; 55 // Android 4.3 browser has window.performance, but not window.performance.now 56 this.currentTime = window.performance && performance.now ? performance.now() : 0; 57 }; 58 59 InternalTimeline.prototype = { 60 _play: function(effect) { 61 effect._timing = shared.normalizeTimingInput(effect.timing); 62 var animation = new scope.Animation(effect); 63 animation._idle = false; 64 animation._timeline = this; 65 this._animations.push(animation); 66 scope.restart(); 67 scope.applyDirtiedAnimation(animation); 68 return animation; 69 } 70 }; 71 72 var _now = undefined; 73 74 if (WEB_ANIMATIONS_TESTING) { 75 var now = function() { return timeline.currentTime; }; 76 } else { 77 var now = function() { 78 if (_now == undefined) 79 _now = window.performance && performance.now ? performance.now() : Date.now(); 80 return _now; 81 }; 82 } 83 84 var ticking = false; 85 var hasRestartedThisFrame = false; 86 87 scope.restart = function() { 88 if (!ticking) { 89 ticking = true; 90 requestAnimationFrame(function() {}); 91 hasRestartedThisFrame = true; 92 } 93 return hasRestartedThisFrame; 94 }; 95 96 // RAF is supposed to be the last script to occur before frame rendering but not 97 // all browsers behave like this. This function is for synchonously updating an 98 // animation's effects whenever its state is mutated by script to work around 99 // incorrect script execution ordering by the browser. 100 scope.applyDirtiedAnimation = function(animation) { 101 if (inTick) { 102 return; 103 } 104 animation._markTarget(); 105 var animations = animation._targetAnimations(); 106 animations.sort(compareAnimations); 107 var inactiveAnimations = tick(scope.timeline.currentTime, false, animations.slice())[1]; 108 inactiveAnimations.forEach(function(animation) { 109 var index = timeline._animations.indexOf(animation); 110 if (index !== -1) { 111 timeline._animations.splice(index, 1); 112 } 113 }); 114 applyPendingEffects(); 115 }; 116 117 var pendingEffects = []; 118 function applyPendingEffects() { 119 pendingEffects.forEach(function(f) { f(); }); 120 pendingEffects.length = 0; 121 } 122 123 var t60hz = 1000 / 60; 124 125 var inTick = false; 126 function tick(t, isAnimationFrame, updatingAnimations) { 127 inTick = true; 128 hasRestartedThisFrame = false; 129 var timeline = scope.timeline; 130 131 timeline.currentTime = t; 132 ticking = false; 133 134 var newPendingClears = []; 135 var newPendingEffects = []; 136 var activeAnimations = []; 137 var inactiveAnimations = []; 138 updatingAnimations.forEach(function(animation) { 139 animation._tick(t, isAnimationFrame); 140 141 if (!animation._inEffect) { 142 newPendingClears.push(animation._effect); 143 animation._unmarkTarget(); 144 } else { 145 newPendingEffects.push(animation._effect); 146 animation._markTarget(); 147 } 148 149 if (animation._needsTick) 150 ticking = true; 151 152 var alive = animation._inEffect || animation._needsTick; 153 animation._inTimeline = alive; 154 if (alive) { 155 activeAnimations.push(animation); 156 } else { 157 inactiveAnimations.push(animation); 158 } 159 }); 160 161 // FIXME: Should remove dupliactes from pendingEffects. 162 pendingEffects.push.apply(pendingEffects, newPendingClears); 163 pendingEffects.push.apply(pendingEffects, newPendingEffects); 164 165 if (ticking) 166 requestAnimationFrame(function() {}); 167 168 inTick = false; 169 return [activeAnimations, inactiveAnimations]; 170 }; 171 172 if (WEB_ANIMATIONS_TESTING) { 173 testing.tick = function(t) { timeline.currentTime = t; processRafCallbacks(t); }; 174 testing.isTicking = function() { return ticking; }; 175 testing.setTicking = function(newVal) { ticking = newVal; }; 176 } 177 178 var timeline = new InternalTimeline(); 179 scope.timeline = timeline; 180 181})(webAnimationsShared, webAnimations1, webAnimationsTesting); 182