Skip to content

Commit 483fb2e

Browse files
committed
Merge pull request #238 from dstockwell/preserve
Add apply-preserving-inline-style
2 parents 24face0 + 4f4dc76 commit 483fb2e

9 files changed

+306
-25
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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._surrogateElement = document.createElement('div');
51+
this._surrogateStyle = this._surrogateElement.style;
52+
this._style = element.style;
53+
this._length = 0;
54+
this._isAnimatedProperty = {};
55+
56+
// Copy the inline style contents over to the surrogate.
57+
for (var i = 0; i < this._style.length; i++) {
58+
var property = this._style[i];
59+
this._surrogateStyle[property] = this._style[property];
60+
}
61+
this._updateIndices();
62+
}
63+
64+
AnimatedCSSStyleDeclaration.prototype = {
65+
get cssText() {
66+
return this._surrogateStyle.cssText;
67+
},
68+
set cssText(text) {
69+
var isAffectedProperty = {};
70+
for (var i = 0; i < this._surrogateStyle.length; i++) {
71+
isAffectedProperty[this._surrogateStyle[i]] = true;
72+
}
73+
this._surrogateStyle.cssText = text;
74+
this._updateIndices();
75+
for (var i = 0; i < this._surrogateStyle.length; i++) {
76+
isAffectedProperty[this._surrogateStyle[i]] = true;
77+
}
78+
for (var property in isAffectedProperty) {
79+
if (!this._isAnimatedProperty[property]) {
80+
this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property));
81+
}
82+
}
83+
},
84+
get length() {
85+
return this._surrogateStyle.length;
86+
},
87+
get parentRule() {
88+
return this._style.parentRule;
89+
},
90+
// Mirror the indexed getters and setters of the surrogate style.
91+
_updateIndices: function() {
92+
while (this._length < this._surrogateStyle.length) {
93+
Object.defineProperty(this, this._length, {
94+
configurable: true,
95+
enumerable: false,
96+
get: (function(index) {
97+
return function() { return this._surrogateStyle[index]; };
98+
})(this._length)
99+
});
100+
this._length++;
101+
}
102+
while (this._length > this._surrogateStyle.length) {
103+
this._length--;
104+
Object.defineProperty(this, this._length, {
105+
configurable: true,
106+
enumerable: false,
107+
value: undefined
108+
});
109+
}
110+
},
111+
_set: function(property, value) {
112+
this._style[property] = value;
113+
this._isAnimatedProperty[property] = true;
114+
},
115+
_clear: function(property) {
116+
this._style[property] = this._surrogateStyle[property];
117+
delete this._isAnimatedProperty[property];
118+
},
119+
};
120+
121+
// Wrap the style methods.
122+
for (var method in styleMethods) {
123+
AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) {
124+
return function() {
125+
var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments);
126+
if (modifiesStyle) {
127+
if (!this._isAnimatedProperty[arguments[0]])
128+
this._style[method].apply(this._style, arguments);
129+
this._updateIndices();
130+
}
131+
return result;
132+
}
133+
})(method, method in styleMutatingMethods);
134+
}
135+
136+
// Wrap the style.cssProperty getters and setters.
137+
for (var property in document.documentElement.style) {
138+
if (property in styleAttributes || property in styleMethods) {
139+
continue;
140+
}
141+
(function(property) {
142+
configureProperty(AnimatedCSSStyleDeclaration.prototype, property, {
143+
get: function() {
144+
return this._surrogateStyle[property];
145+
},
146+
set: function(value) {
147+
this._surrogateStyle[property] = value;
148+
this._updateIndices();
149+
if (!this._isAnimatedProperty[property])
150+
this._style[property] = value;
151+
}
152+
});
153+
})(property);
154+
}
155+
156+
function ensureStyleIsPatched(element) {
157+
if (element._webAnimationsPatchedStyle)
158+
return;
159+
160+
// If this style patch fails (on Safari and iOS) use the apply-preserving-inline-style-methods.js
161+
// module instead and restrict inline style interactions to the methods listed in styleMethods.
162+
var animatedStyle = new AnimatedCSSStyleDeclaration(element);
163+
configureProperty(element, 'style', { get: function() { return animatedStyle; } });
164+
165+
// We must keep a handle on the patched style to prevent it from getting GC'd.
166+
element._webAnimationsPatchedStyle = element.style;
167+
}
168+
169+
scope.apply = function(element, property, value) {
170+
ensureStyleIsPatched(element);
171+
element.style._set(scope.propertyName(property), value);
172+
};
173+
174+
scope.clear = function(element, property) {
175+
if (element._webAnimationsPatchedStyle) {
176+
element.style._clear(scope.propertyName(property));
177+
}
178+
};
179+
180+
if (WEB_ANIMATIONS_TESTING)
181+
testing.ensureStyleIsPatched = ensureStyleIsPatched;
182+
183+
})(webAnimationsMinifill, webAnimationsTesting);

