| // Copyright 2014 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| (function(scope, testing) { |
| |
| var SVG_TRANSFORM_PROP = '_webAnimationsUpdateSvgTransformAttr'; |
| |
| /** |
| * IE/Edge do not support `transform` styles for SVG elements. Instead, |
| * `transform` attribute can be animated with some restrictions. |
| * See https://connect.microsoft.com/IE/feedback/details/811744/ie11-bug-with-implementation-of-css-transforms-in-svg, |
| * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1173754/, |
| * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101242/, etc. |
| * The same problem is exhibited by pre-Chrome Android browsers (ICS). |
| * Unfortunately, there's no easy way to feature-detect it. |
| */ |
| function updateSvgTransformAttr(window, element) { |
| if (!element.namespaceURI || element.namespaceURI.indexOf('/svg') == -1) { |
| return false; |
| } |
| if (!(SVG_TRANSFORM_PROP in window)) { |
| window[SVG_TRANSFORM_PROP] = |
| /Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent); |
| } |
| return window[SVG_TRANSFORM_PROP]; |
| } |
| |
| var styleAttributes = { |
| cssText: 1, |
| length: 1, |
| parentRule: 1, |
| }; |
| |
| var styleMethods = { |
| getPropertyCSSValue: 1, |
| getPropertyPriority: 1, |
| getPropertyValue: 1, |
| item: 1, |
| removeProperty: 1, |
| setProperty: 1, |
| }; |
| |
| var styleMutatingMethods = { |
| removeProperty: 1, |
| setProperty: 1, |
| }; |
| |
| function configureProperty(object, property, descriptor) { |
| descriptor.enumerable = true; |
| descriptor.configurable = true; |
| Object.defineProperty(object, property, descriptor); |
| } |
| |
| function AnimatedCSSStyleDeclaration(element) { |
| WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration), |
| 'Element must not already have an animated style attached.'); |
| |
| this._element = element; |
| // Stores the inline style of the element on its behalf while the |
| // polyfill uses the element's inline style to simulate web animations. |
| // This is needed to fake regular inline style CSSOM access on the element. |
| this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style; |
| this._style = element.style; |
| this._length = 0; |
| this._isAnimatedProperty = {}; |
| this._updateSvgTransformAttr = updateSvgTransformAttr(window, element); |
| this._savedTransformAttr = null; |
| |
| // Copy the inline style contents over to the surrogate. |
| for (var i = 0; i < this._style.length; i++) { |
| var property = this._style[i]; |
| this._surrogateStyle[property] = this._style[property]; |
| } |
| this._updateIndices(); |
| } |
| |
| AnimatedCSSStyleDeclaration.prototype = { |
| get cssText() { |
| return this._surrogateStyle.cssText; |
| }, |
| set cssText(text) { |
| var isAffectedProperty = {}; |
| for (var i = 0; i < this._surrogateStyle.length; i++) { |
| isAffectedProperty[this._surrogateStyle[i]] = true; |
| } |
| this._surrogateStyle.cssText = text; |
| this._updateIndices(); |
| for (var i = 0; i < this._surrogateStyle.length; i++) { |
| isAffectedProperty[this._surrogateStyle[i]] = true; |
| } |
| for (var property in isAffectedProperty) { |
| if (!this._isAnimatedProperty[property]) { |
| this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property)); |
| } |
| } |
| }, |
| get length() { |
| return this._surrogateStyle.length; |
| }, |
| get parentRule() { |
| return this._style.parentRule; |
| }, |
| // Mirror the indexed getters and setters of the surrogate style. |
| _updateIndices: function() { |
| while (this._length < this._surrogateStyle.length) { |
| Object.defineProperty(this, this._length, { |
| configurable: true, |
| enumerable: false, |
| get: (function(index) { |
| return function() { return this._surrogateStyle[index]; }; |
| })(this._length) |
| }); |
| this._length++; |
| } |
| while (this._length > this._surrogateStyle.length) { |
| this._length--; |
| Object.defineProperty(this, this._length, { |
| configurable: true, |
| enumerable: false, |
| value: undefined |
| }); |
| } |
| }, |
| _set: function(property, value) { |
| this._style[property] = value; |
| this._isAnimatedProperty[property] = true; |
| if (this._updateSvgTransformAttr && |
| scope.unprefixedPropertyName(property) == 'transform') { |
| // On IE/Edge, also set SVG element's `transform` attribute to 2d |
| // matrix of the transform. The `transform` style does not work, but |
| // `transform` attribute can be used instead. |
| // Notice, if the platform indeed supports SVG/CSS transforms the CSS |
| // declaration is supposed to override the attribute. |
| if (this._savedTransformAttr == null) { |
| this._savedTransformAttr = this._element.getAttribute('transform'); |
| } |
| this._element.setAttribute('transform', scope.transformToSvgMatrix(value)); |
| } |
| }, |
| _clear: function(property) { |
| this._style[property] = this._surrogateStyle[property]; |
| if (this._updateSvgTransformAttr && |
| scope.unprefixedPropertyName(property) == 'transform') { |
| if (this._savedTransformAttr) { |
| this._element.setAttribute('transform', this._savedTransformAttr); |
| } else { |
| this._element.removeAttribute('transform'); |
| } |
| this._savedTransformAttr = null; |
| } |
| delete this._isAnimatedProperty[property]; |
| }, |
| }; |
| |
| // Wrap the style methods. |
| for (var method in styleMethods) { |
| AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) { |
| return function() { |
| var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments); |
| if (modifiesStyle) { |
| if (!this._isAnimatedProperty[arguments[0]]) |
| this._style[method].apply(this._style, arguments); |
| this._updateIndices(); |
| } |
| return result; |
| } |
| })(method, method in styleMutatingMethods); |
| } |
| |
| // Wrap the style.cssProperty getters and setters. |
| for (var property in document.documentElement.style) { |
| if (property in styleAttributes || property in styleMethods) { |
| continue; |
| } |
| (function(property) { |
| configureProperty(AnimatedCSSStyleDeclaration.prototype, property, { |
| get: function() { |
| return this._surrogateStyle[property]; |
| }, |
| set: function(value) { |
| this._surrogateStyle[property] = value; |
| this._updateIndices(); |
| if (!this._isAnimatedProperty[property]) |
| this._style[property] = value; |
| } |
| }); |
| })(property); |
| } |
| |
| function ensureStyleIsPatched(element) { |
| if (element._webAnimationsPatchedStyle) |
| return; |
| |
| var animatedStyle = new AnimatedCSSStyleDeclaration(element); |
| try { |
| configureProperty(element, 'style', { get: function() { return animatedStyle; } }); |
| } catch (_) { |
| // iOS and older versions of Safari (pre v7) do not support overriding an element's |
| // style object. Animations will clobber any inline styles as a result. |
| element.style._set = function(property, value) { |
| element.style[property] = value; |
| }; |
| element.style._clear = function(property) { |
| element.style[property] = ''; |
| }; |
| } |
| |
| // We must keep a handle on the patched style to prevent it from getting GC'd. |
| element._webAnimationsPatchedStyle = element.style; |
| } |
| |
| scope.apply = function(element, property, value) { |
| ensureStyleIsPatched(element); |
| element.style._set(scope.propertyName(property), value); |
| }; |
| |
| scope.clear = function(element, property) { |
| if (element._webAnimationsPatchedStyle) { |
| element.style._clear(scope.propertyName(property)); |
| } |
| }; |
| |
| if (WEB_ANIMATIONS_TESTING) { |
| testing.ensureStyleIsPatched = ensureStyleIsPatched; |
| testing.updateSvgTransformAttr = updateSvgTransformAttr; |
| } |
| |
| })(webAnimations1, webAnimationsTesting); |