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 tick(t); 41 processing.forEach(function(entry) { entry[1](t); }); 42 if (needsRetick) 43 tick(t); 44 applyPendingEffects(); 45 } 46 47 function comparePlayers(leftPlayer, rightPlayer) { 48 return leftPlayer._sequenceNumber - rightPlayer._sequenceNumber; 49 } 50 51 function InternalTimeline() { 52 this._players = []; 53 // Android 4.3 browser has window.performance, but not window.performance.now 54 this.currentTime = window.performance && performance.now ? performance.now() : 0; 55 }; 56 57 InternalTimeline.prototype = { 58 _play: function(source) { 59 source._timing = shared.normalizeTimingInput(source.timing); 60 var player = new scope.Player(source); 61 player._idle = false; 62 player._timeline = this; 63 this._players.push(player); 64 scope.restart(); 65 scope.invalidateEffects(); 66 return player; 67 } 68 }; 69 70 var ticking = false; 71 var hasRestartedThisFrame = false; 72 73 scope.restart = function() { 74 if (!ticking) { 75 ticking = true; 76 requestAnimationFrame(function() {}); 77 hasRestartedThisFrame = true; 78 } 79 return hasRestartedThisFrame; 80 }; 81 82 var needsRetick = false; 83 scope.invalidateEffects = function() { 84 needsRetick = true; 85 }; 86 87 var pendingEffects = []; 88 function applyPendingEffects() { 89 pendingEffects.forEach(function(f) { f(); }); 90 pendingEffects.length = 0; 91 } 92 93 var originalGetComputedStyle = window.getComputedStyle; 94 Object.defineProperty(window, 'getComputedStyle', { 95 configurable: true, 96 enumerable: true, 97 value: function() { 98 if (needsRetick) tick(timeline.currentTime); 99 applyPendingEffects(); 100 return originalGetComputedStyle.apply(this, arguments); 101 }, 102 }); 103 104 function tick(t) { 105 hasRestartedThisFrame = false; 106 var timeline = scope.timeline; 107 timeline.currentTime = t; 108 timeline._players.sort(comparePlayers); 109 ticking = false; 110 var updatingPlayers = timeline._players; 111 timeline._players = []; 112 113 var newPendingClears = []; 114 var newPendingEffects = []; 115 updatingPlayers = updatingPlayers.filter(function(player) { 116 player._inTimeline = player._tick(t); 117 118 if (!player._inEffect) 119 newPendingClears.push(player._source); 120 else 121 newPendingEffects.push(player._source); 122 123 if (!player.finished && !player.paused && !player._idle) 124 ticking = true; 125 126 return player._inTimeline; 127 }); 128 129 // FIXME: Should remove dupliactes from pendingEffects. 130 pendingEffects.push.apply(pendingEffects, newPendingClears); 131 pendingEffects.push.apply(pendingEffects, newPendingEffects); 132 133 timeline._players.push.apply(timeline._players, updatingPlayers); 134 needsRetick = false; 135 136 if (ticking) 137 requestAnimationFrame(function() {}); 138 }; 139 140 if (WEB_ANIMATIONS_TESTING) { 141 testing.tick = processRafCallbacks; 142 testing.isTicking = function() { return ticking; }; 143 testing.setTicking = function(newVal) { ticking = newVal; }; 144 } 145 146 var timeline = new InternalTimeline(); 147 scope.timeline = timeline; 148 149})(webAnimationsShared, webAnimations1, webAnimationsTesting); 150