Bug 1278485 - Part 1: Limit y1 and y2 control points for cubic-bezier to avoid overflows. r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Tue, 12 Jul 2016 16:24:42 +0900
changeset 386576 47b1ce3baac443d75d4fc1a0a33c0e7b3d07b0f8
parent 386302 214884d507ee369c1cf14edb26527c4f9a97bf48
child 386577 b6d0803523c3768a627ea79da135f5686e347f36
push id22748
push userhiikezoe@mozilla-japan.org
push dateTue, 12 Jul 2016 10:30:30 +0000
reviewersbirtles
bugs1278485
milestone50.0a1
Bug 1278485 - Part 1: Limit y1 and y2 control points for cubic-bezier to avoid overflows. r?birtles MozReview-Commit-ID: Bls260r7Sqn
dom/animation/KeyframeEffect.cpp
dom/animation/test/crashtests/1278485-1.html
dom/animation/test/crashtests/crashtests.list
dom/animation/test/mochitest.ini
dom/animation/test/mozilla/file_cubic_bezier_limits.html
dom/animation/test/mozilla/test_cubic_bezier_limits.html
layout/style/nsCSSParser.cpp
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -708,16 +708,17 @@ KeyframeEffectReadOnly::ComposeStyle(Ref
     double positionInSegment =
       (computedTiming.mProgress.Value() - segment->mFromKey) /
       (segment->mToKey - segment->mFromKey);
     double valuePosition =
       ComputedTimingFunction::GetPortion(segment->mTimingFunction,
                                          positionInSegment,
                                          computedTiming.mBeforeFlag);
 
+    MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
     StyleAnimationValue val;
     if (StyleAnimationValue::Interpolate(prop.mProperty,
                                          segment->mFromValue,
                                          segment->mToValue,
                                          valuePosition, val)) {
       aStyleRule->AddValue(prop.mProperty, Move(val));
     } else if (valuePosition < 0.5) {
       aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/crashtests/1278485-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+  document.body.animate([],
+                        { duration: 6,
+                          easing: "cubic-bezier(0, -1e+39, 0, 0)" });
+  document.body.animate([],
+                        { duration: 6,
+                          easing: "cubic-bezier(0, 1e+39, 0, 0)" });
+  document.body.animate([],
+                        { duration: 6,
+                          easing: "cubic-bezier(0, 0, 0, -1e+39)" });
+  document.body.animate([],
+                        { duration: 6,
+                          easing: "cubic-bezier(0, 0, 0, 1e+39)" });
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -1,9 +1,10 @@
 pref(dom.animations-api.core.enabled,true) load 1239889-1.html
 pref(dom.animations-api.core.enabled,true) load 1244595-1.html
 pref(dom.animations-api.core.enabled,true) load 1216842-1.html
 pref(dom.animations-api.core.enabled,true) load 1216842-2.html
 pref(dom.animations-api.core.enabled,true) load 1216842-3.html
 pref(dom.animations-api.core.enabled,true) load 1216842-4.html
 pref(dom.animations-api.core.enabled,true) load 1216842-5.html
 pref(dom.animations-api.core.enabled,true) load 1216842-6.html
+pref(dom.animations-api.core.enabled,true) load 1278485-1.html
 pref(dom.animations-api.core.enabled,true) load 1277272-1.html
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -30,16 +30,17 @@ support-files =
   css-transitions/file_animation-starttime.html
   css-transitions/file_csstransition-transitionproperty.html
   css-transitions/file_document-get-animations.html
   css-transitions/file_effect-target.html
   css-transitions/file_element-get-animations.html
   css-transitions/file_keyframeeffect-getkeyframes.html
   css-transitions/file_pseudoElement-get-animations.html
   document-timeline/file_document-timeline.html
+  mozilla/file_cubic_bezier_limits.html
   mozilla/file_deferred_start.html
   mozilla/file_disabled_properties.html
   mozilla/file_document-timeline-origin-time-range.html
   mozilla/file_hide_and_show.html
   mozilla/file_partial_keyframes.html
   style/file_animation-seeking-with-current-time.html
   style/file_animation-seeking-with-start-time.html
   testcommon.js
@@ -75,16 +76,17 @@ skip-if = buildapp == 'mulet'
 [css-transitions/test_effect-target.html]
 [css-transitions/test_element-get-animations.html]
 skip-if = buildapp == 'mulet'
 [css-transitions/test_keyframeeffect-getkeyframes.html]
 [css-transitions/test_pseudoElement-get-animations.html]
 [document-timeline/test_document-timeline.html]
 [document-timeline/test_request_animation_frame.html]
 skip-if = buildapp == 'mulet'
+[mozilla/test_cubic_bezier_limits.html]
 [mozilla/test_deferred_start.html]
 skip-if = (toolkit == 'gonk' && debug)
 [mozilla/test_disabled_properties.html]
 [mozilla/test_document-timeline-origin-time-range.html]
 [mozilla/test_hide_and_show.html]
 [mozilla/test_partial_keyframes.html]
 [style/test_animation-seeking-with-current-time.html]
 [style/test_animation-seeking-with-start-time.html]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/file_cubic_bezier_limits.html
@@ -0,0 +1,141 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<body>
+<style>
+@keyframes anim {
+  to { margin-left: 100px; }
+}
+
+.transition-div {
+  margin-left: 100px;
+}
+</style>
+<script>
+'use strict';
+
+// We clamp +infinity or -inifinity value in floating point to
+// maximum floating point value or -maxinum floating point value.
+const max_float = 3.40282e+38;
+
+test(function(t) {
+  var div = addDiv(t);
+  var anim = div.animate({ }, 100 * MS_PER_SEC);
+
+  anim.effect.timing.easing = 'cubic-bezier(0, 1e+39, 0, 0)';
+  assert_equals(anim.effect.timing.easing,
+    'cubic-bezier(0, ' + max_float + ', 0, 0)',
+    'y1 control point for effect easing is out of upper boundary');
+
+  anim.effect.timing.easing = 'cubic-bezier(0, 0, 0, 1e+39)';
+  assert_equals(anim.effect.timing.easing,
+    'cubic-bezier(0, 0, 0, ' + max_float + ')',
+    'y2 control point for effect easing is out of upper boundary');
+
+  anim.effect.timing.easing = 'cubic-bezier(0, -1e+39, 0, 0)';
+  assert_equals(anim.effect.timing.easing,
+    'cubic-bezier(0, ' + -max_float + ', 0, 0)',
+    'y1 control point for effect easing is out of lower boundary');
+
+  anim.effect.timing.easing = 'cubic-bezier(0, 0, 0, -1e+39)';
+  assert_equals(anim.effect.timing.easing,
+    'cubic-bezier(0, 0, 0, ' + -max_float + ')',
+    'y2 control point for effect easing is out of lower boundary');
+
+}, 'Clamp y1 and y2 control point out of boundaries for effect easing' );
+
+test(function(t) {
+  var div = addDiv(t);
+  var anim = div.animate({ }, 100 * MS_PER_SEC);
+
+  anim.effect.setKeyframes([ { easing: 'cubic-bezier(0, 1e+39, 0, 0)' }]);
+  assert_equals(anim.effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + max_float + ', 0, 0)',
+    'y1 control point for keyframe easing is out of upper boundary');
+
+  anim.effect.setKeyframes([ { easing: 'cubic-bezier(0, 0, 0, 1e+39)' }]);
+  assert_equals(anim.effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + max_float + ')',
+    'y2 control point for keyframe easing is out of upper boundary');
+
+  anim.effect.setKeyframes([ { easing: 'cubic-bezier(0, -1e+39, 0, 0)' }]);
+  assert_equals(anim.effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + -max_float + ', 0, 0)',
+    'y1 control point for keyframe easing is out of lower boundary');
+
+  anim.effect.setKeyframes([ { easing: 'cubic-bezier(0, 0, 0, -1e+39)' }]);
+  assert_equals(anim.effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + -max_float + ')',
+    'y2 control point for keyframe easing is out of lower boundary');
+
+}, 'Clamp y1 and y2 control point out of boundaries for keyframe easing' );
+
+test(function(t) {
+  var div = addDiv(t);
+
+  div.style.animation = 'anim 100s cubic-bezier(0, 1e+39, 0, 0)';
+
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + max_float + ', 0, 0)',
+    'y1 control point for CSS animation is out of upper boundary');
+
+  div.style.animation = 'anim 100s cubic-bezier(0, 0, 0, 1e+39)';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + max_float + ')',
+    'y2 control point for CSS animation is out of upper boundary');
+
+  div.style.animation = 'anim 100s cubic-bezier(0, -1e+39, 0, 0)';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + -max_float + ', 0, 0)',
+    'y1 control point for CSS animation is out of lower boundary');
+
+  div.style.animation = 'anim 100s cubic-bezier(0, 0, 0, -1e+39)';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + -max_float + ')',
+    'y2 control point for CSS animation is out of lower boundary');
+
+}, 'Clamp y1 and y2 control point out of boundaries for CSS animation' );
+
+test(function(t) {
+  var div = addDiv(t, {'class': 'transition-div'});
+
+  div.style.transition = 'margin-left 100s cubic-bezier(0, 1e+39, 0, 0)';
+  flushComputedStyle(div);
+  div.style.marginLeft = '0px';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + max_float + ', 0, 0)',
+    'y1 control point for CSS transition on upper boundary');
+  div.style.transition = '';
+  div.style.marginLeft = '';
+
+  div.style.transition = 'margin-left 100s cubic-bezier(0, 0, 0, 1e+39)';
+  flushComputedStyle(div);
+  div.style.marginLeft = '0px';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + max_float + ')',
+    'y2 control point for CSS transition on upper boundary');
+  div.style.transition = '';
+  div.style.marginLeft = '';
+
+  div.style.transition = 'margin-left 100s cubic-bezier(0, -1e+39, 0, 0)';
+  flushComputedStyle(div);
+  div.style.marginLeft = '0px';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, ' + -max_float + ', 0, 0)',
+    'y1 control point for CSS transition on lower boundary');
+  div.style.transition = '';
+  div.style.marginLeft = '';
+
+  div.style.transition = 'margin-left 100s cubic-bezier(0, 0, 0, -1e+39)';
+  flushComputedStyle(div);
+  div.style.marginLeft = '0px';
+  assert_equals(div.getAnimations()[0].effect.getKeyframes()[0].easing,
+    'cubic-bezier(0, 0, 0, ' + -max_float + ')',
+    'y2 control point for CSS transition on lower boundary');
+
+}, 'Clamp y1 and y2 control point out of boundaries for CSS transition' );
+
+done();
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/test_cubic_bezier_limits.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_cubic_bezier_limits.html");
+  });
+</script>
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -8,18 +8,20 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/TypedEnumBits.h"
 
 #include <algorithm> // for std::stable_sort
