Bug 1253470 - Part 4: Produce console warnings for invalid easing. r=birtles
authorDaisuke Akatsuka <daisuke@mozilla-japan.org>
Fri, 18 Mar 2016 16:28:22 +0900
changeset 289744 a0c0ea60eaefc2f2907527e77fd71fe8b84bb7eb
parent 289743 b798c62e43652ecc167d39c020f430f302716a2e
child 289745 b60ea1ba5f7ffb512b4b8f78da6e2cc27d8aedca
push id30108
push usercbook@mozilla.com
push dateTue, 22 Mar 2016 11:14:31 +0000
treeherdermozilla-central@ea6298e1b4f7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1253470
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1253470 - Part 4: Produce console warnings for invalid easing. r=birtles
dom/animation/AnimationUtils.cpp
dom/animation/KeyframeEffect.cpp
dom/animation/TimingParams.cpp
dom/animation/TimingParams.h
dom/bindings/Errors.msg
testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -1,25 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationUtils.h"
 
-#include "nsCSSParser.h" // For nsCSSParser
 #include "nsDebug.h"
 #include "nsIAtom.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsGlobalWindow.h"
 #include "nsString.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction
 #include "xpcpublic.h" // For xpc::NativeGlobal
 
 namespace mozilla {
 
 /* static */ void
 AnimationUtils::LogAsyncAnimationFailure(nsCString& aMessage,
                                          const nsIContent* aContent)
 {
@@ -34,74 +31,16 @@ AnimationUtils::LogAsyncAnimationFailure
       aMessage.Append('\'');
     }
     aMessage.Append(']');
   }
   aMessage.Append('\n');
   printf_stderr("%s", aMessage.get());
 }
 
