Bug 1339690 - Part 7: Stop storing invalid property value. draft
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 14 Jun 2017 11:43:47 +0800
changeset 594710 c2c385fbee2efd575b216bdc266389d2d2a9b271
parent 594709 5dfddca5201194de9ffc34836e2296792c3629dc
child 594711 6f60f44816917bc7fcaa2a4e3a0fc822132ebf7f
push id64112
push userbmo:boris.chiou@gmail.com
push dateThu, 15 Jun 2017 10:38:49 +0000
bugs1339690
milestone56.0a1
Bug 1339690 - Part 7: Stop storing invalid property value. MozReview-Commit-ID: H3aRcJIk7CV
dom/animation/Keyframe.h
dom/animation/KeyframeUtils.cpp
layout/style/nsAnimationManager.cpp
layout/style/nsTransitionManager.cpp
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html
testing/web-platform/tests/web-animations/resources/keyframe-utils.js
--- a/dom/animation/Keyframe.h
+++ b/dom/animation/Keyframe.h
@@ -20,29 +20,38 @@ namespace dom {
 enum class CompositeOperation : uint8_t;
 }
 
 /**
  * A property-value pair specified on a keyframe.
  */
 struct PropertyValuePair
 {
+  PropertyValuePair(nsCSSPropertyID aProperty, nsCSSValue&& aValue)
+    : mProperty(aProperty), mValue(Move(aValue)) { }
+  PropertyValuePair(nsCSSPropertyID aProperty,
+                    RefPtr<RawServoDeclarationBlock>&& aValue)
+    : mProperty(aProperty), mServoDeclarationBlock(Move(aValue))
+  {
+    MOZ_ASSERT(mServoDeclarationBlock, "Should be valid property value");
+  }
+
   nsCSSPropertyID mProperty;
-  // The specified value for the property. For shorthand properties or invalid
-  // property values, we store the specified property value as a token stream
-  // (string).
+  // The specified value for the property. For shorthand property values,
+  // we store the specified property value as a token stream (string).
+  // If this is uninitialized, we use the underlying value.
   nsCSSValue mValue;
 
   // The specified value when using the Servo backend.
   RefPtr<RawServoDeclarationBlock> mServoDeclarationBlock;
 
 #ifdef DEBUG
   // Flag to indicate that when we call StyleAnimationValue::ComputeValues on
   // this value we should behave as if that function had failed.
-  bool mSimulateComputeValuesFailure;
+  bool mSimulateComputeValuesFailure = false;
 #endif
 
   bool operator==(const PropertyValuePair&) const;
 };
 
 /**
  * A single keyframe.
  *
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -309,45 +309,16 @@ public:
   static bool LessThan(const Keyframe& aLhs, const Keyframe& aRhs)
   {
     return aLhs.mComputedOffset < aRhs.mComputedOffset;
   }
 };
 
 // ------------------------------------------------------------------
 //
-// Inlined helper methods
-//
-// ------------------------------------------------------------------
-
-inline bool
-IsInvalidValuePair(const PropertyValuePair& aPair, StyleBackendType aBackend)
-{
-  if (aBackend == StyleBackendType::Servo) {
-    return !aPair.mServoDeclarationBlock;
-  }
-
-  // There are three types of values we store as token streams:
-  //
-  // * Shorthand values (where we manually extract the token stream's string
-  //   value) and pass that along to various parsing methods
-  // * Longhand values with variable references
-  // * Invalid values
-  //
-  // We can distinguish between the last two cases because for invalid values
-  // we leave the token stream's mPropertyID as eCSSProperty_UNKNOWN.
-  return !nsCSSProps::IsShorthand(aPair.mProperty) &&
-         aPair.mValue.GetUnit() == eCSSUnit_TokenStream &&
-         aPair.mValue.GetTokenStreamValue()->mPropertyID
-           == eCSSProperty_UNKNOWN;
-}
-
-
-// ------------------------------------------------------------------
-//
 // Internal helper method declarations
 //
 // ------------------------------------------------------------------
 
 static void
 GetKeyframeListFromKeyframeSequence(JSContext* aCx,
                                     nsIDocument* aDocument,
                                     JS::ForOfIterator& aIterator,
@@ -373,17 +344,17 @@ AppendStringOrStringSequenceToArray(JSCo
                                     ListAllowance aAllowLists,
                                     nsTArray<nsString>& aValues);
 
 static bool
 AppendValueAsString(JSContext* aCx,
                     nsTArray<nsString>& aValues,
                     JS::Handle<JS::Value> aValue);
 
-static PropertyValuePair
+static Maybe<PropertyValuePair>
 MakePropertyValuePair(nsCSSPropertyID aProperty, const nsAString& aStringValue,
                       nsCSSParser& aParser, nsIDocument* aDocument);
 
 static bool
 HasValidOffsets(const nsTArray<Keyframe>& aKeyframes);
 
 #ifdef DEBUG
 static void
@@ -714,19 +685,24 @@ ConvertKeyframeSequence(JSContext* aCx,
                                   aDocument->GetStyleBackendType(),
                                   propertyValuePairs)) {
         return false;
       }
     }
 
     for (PropertyValuesPair& pair : propertyValuePairs) {
       MOZ_ASSERT(pair.mValues.Length() == 1);
-      keyframe->mPropertyValues.AppendElement(
+
+      Maybe<PropertyValuePair> valuePair =
         MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser,
-                              aDocument));
+                              aDocument);
+      if (!valuePair) {
+        continue;
+      }
+      keyframe->mPropertyValues.AppendElement(Move(valuePair.ref()));
 
 #ifdef DEBUG
       // When we go to convert keyframes into arrays of property values we
       // call StyleAnimation::ComputeValues. This should normally return true
       // but in order to test the case where it does not, BaseKeyframeDict
       // includes a chrome-only member that can be set to indicate that
       // ComputeValues should fail for shorthand property values on that
       // keyframe.
@@ -872,76 +848,73 @@ AppendValueAsString(JSContext* aCx,
 /**
  * Construct a PropertyValuePair parsing the given string into a suitable
  * nsCSSValue object.
  *
  * @param aProperty The CSS property.
  * @param aStringValue The property value to parse.
  * @param aParser The CSS parser object to use.
  * @param aDocument The document to use when parsing.
- * @return The constructed PropertyValuePair object.
+ * @return The constructed PropertyValuePair, or Nothing() if |aStringValue| is
+ *   an invalid property value.
  */