src/apply.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,12 @@
1414

1515
(function(scope, testing) {
1616

17-
var aliased = {};
18-
19-
function alias(name, aliases) {
20-
aliases.concat([name]).forEach(function(candidate) {
21-
if (candidate in document.documentElement.style) {
22-
aliased[name] = candidate;
23-
}
24-
});
25-
}
26-
alias('transform', ['webkitTransform', 'msTransform']);
27-
alias('transformOrigin', ['webkitTransformOrigin']);
28-
alias('perspective', ['webkitPerspective']);
29-
alias('perspectiveOrigin', ['webkitPerspectiveOrigin']);
30-
31-
function propertyName(property) {
32-
return aliased[property] || property;
33-
}
34-
3517
scope.apply = function(element, property, value) {
36-
element.style[propertyName(property)] = value;
18+
element.style[scope.propertyName(property)] = value;
3719
};
3820

3921
scope.clear = function(element, property) {
40-
element.style[propertyName(property)] = '';
22+
element.style[scope.propertyName(property)] = '';
4123
};
4224

4325
})(webAnimationsMinifill, webAnimationsTesting);

src/property-names.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 aliased = {};
18+
19+
function alias(name, aliases) {
20+
aliases.concat([name]).forEach(function(candidate) {
21+
if (candidate in document.documentElement.style) {
22+
aliased[name] = candidate;
23+
}
24+
});
25+
}
26+
alias('transform', ['webkitTransform', 'msTransform']);
27+
alias('transformOrigin', ['webkitTransformOrigin']);
28+
alias('perspective', ['webkitPerspective']);
29+
alias('perspectiveOrigin', ['webkitPerspectiveOrigin']);
30+
31+
scope.propertyName = function(property) {
32+
return aliased[property] || property;
33+
};
34+
35+
})(webAnimationsMinifill, webAnimationsTesting);

target-config.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
'src/effect.js',
99
'src/property-interpolation.js',
1010
'src/animation.js',
11-
'src/apply.js',
11+
'src/apply-preserving-inline-style.js',
1212
'src/element-animatable.js',
1313
'src/interpolation.js',
1414
'src/matrix-interpolation.js',
@@ -25,7 +25,9 @@
2525
'src/transform-handler.js',
2626
'src/font-weight-handler.js',
2727
'src/position-handler.js',
28-
'src/shape-handler.js'];
28+
'src/shape-handler.js',
29+
'src/property-names.js',
30+
];
2931

3032
var liteMinifillSrc = [
3133
'src/animation-node.js',
@@ -44,7 +46,9 @@
4446
'src/color-handler.js',
4547
'src/dimension-handler.js',
4648
'src/box-handler.js',
47-
'src/transform-handler.js'];
49+
'src/transform-handler.js',
50+
'src/property-names.js',
51+
];
4852

4953