+#include <limits> // for std::numeric_limits
 
 #include "nsCSSParser.h"
+#include "nsAlgorithm.h"
 #include "nsCSSProps.h"
 #include "nsCSSKeywords.h"
 #include "nsCSSScanner.h"
 #include "mozilla/css/ErrorReporter.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/css/StyleRule.h"
 #include "mozilla/css/ImportRule.h"
 #include "nsCSSRules.h"
@@ -1062,17 +1064,17 @@ protected:
   bool ParseTouchAction(nsCSSValue& aValue);
 
   bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
   bool ParseShadowList(nsCSSProperty aProperty);
   bool ParseTransitionProperty();
   bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
   bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                      char aStop,
-                                                     bool aCheckRange);
+                                                     bool aIsXPoint);
   bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
   enum ParseAnimationOrTransitionShorthandResult {
     eParseAnimationOrTransitionShorthand_Values,
     eParseAnimationOrTransitionShorthand_Inherit,
     eParseAnimationOrTransitionShorthand_Error
   };
   ParseAnimationOrTransitionShorthandResult
     ParseAnimationOrTransitionShorthand(const nsCSSProperty* aProperties,
@@ -16376,25 +16378,32 @@ CSSParserImpl::ParseTransitionTimingFunc
   aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier);
 
   return true;
 }
 
 bool
 CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                            char aStop,
-                                                           bool aCheckRange)
+                                                           bool aIsXPoint)
 {
   if (!GetToken(true)) {
     return false;
   }
   nsCSSToken* tk = &mToken;
   if (tk->mType == eCSSToken_Number) {
     float num = tk->mNumber;
-    if (aCheckRange && (num < 0.0 || num > 1.0)) {
+
+    // Clamp infinity or -infinity values to max float or -max float to avoid
+    // calculations with infinity.
+    num = mozilla::clamped(num, -std::numeric_limits<float>::max(),
+                                 std::numeric_limits<float>::max());
+
+    // X control point should be inside [0, 1] range.
+    if (aIsXPoint && (num < 0.0 || num > 1.0)) {
       return false;
     }
     aComponent = num;
     if (ExpectSymbol(aStop, true)) {
       return true;
     }
   }
   return false;