-static PropertyValuePair
+static Maybe<PropertyValuePair>
 MakePropertyValuePair(nsCSSPropertyID aProperty, const nsAString& aStringValue,
                       nsCSSParser& aParser, nsIDocument* aDocument)
 {
   MOZ_ASSERT(aDocument);
-  PropertyValuePair result;
-
-  result.mProperty = aProperty;
-
-#ifdef DEBUG
-  result.mSimulateComputeValuesFailure = false;
-#endif
+  Maybe<PropertyValuePair> result;
 
   if (aDocument->GetStyleBackendType() == StyleBackendType::Servo) {
     RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
       KeyframeUtils::ParseProperty(aProperty, aStringValue, aDocument);
 
     if (servoDeclarationBlock) {
-      result.mServoDeclarationBlock = servoDeclarationBlock.forget();
+      result.emplace(aProperty, Move(servoDeclarationBlock));
     }
     return result;
   }
 
   nsCSSValue value;
   if (!nsCSSProps::IsShorthand(aProperty)) {
     aParser.ParseLonghandProperty(aProperty,
                                   aStringValue,
                                   aDocument->GetDocumentURI(),
                                   aDocument->GetDocumentURI(),
                                   aDocument->NodePrincipal(),
                                   value);
+    if (value.GetUnit() == eCSSUnit_Null) {
+      // Invalid property value, so return Nothing.
+      return result;
+    }
   }
 
   if (value.GetUnit() == eCSSUnit_Null) {
-    // Either we have a shorthand, or we failed to parse a longhand.
-    // In either case, store the string value as a token stream.
+    // If we have a shorthand, store the string value as a token stream.
     nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
     tokenStream->mTokenStream = aStringValue;
 
     // We are about to convert a null value to a token stream value but
     // by leaving the mPropertyID as unknown, we will be able to
-    // distinguish between invalid values and valid token stream values
+    // distinguish between shorthand values and valid token stream values
     // (e.g. values with variable references).
     MOZ_ASSERT(tokenStream->mPropertyID == eCSSProperty_UNKNOWN,
                "The property of a token stream should be initialized"
                " to unknown");
 
     // By leaving mShorthandPropertyID as unknown, we ensure that when
     // we call nsCSSValue::AppendToString we get back the string stored
     // in mTokenStream.
     MOZ_ASSERT(tokenStream->mShorthandPropertyID == eCSSProperty_UNKNOWN,
                "The shorthand property of a token stream should be initialized"
                " to unknown");
     value.SetTokenStreamValue(tokenStream);
   }
 
-  result.mValue = value;
-
+  result.emplace(aProperty, Move(value));
   return result;
 }
 
 /**
  * Checks that the given keyframes are loosely ordered (each keyframe's
  * offset that is not null is greater than or equal to the previous
  * non-null offset) and that all values are within the range [0.0, 1.0].
  *
@@ -1029,20 +1002,16 @@ GetComputedKeyframeValues(const nsTArray
     nsCSSPropertyIDSet propertiesOnThisKeyframe;
     ComputedKeyframeValues* computedValues = result.AppendElement();
     for (const PropertyValuePair& pair :
            PropertyPriorityIterator(frame.mPropertyValues)) {
       MOZ_ASSERT(!pair.mServoDeclarationBlock,
                  "Animation values were parsed using Servo backend but target"
                  " element is not using Servo backend?");
 
-      if (IsInvalidValuePair(pair, StyleBackendType::Gecko)) {
-        continue;
-      }
-
       // Expand each value into the set of longhands and produce
       // a KeyframeValueEntry for each value.
       nsTArray<PropertyStyleAnimationValuePair> values;
 
       // For shorthands, we store the string as a token stream so we need to
       // extract that first.
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
@@ -1438,18 +1407,23 @@ GetKeyframeListFromPropertyIndexedKeyfra
       // keyframe with offset 1.
       double offset = n ? i++ / double(n) : 1;
       Keyframe* keyframe = processedKeyframes.LookupOrAdd(offset);
       if (keyframe->mPropertyValues.IsEmpty()) {
         keyframe->mTimingFunction = easing;
         keyframe->mComposite = composite;
         keyframe->mComputedOffset = offset;
       }
-      keyframe->mPropertyValues.AppendElement(
-        MakePropertyValuePair(pair.mProperty, stringValue, parser, aDocument));
+
+      Maybe<PropertyValuePair> valuePair =
+        MakePropertyValuePair(pair.mProperty, stringValue, parser, aDocument);
+      if (!valuePair) {
+        continue;
+      }
+      keyframe->mPropertyValues.AppendElement(Move(valuePair.ref()));
     }
   }
 
   aResult.SetCapacity(processedKeyframes.Count());
   for (auto iter = processedKeyframes.Iter(); !iter.Done(); iter.Next()) {
     aResult.AppendElement(Move(*iter.UserData()));
   }
 
@@ -1509,33 +1483,27 @@ RequiresAdditiveAnimation(const nsTArray
     double computedOffset = i == len - 1
                             ? 1.0
                             : i == 0 ? 0.0 : 0.5;
     double offsetToUse = frame.mOffset
                          ? frame.mOffset.value()
                          : computedOffset;
 
     for (const PropertyValuePair& pair : frame.mPropertyValues) {
-      if (IsInvalidValuePair(pair, styleBackend)) {
-        continue;
-      }
-
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         if (styleBackend == StyleBackendType::Gecko) {
           nsCSSValueTokenStream* tokenStream =
             pair.mValue.GetTokenStreamValue();
           nsCSSParser parser(aDocument->CSSLoader());
           if (!parser.IsValueValidForProperty(pair.mProperty,
                                               tokenStream->mTokenStream)) {
             continue;
           }
         }
-        // For the Servo backend, invalid shorthand values are represented by
-        // a null mServoDeclarationBlock member which we skip above in
-        // IsInvalidValuePair.
+
         MOZ_ASSERT(styleBackend != StyleBackendType::Servo ||
                    pair.mServoDeclarationBlock);
         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
             prop, pair.mProperty, CSSEnabledState::eForAllContent) {
           addToPropertySets(*prop, offsetToUse);
         }
       } else {
         addToPropertySets(pair.mProperty, offsetToUse);
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -851,33 +851,32 @@ GeckoCSSAnimationBuilder::GetKeyframePro
   for (nsCSSPropertyID prop = nsCSSPropertyID(0);
        prop < eCSSProperty_COUNT_no_shorthands;
        prop = nsCSSPropertyID(prop + 1)) {
     if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None ||
         !aKeyframeRule->Declaration()->HasNonImportantValueFor(prop)) {
       continue;
     }
 
-    PropertyValuePair pair;
-    pair.mProperty = prop;
-
     StyleAnimationValue computedValue;
     if (!StyleAnimationValue::ExtractComputedValue(prop, styleContext,
                                                    computedValue)) {
       continue;
     }
+
+    nsCSSValue propertyValue;
     DebugOnly<bool> uncomputeResult =
       StyleAnimationValue::UncomputeValue(prop, Move(computedValue),
-                                          pair.mValue);
+                                          propertyValue);
     MOZ_ASSERT(uncomputeResult,
                "Unable to get specified value from computed value");
-    MOZ_ASSERT(pair.mValue.GetUnit() != eCSSUnit_Null,
+    MOZ_ASSERT(propertyValue.GetUnit() != eCSSUnit_Null,
                "Not expecting to read invalid properties");
 
-    result.AppendElement(Move(pair));
+    result.AppendElement(Move(PropertyValuePair(prop, Move(propertyValue))));
     aAnimatedProperties.AddProperty(prop);
   }
 
   return result;
 }
 
 void
 GeckoCSSAnimationBuilder::FillInMissingKeyframeValues(
@@ -936,23 +935,23 @@ GeckoCSSAnimationBuilder::FillInMissingK
   for (nsCSSPropertyID prop = nsCSSPropertyID(0);
        prop < eCSSProperty_COUNT_no_shorthands;
        prop = nsCSSPropertyID(prop + 1)) {
     if (!aAnimatedProperties.HasProperty(prop)) {
       continue;
     }
 
     if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
-      PropertyValuePair propertyValue;
-      propertyValue.mProperty = prop;
+      // An uninitialized nsCSSValue represents the underlying value.
+      PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
       startKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
     }
     if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
-      PropertyValuePair propertyValue;
-      propertyValue.mProperty = prop;
+      // An uninitialized nsCSSValue represents the underlying value.
+      PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
       endKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
     }
   }
 }
 
 template<class BuilderType>
 static nsAnimationManager::OwningCSSAnimationPtrArray
 BuildAnimations(nsPresContext* aPresContext,
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -786,28 +786,31 @@ nsTransitionManager::DoUpdateTransitions
 static Keyframe&
 AppendKeyframe(double aOffset,
                nsCSSPropertyID aProperty,
                AnimationValue&& aValue,
                nsTArray<Keyframe>& aKeyframes)
 {
   Keyframe& frame = *aKeyframes.AppendElement();
   frame.mOffset.emplace(aOffset);
-  PropertyValuePair& pv = *frame.mPropertyValues.AppendElement();
-  pv.mProperty = aProperty;
 
   if (aValue.mServo) {
-    pv.mServoDeclarationBlock =
+    RefPtr<RawServoDeclarationBlock> decl =
       Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
+    frame.mPropertyValues.AppendElement(
+      Move(PropertyValuePair(aProperty, Move(decl))));
   } else {
+    nsCSSValue propertyValue;
     DebugOnly<bool> uncomputeResult =
       StyleAnimationValue::UncomputeValue(aProperty, Move(aValue.mGecko),
-                                          pv.mValue);
+                                          propertyValue);
     MOZ_ASSERT(uncomputeResult,
                "Unable to get specified value from computed value");
+    frame.mPropertyValues.AppendElement(
+      Move(PropertyValuePair(aProperty, Move(propertyValue))));
   }
   return frame;
 }
 
 static nsTArray<Keyframe>
 GetTransitionKeyframes(nsCSSPropertyID aProperty,
                        AnimationValue&& aStartValue,
                        AnimationValue&& aEndValue,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -220150,17 +220150,17 @@
    "c9dcf7c17010e5495007e000b33aeb4dc89f92b7",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
    "2ed50cdb27335345015d8b13c64ef86c67048757",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html": [
-   "6f561694e38420194fd1817ee965436348221d06",
+   "9f828bd1b3785d240e9a99df8c3826dd489a224e",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/setKeyframes.html": [
    "2982bb6f57bb52c6e4e0483e4e47b22868a6010d",
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/setTarget.html": [
    "8c75e6605a134c96e261e5383414b7e15b32d121",
@@ -220170,17 +220170,17 @@
    "8ef986f13e7fe7ffeb7403f647b4169ac0d6a138",
    "testharness"
   ],
   "web-animations/resources/easing-tests.js": [
    "5ef1183a4d3e12ad3edfe678c9fa002e7edce888",
    "support"
   ],
   "web-animations/resources/keyframe-utils.js": [
-   "ff5700466b5af6ffaad824437d6566003a22e25b",
+   "2903e56e508f7034e1918b5688c8a605db9bae51",
    "support"
   ],
   "web-animations/testcommon.js": [
    "d057ad66c4561ef32f83770e4948f2019da89d48",
    "support"
   ],
   "web-animations/timing-model/animation-effects/active-time.html": [
    "42eb1a23e89ae60ccd0a3664a9a583df1eb30d49",
--- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html
@@ -253,19 +253,20 @@ test(() => {
 }, 'Custom iterator with non object keyframe should throw.');
 
 test(() => {
   var effect = new KeyframeEffect(null, createIterable([
     {done: false, value: {left: ['100px', '200px']}},
     {done: true},
   ]));
   assert_frame_lists_equal(effect.getKeyframes(), [
-    {offset: null, computedOffset: 1, easing: 'linear', left: '100px,200px'}
+    {offset: null, computedOffset: 1, easing: 'linear'}
   ]);
-}, 'Custom iterator with value list in keyframe should give bizarre string representation of list.');
+}, 'Custom iterator with value list in keyframe should not contain invalid ' +
+   'property value pair of list.');
 
 test(function(t) {
   var keyframe = {};
   Object.defineProperty(keyframe, 'width', {value: '200px'});
   Object.defineProperty(keyframe, 'height', {
     value: '100px',
     enumerable: true});
   assert_equals(keyframe.width, '200px', 'width of keyframe is readable');
--- a/testing/web-platform/tests/web-animations/resources/keyframe-utils.js
+++ b/testing/web-platform/tests/web-animations/resources/keyframe-utils.js
@@ -123,17 +123,17 @@ var gPropertyIndexedKeyframesTests = [
   { desc:   "a property-indexed keyframes specification with an invalid value",
     input:  { left: ["10px", "20px", "30px", "40px", "50px"],
               top:  ["15px", "25px", "invalid", "45px", "55px"] },
     output: [{ offset: null, computedOffset: 0.00, easing: "linear",
                left: "10px", top: "15px" },
              { offset: null, computedOffset: 0.25, easing: "linear",
                left: "20px", top: "25px" },
              { offset: null, computedOffset: 0.50, easing: "linear",
-               left: "30px", top: "invalid" },
+               left: "30px" },
              { offset: null, computedOffset: 0.75, easing: "linear",
                left: "40px", top: "45px" },
              { offset: null, computedOffset: 1.00, easing: "linear",
                left: "50px", top: "55px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " that needs to stringify its values",
     input:  { opacity: [0, 1] },
     output: [{ offset: null, computedOffset: 0, easing: "linear",
@@ -161,27 +161,25 @@ var gPropertyIndexedKeyframesTests = [
   { desc:   "a one property one non-array value property-indexed keyframes"
             + " specification",
     input:  { left: "10px" },
     output: [{ offset: null, computedOffset: 1, easing: "linear",
                left: "10px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " where the first value is invalid",
     input:  { left: ["invalid", "10px"] },
-    output: [{ offset: null, computedOffset: 0, easing: "linear",
-               left: "invalid" },
+    output: [{ offset: null, computedOffset: 0, easing: "linear" },
              { offset: null, computedOffset: 1, easing: "linear",
                left: "10px" }] },
   { desc:   "a one property two value property-indexed keyframes specification"
             + " where the second value is invalid",
     input:  { left: ["10px", "invalid"] },
     output: [{ offset: null, computedOffset: 0, easing: "linear",
                left: "10px" },
-             { offset: null, computedOffset: 1, easing: "linear",
-               left: "invalid" }] },
+             { offset: null, computedOffset: 1, easing: "linear" }] },
 ];
 
 var gKeyframeSequenceTests = [
   { desc:   "a one property one keyframe sequence",
     input:  [{ offset: 1, left: "10px" }],
     output: [{ offset: 1, computedOffset: 1, easing: "linear",
                left: "10px" }] },
   { desc:   "a one property two keyframe sequence",