Skip to content

Commit 3753c60

Browse files
authored
Merge pull request #471 from alancutter/throwMoarTypeErrors
Throw TypeErrors for invalid keyframe inputs
2 parents 3a241ba + f8d8bf7 commit 3753c60

6 files changed

+54
-52
lines changed

src/keyframe-interpolations.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
}).forEach(function(interpolation) {
2626
var offsetFraction = fraction - interpolation.startOffset;
2727
var localDuration = interpolation.endOffset - interpolation.startOffset;
28-
var scaledLocalTime = localDuration == 0 ? 0 : interpolation.easing(offsetFraction / localDuration);
28+
var scaledLocalTime = localDuration == 0 ? 0 : interpolation.easingFunction(offsetFraction / localDuration);
2929
scope.apply(target, interpolation.property, interpolation.interpolation(scaledLocalTime));
3030
});
3131
} else {
@@ -95,13 +95,12 @@
9595
}
9696
}
9797

98-
var easing = keyframes[startIndex].easing;
9998
interpolations.push({
10099
applyFrom: applyFrom,
101100
applyTo: applyTo,
102101
startOffset: keyframes[startIndex].offset,
103102
endOffset: keyframes[endIndex].offset,
104-
easing: shared.toTimingFunction(easing ? easing : 'linear'),
103+
easingFunction: shared.parseEasingFunction(keyframes[startIndex].easing),
105104
property: groupName,
106105
interpolation: scope.propertyInterpolation(groupName,
107106
keyframes[startIndex].value,

src/normalize-keyframes.js

+17-11
Original file line numberDiff line numberDiff line change
@@ -231,21 +231,31 @@
231231
if (memberValue != null) {
232232
memberValue = Number(memberValue);
233233
if (!isFinite(memberValue))
234-
throw new TypeError('keyframe offsets must be numbers.');
234+
throw new TypeError('Keyframe offsets must be numbers.');
235+
if (memberValue < 0 || memberValue > 1)
236+
throw new TypeError('Keyframe offsets must be between 0 and 1.');
235237
}
236238
} else if (member == 'composite') {
237-
throw {
238-
type: DOMException.NOT_SUPPORTED_ERR,
239-
name: 'NotSupportedError',
240-
message: 'add compositing is not supported'
241-
};
239+
if (memberValue == 'add' || memberValue == 'accumulate') {
240+
throw {
241+
type: DOMException.NOT_SUPPORTED_ERR,
242+
name: 'NotSupportedError',
243+
message: 'add compositing is not supported'
244+
};
245+
} else if (memberValue != 'replace') {
246+
throw new TypeError('Invalid composite mode ' + memberValue + '.');
247+
}
248+
} else if (member == 'easing') {
249+
memberValue = shared.normalizeEasing(memberValue);
242250
} else {
243251
memberValue = '' + memberValue;
244252
}
245253
expandShorthandAndAntiAlias(member, memberValue, keyframe);
246254
}
247255
if (keyframe.offset == undefined)
248256
keyframe.offset = null;
257+
if (keyframe.easing == undefined)
258+
keyframe.easing = 'linear';
249259
return keyframe;
250260
});
251261