5054
var sharedSrc = [
@@ -60,6 +64,7 @@
6064

6165
var minifillTest = [
6266
'test/js/animation-node.js',
67+
'test/js/apply-preserving-inline-style.js',
6368
'test/js/box-handler.js',
6469
'test/js/color-handler.js',
6570
'test/js/dimension-handler.js',
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
suite('apply-preserving-inline-style', function() {
2+
setup(function() {
3+
this.element = document.createElement('div');
4+
ensureStyleIsPatched(this.element);
5+
this.style = this.element.style;
6+
document.documentElement.appendChild(this.element);
7+
});
8+
teardown(function() {
9+
this.element.remove();
10+
});
11+
12+
test('Style is patched', function() {
13+
assert(this.element._webAnimationsPatchedStyle);
14+
});
15+
test('Setting animated style', function() {
16+
this.style.left = '0px';
17+
this.element.style._set('left', '100px');
18+
assert.equal(this.style.left, '0px');
19+
});
20+
test('Clearing animated style', function() {
21+
this.style.left = '0px';
22+
this.element.style._set('left', '100px');
23+
this.element.style._clear('left');
24+
assert.equal(this.style.left, '0px');
25+
});
26+
test('Patched length', function() {
27+
this.element.style._set('left', '100px');
28+
this.style.cssText = 'left: 0px; background-color: green;';
29+
assert.equal(this.style.cssText, 'left: 0px; background-color: green;');
30+
assert.equal(this.style.left, '0px');
31+
assert.equal(this.style.backgroundColor, 'green');
32+
assert.equal(this.style.length, 2);
33+
});
34+
test('Patched property getters and setters', function() {
35+
this.style._set('left', '100px');
36+
this.style.left = '0px';
37+
this.style.backgroundColor = 'rgb(1, 2, 3)';
38+
assert.equal(this.style.left, '0px');
39+
assert.equal(this.style.backgroundColor, 'rgb(1, 2, 3)');
40+
assert.equal(getComputedStyle(this.element).left, '100px');
41+
assert.equal(getComputedStyle(this.element).backgroundColor, 'rgb(1, 2, 3)');
42+
});
43+
test('Patched setProperty/getPropertyValue', function() {
44+
this.style._set('left', '100px');
45+
this.style.setProperty('left', '0px');
46+
this.style.setProperty('background-color', 'rgb(1, 2, 3)');
47+
assert.equal(this.style.getPropertyValue('left'), '0px');
48+
assert.equal(this.style.getPropertyValue('background-color'), 'rgb(1, 2, 3)');
49+
assert.equal(getComputedStyle(this.element).left, '100px');
50+
assert.equal(getComputedStyle(this.element).backgroundColor, 'rgb(1, 2, 3)');
51+
});
52+
test('Patched item()', function() {
53+
this.style._set('left', '100px');
54+
this.style.setProperty('left', '0px');
55+
this.style.setProperty('background-color', 'rgb(1, 2, 3)');
56+
assert.equal(this.style.item(0), 'left');
57+
assert.equal(this.style.item(1), 'background-color');
58+
assert.equal(this.style.item(2), '');
59+
this.style.cssText = 'top: 0px';
60+
assert.equal(this.style.item(0), 'top');
61+
assert.equal(this.style.item(1), '');
62+
});
63+
test('Patched cssText', function() {
64+
this.style._set('left', '100px');
65+
assert.equal(this.style.length, 0);
66+
this.style.setProperty('left', '0px');
67+
this.style.setProperty('background-color', 'rgb(1, 2, 3)');
68+
assert.equal(this.style.length, 2);
69+
this.style.cssText = 'top: 0px';
70+
assert.equal(this.style.length, 1);
71+
});
72+
});

web-animations-next-lite.dev.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<script src="src/dimension-handler.js"></script>
3535
<script src="src/box-handler.js"></script>
3636
<script src="src/transform-handler.js"></script>
37+
<script src="src/property-names.js"></script>
3738
<script src="src/timeline.js"></script>
3839
<script src="src/maxifill-player.js"></script>
3940
<script src="src/animation-constructor.js"></script>

web-animations-next.dev.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<script src="src/effect.js"></script>
2222
<script src="src/property-interpolation.js"></script>
2323
<script src="src/animation.js"></script>
24-
<script src="src/apply.js"></script>
24+
<script src="src/apply-preserving-inline-style.js"></script>
2525
<script src="src/element-animatable.js"></script>
2626
<script src="src/interpolation.js"></script>
2727
<script src="src/matrix-interpolation.js"></script>
@@ -39,6 +39,7 @@
3939
<script src="src/font-weight-handler.js"></script>
4040
<script src="src/position-handler.js"></script>
4141
<script src="src/shape-handler.js"></script>
42+
<script src="src/property-names.js"></script>
4243
<script src="src/timeline.js"></script>
4344
<script src="src/maxifill-player.js"></script>
4445
<script src="src/animation-constructor.js"></script>

0 commit comments

Comments
 (0)