-/* static */ Maybe<ComputedTimingFunction>
-AnimationUtils::ParseEasing(const nsAString& aEasing,
-                            nsIDocument* aDocument)
-{
-  MOZ_ASSERT(aDocument);
-
-  nsCSSValue value;
-  nsCSSParser parser;
-  parser.ParseLonghandProperty(eCSSProperty_animation_timing_function,
-                               aEasing,
-                               aDocument->GetDocumentURI(),
-                               aDocument->GetDocumentURI(),
-                               aDocument->NodePrincipal(),
-                               value);
-
-  switch (value.GetUnit()) {
-    case eCSSUnit_List: {
-      const nsCSSValueList* list = value.GetListValue();
-      if (list->mNext) {
-        // don't support a list of timing functions
-        break;
-      }
-      switch (list->mValue.GetUnit()) {
-        case eCSSUnit_Enumerated:
-          // Return Nothing() if "linear" is passed in.
-          if (list->mValue.GetIntValue() ==
-              NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR) {
-            return Nothing();
-          }
-          MOZ_FALLTHROUGH;
-        case eCSSUnit_Cubic_Bezier:
-        case eCSSUnit_Steps: {
-          nsTimingFunction timingFunction;
-          nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
-          ComputedTimingFunction computedTimingFunction;
-          computedTimingFunction.Init(timingFunction);
-          return Some(computedTimingFunction);
-        }
-        default:
-          MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function list "
-                                 "item unit");
-        break;
-      }
-      break;
-    }
-    case eCSSUnit_Null:
-    case eCSSUnit_Inherit:
-    case eCSSUnit_Initial:
-    case eCSSUnit_Unset:
-    case eCSSUnit_TokenStream:
-      break;
-    default:
-      MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit");
-      break;
-  }
-  return Nothing();
-}
-
 /* static */ nsIDocument*
 AnimationUtils::GetCurrentRealmDocument(JSContext* aCx)
 {
   nsGlobalWindow* win = xpc::CurrentWindowOrNull(aCx);
   if (!win) {
     return nullptr;
   }
   return win->GetDoc();
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -1287,20 +1287,23 @@ GenerateValueEntries(Element* aTarget,
                      nsTArray<KeyframeValueEntry>& aResult,
                      ErrorResult& aRv)
 {
   nsCSSPropertySet properties;              // All properties encountered.
   nsCSSPropertySet propertiesWithFromValue; // Those with a defined 0% value.
   nsCSSPropertySet propertiesWithToValue;   // Those with a defined 100% value.
 
   for (OffsetIndexedKeyframe& keyframe : aKeyframes) {
+    Maybe<ComputedTimingFunction> easing =
+      TimingParams::ParseEasing(keyframe.mKeyframeDict.mEasing,
+                                aTarget->OwnerDoc(), aRv);
+    if (aRv.Failed()) {
+      return;
+    }
     float offset = float(keyframe.mKeyframeDict.mOffset.Value());
-    Maybe<ComputedTimingFunction> easing =
-      AnimationUtils::ParseEasing(keyframe.mKeyframeDict.mEasing,
-                                  aTarget->OwnerDoc());
     // We ignore keyframe.mKeyframeDict.mComposite since we don't support
     // composite modes on keyframes yet.
 
     // keyframe.mPropertyValuePairs is currently sorted by CSS property IDL
     // name, since that was the order we read the properties from the JS
     // object.  Re-sort the list so that longhand properties appear before
     // shorthands, and with shorthands all appearing in increasing order of
     // number of components.  For two longhand properties, or two shorthands
@@ -1555,17 +1558,17 @@ BuildAnimationPropertyListFromPropertyIn
   binding_detail::FastPropertyIndexedKeyframes keyframes;
   if (!keyframes.Init(aCx, aValue, "PropertyIndexedKeyframes argument",
                       false)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   Maybe<ComputedTimingFunction> easing =
-    AnimationUtils::ParseEasing(keyframes.mEasing, aTarget->OwnerDoc());
+    TimingParams::ParseEasing(keyframes.mEasing, aTarget->OwnerDoc(), aRv);
 
   // We ignore easing.mComposite since we don't support composite modes on
   // keyframes yet.
 
   // Get all the property--value-list pairs off the object.
   JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
   nsTArray<PropertyValuesPair> propertyValuesPairs;
   if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/TimingParams.h"
 
+#include "nsCSSParser.h" // For nsCSSParser
 #include "nsIDocument.h"
 
 namespace mozilla {
 
 template <class OptionsType>
 static const dom::AnimationEffectTimingProperties&
 GetTimingProperties(const OptionsType& aOptions);
 
@@ -59,25 +60,30 @@ TimingParamsFromOptionsUnion(const Optio
     TimingParams::ValidateIterationStart(timing.mIterationStart, aRv);
     if (aRv.Failed()) {
       return result;
     }
     TimingParams::ValidateIterations(timing.mIterations, aRv);
     if (aRv.Failed()) {
       return result;
     }
+    Maybe<ComputedTimingFunction> easing =
+      TimingParams::ParseEasing(timing.mEasing, aDocument, aRv);
+    if (aRv.Failed()) {
+      return result;
+    }
 
     result.mDuration = duration;
     result.mDelay = TimeDuration::FromMilliseconds(timing.mDelay);
     result.mEndDelay = TimeDuration::FromMilliseconds(timing.mEndDelay);
     result.mIterations = timing.mIterations;
     result.mIterationStart = timing.mIterationStart;
     result.mDirection = timing.mDirection;
     result.mFill = timing.mFill;
-    result.mFunction = AnimationUtils::ParseEasing(timing.mEasing, aDocument);
+    result.mFunction = easing;
   }
   return result;
 }
 
 /* static */ TimingParams
 TimingParams::FromOptionsUnion(
   const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
   nsIDocument* aDocument,
@@ -90,16 +96,76 @@ TimingParams::FromOptionsUnion(
 TimingParams::FromOptionsUnion(
   const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
   nsIDocument* aDocument,
   ErrorResult& aRv)
 {
   return TimingParamsFromOptionsUnion(aOptions, aDocument, aRv);
 }
 
+/* static */ Maybe<ComputedTimingFunction>
+TimingParams::ParseEasing(const nsAString& aEasing,
+                          nsIDocument* aDocument,
+                          ErrorResult& aRv)
+{
+  MOZ_ASSERT(aDocument);
+
+  nsCSSValue value;
+  nsCSSParser parser;
+  parser.ParseLonghandProperty(eCSSProperty_animation_timing_function,
+                               aEasing,
+                               aDocument->GetDocumentURI(),
+                               aDocument->GetDocumentURI(),
+                               aDocument->NodePrincipal(),
+                               value);
+
+  switch (value.GetUnit()) {
+    case eCSSUnit_List: {
+      const nsCSSValueList* list = value.GetListValue();
+      if (list->mNext) {
+        // don't support a list of timing functions
+        break;
+      }
+      switch (list->mValue.GetUnit()) {
+        case eCSSUnit_Enumerated:
+          // Return Nothing() if "linear" is passed in.
+          if (list->mValue.GetIntValue() ==
+              NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR) {
+            return Nothing();
+          }
+          MOZ_FALLTHROUGH;
+        case eCSSUnit_Cubic_Bezier:
+        case eCSSUnit_Steps: {
+          nsTimingFunction timingFunction;
+          nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
+          ComputedTimingFunction computedTimingFunction;
+          computedTimingFunction.Init(timingFunction);
+          return Some(computedTimingFunction);
+        }
+        default:
+          MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function list "
+                                 "item unit");
+        break;
+      }
+      break;
+    }
+    case eCSSUnit_Inherit:
+    case eCSSUnit_Initial:
+    case eCSSUnit_Unset:
+    case eCSSUnit_TokenStream:
+    case eCSSUnit_Null:
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit");
+      break;
+  }
+  aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>();
+  return Nothing();
+}
+
 bool
 TimingParams::operator==(const TimingParams& aOther) const
 {
   return mDuration == aOther.mDuration &&
          mDelay == aOther.mDelay &&
          mIterations == aOther.mIterations &&
          mIterationStart == aOther.mIterationStart &&
          mDirection == aOther.mDirection &&
--- a/dom/animation/TimingParams.h
+++ b/dom/animation/TimingParams.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_TimingParams_h
 #define mozilla_TimingParams_h
 
+#include "nsStringFwd.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/UnionTypes.h" // For OwningUnrestrictedDoubleOrString
 #include "mozilla/ComputedTimingFunction.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/TimeStamp.h" // for TimeDuration
 
 // X11 has a #define for None
 #ifdef None
@@ -75,16 +76,20 @@ struct TimingParams
   static void ValidateIterations(double aIterations, ErrorResult& aRv)
   {
     if (IsNaN(aIterations) || aIterations < 0) {
       aRv.ThrowTypeError<dom::MSG_ENFORCE_RANGE_OUT_OF_RANGE>(
         NS_LITERAL_STRING("iterations"));
     }
   }
 
+  static Maybe<ComputedTimingFunction> ParseEasing(const nsAString& aEasing,
+                                                   nsIDocument* aDocument,
+                                                   ErrorResult& aRv);
+
   // mDuration.isNothing() represents the "auto" value
   Maybe<StickyTimeDuration> mDuration;
   TimeDuration mDelay;      // Initializes to zero
   TimeDuration mEndDelay;
   double mIterations = 1.0; // Can be NaN, negative, +/-Infinity
   double mIterationStart = 0.0;
   dom::PlaybackDirection mDirection = dom::PlaybackDirection::Normal;
   dom::FillMode mFill = dom::FillMode::Auto;
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -87,9 +87,10 @@ MSG_DEF(MSG_PROMISE_CAPABILITY_HAS_SOMET
 MSG_DEF(MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable")
 MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise")
 MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.")
 MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.")
 MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, JSEXN_TYPEERR, "{0} can't be a typed array on SharedArrayBuffer")
 MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 3, JSEXN_TYPEERR, "Cache got {0} response with bad status {1} while trying to add request {2}")
-MSG_DEF(MSG_INVALID_DURATION_ERROR, 0, JSEXN_TYPEERR, "Invalid duration.")
\ No newline at end of file
+MSG_DEF(MSG_INVALID_DURATION_ERROR, 0, JSEXN_TYPEERR, "Invalid duration.")
+MSG_DEF(MSG_INVALID_EASING_ERROR, 0, JSEXN_TYPEERR, "Invalid easing.")
\ No newline at end of file
--- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
+++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html
@@ -57,23 +57,18 @@ test(function(t) {
   gEmptyKeyframeListTests.forEach(function(frames) {
     assert_equals(new KeyframeEffectReadOnly(target, frames).getFrames().length,
                   0, "number of frames for " + JSON.stringify(frames));
   });
 }, "a KeyframeEffectReadOnly can be constructed with no frames");
 
 // [specified easing value, expected easing value]
 var gEasingValueTests = [
-  ["unrecognised", "linear"],
   ["linear", "linear"],
   ["ease-in-out", "ease-in-out"],
-  ["initial", "linear"],
-  ["inherit", "linear"],
-  ["var(--x)", "linear"],
-  ["ease-in-out, ease-out", "linear"],
   ["Ease\\2d in-out", "ease-in-out"],
   ["ease /**/", "ease"],
 ];
 
 test(function(t) {
   gEasingValueTests.forEach(function(subtest) {
     var easing = subtest[0];
     var expected = subtest[1];
@@ -444,16 +439,39 @@ gKeyframeSequenceTests.forEach(function(
   test(function(t) {
     var effect = new KeyframeEffectReadOnly(target, subtest.input);
     var secondEffect = new KeyframeEffectReadOnly(target, effect.getFrames());
     assert_frame_lists_equal(secondEffect.getFrames(), effect.getFrames());
   }, "a KeyframeEffectReadOnly constructed with " + subtest.desc +
      " roundtrips");
 });
 
+var gInvalidEasingInKeyframeSequenceTests = [
+  { desc:   "a blank easing",
+    input:  [{ easing: "" }] },
+  { desc:   "an unrecognized easing",
+    input:  [{ easing: "unrecognized" }] },
+  { desc:   "an 'initial' easing",
+    input:  [{ easing: "initial" }] },
+  { desc:   "an 'inherit' easing",
+    input:  [{ easing: "inherit" }] },
+  { desc:   "a variable easing",
+    input:  [{ easing: "var(--x)" }] },
+  { desc:   "a multi-value easing",
+    input:  [{ easing: "ease-in-out, ease-out" }] }
+];
+
+gInvalidEasingInKeyframeSequenceTests.forEach(function(subtest) {
+  test(function(t) {
+    assert_throws(new TypeError, function() {
+      new KeyframeEffectReadOnly(target, subtest.input);
+    });
+  }, "Invalid easing [" + subtest.desc + "] in KeyframeSequence " +
+     "should be thrown");
+});
 
 test(function(t) {
   var effect = new KeyframeEffectReadOnly(target,
                                           {left: ["10px", "20px"]});
 
   var timing = effect.timing;
   assert_equals(timing.delay, 0, "default delay");
   assert_equals(timing.endDelay, 0, "default endDelay");
@@ -562,24 +580,42 @@ var gInvalidKeyframeEffectOptionTests = 
   { desc:     "a negative Infinity iterations",
     input:    { iterations: -Infinity},
     expected: { name: "TypeError" } },
   { desc:     "a NaN iterations",
     input:    { iterations: NaN },
     expected: { name: "TypeError" } },
   { desc:     "a negative iterations",
     input:    { iterations: -1 },
+    expected: { name: "TypeError" } },
+  { desc:     "a blank easing",
+    input:    { easing: "" },
+    expected: { name: "TypeError" } },
+  { desc:     "an unrecognized easing",
+    input:    { easing: "unrecognised" },
+    expected: { name: "TypeError" } },
+  { desc:     "an 'initial' easing",
+    input:    { easing: "initial" },
+    expected: { name: "TypeError" } },
+  { desc:     "an 'inherit' easing",
+    input:    { easing: "inherit" },
+    expected: { name: "TypeError" } },
+  { desc:     "a variable easing",
+    input:    { easing: "var(--x)" },
+    expected: { name: "TypeError" } },
+  { desc:     "a multi-value easing",
+    input:    { easing: "ease-in-out, ease-out" },
     expected: { name: "TypeError" } }
 ];
 
 gInvalidKeyframeEffectOptionTests.forEach(function(stest) {
   test(function(t) {
     assert_throws(stest.expected, function() {
       new KeyframeEffectReadOnly(target,
-                                 {left: ["10px", "20px"]},
+                                 { left: ["10px", "20px"] },
                                  stest.input);
     });
   }, "Invalid KeyframeEffectReadOnly option by " + stest.desc);
 });
 
 test(function(t) {
   var effect = new KeyframeEffect(target,
                                   { left: ["10px", "20px"] });