• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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