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(function(shared, scope, testing) { 16 17 var disassociate = function(effect) { 18 effect._animation = undefined; 19 if (effect instanceof window.SequenceEffect || effect instanceof window.GroupEffect) { 20 for (var i = 0; i < effect.children.length; i++) { 21 disassociate(effect.children[i]); 22 } 23 } 24 }; 25 26 scope.removeMulti = function(effects) { 27 var oldParents = []; 28 for (var i = 0; i < effects.length; i++) { 29 var effect = effects[i]; 30 if (effect._parent) { 31 if (oldParents.indexOf(effect._parent) == -1) { 32 oldParents.push(effect._parent); 33 } 34 effect._parent.children.splice(effect._parent.children.indexOf(effect), 1); 35 effect._parent = null; 36 disassociate(effect); 37 } else if (effect._animation && (effect._animation.effect == effect)) { 38 effect._animation.cancel(); 39 effect._animation.effect = new KeyframeEffect(null, []); 40 if (effect._animation._callback) { 41 effect._animation._callback._animation = null; 42 } 43 effect._animation._rebuildUnderlyingAnimation(); 44 disassociate(effect); 45 } 46 } 47 for (i = 0; i < oldParents.length; i++) { 48 oldParents[i]._rebuild(); 49 } 50 }; 51 52 function KeyframeList(effectInput) { 53 this._frames = shared.normalizeKeyframes(effectInput); 54 } 55 56 scope.KeyframeEffect = function(target, effectInput, timingInput, id) { 57 this.target = target; 58 this._parent = null; 59 60 timingInput = shared.numericTimingToObject(timingInput); 61 this._timingInput = shared.cloneTimingInput(timingInput); 62 this._timing = shared.normalizeTimingInput(timingInput); 63 64 this.timing = shared.makeTiming(timingInput, false, this); 65 this.timing._effect = this; 66 if (typeof effectInput == 'function') { 67 shared.deprecated('Custom KeyframeEffect', '2015-06-22', 'Use KeyframeEffect.onsample instead.'); 68 this._normalizedKeyframes = effectInput; 69 } else { 70 this._normalizedKeyframes = new KeyframeList(effectInput); 71 } 72 this._keyframes = effectInput; 73 this.activeDuration = shared.calculateActiveDuration(this._timing); 74 this._id = id; 75 return this; 76 }; 77 78 scope.KeyframeEffect.prototype = { 79 getFrames: function() { 80 if (typeof this._normalizedKeyframes == 'function') 81 return this._normalizedKeyframes; 82 return this._normalizedKeyframes._frames; 83 }, 84 set onsample(callback) { 85 if (typeof this.getFrames() == 'function') { 86 throw new Error('Setting onsample on custom effect KeyframeEffect is not supported.'); 87 } 88 this._onsample = callback; 89 if (this._animation) { 90 this._animation._rebuildUnderlyingAnimation(); 91 } 92 }, 93 get parent() { 94 return this._parent; 95 }, 96 clone: function() { 97 if (typeof this.getFrames() == 'function') { 98 throw new Error('Cloning custom effects is not supported.'); 99 } 100 var clone = new KeyframeEffect(this.target, [], shared.cloneTimingInput(this._timingInput), this._id); 101 clone._normalizedKeyframes = this._normalizedKeyframes; 102 clone._keyframes = this._keyframes; 103 return clone; 104 }, 105 remove: function() { 106 scope.removeMulti([this]); 107 } 108 }; 109 110 var originalElementAnimate = Element.prototype.animate; 111 Element.prototype.animate = function(effectInput, options) { 112 var id = ''; 113 if (options && options.id) { 114 id = options.id; 115 } 116 return scope.timeline._play(new scope.KeyframeEffect(this, effectInput, options, id)); 117 }; 118 119 var nullTarget = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); 120 scope.newUnderlyingAnimationForKeyframeEffect = function(keyframeEffect) { 121 if (keyframeEffect) { 122 var target = keyframeEffect.target || nullTarget; 123 var keyframes = keyframeEffect._keyframes; 124 if (typeof keyframes == 'function') { 125 keyframes = []; 126 } 127 var options = keyframeEffect._timingInput; 128 options.id = keyframeEffect._id; 129 } else { 130 var target = nullTarget; 131 var keyframes = []; 132 var options = 0; 133 } 134 return originalElementAnimate.apply(target, [keyframes, options]); 135 }; 136 137 // TODO: Remove this once we remove support for custom KeyframeEffects. 138 scope.bindAnimationForKeyframeEffect = function(animation) { 139 if (animation.effect && typeof animation.effect._normalizedKeyframes == 'function') { 140 scope.bindAnimationForCustomEffect(animation); 141 } 142 }; 143 144 var pendingGroups = []; 145 scope.awaitStartTime = function(groupAnimation) { 146 if (groupAnimation.startTime !== null || !groupAnimation._isGroup) 147 return; 148 if (pendingGroups.length == 0) { 149 requestAnimationFrame(updatePendingGroups); 150 } 151 pendingGroups.push(groupAnimation); 152 }; 153 function updatePendingGroups() { 154 var updated = false; 155 while (pendingGroups.length) { 156 var group = pendingGroups.shift(); 157 group._updateChildren(); 158 updated = true; 159 } 160 return updated; 161 } 162 var originalGetComputedStyle = window.getComputedStyle; 163 Object.defineProperty(window, 'getComputedStyle', { 164 configurable: true, 165 enumerable: true, 166 value: function() { 167 scope.timeline._updateAnimationsPromises(); 168 var result = originalGetComputedStyle.apply(this, arguments); 169 if (updatePendingGroups()) 170 result = originalGetComputedStyle.apply(this, arguments); 171 scope.timeline._updateAnimationsPromises(); 172 return result; 173 }, 174 }); 175 176 window.KeyframeEffect = scope.KeyframeEffect; 177 window.Element.prototype.getAnimations = function() { 178 return document.timeline.getAnimations().filter(function(animation) { 179 return animation.effect !== null && animation.effect.target == this; 180 }.bind(this)); 181 }; 182 183}(webAnimationsShared, webAnimationsNext, webAnimationsTesting)); 184