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