@@ -256,11 +266,7 @@
256266
var offset = keyframes[i].offset;
257267
if (offset != null) {
258268
if (offset < previousOffset) {
259-
throw {
260-
code: DOMException.INVALID_MODIFICATION_ERR,
261-
name: 'InvalidModificationError',
262-
message: 'Keyframes are not loosely sorted by offset. Sort or specify offsets.'
263-
};
269+
throw new TypeError('Keyframes are not loosely sorted by offset. Sort or specify offsets.');
264270
}
265271
previousOffset = offset;
266272
} else {

src/timing-utilities.js

+20-11
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
return this._direction;
105105
},
106106
set easing(value) {
107-
this._easingFunction = toTimingFunction(value);
107+
this._easingFunction = parseEasingFunction(normalizeEasing(value));
108108
this._setMember('easing', value);
109109
},
110110
get easing() {
@@ -224,32 +224,39 @@
224224
var cubicBezierRe = new RegExp('cubic-bezier\\(' + numberString + ',' + numberString + ',' + numberString + ',' + numberString + '\\)');
225225
var stepRe = /steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/;
226226

227-
function toTimingFunction(easing) {
227+
function normalizeEasing(easing) {
228228
if (!styleForCleaning) {
229229
styleForCleaning = document.createElement('div').style;
230230
}
231231
styleForCleaning.animationTimingFunction = '';
232232
styleForCleaning.animationTimingFunction = easing;
233-
var validatedEasing = styleForCleaning.animationTimingFunction;
234-
235-
if (validatedEasing == '' && isInvalidTimingDeprecated()) {
233+
var normalizedEasing = styleForCleaning.animationTimingFunction;
234+
if (normalizedEasing == '' && isInvalidTimingDeprecated()) {
236235
throw new TypeError(easing + ' is not a valid value for easing');
237236
}
237+
return normalizedEasing;
238+
}
238239

239-
var cubicData = cubicBezierRe.exec(validatedEasing);
240+
function parseEasingFunction(normalizedEasing) {
241+
if (normalizedEasing == 'linear') {
242+
return linear;
243+
}
244+
var cubicData = cubicBezierRe.exec(normalizedEasing);
240245
if (cubicData) {
241246
return cubic.apply(this, cubicData.slice(1).map(Number));
242247
}
243-
var stepData = stepRe.exec(validatedEasing);
248+
var stepData = stepRe.exec(normalizedEasing);
244249
if (stepData) {
245250
return step(Number(stepData[1]), {'start': Start, 'middle': Middle, 'end': End}[stepData[2]]);
246251
}
247-
var preset = presets[validatedEasing];
252+
var preset = presets[normalizedEasing];
248253
if (preset) {
249254
return preset;
250255
}
256+
// At this point none of our parse attempts succeeded; the easing is invalid.
257+
// Fall back to linear in the interest of not crashing the page.
251258
return linear;
252-
};
259+
}
253260

254261
function calculateActiveDuration(timing) {
255262
return Math.abs(repeatedDuration(timing) / timing.playbackRate);
@@ -345,11 +352,13 @@
345352
shared.calculateActiveDuration = calculateActiveDuration;
346353
shared.calculateTimeFraction = calculateTimeFraction;
347354
shared.calculatePhase = calculatePhase;
348-
shared.toTimingFunction = toTimingFunction;
355+
shared.normalizeEasing = normalizeEasing;
356+
shared.parseEasingFunction = parseEasingFunction;
349357

350358
if (WEB_ANIMATIONS_TESTING) {
351359
testing.normalizeTimingInput = normalizeTimingInput;
352-
testing.toTimingFunction = toTimingFunction;
360+
testing.normalizeEasing = normalizeEasing;
361+
testing.parseEasingFunction = parseEasingFunction;
353362
testing.calculateActiveDuration = calculateActiveDuration;
354363
testing.calculatePhase = calculatePhase;
355364
testing.PhaseNone = PhaseNone;

test/js/keyframes.js

+2-8
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ suite('keyframes', function() {
4545

4646
test('Normalize keyframes with some offsets not specified, but sorted by offset where specified. Some offsets are out of [0, 1] range.', function() {
4747
var normalizedKeyframes;
48-
assert.doesNotThrow(function() {
48+
assert.throws(function() {
4949
normalizedKeyframes = normalizeKeyframes([
5050
{offset: -1},
5151
{offset: 0},
@@ -55,11 +55,6 @@ suite('keyframes', function() {
5555
{offset: 2}
5656
]);
5757
});
58-
assert.equal(normalizedKeyframes.length, 4);
59-
assert.closeTo(normalizedKeyframes[0].offset, 0, 0.001);
60-
assert.closeTo(normalizedKeyframes[1].offset, 0.5, 0.001);
61-
assert.closeTo(normalizedKeyframes[2].offset, 0.75, 0.001);
62-
assert.closeTo(normalizedKeyframes[3].offset, 1, 0.001);
6358
});
6459

6560
test('Normalize keyframes with some offsets not specified, but sorted by offset where specified. All specified offsets in [0, 1] range.', function() {
@@ -151,14 +146,13 @@ suite('keyframes', function() {
151146

152147
test('Normalize keyframes with invalid specified easing.', function() {
153148
var normalizedKeyframes;
154-
assert.doesNotThrow(function() {
149+
assert.throws(function() {
155150
normalizedKeyframes = normalizeKeyframes([
156151
{left: '0px', easing: 'easy-peasy'},
157152
{left: '10px'},
158153
{left: '0px'}
159154
]);
160155
});
161-
assert.equal(normalizedKeyframes[0].easing, 'easy-peasy');
162156
});
163157

164158
test('Normalize keyframes where some properties are given non-string, non-number values.', function() {

test/js/timing-utilities.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ suite('timing-utilities', function() {
88
assert.equal(calculateActiveDuration({duration: 1000, playbackRate: 4, iterations: 20}), 5000);
99
assert.equal(calculateActiveDuration({duration: 500, playbackRate: 0.1, iterations: 300}), 1500000);
1010
});
11+
12+
function convertEasing(easing) {
13+
return parseEasingFunction(normalizeEasing(easing));
14+
}
15+
1116
test('conversion of timing functions', function() {
1217
function assertTimingFunctionsEqual(tf1, tf2, message) {
1318
for (var i = 0; i <= 1; i += 0.1) {
@@ -16,12 +21,12 @@ suite('timing-utilities', function() {
1621
}
1722

1823
assertTimingFunctionsEqual(
19-
toTimingFunction('ease-in-out'),
20-
toTimingFunction('eAse\\2d iN-ouT'),
24+
convertEasing('ease-in-out'),
25+
convertEasing('eAse\\2d iN-ouT'),
2126
'Should accept arbitrary casing and escape chararcters');
2227

23-
var f = toTimingFunction('ease');
24-
var g = toTimingFunction('cubic-bezier(.25, 0.1, 0.25, 1.0)');
28+
var f = convertEasing('ease');
29+
var g = convertEasing('cubic-bezier(.25, 0.1, 0.25, 1.0)');
2530
assertTimingFunctionsEqual(f, g, 'ease should map onto preset cubic-bezier');
2631
assert.closeTo(f(0.1844), 0.2599, 0.001);
2732
assert.closeTo(g(0.1844), 0.2599, 0.001);
@@ -30,12 +35,12 @@ suite('timing-utilities', function() {
3035
assert.equal(g(0), 0);
3136
assert.equal(g(1), 1);
3237

33-
f = toTimingFunction('cubic-bezier(0, 1, 1, 0)');
38+
f = convertEasing('cubic-bezier(0, 1, 1, 0)');
3439
assert.closeTo(f(0.104), 0.3920, 0.001);
3540

3641
function assertInvalidEasingThrows(easing) {
3742
assert.throws(function() {
38-
toTimingFunction(easing);
43+
convertEasing(easing);
3944
}, easing);
4045
}
4146

@@ -45,7 +50,7 @@ suite('timing-utilities', function() {
4550
assertInvalidEasingThrows('cubic-bezier(-1, 1, 1, 1)');
4651
assertInvalidEasingThrows('cubic-bezier(1, 1, 1)');
4752

48-
f = toTimingFunction('steps(10, end)');
53+
f = convertEasing('steps(10, end)');
4954
assert.equal(f(0), 0);
5055
assert.equal(f(0.09), 0);
5156
assert.equal(f(0.1), 0.1);

test/web-platform-tests-expectations.js

+1-12
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,7 @@ module.exports = {
1717
'Element.animate() creates an Animation object':
1818
'assert_equals: Returned object is an Animation expected "[object Animation]" but got "[object Object]"',
1919

20-
'Element.animate() does not accept keyframes not loosely sorted by offset':
21-
'assert_throws: function "function () {\n"use strict";\n\n div.animate(subtest.input, 2000);\n }" threw object "[object Object]" ("InvalidModificationError") expected object "[object Object]" ("TypeError")',
22-
23-
'Element.animate() does not accept keyframes with an invalid composite value':
24-
'assert_throws: function "function () {\n"use strict";\n\n div.animate(subtest.input, 2000);\n }" threw object "[object Object]" ("NotSupportedError") expected object "[object Object]" ("TypeError")',
25-
26-
'Element.animate() does not accept keyframes with an out-of-bounded negative offset':
27-
'assert_throws: function "function () {\n"use strict";\n\n div.animate(subtest.input, 2000);\n }" did not throw',
28-
29-
'Element.animate() does not accept keyframes with an out-of-bounded positive offset':
30-
'assert_throws: function "function () {\n"use strict";\n\n div.animate(subtest.input, 2000);\n }" did not throw',
31-
20+
// Seems to be a bug in Firefox 47? The TypeError is thrown but disappears by the time it bubbles up to assert_throws().
3221
'Element.animate() does not accept property-indexed keyframes with an invalid easing value':
3322
'assert_throws: function "function () {\n"use strict";\n\n div.animate(subtest.input, 2000);\n }" did not throw',
3423
},

0 commit comments

Comments
 (0)