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(scope, testing) { 16 17 var styleAttributes = { 18 cssText: 1, 19 length: 1, 20 parentRule: 1, 21 }; 22 23 var styleMethods = { 24 getPropertyCSSValue: 1, 25 getPropertyPriority: 1, 26 getPropertyValue: 1, 27 item: 1, 28 removeProperty: 1, 29 setProperty: 1, 30 }; 31 32 var styleMutatingMethods = { 33 removeProperty: 1, 34 setProperty: 1, 35 }; 36 37 function configureProperty(object, property, descriptor) { 38 descriptor.enumerable = true; 39 descriptor.configurable = true; 40 Object.defineProperty(object, property, descriptor); 41 } 42 43 function AnimatedCSSStyleDeclaration(element) { 44 WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration), 45 'Element must not already have an animated style attached.'); 46 47 // Stores the inline style of the element on its behalf while the 48 // polyfill uses the element's inline style to simulate web animations. 49 // This is needed to fake regular inline style CSSOM access on the element. 50 this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style; 51 this._style = element.style; 52 this._length = 0; 53 this._isAnimatedProperty = {}; 54 55 // Copy the inline style contents over to the surrogate. 56 for (var i = 0; i < this._style.length; i++) { 57 var property = this._style[i]; 58 this._surrogateStyle[property] = this._style[property]; 59 } 60 this._updateIndices(); 61 } 62 63 AnimatedCSSStyleDeclaration.prototype = { 64 get cssText() { 65 return this._surrogateStyle.cssText; 66 }, 67 set cssText(text) { 68 var isAffectedProperty = {}; 69 for (var i = 0; i < this._surrogateStyle.length; i++) { 70 isAffectedProperty[this._surrogateStyle[i]] = true; 71 } 72 this._surrogateStyle.cssText = text; 73 this._updateIndices(); 74 for (var i = 0; i < this._surrogateStyle.length; i++) { 75 isAffectedProperty[this._surrogateStyle[i]] = true; 76 } 77 for (var property in isAffectedProperty) { 78 if (!this._isAnimatedProperty[property]) { 79 this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property)); 80 } 81 } 82 }, 83 get length() { 84 return this._surrogateStyle.length; 85 }, 86 get parentRule() { 87 return this._style.parentRule; 88 }, 89 // Mirror the indexed getters and setters of the surrogate style. 90 _updateIndices: function() { 91 while (this._length < this._surrogateStyle.length) { 92 Object.defineProperty(this, this._length, { 93 configurable: true, 94 enumerable: false, 95 get: (function(index) { 96 return function() { return this._surrogateStyle[index]; }; 97 })(this._length) 98 }); 99 this._length++; 100 } 101 while (this._length > this._surrogateStyle.length) { 102 this._length--; 103 Object.defineProperty(this, this._length, { 104 configurable: true, 105 enumerable: false, 106 value: undefined 107 }); 108 } 109 }, 110 _set: function(property, value) { 111 this._style[property] = value; 112 this._isAnimatedProperty[property] = true; 113 }, 114 _clear: function(property) { 115 this._style[property] = this._surrogateStyle[property]; 116 delete this._isAnimatedProperty[property]; 117 }, 118 }; 119 120 // Wrap the style methods. 121 for (var method in styleMethods) { 122 AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) { 123 return function() { 124 var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments); 125 if (modifiesStyle) { 126 if (!this._isAnimatedProperty[arguments[0]]) 127 this._style[method].apply(this._style, arguments); 128 this._updateIndices(); 129 } 130 return result; 131 } 132 })(method, method in styleMutatingMethods); 133 } 134 135 // Wrap the style.cssProperty getters and setters. 136 for (var property in document.documentElement.style) { 137 if (property in styleAttributes || property in styleMethods) { 138 continue; 139 } 140 (function(property) { 141 configureProperty(AnimatedCSSStyleDeclaration.prototype, property, { 142 get: function() { 143 return this._surrogateStyle[property]; 144 }, 145 set: function(value) { 146 this._surrogateStyle[property] = value; 147 this._updateIndices(); 148 if (!this._isAnimatedProperty[property]) 149 this._style[property] = value; 150 } 151 }); 152 })(property); 153 } 154 155 function ensureStyleIsPatched(element) { 156 if (element._webAnimationsPatchedStyle) 157 return; 158 159 var animatedStyle = new AnimatedCSSStyleDeclaration(element); 160 try { 161 configureProperty(element, 'style', { get: function() { return animatedStyle; } }); 162 } catch (_) { 163 // iOS and older versions of Safari (pre v7) do not support overriding an element's 164 // style object. Animations will clobber any inline styles as a result. 165 element.style._set = function(property, value) { 166 element.style[property] = value; 167 }; 168 element.style._clear = function(property) { 169 element.style[property] = ''; 170 }; 171 } 172 173 // We must keep a handle on the patched style to prevent it from getting GC'd. 174 element._webAnimationsPatchedStyle = element.style; 175 } 176 177 scope.apply = function(element, property, value) { 178 ensureStyleIsPatched(element); 179 element.style._set(scope.propertyName(property), value); 180 }; 181 182 scope.clear = function(element, property) { 183 if (element._webAnimationsPatchedStyle) { 184 element.style._clear(scope.propertyName(property)); 185 } 186 }; 187 188 if (WEB_ANIMATIONS_TESTING) 189 testing.ensureStyleIsPatched = ensureStyleIsPatched; 190 191})(webAnimations1, webAnimationsTesting); 192