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 SVG_TRANSFORM_PROP = '_webAnimationsUpdateSvgTransformAttr'; 18 19 /** 20 * IE/Edge do not support `transform` styles for SVG elements. Instead, 21 * `transform` attribute can be animated with some restrictions. 22 * See https://connect.microsoft.com/IE/feedback/details/811744/ie11-bug-with-implementation-of-css-transforms-in-svg, 23 * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1173754/, 24 * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101242/, etc. 25 * The same problem is exhibited by pre-Chrome Android browsers (ICS). 26 * Unfortunately, there's no easy way to feature-detect it. 27 */ 28 function updateSvgTransformAttr(window, element) { 29 if (!element.namespaceURI || element.namespaceURI.indexOf('/svg') == -1) { 30 return false; 31 } 32 if (!(SVG_TRANSFORM_PROP in window)) { 33 window[SVG_TRANSFORM_PROP] = 34 /Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent); 35 } 36 return window[SVG_TRANSFORM_PROP]; 37 } 38 39 var styleAttributes = { 40 cssText: 1, 41 length: 1, 42 parentRule: 1, 43 }; 44 45 var styleMethods = { 46 getPropertyCSSValue: 1, 47 getPropertyPriority: 1, 48 getPropertyValue: 1, 49 item: 1, 50 removeProperty: 1, 51 setProperty: 1, 52 }; 53 54 var styleMutatingMethods = { 55 removeProperty: 1, 56 setProperty: 1, 57 }; 58 59 function configureProperty(object, property, descriptor) { 60 descriptor.enumerable = true; 61 descriptor.configurable = true; 62 Object.defineProperty(object, property, descriptor); 63 } 64 65 function AnimatedCSSStyleDeclaration(element) { 66 WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration), 67 'Element must not already have an animated style attached.'); 68 69 this._element = element; 70 // Stores the inline style of the element on its behalf while the 71 // polyfill uses the element's inline style to simulate web animations. 72 // This is needed to fake regular inline style CSSOM access on the element. 73 this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style; 74 this._style = element.style; 75 this._length = 0; 76 this._isAnimatedProperty = {}; 77 this._updateSvgTransformAttr = updateSvgTransformAttr(window, element); 78 this._savedTransformAttr = null; 79 80 // Copy the inline style contents over to the surrogate. 81 for (var i = 0; i < this._style.length; i++) { 82 var property = this._style[i]; 83 this._surrogateStyle[property] = this._style[property]; 84 } 85 this._updateIndices(); 86 } 87 88 AnimatedCSSStyleDeclaration.prototype = { 89 get cssText() { 90 return this._surrogateStyle.cssText; 91 }, 92 set cssText(text) { 93 var isAffectedProperty = {}; 94 for (var i = 0; i < this._surrogateStyle.length; i++) { 95 isAffectedProperty[this._surrogateStyle[i]] = true; 96 } 97 this._surrogateStyle.cssText = text; 98 this._updateIndices(); 99 for (var i = 0; i < this._surrogateStyle.length; i++) { 100 isAffectedProperty[this._surrogateStyle[i]] = true; 101 } 102 for (var property in isAffectedProperty) { 103 if (!this._isAnimatedProperty[property]) { 104 this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property)); 105 } 106 } 107 }, 108 get length() { 109 return this._surrogateStyle.length; 110 }, 111 get parentRule() { 112 return this._style.parentRule; 113 }, 114 // Mirror the indexed getters and setters of the surrogate style. 115 _updateIndices: function() { 116 while (this._length < this._surrogateStyle.length) { 117 Object.defineProperty(this, this._length, { 118 configurable: true, 119 enumerable: false, 120 get: (function(index) { 121 return function() { return this._surrogateStyle[index]; }; 122 })(this._length) 123 }); 124 this._length++; 125 } 126 while (this._length > this._surrogateStyle.length) { 127 this._length--; 128 Object.defineProperty(this, this._length, { 129 configurable: true, 130 enumerable: false, 131 value: undefined 132 }); 133 } 134 }, 135 _set: function(property, value) { 136 this._style[property] = value; 137 this._isAnimatedProperty[property] = true; 138 if (this._updateSvgTransformAttr && 139 scope.unprefixedPropertyName(property) == 'transform') { 140 // On IE/Edge, also set SVG element's `transform` attribute to 2d 141 // matrix of the transform. The `transform` style does not work, but 142 // `transform` attribute can be used instead. 143 // Notice, if the platform indeed supports SVG/CSS transforms the CSS 144 // declaration is supposed to override the attribute. 145 if (this._savedTransformAttr == null) { 146 this._savedTransformAttr = this._element.getAttribute('transform'); 147 } 148 this._element.setAttribute('transform', scope.transformToSvgMatrix(value)); 149 } 150 }, 151 _clear: function(property) { 152 this._style[property] = this._surrogateStyle[property]; 153 if (this._updateSvgTransformAttr && 154 scope.unprefixedPropertyName(property) == 'transform') { 155 if (this._savedTransformAttr) { 156 this._element.setAttribute('transform', this._savedTransformAttr); 157 } else { 158 this._element.removeAttribute('transform'); 159 } 160 this._savedTransformAttr = null; 161 } 162 delete this._isAnimatedProperty[property]; 163 }, 164 }; 165 166 // Wrap the style methods. 167 for (var method in styleMethods) { 168 AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) { 169 return function() { 170 var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments); 171 if (modifiesStyle) { 172 if (!this._isAnimatedProperty[arguments[0]]) 173 this._style[method].apply(this._style, arguments); 174 this._updateIndices(); 175 } 176 return result; 177 } 178 })(method, method in styleMutatingMethods); 179 } 180 181 // Wrap the style.cssProperty getters and setters. 182 for (var property in document.documentElement.style) { 183 if (property in styleAttributes || property in styleMethods) { 184 continue; 185 } 186 (function(property) { 187 configureProperty(AnimatedCSSStyleDeclaration.prototype, property, { 188 get: function() { 189 return this._surrogateStyle[property]; 190 }, 191 set: function(value) { 192 this._surrogateStyle[property] = value; 193 this._updateIndices(); 194 if (!this._isAnimatedProperty[property]) 195 this._style[property] = value; 196 } 197 }); 198 })(property); 199 } 200 201 function ensureStyleIsPatched(element) { 202 if (element._webAnimationsPatchedStyle) 203 return; 204 205 var animatedStyle = new AnimatedCSSStyleDeclaration(element); 206 try { 207 configureProperty(element, 'style', { get: function() { return animatedStyle; } }); 208 } catch (_) { 209 // iOS and older versions of Safari (pre v7) do not support overriding an element's 210 // style object. Animations will clobber any inline styles as a result. 211 element.style._set = function(property, value) { 212 element.style[property] = value; 213 }; 214 element.style._clear = function(property) { 215 element.style[property] = ''; 216 }; 217 } 218 219 // We must keep a handle on the patched style to prevent it from getting GC'd. 220 element._webAnimationsPatchedStyle = element.style; 221 } 222 223 scope.apply = function(element, property, value) { 224 ensureStyleIsPatched(element); 225 element.style._set(scope.propertyName(property), value); 226 }; 227 228 scope.clear = function(element, property) { 229 if (element._webAnimationsPatchedStyle) { 230 element.style._clear(scope.propertyName(property)); 231 } 232 }; 233 234 if (WEB_ANIMATIONS_TESTING) { 235 testing.ensureStyleIsPatched = ensureStyleIsPatched; 236 testing.updateSvgTransformAttr = updateSvgTransformAttr; 237 } 238 239})(webAnimations1, webAnimationsTesting); 240