LCOV - code coverage report
Current view: top level - dom/animation - KeyframeUtils.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 137 532 25.8 %
Date: 2017-07-14 16:53:18 Functions: 21 54 38.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
       4             :  * You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "mozilla/KeyframeUtils.h"
       7             : 
       8             : #include "mozilla/AnimationUtils.h"
       9             : #include "mozilla/ErrorResult.h"
      10             : #include "mozilla/Move.h"
      11             : #include "mozilla/RangedArray.h"
      12             : #include "mozilla/ServoBindings.h"
      13             : #include "mozilla/ServoBindingTypes.h"
      14             : #include "mozilla/StyleAnimationValue.h"
      15             : #include "mozilla/TimingParams.h"
      16             : #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
      17             : #include "mozilla/dom/Element.h"
      18             : #include "mozilla/dom/KeyframeEffectBinding.h"
      19             : #include "mozilla/dom/KeyframeEffectReadOnly.h" // For PropertyValuesPair etc.
      20             : #include "jsapi.h" // For ForOfIterator etc.
      21             : #include "nsClassHashtable.h"
      22             : #include "nsContentUtils.h" // For GetContextForContent
      23             : #include "nsCSSParser.h"
      24             : #include "nsCSSPropertyIDSet.h"
      25             : #include "nsCSSProps.h"
      26             : #include "nsCSSPseudoElements.h" // For CSSPseudoElementType
      27             : #include "nsIScriptError.h"
      28             : #include "nsStyleContext.h"
      29             : #include "nsTArray.h"
      30             : #include <algorithm> // For std::stable_sort
      31             : 
      32             : namespace mozilla {
      33             : 
      34             : // ------------------------------------------------------------------
      35             : //
      36             : // Internal data types
      37             : //
      38             : // ------------------------------------------------------------------
      39             : 
      40             : // For the aAllowList parameter of AppendStringOrStringSequence and
      41             : // GetPropertyValuesPairs.
      42             : enum class ListAllowance { eDisallow, eAllow };
      43             : 
      44             : /**
      45             :  * A comparator to sort nsCSSPropertyID values such that longhands are sorted
      46             :  * before shorthands, and shorthands with fewer components are sorted before
      47             :  * shorthands with more components.
      48             :  *
      49             :  * Using this allows us to prioritize values specified by longhands (or smaller
      50             :  * shorthand subsets) when longhands and shorthands are both specified
      51             :  * on the one keyframe.
      52             :  *
      53             :  * Example orderings that result from this:
      54             :  *
      55             :  *   margin-left, margin
      56             :  *
      57             :  * and:
      58             :  *
      59             :  *   border-top-color, border-color, border-top, border
      60             :  */
      61             : class PropertyPriorityComparator
      62             : {
      63             : public:
      64          16 :   PropertyPriorityComparator()
      65          16 :     : mSubpropertyCountInitialized(false) {}
      66             : 
      67           0 :   bool Equals(nsCSSPropertyID aLhs, nsCSSPropertyID aRhs) const
      68             :   {
      69           0 :     return aLhs == aRhs;
      70             :   }
      71             : 
      72           0 :   bool LessThan(nsCSSPropertyID aLhs,
      73             :                 nsCSSPropertyID aRhs) const
      74             :   {
      75           0 :     bool isShorthandLhs = nsCSSProps::IsShorthand(aLhs);
      76           0 :     bool isShorthandRhs = nsCSSProps::IsShorthand(aRhs);
      77             : 
      78           0 :     if (isShorthandLhs) {
      79           0 :       if (isShorthandRhs) {
      80             :         // First, sort shorthands by the number of longhands they have.
      81           0 :         uint32_t subpropCountLhs = SubpropertyCount(aLhs);
      82           0 :         uint32_t subpropCountRhs = SubpropertyCount(aRhs);
      83           0 :         if (subpropCountLhs != subpropCountRhs) {
      84           0 :           return subpropCountLhs < subpropCountRhs;
      85             :         }
      86             :         // Otherwise, sort by IDL name below.
      87             :       } else {
      88             :         // Put longhands before shorthands.
      89           0 :         return false;
      90             :       }
      91             :     } else {
      92           0 :       if (isShorthandRhs) {
      93             :         // Put longhands before shorthands.
      94           0 :         return true;
      95             :       }
      96             :     }
      97             :     // For two longhand properties, or two shorthand with the same number
      98             :     // of longhand components, sort by IDL name.
      99           0 :     return nsCSSProps::PropertyIDLNameSortPosition(aLhs) <
     100           0 :            nsCSSProps::PropertyIDLNameSortPosition(aRhs);
     101             :   }
     102             : 
     103           0 :   uint32_t SubpropertyCount(nsCSSPropertyID aProperty) const
     104             :   {
     105           0 :     if (!mSubpropertyCountInitialized) {
     106           0 :       PodZero(&mSubpropertyCount);
     107           0 :       mSubpropertyCountInitialized = true;
     108             :     }
     109           0 :     if (mSubpropertyCount[aProperty] == 0) {
     110           0 :       uint32_t count = 0;
     111           0 :       CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
     112             :           p, aProperty, CSSEnabledState::eForAllContent) {
     113           0 :         ++count;
     114             :       }
     115           0 :       mSubpropertyCount[aProperty] = count;
     116             :     }
     117           0 :     return mSubpropertyCount[aProperty];
     118             :   }
     119             : 
     120             : private:
     121             :   // Cache of shorthand subproperty counts.
     122             :   mutable RangedArray<
     123             :     uint32_t,
     124             :     eCSSProperty_COUNT_no_shorthands,
     125             :     eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands> mSubpropertyCount;
     126             :   mutable bool mSubpropertyCountInitialized;
     127             : };
     128             : 
     129             : /**
     130             :  * Adaptor for PropertyPriorityComparator to sort objects which have
     131             :  * a mProperty member.
     132             :  */
     133             : template <typename T>
     134          16 : class TPropertyPriorityComparator : PropertyPriorityComparator
     135             : {
     136             : public:
     137           0 :   bool Equals(const T& aLhs, const T& aRhs) const
     138             :   {
     139           0 :     return PropertyPriorityComparator::Equals(aLhs.mProperty, aRhs.mProperty);
     140             :   }
     141           0 :   bool LessThan(const T& aLhs, const T& aRhs) const
     142             :   {
     143           0 :     return PropertyPriorityComparator::LessThan(aLhs.mProperty, aRhs.mProperty);
     144             :   }
     145             : };
     146             : 
     147             : /**
     148             :  * Iterator to walk through a PropertyValuePair array using the ordering
     149             :  * provided by PropertyPriorityComparator.
     150             :  */
     151          16 : class PropertyPriorityIterator
     152             : {
     153             : public:
     154          16 :   explicit PropertyPriorityIterator(
     155             :     const nsTArray<PropertyValuePair>& aProperties)
     156          16 :     : mProperties(aProperties)
     157             :   {
     158          16 :     mSortedPropertyIndices.SetCapacity(mProperties.Length());
     159          32 :     for (size_t i = 0, len = mProperties.Length(); i < len; ++i) {
     160          16 :       PropertyAndIndex propertyIndex = { mProperties[i].mProperty, i };
     161          16 :       mSortedPropertyIndices.AppendElement(propertyIndex);
     162             :     }
     163          16 :     mSortedPropertyIndices.Sort(PropertyAndIndex::Comparator());
     164          16 :   }
     165             : 
     166             :   class Iter
     167             :   {
     168             :   public:
     169          32 :     explicit Iter(const PropertyPriorityIterator& aParent)
     170          32 :       : mParent(aParent)
     171          32 :       , mIndex(0) { }
     172             : 
     173          16 :     static Iter EndIter(const PropertyPriorityIterator &aParent)
     174             :     {
     175          16 :       Iter iter(aParent);
     176          16 :       iter.mIndex = aParent.mSortedPropertyIndices.Length();
     177          16 :       return iter;
     178             :     }
     179             : 
     180          32 :     bool operator!=(const Iter& aOther) const
     181             :     {
     182          32 :       return mIndex != aOther.mIndex;
     183             :     }
     184             : 
     185          16 :     Iter& operator++()
     186             :     {
     187          16 :       MOZ_ASSERT(mIndex + 1 <= mParent.mSortedPropertyIndices.Length(),
     188             :                  "Should not seek past end iterator");
     189          16 :       mIndex++;
     190          16 :       return *this;
     191             :     }
     192             : 
     193          16 :     const PropertyValuePair& operator*()
     194             :     {
     195          16 :       MOZ_ASSERT(mIndex < mParent.mSortedPropertyIndices.Length(),
     196             :                  "Should not try to dereference an end iterator");
     197          16 :       return mParent.mProperties[mParent.mSortedPropertyIndices[mIndex].mIndex];
     198             :     }
     199             : 
     200             :   private:
     201             :     const PropertyPriorityIterator& mParent;
     202             :     size_t mIndex;
     203             :   };
     204             : 
     205          16 :   Iter begin() { return Iter(*this); }
     206          16 :   Iter end()   { return Iter::EndIter(*this); }
     207             : 
     208             : private:
     209             :   struct PropertyAndIndex
     210             :   {
     211             :     nsCSSPropertyID mProperty;
     212             :     size_t mIndex; // Index of mProperty within mProperties
     213             : 
     214             :     typedef TPropertyPriorityComparator<PropertyAndIndex> Comparator;
     215             :   };
     216             : 
     217             :   const nsTArray<PropertyValuePair>& mProperties;
     218             :   nsTArray<PropertyAndIndex> mSortedPropertyIndices;
     219             : };
     220             : 
     221             : /**
     222             :  * A property-values pair obtained from the open-ended properties
     223             :  * discovered on a regular keyframe or property-indexed keyframe object.
     224             :  *
     225             :  * Single values (as required by a regular keyframe, and as also supported
     226             :  * on property-indexed keyframes) are stored as the only element in
     227             :  * mValues.
     228             :  */
     229           0 : struct PropertyValuesPair
     230             : {
     231             :   nsCSSPropertyID mProperty;
     232             :   nsTArray<nsString> mValues;
     233             : 
     234             :   typedef TPropertyPriorityComparator<PropertyValuesPair> Comparator;
     235             : };
     236             : 
     237             : /**
     238             :  * An additional property (for a property-values pair) found on a
     239             :  * BaseKeyframe or BasePropertyIndexedKeyframe object.
     240             :  */
     241             : struct AdditionalProperty
     242             : {
     243             :   nsCSSPropertyID mProperty;
     244             :   size_t mJsidIndex;        // Index into |ids| in GetPropertyValuesPairs.
     245             : 
     246             :   struct PropertyComparator
     247             :   {
     248           0 :     bool Equals(const AdditionalProperty& aLhs,
     249             :                 const AdditionalProperty& aRhs) const
     250             :     {
     251           0 :       return aLhs.mProperty == aRhs.mProperty;
     252             :     }
     253           0 :     bool LessThan(const AdditionalProperty& aLhs,
     254             :                   const AdditionalProperty& aRhs) const
     255             :     {
     256           0 :       return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
     257           0 :              nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
     258             :     }
     259             :   };
     260             : };
     261             : 
     262             : /**
     263             :  * Data for a segment in a keyframe animation of a given property
     264             :  * whose value is a StyleAnimationValue.
     265             :  *
     266             :  * KeyframeValueEntry is used in GetAnimationPropertiesFromKeyframes
     267             :  * to gather data for each individual segment.
     268             :  */
     269          88 : struct KeyframeValueEntry
     270             : {
     271             :   nsCSSPropertyID mProperty;
     272             :   AnimationValue mValue;
     273             : 
     274             :   float mOffset;
     275             :   Maybe<ComputedTimingFunction> mTimingFunction;
     276             :   dom::CompositeOperation mComposite;
     277             : 
     278             :   struct PropertyOffsetComparator
     279             :   {
     280             :     static bool Equals(const KeyframeValueEntry& aLhs,
     281             :                        const KeyframeValueEntry& aRhs)
     282             :     {
     283             :       return aLhs.mProperty == aRhs.mProperty &&
     284             :              aLhs.mOffset == aRhs.mOffset;
     285             :     }
     286           8 :     static bool LessThan(const KeyframeValueEntry& aLhs,
     287             :                          const KeyframeValueEntry& aRhs)
     288             :     {
     289             :       // First, sort by property IDL name.
     290           8 :       int32_t order = nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) -
     291           8 :                       nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
     292           8 :       if (order != 0) {
     293           0 :         return order < 0;
     294             :       }
     295             : 
     296             :       // Then, by offset.
     297           8 :       return aLhs.mOffset < aRhs.mOffset;
     298             :     }
     299             :   };
     300             : };
     301             : 
     302             : class ComputedOffsetComparator
     303             : {
     304             : public:
     305           0 :   static bool Equals(const Keyframe& aLhs, const Keyframe& aRhs)
     306             :   {
     307           0 :     return aLhs.mComputedOffset == aRhs.mComputedOffset;
     308             :   }
     309             : 
     310           0 :   static bool LessThan(const Keyframe& aLhs, const Keyframe& aRhs)
     311             :   {
     312           0 :     return aLhs.mComputedOffset < aRhs.mComputedOffset;
     313             :   }
     314             : };
     315             : 
     316             : // ------------------------------------------------------------------
     317             : //
     318             : // Internal helper method declarations
     319             : //
     320             : // ------------------------------------------------------------------
     321             : 
     322             : static void
     323             : GetKeyframeListFromKeyframeSequence(JSContext* aCx,
     324             :                                     nsIDocument* aDocument,
     325             :                                     JS::ForOfIterator& aIterator,
     326             :                                     nsTArray<Keyframe>& aResult,
     327             :                                     ErrorResult& aRv);
     328             : 
     329             : static bool
     330             : ConvertKeyframeSequence(JSContext* aCx,
     331             :                         nsIDocument* aDocument,
     332             :                         JS::ForOfIterator& aIterator,
     333             :                         nsTArray<Keyframe>& aResult);
     334             : 
     335             : static bool
     336             : GetPropertyValuesPairs(JSContext* aCx,
     337             :                        JS::Handle<JSObject*> aObject,
     338             :                        ListAllowance aAllowLists,
     339             :                        StyleBackendType aBackend,
     340             :                        nsTArray<PropertyValuesPair>& aResult);
     341             : 
     342             : static bool
     343             : AppendStringOrStringSequenceToArray(JSContext* aCx,
     344             :                                     JS::Handle<JS::Value> aValue,
     345             :                                     ListAllowance aAllowLists,
     346             :                                     nsTArray<nsString>& aValues);
     347             : 
     348             : static bool
     349             : AppendValueAsString(JSContext* aCx,
     350             :                     nsTArray<nsString>& aValues,
     351             :                     JS::Handle<JS::Value> aValue);
     352             : 
     353             : static Maybe<PropertyValuePair>
     354             : MakePropertyValuePair(nsCSSPropertyID aProperty, const nsAString& aStringValue,
     355             :                       nsCSSParser& aParser, nsIDocument* aDocument);
     356             : 
     357             : static bool
     358             : HasValidOffsets(const nsTArray<Keyframe>& aKeyframes);
     359             : 
     360             : #ifdef DEBUG
     361             : static void
     362             : MarkAsComputeValuesFailureKey(PropertyValuePair& aPair);
     363             : 
     364             : static bool
     365             : IsComputeValuesFailureKey(const PropertyValuePair& aPair);
     366             : #endif
     367             : 
     368             : static nsTArray<ComputedKeyframeValues>
     369             : GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
     370             :                           dom::Element* aElement,
     371             :                           nsStyleContext* aStyleContext);
     372             : 
     373             : static nsTArray<ComputedKeyframeValues>
     374             : GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
     375             :                           dom::Element* aElement,
     376             :                           const ServoComputedValues* aComputedValues);
     377             : 
     378             : static void
     379             : BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
     380             :                               nsTArray<AnimationProperty>& aResult);
     381             : 
     382             : static void
     383             : GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
     384             :                                            nsIDocument* aDocument,
     385             :                                            JS::Handle<JS::Value> aValue,
     386             :                                            nsTArray<Keyframe>& aResult,
     387             :                                            ErrorResult& aRv);
     388             : 
     389             : static bool
     390             : RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
     391             :                           nsIDocument* aDocument);
     392             : 
     393             : static void
     394             : DistributeRange(const Range<Keyframe>& aRange);
     395             : 
     396             : // ------------------------------------------------------------------
     397             : //
     398             : // Public API
     399             : //
     400             : // ------------------------------------------------------------------
     401             : 
     402             : /* static */ nsTArray<Keyframe>
     403           0 : KeyframeUtils::GetKeyframesFromObject(JSContext* aCx,
     404             :                                       nsIDocument* aDocument,
     405             :                                       JS::Handle<JSObject*> aFrames,
     406             :                                       ErrorResult& aRv)
     407             : {
     408           0 :   MOZ_ASSERT(!aRv.Failed());
     409             : 
     410           0 :   nsTArray<Keyframe> keyframes;
     411             : 
     412           0 :   if (!aFrames) {
     413             :     // The argument was explicitly null meaning no keyframes.
     414           0 :     return keyframes;
     415             :   }
     416             : 
     417             :   // At this point we know we have an object. We try to convert it to a
     418             :   // sequence of keyframes first, and if that fails due to not being iterable,
     419             :   // we try to convert it to a property-indexed keyframe.
     420           0 :   JS::Rooted<JS::Value> objectValue(aCx, JS::ObjectValue(*aFrames));
     421           0 :   JS::ForOfIterator iter(aCx);
     422           0 :   if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
     423           0 :     aRv.Throw(NS_ERROR_FAILURE);
     424           0 :     return keyframes;
     425             :   }
     426             : 
     427           0 :   if (iter.valueIsIterable()) {
     428           0 :     GetKeyframeListFromKeyframeSequence(aCx, aDocument, iter, keyframes, aRv);
     429             :   } else {
     430           0 :     GetKeyframeListFromPropertyIndexedKeyframe(aCx, aDocument, objectValue,
     431           0 :                                                keyframes, aRv);
     432             :   }
     433             : 
     434           0 :   if (aRv.Failed()) {
     435           0 :     MOZ_ASSERT(keyframes.IsEmpty(),
     436             :                "Should not set any keyframes when there is an error");
     437           0 :     return keyframes;
     438             :   }
     439             : 
     440           0 :   if (!AnimationUtils::IsCoreAPIEnabled() &&
     441           0 :       RequiresAdditiveAnimation(keyframes, aDocument)) {
     442           0 :     keyframes.Clear();
     443           0 :     aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
     444             :   }
     445             : 
     446           0 :   return keyframes;
     447             : }
     448             : 
     449             : /* static */ void
     450           2 : KeyframeUtils::DistributeKeyframes(nsTArray<Keyframe>& aKeyframes)
     451             : {
     452           2 :   if (aKeyframes.IsEmpty()) {
     453           0 :     return;
     454             :   }
     455             : 
     456             :   // If the first keyframe has an unspecified offset, fill it in with 0%.
     457             :   // If there is only a single keyframe, then it gets 100%.
     458           2 :   if (aKeyframes.Length() > 1) {
     459           2 :     Keyframe& firstElement = aKeyframes[0];
     460           2 :     firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0);
     461             :     // We will fill in the last keyframe's offset below
     462             :   } else {
     463           0 :     Keyframe& lastElement = aKeyframes.LastElement();
     464           0 :     lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
     465             :   }
     466             : 
     467             :   // Fill in remaining missing offsets.
     468           2 :   const Keyframe* const last = &aKeyframes.LastElement();
     469           2 :   const RangedPtr<Keyframe> begin(aKeyframes.Elements(), aKeyframes.Length());
     470           2 :   RangedPtr<Keyframe> keyframeA = begin;
     471           6 :   while (keyframeA != last) {
     472             :     // Find keyframe A and keyframe B *between* which we will apply spacing.
     473           2 :     RangedPtr<Keyframe> keyframeB = keyframeA + 1;
     474           2 :     while (keyframeB->mOffset.isNothing() && keyframeB != last) {
     475           0 :       ++keyframeB;
     476             :     }
     477           2 :     keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
     478             : 
     479             :     // Fill computed offsets in (keyframe A, keyframe B).
     480           2 :     DistributeRange(Range<Keyframe>(keyframeA, keyframeB + 1));
     481           2 :     keyframeA = keyframeB;
     482             :   }
     483             : }
     484             : 
     485             : template<typename StyleType>
     486             : /* static */ nsTArray<AnimationProperty>
     487           8 : KeyframeUtils::GetAnimationPropertiesFromKeyframes(
     488             :   const nsTArray<Keyframe>& aKeyframes,
     489             :   dom::Element* aElement,
     490             :   StyleType* aStyle,
     491             :   dom::CompositeOperation aEffectComposite)
     492             : {
     493             :   const nsTArray<ComputedKeyframeValues> computedValues =
     494          16 :     GetComputedKeyframeValues(aKeyframes, aElement, aStyle);
     495           8 :   MOZ_ASSERT(aKeyframes.Length() == computedValues.Length(),
     496             :              "Array length mismatch");
     497             : 
     498          16 :   nsTArray<KeyframeValueEntry> entries(aKeyframes.Length());
     499             : 
     500           8 :   const size_t len = aKeyframes.Length();
     501          24 :   for (size_t i = 0; i < len; ++i) {
     502          16 :     const Keyframe& frame = aKeyframes[i];
     503          32 :     for (auto& value : computedValues[i]) {
     504          16 :       MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet,
     505             :                  "Invalid computed offset");
     506          16 :       KeyframeValueEntry* entry = entries.AppendElement();
     507          16 :       entry->mOffset = frame.mComputedOffset;
     508          16 :       entry->mProperty = value.mProperty;
     509          16 :       entry->mValue = value.mValue;
     510          16 :       entry->mTimingFunction = frame.mTimingFunction;
     511          16 :       entry->mComposite =
     512           0 :         frame.mComposite ? frame.mComposite.value() : aEffectComposite;
     513             :     }
     514             :   }
     515             : 
     516           8 :   nsTArray<AnimationProperty> result;
     517           8 :   BuildSegmentsFromValueEntries(entries, result);
     518          16 :   return result;
     519             : }
     520             : 
     521             : /* static */ bool
     522           0 : KeyframeUtils::IsAnimatableProperty(nsCSSPropertyID aProperty,
     523             :                                     StyleBackendType aBackend)
     524             : {
     525             :   // Regardless of the backend type, treat the 'display' property as not
     526             :   // animatable. (The Servo backend will report it as being animatable, since
     527             :   // it is in fact animatable by SMIL.)
     528           0 :   if (aProperty == eCSSProperty_display) {
     529           0 :     return false;
     530             :   }
     531             : 
     532           0 :   if (aBackend == StyleBackendType::Servo) {
     533           0 :     return Servo_Property_IsAnimatable(aProperty);
     534             :   }
     535             : 
     536           0 :   if (aProperty == eCSSProperty_UNKNOWN) {
     537           0 :     return false;
     538             :   }
     539             : 
     540           0 :   if (!nsCSSProps::IsShorthand(aProperty)) {
     541           0 :     return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
     542             :   }
     543             : 
     544           0 :   CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, aProperty,
     545             :                                        CSSEnabledState::eForAllContent) {
     546           0 :     if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) {
     547           0 :       return true;
     548             :     }
     549             :   }
     550             : 
     551           0 :   return false;
     552             : }
     553             : 
     554             : /* static */ already_AddRefed<RawServoDeclarationBlock>
     555           0 : KeyframeUtils::ParseProperty(nsCSSPropertyID aProperty,
     556             :                              const nsAString& aValue,
     557             :                              nsIDocument* aDocument)
     558             : {
     559           0 :   MOZ_ASSERT(aDocument);
     560             : 
     561           0 :   NS_ConvertUTF16toUTF8 value(aValue);
     562             :   // FIXME this is using the wrong base uri (bug 1343919)
     563           0 :   RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
     564           0 :                                                aDocument->GetDocumentURI(),
     565           0 :                                                aDocument->NodePrincipal());
     566           0 :   return Servo_ParseProperty(aProperty,
     567             :                              &value,
     568             :                              data,
     569             :                              ParsingMode::Default,
     570             :                              aDocument->GetCompatibilityMode(),
     571           0 :                              aDocument->CSSLoader()).Consume();
     572             : }
     573             : 
     574             : // ------------------------------------------------------------------
     575             : //
     576             : // Internal helpers
     577             : //
     578             : // ------------------------------------------------------------------
     579             : 
     580             : /**
     581             :  * Converts a JS object to an IDL sequence<Keyframe>.
     582             :  *
     583             :  * @param aCx The JSContext corresponding to |aIterator|.
     584             :  * @param aDocument The document to use when parsing CSS properties.
     585             :  * @param aIterator An already-initialized ForOfIterator for the JS
     586             :  *   object to iterate over as a sequence.
     587             :  * @param aResult The array into which the resulting Keyframe objects will be
     588             :  *   appended.
     589             :  * @param aRv Out param to store any errors thrown by this function.
     590             :  */
     591             : static void
     592           0 : GetKeyframeListFromKeyframeSequence(JSContext* aCx,
     593             :                                     nsIDocument* aDocument,
     594             :                                     JS::ForOfIterator& aIterator,
     595             :                                     nsTArray<Keyframe>& aResult,
     596             :                                     ErrorResult& aRv)
     597             : {
     598           0 :   MOZ_ASSERT(!aRv.Failed());
     599           0 :   MOZ_ASSERT(aResult.IsEmpty());
     600             : 
     601             :   // Convert the object in aIterator to a sequence of keyframes producing
     602             :   // an array of Keyframe objects.
     603           0 :   if (!ConvertKeyframeSequence(aCx, aDocument, aIterator, aResult)) {
     604           0 :     aResult.Clear();
     605           0 :     aRv.Throw(NS_ERROR_FAILURE);
     606           0 :     return;
     607             :   }
     608             : 
     609             :   // If the sequence<> had zero elements, we won't generate any
     610             :   // keyframes.
     611           0 :   if (aResult.IsEmpty()) {
     612           0 :     return;
     613             :   }
     614             : 
     615             :   // Check that the keyframes are loosely sorted and with values all
     616             :   // between 0% and 100%.
     617           0 :   if (!HasValidOffsets(aResult)) {
     618           0 :     aRv.ThrowTypeError<dom::MSG_INVALID_KEYFRAME_OFFSETS>();
     619           0 :     aResult.Clear();
     620           0 :     return;
     621             :   }
     622             : }
     623             : 
     624             : /**
     625             :  * Converts a JS object wrapped by the given JS::ForIfIterator to an
     626             :  * IDL sequence<Keyframe> and stores the resulting Keyframe objects in
     627             :  * aResult.
     628             :  */
     629             : static bool
     630           0 : ConvertKeyframeSequence(JSContext* aCx,
     631             :                         nsIDocument* aDocument,
     632             :                         JS::ForOfIterator& aIterator,
     633             :                         nsTArray<Keyframe>& aResult)
     634             : {
     635           0 :   JS::Rooted<JS::Value> value(aCx);
     636           0 :   nsCSSParser parser(aDocument->CSSLoader());
     637             : 
     638             :   for (;;) {
     639             :     bool done;
     640           0 :     if (!aIterator.next(&value, &done)) {
     641           0 :       return false;
     642             :     }
     643           0 :     if (done) {
     644           0 :       break;
     645             :     }
     646             :     // Each value found when iterating the object must be an object
     647             :     // or null/undefined (which gets treated as a default {} dictionary
     648             :     // value).
     649           0 :     if (!value.isObject() && !value.isNullOrUndefined()) {
     650             :       dom::ThrowErrorMessage(aCx, dom::MSG_NOT_OBJECT,
     651           0 :                              "Element of sequence<Keyframe> argument");
     652           0 :       return false;
     653             :     }
     654             : 
     655             :     // Convert the JS value into a BaseKeyframe dictionary value.
     656           0 :     dom::binding_detail::FastBaseKeyframe keyframeDict;
     657           0 :     if (!keyframeDict.Init(aCx, value,
     658             :                            "Element of sequence<Keyframe> argument")) {
     659           0 :       return false;
     660             :     }
     661             : 
     662           0 :     Keyframe* keyframe = aResult.AppendElement(fallible);
     663           0 :     if (!keyframe) {
     664           0 :       return false;
     665             :     }
     666           0 :     if (!keyframeDict.mOffset.IsNull()) {
     667           0 :       keyframe->mOffset.emplace(keyframeDict.mOffset.Value());
     668             :     }
     669             : 
     670           0 :     if (keyframeDict.mComposite.WasPassed()) {
     671           0 :       keyframe->mComposite.emplace(keyframeDict.mComposite.Value());
     672             :     }
     673             : 
     674           0 :     ErrorResult rv;
     675             :     keyframe->mTimingFunction =
     676           0 :       TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, rv);
     677           0 :     if (rv.MaybeSetPendingException(aCx)) {
     678           0 :       return false;
     679             :     }
     680             : 
     681             :     // Look for additional property-values pairs on the object.
     682           0 :     nsTArray<PropertyValuesPair> propertyValuePairs;
     683           0 :     if (value.isObject()) {
     684           0 :       JS::Rooted<JSObject*> object(aCx, &value.toObject());
     685           0 :       if (!GetPropertyValuesPairs(aCx, object,
     686             :                                   ListAllowance::eDisallow,
     687             :                                   aDocument->GetStyleBackendType(),
     688             :                                   propertyValuePairs)) {
     689           0 :         return false;
     690             :       }
     691             :     }
     692             : 
     693           0 :     for (PropertyValuesPair& pair : propertyValuePairs) {
     694           0 :       MOZ_ASSERT(pair.mValues.Length() == 1);
     695             : 
     696             :       Maybe<PropertyValuePair> valuePair =
     697           0 :         MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser,
     698           0 :                               aDocument);
     699           0 :       if (!valuePair) {
     700           0 :         continue;
     701             :       }
     702           0 :       keyframe->mPropertyValues.AppendElement(Move(valuePair.ref()));
     703             : 
     704             : #ifdef DEBUG
     705             :       // When we go to convert keyframes into arrays of property values we
     706             :       // call StyleAnimation::ComputeValues. This should normally return true
     707             :       // but in order to test the case where it does not, BaseKeyframeDict
     708             :       // includes a chrome-only member that can be set to indicate that
     709             :       // ComputeValues should fail for shorthand property values on that
     710             :       // keyframe.
     711           0 :       if (nsCSSProps::IsShorthand(pair.mProperty) &&
     712           0 :           keyframeDict.mSimulateComputeValuesFailure) {
     713           0 :         MarkAsComputeValuesFailureKey(keyframe->mPropertyValues.LastElement());
     714             :       }
     715             : #endif
     716             :     }
     717           0 :   }
     718             : 
     719           0 :   return true;
     720             : }
     721             : 
     722             : /**
     723             :  * Reads the property-values pairs from the specified JS object.
     724             :  *
     725             :  * @param aObject The JS object to look at.
     726             :  * @param aAllowLists If eAllow, values will be converted to
     727             :  *   (DOMString or sequence<DOMString); if eDisallow, values
     728             :  *   will be converted to DOMString.
     729             :  * @param aBackend The style backend in use. Used to determine which properties
     730             :  *   are animatable since only animatable properties are read.
     731             :  * @param aResult The array into which the enumerated property-values
     732             :  *   pairs will be stored.
     733             :  * @return false on failure or JS exception thrown while interacting
     734             :  *   with aObject; true otherwise.
     735             :  */
     736             : static bool
     737           0 : GetPropertyValuesPairs(JSContext* aCx,
     738             :                        JS::Handle<JSObject*> aObject,
     739             :                        ListAllowance aAllowLists,
     740             :                        StyleBackendType aBackend,
     741             :                        nsTArray<PropertyValuesPair>& aResult)
     742             : {
     743           0 :   nsTArray<AdditionalProperty> properties;
     744             : 
     745             :   // Iterate over all the properties on aObject and append an
     746             :   // entry to properties for them.
     747             :   //
     748             :   // We don't compare the jsids that we encounter with those for
     749             :   // the explicit dictionary members, since we know that none
     750             :   // of the CSS property IDL names clash with them.
     751           0 :   JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
     752           0 :   if (!JS_Enumerate(aCx, aObject, &ids)) {
     753           0 :     return false;
     754             :   }
     755           0 :   for (size_t i = 0, n = ids.length(); i < n; i++) {
     756           0 :     nsAutoJSString propName;
     757           0 :     if (!propName.init(aCx, ids[i])) {
     758           0 :       return false;
     759             :     }
     760             :     nsCSSPropertyID property =
     761             :       nsCSSProps::LookupPropertyByIDLName(propName,
     762           0 :                                           CSSEnabledState::eForAllContent);
     763           0 :     if (KeyframeUtils::IsAnimatableProperty(property, aBackend)) {
     764           0 :       AdditionalProperty* p = properties.AppendElement();
     765           0 :       p->mProperty = property;
     766           0 :       p->mJsidIndex = i;
     767             :     }
     768             :   }
     769             : 
     770             :   // Sort the entries by IDL name and then get each value and
     771             :   // convert it either to a DOMString or to a
     772             :   // (DOMString or sequence<DOMString>), depending on aAllowLists,
     773             :   // and build up aResult.
     774           0 :   properties.Sort(AdditionalProperty::PropertyComparator());
     775             : 
     776           0 :   for (AdditionalProperty& p : properties) {
     777           0 :     JS::Rooted<JS::Value> value(aCx);
     778           0 :     if (!JS_GetPropertyById(aCx, aObject, ids[p.mJsidIndex], &value)) {
     779           0 :       return false;
     780             :     }
     781           0 :     PropertyValuesPair* pair = aResult.AppendElement();
     782           0 :     pair->mProperty = p.mProperty;
     783           0 :     if (!AppendStringOrStringSequenceToArray(aCx, value, aAllowLists,
     784             :                                              pair->mValues)) {
     785           0 :       return false;
     786             :     }
     787             :   }
     788             : 
     789           0 :   return true;
     790             : }
     791             : 
     792             : /**
     793             :  * Converts aValue to DOMString, if aAllowLists is eDisallow, or
     794             :  * to (DOMString or sequence<DOMString>) if aAllowLists is aAllow.
     795             :  * The resulting strings are appended to aValues.
     796             :  */
     797             : static bool
     798           0 : AppendStringOrStringSequenceToArray(JSContext* aCx,
     799             :                                     JS::Handle<JS::Value> aValue,
     800             :                                     ListAllowance aAllowLists,
     801             :                                     nsTArray<nsString>& aValues)
     802             : {
     803           0 :   if (aAllowLists == ListAllowance::eAllow && aValue.isObject()) {
     804             :     // The value is an object, and we want to allow lists; convert
     805             :     // aValue to (DOMString or sequence<DOMString>).
     806           0 :     JS::ForOfIterator iter(aCx);
     807           0 :     if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
     808           0 :       return false;
     809             :     }
     810           0 :     if (iter.valueIsIterable()) {
     811             :       // If the object is iterable, convert it to sequence<DOMString>.
     812           0 :       JS::Rooted<JS::Value> element(aCx);
     813             :       for (;;) {
     814             :         bool done;
     815           0 :         if (!iter.next(&element, &done)) {
     816           0 :           return false;
     817             :         }
     818           0 :         if (done) {
     819           0 :           break;
     820             :         }
     821           0 :         if (!AppendValueAsString(aCx, aValues, element)) {
     822           0 :           return false;
     823             :         }
     824           0 :       }
     825           0 :       return true;
     826             :     }
     827             :   }
     828             : 
     829             :   // Either the object is not iterable, or aAllowLists doesn't want
     830             :   // a list; convert it to DOMString.
     831           0 :   if (!AppendValueAsString(aCx, aValues, aValue)) {
     832           0 :     return false;
     833             :   }
     834             : 
     835           0 :   return true;
     836             : }
     837             : 
     838             : /**
     839             :  * Converts aValue to DOMString and appends it to aValues.
     840             :  */
     841             : static bool
     842           0 : AppendValueAsString(JSContext* aCx,
     843             :                     nsTArray<nsString>& aValues,
     844             :                     JS::Handle<JS::Value> aValue)
     845             : {
     846             :   return ConvertJSValueToString(aCx, aValue, dom::eStringify, dom::eStringify,
     847           0 :                                 *aValues.AppendElement());
     848             : }
     849             : 
     850             : static void
     851           0 : ReportInvalidPropertyValueToConsole(nsCSSPropertyID aProperty,
     852             :                                     const nsAString& aInvalidPropertyValue,
     853             :                                     nsIDocument* aDoc)
     854             : {
     855           0 :   const nsString& invalidValue = PromiseFlatString(aInvalidPropertyValue);
     856             :   const NS_ConvertASCIItoUTF16 propertyName(
     857           0 :     nsCSSProps::GetStringValue(aProperty));
     858           0 :   const char16_t* params[] = { invalidValue.get(), propertyName.get() };
     859           0 :   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
     860           0 :                                   NS_LITERAL_CSTRING("Animation"),
     861             :                                   aDoc,
     862             :                                   nsContentUtils::eDOM_PROPERTIES,
     863             :                                   "InvalidKeyframePropertyValue",
     864           0 :                                   params, ArrayLength(params));
     865           0 : }
     866             : 
     867             : /**
     868             :  * Construct a PropertyValuePair parsing the given string into a suitable
     869             :  * nsCSSValue object.
     870             :  *
     871             :  * @param aProperty The CSS property.
     872             :  * @param aStringValue The property value to parse.
     873             :  * @param aParser The CSS parser object to use.
     874             :  * @param aDocument The document to use when parsing.
     875             :  * @return The constructed PropertyValuePair, or Nothing() if |aStringValue| is
     876             :  *   an invalid property value.
     877             :  */
     878             : static Maybe<PropertyValuePair>
     879           0 : MakePropertyValuePair(nsCSSPropertyID aProperty, const nsAString& aStringValue,
     880             :                       nsCSSParser& aParser, nsIDocument* aDocument)
     881             : {
     882           0 :   MOZ_ASSERT(aDocument);
     883           0 :   Maybe<PropertyValuePair> result;
     884             : 
     885           0 :   if (aDocument->GetStyleBackendType() == StyleBackendType::Servo) {
     886             :     RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
     887           0 :       KeyframeUtils::ParseProperty(aProperty, aStringValue, aDocument);
     888             : 
     889           0 :     if (servoDeclarationBlock) {
     890           0 :       result.emplace(aProperty, Move(servoDeclarationBlock));
     891             :     } else {
     892           0 :       ReportInvalidPropertyValueToConsole(aProperty, aStringValue, aDocument);
     893             :     }
     894           0 :     return result;
     895             :   }
     896             : 
     897           0 :   nsCSSValue value;
     898           0 :   if (!nsCSSProps::IsShorthand(aProperty)) {
     899           0 :     aParser.ParseLonghandProperty(aProperty,
     900             :                                   aStringValue,
     901             :                                   aDocument->GetDocumentURI(),
     902             :                                   aDocument->GetDocumentURI(),
     903             :                                   aDocument->NodePrincipal(),
     904           0 :                                   value);
     905           0 :     if (value.GetUnit() == eCSSUnit_Null) {
     906             :       // Invalid property value, so return Nothing.
     907           0 :       ReportInvalidPropertyValueToConsole(aProperty, aStringValue, aDocument);
     908           0 :       return result;
     909             :     }
     910             :   }
     911             : 
     912           0 :   if (value.GetUnit() == eCSSUnit_Null) {
     913             :     // If we have a shorthand, store the string value as a token stream.
     914           0 :     nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
     915           0 :     tokenStream->mTokenStream = aStringValue;
     916             : 
     917             :     // We are about to convert a null value to a token stream value but
     918             :     // by leaving the mPropertyID as unknown, we will be able to
     919             :     // distinguish between shorthand values and valid token stream values
     920             :     // (e.g. values with variable references).
     921           0 :     MOZ_ASSERT(tokenStream->mPropertyID == eCSSProperty_UNKNOWN,
     922             :                "The property of a token stream should be initialized"
     923             :                " to unknown");
     924             : 
     925             :     // By leaving mShorthandPropertyID as unknown, we ensure that when
     926             :     // we call nsCSSValue::AppendToString we get back the string stored
     927             :     // in mTokenStream.
     928           0 :     MOZ_ASSERT(tokenStream->mShorthandPropertyID == eCSSProperty_UNKNOWN,
     929             :                "The shorthand property of a token stream should be initialized"
     930             :                " to unknown");
     931           0 :     value.SetTokenStreamValue(tokenStream);
     932             :   }
     933             : 
     934           0 :   result.emplace(aProperty, Move(value));
     935           0 :   return result;
     936             : }
     937             : 
     938             : /**
     939             :  * Checks that the given keyframes are loosely ordered (each keyframe's
     940             :  * offset that is not null is greater than or equal to the previous
     941             :  * non-null offset) and that all values are within the range [0.0, 1.0].
     942             :  *
     943             :  * @return true if the keyframes' offsets are correctly ordered and
     944             :  *   within range; false otherwise.
     945             :  */
     946             : static bool
     947           0 : HasValidOffsets(const nsTArray<Keyframe>& aKeyframes)
     948             : {
     949           0 :   double offset = 0.0;
     950           0 :   for (const Keyframe& keyframe : aKeyframes) {
     951           0 :     if (keyframe.mOffset) {
     952           0 :       double thisOffset = keyframe.mOffset.value();
     953           0 :       if (thisOffset < offset || thisOffset > 1.0f) {
     954           0 :         return false;
     955             :       }
     956           0 :       offset = thisOffset;
     957             :     }
     958             :   }
     959           0 :   return true;
     960             : }
     961             : 
     962             : #ifdef DEBUG
     963             : /**
     964             :  * Takes a property-value pair for a shorthand property and modifies the
     965             :  * value to indicate that when we call StyleAnimationValue::ComputeValues on
     966             :  * that value we should behave as if that function had failed.
     967             :  *
     968             :  * @param aPair The PropertyValuePair to modify. |aPair.mProperty| must be
     969             :  *              a shorthand property.
     970             :  */
     971             : static void
     972           0 : MarkAsComputeValuesFailureKey(PropertyValuePair& aPair)
     973             : {
     974           0 :   MOZ_ASSERT(nsCSSProps::IsShorthand(aPair.mProperty),
     975             :              "Only shorthand property values can be marked as failure values");
     976             : 
     977           0 :   aPair.mSimulateComputeValuesFailure = true;
     978           0 : }
     979             : 
     980             : /**
     981             :  * Returns true if |aPair| is a property-value pair on which we have
     982             :  * previously called MarkAsComputeValuesFailureKey (and hence we should
     983             :  * simulate failure when calling StyleAnimationValue::ComputeValues using its
     984             :  * value).
     985             :  *
     986             :  * @param aPair The property-value pair to test.
     987             :  * @return True if |aPair| represents a failure value.
     988             :  */
     989             : static bool
     990           0 : IsComputeValuesFailureKey(const PropertyValuePair& aPair)
     991             : {
     992           0 :   return nsCSSProps::IsShorthand(aPair.mProperty) &&
     993           0 :          aPair.mSimulateComputeValuesFailure;
     994             : }
     995             : #endif
     996             : 
     997             : /**
     998             :  * Calculate the StyleAnimationValues of properties of each keyframe.
     999             :  * This involves expanding shorthand properties into longhand properties,
    1000             :  * removing the duplicated properties for each keyframe, and creating an
    1001             :  * array of |property:computed value| pairs for each keyframe.
    1002             :  *
    1003             :  * These computed values are used when computing the final set of
    1004             :  * per-property animation values (see GetAnimationPropertiesFromKeyframes).
    1005             :  *
    1006             :  * @param aKeyframes The input keyframes.
    1007             :  * @param aElement The context element.
    1008             :  * @param aStyleContext The style context to use when computing values.
    1009             :  * @return The set of ComputedKeyframeValues. The length will be the same as
    1010             :  *   aFrames.
    1011             :  */
    1012             : static nsTArray<ComputedKeyframeValues>
    1013           8 : GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
    1014             :                           dom::Element* aElement,
    1015             :                           nsStyleContext* aStyleContext)
    1016             : {
    1017           8 :   MOZ_ASSERT(aStyleContext);
    1018           8 :   MOZ_ASSERT(aElement);
    1019             : 
    1020           8 :   const size_t len = aKeyframes.Length();
    1021           8 :   nsTArray<ComputedKeyframeValues> result(len);
    1022             : 
    1023          24 :   for (const Keyframe& frame : aKeyframes) {
    1024          16 :     nsCSSPropertyIDSet propertiesOnThisKeyframe;
    1025          16 :     ComputedKeyframeValues* computedValues = result.AppendElement();
    1026          48 :     for (const PropertyValuePair& pair :
    1027          48 :            PropertyPriorityIterator(frame.mPropertyValues)) {
    1028          16 :       MOZ_ASSERT(!pair.mServoDeclarationBlock,
    1029             :                  "Animation values were parsed using Servo backend but target"
    1030             :                  " element is not using Servo backend?");
    1031             : 
    1032             :       // Expand each value into the set of longhands and produce
    1033             :       // a KeyframeValueEntry for each value.
    1034          32 :       nsTArray<PropertyStyleAnimationValuePair> values;
    1035             : 
    1036             :       // For shorthands, we store the string as a token stream so we need to
    1037             :       // extract that first.
    1038          16 :       if (nsCSSProps::IsShorthand(pair.mProperty)) {
    1039           0 :         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
    1040           0 :         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
    1041             :               CSSEnabledState::eForAllContent, aElement, aStyleContext,
    1042             :               tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
    1043           0 :           continue;
    1044             :         }
    1045             : 
    1046             : #ifdef DEBUG
    1047           0 :         if (IsComputeValuesFailureKey(pair)) {
    1048           0 :           continue;
    1049             :         }
    1050             : #endif
    1051          16 :       } else if (pair.mValue.GetUnit() == eCSSUnit_Null) {
    1052             :         // An uninitialized nsCSSValue represents the underlying value which
    1053             :         // we represent as an uninitialized AnimationValue so we just leave
    1054             :         // neutralPair->mValue as-is.
    1055           0 :         PropertyStyleAnimationValuePair* neutralPair = values.AppendElement();
    1056           0 :         neutralPair->mProperty = pair.mProperty;
    1057             :       } else {
    1058          16 :         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
    1059             :               CSSEnabledState::eForAllContent, aElement, aStyleContext,
    1060             :               pair.mValue, /* aUseSVGMode */ false, values)) {
    1061           0 :           continue;
    1062             :         }
    1063          16 :         MOZ_ASSERT(values.Length() == 1,
    1064             :                   "Longhand properties should produce a single"
    1065             :                   " StyleAnimationValue");
    1066             :       }
    1067             : 
    1068          32 :       for (auto& value : values) {
    1069             :         // If we already got a value for this property on the keyframe,
    1070             :         // skip this one.
    1071          16 :         if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
    1072           0 :           continue;
    1073             :         }
    1074          16 :         propertiesOnThisKeyframe.AddProperty(value.mProperty);
    1075          16 :         computedValues->AppendElement(Move(value));
    1076             :       }
    1077             :     }
    1078             :   }
    1079             : 
    1080           8 :   MOZ_ASSERT(result.Length() == aKeyframes.Length(), "Array length mismatch");
    1081           8 :   return result;
    1082             : }
    1083             : 
    1084             : /**
    1085             :  * The variation of the above function. This is for Servo backend.
    1086             :  */
    1087             : static nsTArray<ComputedKeyframeValues>
    1088           0 : GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
    1089             :                           dom::Element* aElement,
    1090             :                           const ServoComputedValues* aComputedValues)
    1091             : {
    1092           0 :   MOZ_ASSERT(aElement);
    1093           0 :   MOZ_ASSERT(aElement->IsStyledByServo());
    1094             : 
    1095           0 :   nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
    1096           0 :   MOZ_ASSERT(presContext);
    1097             : 
    1098           0 :   return presContext->StyleSet()->AsServo()
    1099           0 :     ->GetComputedKeyframeValuesFor(aKeyframes, aElement, aComputedValues);
    1100             : }
    1101             : 
    1102             : static void
    1103           0 : AppendInitialSegment(AnimationProperty* aAnimationProperty,
    1104             :                      const KeyframeValueEntry& aFirstEntry)
    1105             : {
    1106             :   AnimationPropertySegment* segment =
    1107           0 :     aAnimationProperty->mSegments.AppendElement();
    1108           0 :   segment->mFromKey        = 0.0f;
    1109           0 :   segment->mToKey          = aFirstEntry.mOffset;
    1110           0 :   segment->mToValue        = aFirstEntry.mValue;
    1111           0 :   segment->mToComposite    = aFirstEntry.mComposite;
    1112           0 : }
    1113             : 
    1114             : static void
    1115           0 : AppendFinalSegment(AnimationProperty* aAnimationProperty,
    1116             :                    const KeyframeValueEntry& aLastEntry)
    1117             : {
    1118             :   AnimationPropertySegment* segment =
    1119           0 :     aAnimationProperty->mSegments.AppendElement();
    1120           0 :   segment->mFromKey        = aLastEntry.mOffset;
    1121           0 :   segment->mFromValue      = aLastEntry.mValue;
    1122           0 :   segment->mFromComposite  = aLastEntry.mComposite;
    1123           0 :   segment->mToKey          = 1.0f;
    1124           0 :   segment->mTimingFunction = aLastEntry.mTimingFunction;
    1125           0 : }
    1126             : 
    1127             : // Returns a newly created AnimationProperty if one was created to fill-in the
    1128             : // missing keyframe, nullptr otherwise (if we decided not to fill the keyframe
    1129             : // becase we don't support additive animation).
    1130             : static AnimationProperty*
    1131           0 : HandleMissingInitialKeyframe(nsTArray<AnimationProperty>& aResult,
    1132             :                              const KeyframeValueEntry& aEntry)
    1133             : {
    1134           0 :   MOZ_ASSERT(aEntry.mOffset != 0.0f,
    1135             :              "The offset of the entry should not be 0.0");
    1136             : 
    1137             :   // If the preference of the core Web Animations API is not enabled, don't fill
    1138             :   // in the missing keyframe since the missing keyframe requires support for
    1139             :   // additive animation which is guarded by this pref.
    1140           0 :   if (!AnimationUtils::IsCoreAPIEnabled()){
    1141           0 :     return nullptr;
    1142             :   }
    1143             : 
    1144           0 :   AnimationProperty* result = aResult.AppendElement();
    1145           0 :   result->mProperty = aEntry.mProperty;
    1146             : 
    1147           0 :   AppendInitialSegment(result, aEntry);
    1148             : 
    1149           0 :   return result;
    1150             : }
    1151             : 
    1152             : static void
    1153           0 : HandleMissingFinalKeyframe(nsTArray<AnimationProperty>& aResult,
    1154             :                            const KeyframeValueEntry& aEntry,
    1155             :                            AnimationProperty* aCurrentAnimationProperty)
    1156             : {
    1157           0 :   MOZ_ASSERT(aEntry.mOffset != 1.0f,
    1158             :              "The offset of the entry should not be 1.0");
    1159             : 
    1160             :   // If the preference of the core Web Animations API is not enabled, don't fill
    1161             :   // in the missing keyframe since the missing keyframe requires support for
    1162             :   // additive animation which is guarded by this pref.
    1163           0 :   if (!AnimationUtils::IsCoreAPIEnabled()){
    1164             :     // If we have already appended a new entry for the property so we have to
    1165             :     // remove it.
    1166           0 :     if (aCurrentAnimationProperty) {
    1167           0 :       aResult.RemoveElementAt(aResult.Length() - 1);
    1168             :     }
    1169           0 :     return;
    1170             :   }
    1171             : 
    1172             :   // If |aCurrentAnimationProperty| is nullptr, that means this is the first
    1173             :   // entry for the property, we have to append a new AnimationProperty for this
    1174             :   // property.
    1175           0 :   if (!aCurrentAnimationProperty) {
    1176           0 :     aCurrentAnimationProperty = aResult.AppendElement();
    1177           0 :     aCurrentAnimationProperty->mProperty = aEntry.mProperty;
    1178             : 
    1179             :     // If we have only one entry whose offset is neither 1 nor 0 for this
    1180             :     // property, we need to append the initial segment as well.
    1181           0 :     if (aEntry.mOffset != 0.0f) {
    1182           0 :       AppendInitialSegment(aCurrentAnimationProperty, aEntry);
    1183             :     }
    1184             :   }
    1185           0 :   AppendFinalSegment(aCurrentAnimationProperty, aEntry);
    1186             : }
    1187             : 
    1188             : /**
    1189             :  * Builds an array of AnimationProperty objects to represent the keyframe
    1190             :  * animation segments in aEntries.
    1191             :  */
    1192             : static void
    1193           8 : BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
    1194             :                               nsTArray<AnimationProperty>& aResult)
    1195             : {
    1196           8 :   if (aEntries.IsEmpty()) {
    1197           0 :     return;
    1198             :   }
    1199             : 
    1200             :   // Sort the KeyframeValueEntry objects so that all entries for a given
    1201             :   // property are together, and the entries are sorted by offset otherwise.
    1202          16 :   std::stable_sort(aEntries.begin(), aEntries.end(),
    1203           8 :                    &KeyframeValueEntry::PropertyOffsetComparator::LessThan);
    1204             : 
    1205             :   // For a given index i, we want to generate a segment from aEntries[i]
    1206             :   // to aEntries[j], if:
    1207             :   //
    1208             :   //   * j > i,
    1209             :   //   * aEntries[i + 1]'s offset/property is different from aEntries[i]'s, and
    1210             :   //   * aEntries[j - 1]'s offset/property is different from aEntries[j]'s.
    1211             :   //
    1212             :   // That will eliminate runs of same offset/property values where there's no
    1213             :   // point generating zero length segments in the middle of the animation.
    1214             :   //
    1215             :   // Additionally we need to generate a zero length segment at offset 0 and at
    1216             :   // offset 1, if we have multiple values for a given property at that offset,
    1217             :   // since we need to retain the very first and very last value so they can
    1218             :   // be used for reverse and forward filling.
    1219             :   //
    1220             :   // Typically, for each property in |aEntries|, we expect there to be at least
    1221             :   // one KeyframeValueEntry with offset 0.0, and at least one with offset 1.0.
    1222             :   // However, since it is possible that when building |aEntries|, the call to
    1223             :   // StyleAnimationValue::ComputeValues might fail, this can't be guaranteed.
    1224             :   // Furthermore, if additive animation is disabled, the following loop takes
    1225             :   // care to identify properties that lack a value at offset 0.0/1.0 and drops
    1226             :   // those properties from |aResult|.
    1227             : 
    1228           8 :   nsCSSPropertyID lastProperty = eCSSProperty_UNKNOWN;
    1229           8 :   AnimationProperty* animationProperty = nullptr;
    1230             : 
    1231           8 :   size_t i = 0, n = aEntries.Length();
    1232             : 
    1233          24 :   while (i < n) {
    1234             :     // If we've reached the end of the array of entries, synthesize a final (and
    1235             :     // initial) segment if necessary.
    1236          16 :     if (i + 1 == n) {
    1237           8 :       if (aEntries[i].mOffset != 1.0f) {
    1238           0 :         HandleMissingFinalKeyframe(aResult, aEntries[i], animationProperty);
    1239           8 :       } else if (aEntries[i].mOffset == 1.0f && !animationProperty) {
    1240             :         // If the last entry with offset 1 and no animation property, that means
    1241             :         // it is the only entry for this property so append a single segment
    1242             :         // from 0 offset to |aEntry[i].offset|.
    1243           0 :         Unused << HandleMissingInitialKeyframe(aResult, aEntries[i]);
    1244             :       }
    1245           8 :       animationProperty = nullptr;
    1246           8 :       break;
    1247             :     }
    1248             : 
    1249           8 :     MOZ_ASSERT(aEntries[i].mProperty != eCSSProperty_UNKNOWN &&
    1250             :                aEntries[i + 1].mProperty != eCSSProperty_UNKNOWN,
    1251             :                "Each entry should specify a valid property");
    1252             : 
    1253             :     // No keyframe for this property at offset 0.
    1254          16 :     if (aEntries[i].mProperty != lastProperty &&
    1255           8 :         aEntries[i].mOffset != 0.0f) {
    1256             :       // If we don't support additive animation we can't fill in the missing
    1257             :       // keyframes and we should just skip this property altogether. Since the
    1258             :       // entries are sorted by offset for a given property, and since we don't
    1259             :       // update |lastProperty|, we will keep hitting this condition until we
    1260             :       // change property.
    1261           0 :       animationProperty = HandleMissingInitialKeyframe(aResult, aEntries[i]);
    1262           0 :       if (animationProperty) {
    1263           0 :         lastProperty = aEntries[i].mProperty;
    1264             :       } else {
    1265             :         // Skip this entry if we did not handle the missing entry.
    1266           0 :         ++i;
    1267           0 :         continue;
    1268             :       }
    1269             :     }
    1270             : 
    1271             :     // Skip this entry if the next entry has the same offset except for initial
    1272             :     // and final ones. We will handle missing keyframe in the next loop
    1273             :     // if the property is changed on the next entry.
    1274          24 :     if (aEntries[i].mProperty == aEntries[i + 1].mProperty &&
    1275           8 :         aEntries[i].mOffset == aEntries[i + 1].mOffset &&
    1276           8 :         aEntries[i].mOffset != 1.0f && aEntries[i].mOffset != 0.0f) {
    1277           0 :       ++i;
    1278           0 :       continue;
    1279             :     }
    1280             : 
    1281             :     // No keyframe for this property at offset 1.
    1282           8 :     if (aEntries[i].mProperty != aEntries[i + 1].mProperty &&
    1283           0 :         aEntries[i].mOffset != 1.0f) {
    1284           0 :       HandleMissingFinalKeyframe(aResult, aEntries[i], animationProperty);
    1285             :       // Move on to new property.
    1286           0 :       animationProperty = nullptr;
    1287           0 :       ++i;
    1288           0 :       continue;
    1289             :     }
    1290             : 
    1291             :     // Starting from i + 1, determine the next [i, j] interval from which to
    1292             :     // generate a segment. Basically, j is i + 1, but there are some special
    1293             :     // cases for offset 0 and 1, so we need to handle them specifically.
    1294             :     // Note: From this moment, we make sure [i + 1] is valid and
    1295             :     //       there must be an initial entry (i.e. mOffset = 0.0) and
    1296             :     //       a final entry (i.e. mOffset = 1.0). Besides, all the entries
    1297             :     //       with the same offsets except for initial/final ones are filtered
    1298             :     //       out already.
    1299           8 :     size_t j = i + 1;
    1300           8 :     if (aEntries[i].mOffset == 0.0f && aEntries[i + 1].mOffset == 0.0f) {
    1301             :       // We need to generate an initial zero-length segment.
    1302           0 :       MOZ_ASSERT(aEntries[i].mProperty == aEntries[i + 1].mProperty);
    1303           0 :       while (j + 1 < n &&
    1304           0 :              aEntries[j + 1].mOffset == 0.0f &&
    1305           0 :              aEntries[j + 1].mProperty == aEntries[j].mProperty) {
    1306           0 :         ++j;
    1307             :       }
    1308           8 :     } else if (aEntries[i].mOffset == 1.0f) {
    1309           0 :       if (aEntries[i + 1].mOffset == 1.0f &&
    1310           0 :           aEntries[i + 1].mProperty == aEntries[i].mProperty) {
    1311             :         // We need to generate a final zero-length segment.
    1312           0 :         while (j + 1 < n &&
    1313           0 :                aEntries[j + 1].mOffset == 1.0f &&
    1314           0 :                aEntries[j + 1].mProperty == aEntries[j].mProperty) {
    1315           0 :           ++j;
    1316             :         }
    1317             :       } else {
    1318             :         // New property.
    1319           0 :         MOZ_ASSERT(aEntries[i].mProperty != aEntries[i + 1].mProperty);
    1320           0 :         animationProperty = nullptr;
    1321           0 :         ++i;
    1322           0 :         continue;
    1323             :       }
    1324             :     }
    1325             : 
    1326             :     // If we've moved on to a new property, create a new AnimationProperty
    1327             :     // to insert segments into.
    1328           8 :     if (aEntries[i].mProperty != lastProperty) {
    1329           8 :       MOZ_ASSERT(aEntries[i].mOffset == 0.0f);
    1330           8 :       MOZ_ASSERT(!animationProperty);
    1331           8 :       animationProperty = aResult.AppendElement();
    1332           8 :       animationProperty->mProperty = aEntries[i].mProperty;
    1333           8 :       lastProperty = aEntries[i].mProperty;
    1334             :     }
    1335             : 
    1336           8 :     MOZ_ASSERT(animationProperty, "animationProperty should be valid pointer.");
    1337             : 
    1338             :     // Now generate the segment.
    1339             :     AnimationPropertySegment* segment =
    1340           8 :       animationProperty->mSegments.AppendElement();
    1341           8 :     segment->mFromKey        = aEntries[i].mOffset;
    1342           8 :     segment->mToKey          = aEntries[j].mOffset;
    1343           8 :     segment->mFromValue      = aEntries[i].mValue;
    1344           8 :     segment->mToValue        = aEntries[j].mValue;
    1345           8 :     segment->mTimingFunction = aEntries[i].mTimingFunction;
    1346           8 :     segment->mFromComposite  = aEntries[i].mComposite;
    1347           8 :     segment->mToComposite    = aEntries[j].mComposite;
    1348             : 
    1349           8 :     i = j;
    1350             :   }
    1351             : }
    1352             : 
    1353             : /**
    1354             :  * Converts a JS object representing a property-indexed keyframe into
    1355             :  * an array of Keyframe objects.
    1356             :  *
    1357             :  * @param aCx The JSContext for |aValue|.
    1358             :  * @param aDocument The document to use when parsing CSS properties.
    1359             :  * @param aValue The JS object.
    1360             :  * @param aResult The array into which the resulting AnimationProperty
    1361             :  *   objects will be appended.
    1362             :  * @param aRv Out param to store any errors thrown by this function.
    1363             :  */
    1364             : static void
    1365           0 : GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
    1366             :                                            nsIDocument* aDocument,
    1367             :                                            JS::Handle<JS::Value> aValue,
    1368             :                                            nsTArray<Keyframe>& aResult,
    1369             :                                            ErrorResult& aRv)
    1370             : {
    1371           0 :   MOZ_ASSERT(aValue.isObject());
    1372           0 :   MOZ_ASSERT(aResult.IsEmpty());
    1373           0 :   MOZ_ASSERT(!aRv.Failed());
    1374             : 
    1375             :   // Convert the object to a property-indexed keyframe dictionary to
    1376             :   // get its explicit dictionary members.
    1377           0 :   dom::binding_detail::FastBasePropertyIndexedKeyframe keyframeDict;
    1378           0 :   if (!keyframeDict.Init(aCx, aValue, "BasePropertyIndexedKeyframe argument",
    1379             :                          false)) {
    1380           0 :     aRv.Throw(NS_ERROR_FAILURE);
    1381           0 :     return;
    1382             :   }
    1383             : 
    1384           0 :   Maybe<dom::CompositeOperation> composite;
    1385           0 :   if (keyframeDict.mComposite.WasPassed()) {
    1386           0 :     composite.emplace(keyframeDict.mComposite.Value());
    1387             :   }
    1388             : 
    1389             :   Maybe<ComputedTimingFunction> easing =
    1390           0 :     TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, aRv);
    1391           0 :   if (aRv.Failed()) {
    1392           0 :     return;
    1393             :   }
    1394             : 
    1395             :   // Get all the property--value-list pairs off the object.
    1396           0 :   JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
    1397           0 :   nsTArray<PropertyValuesPair> propertyValuesPairs;
    1398           0 :   if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
    1399             :                               aDocument->GetStyleBackendType(),
    1400             :                               propertyValuesPairs)) {
    1401           0 :     aRv.Throw(NS_ERROR_FAILURE);
    1402           0 :     return;
    1403             :   }
    1404             : 
    1405             :   // Create a set of keyframes for each property.
    1406           0 :   nsCSSParser parser(aDocument->CSSLoader());
    1407           0 :   nsClassHashtable<nsFloatHashKey, Keyframe> processedKeyframes;
    1408           0 :   for (const PropertyValuesPair& pair : propertyValuesPairs) {
    1409           0 :     size_t count = pair.mValues.Length();
    1410           0 :     if (count == 0) {
    1411             :       // No animation values for this property.
    1412           0 :       continue;
    1413             :     }
    1414             : 
    1415             :     // If we only have one value, we should animate from the underlying value
    1416             :     // using additive animation--however, we don't support additive animation
    1417             :     // when the core animation API pref is switched off.
    1418           0 :     if ((!AnimationUtils::IsCoreAPIEnabled()) &&
    1419             :         count == 1) {
    1420           0 :       aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
    1421           0 :       return;
    1422             :     }
    1423             : 
    1424           0 :     size_t n = pair.mValues.Length() - 1;
    1425           0 :     size_t i = 0;
    1426             : 
    1427           0 :     for (const nsString& stringValue : pair.mValues) {
    1428             :       // For single-valued lists, the single value should be added to a
    1429             :       // keyframe with offset 1.
    1430           0 :       double offset = n ? i++ / double(n) : 1;
    1431           0 :       Keyframe* keyframe = processedKeyframes.LookupOrAdd(offset);
    1432           0 :       if (keyframe->mPropertyValues.IsEmpty()) {
    1433           0 :         keyframe->mTimingFunction = easing;
    1434           0 :         keyframe->mComposite = composite;
    1435           0 :         keyframe->mComputedOffset = offset;
    1436             :       }
    1437             : 
    1438             :       Maybe<PropertyValuePair> valuePair =
    1439           0 :         MakePropertyValuePair(pair.mProperty, stringValue, parser, aDocument);
    1440           0 :       if (!valuePair) {
    1441           0 :         continue;
    1442             :       }
    1443           0 :       keyframe->mPropertyValues.AppendElement(Move(valuePair.ref()));
    1444             :     }
    1445             :   }
    1446             : 
    1447           0 :   aResult.SetCapacity(processedKeyframes.Count());
    1448           0 :   for (auto iter = processedKeyframes.Iter(); !iter.Done(); iter.Next()) {
    1449           0 :     aResult.AppendElement(Move(*iter.UserData()));
    1450             :   }
    1451             : 
    1452           0 :   aResult.Sort(ComputedOffsetComparator());
    1453             : }
    1454             : 
    1455             : /**
    1456             :  * Returns true if the supplied set of keyframes has keyframe values for
    1457             :  * any property for which it does not also supply a value for the 0% and 100%
    1458             :  * offsets. In this case we are supposed to synthesize an additive zero value
    1459             :  * but since we don't support additive animation yet we can't support this
    1460             :  * case. We try to detect that here so we can throw an exception. The check is
    1461             :  * not entirely accurate but should detect most common cases.
    1462             :  *
    1463             :  * @param aKeyframes The set of keyframes to analyze.
    1464             :  * @param aDocument The document to use when parsing keyframes so we can
    1465             :  *   try to detect where we have an invalid value at 0%/100%.
    1466             :  */
    1467             : static bool
    1468           0 : RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
    1469             :                           nsIDocument* aDocument)
    1470             : {
    1471             :   // We are looking to see if that every property referenced in |aKeyframes|
    1472             :   // has a valid property at offset 0.0 and 1.0. The check as to whether a
    1473             :   // property is valid or not, however, is not precise. We only check if the
    1474             :   // property can be parsed, NOT whether it can also be converted to a
    1475             :   // StyleAnimationValue since doing that requires a target element bound to
    1476             :   // a document which we might not always have at the point where we want to
    1477             :   // perform this check.
    1478             :   //
    1479             :   // This is only a temporary measure until we implement additive animation.
    1480             :   // So as long as this check catches most cases, and we don't do anything
    1481             :   // horrible in one of the cases we can't detect, it should be sufficient.
    1482             : 
    1483           0 :   nsCSSPropertyIDSet properties;              // All properties encountered.
    1484           0 :   nsCSSPropertyIDSet propertiesWithFromValue; // Those with a defined 0% value.
    1485           0 :   nsCSSPropertyIDSet propertiesWithToValue;   // Those with a defined 100% value.
    1486             : 
    1487           0 :   auto addToPropertySets = [&](nsCSSPropertyID aProperty, double aOffset) {
    1488           0 :     properties.AddProperty(aProperty);
    1489           0 :     if (aOffset == 0.0) {
    1490           0 :       propertiesWithFromValue.AddProperty(aProperty);
    1491           0 :     } else if (aOffset == 1.0) {
    1492           0 :       propertiesWithToValue.AddProperty(aProperty);
    1493             :     }
    1494           0 :   };
    1495             : 
    1496           0 :   StyleBackendType styleBackend = aDocument->GetStyleBackendType();
    1497             : 
    1498           0 :   for (size_t i = 0, len = aKeyframes.Length(); i < len; i++) {
    1499           0 :     const Keyframe& frame = aKeyframes[i];
    1500             : 
    1501             :     // We won't have called DistributeKeyframes when this is called so
    1502             :     // we can't use frame.mComputedOffset. Instead we do a rough version
    1503             :     // of that algorithm that substitutes null offsets with 0.0 for the first
    1504             :     // frame, 1.0 for the last frame, and 0.5 for everything else.
    1505           0 :     double computedOffset = i == len - 1
    1506           0 :                             ? 1.0
    1507           0 :                             : i == 0 ? 0.0 : 0.5;
    1508             :     double offsetToUse = frame.mOffset
    1509           0 :                          ? frame.mOffset.value()
    1510           0 :                          : computedOffset;
    1511             : 
    1512           0 :     for (const PropertyValuePair& pair : frame.mPropertyValues) {
    1513           0 :       if (nsCSSProps::IsShorthand(pair.mProperty)) {
    1514           0 :         if (styleBackend == StyleBackendType::Gecko) {
    1515             :           nsCSSValueTokenStream* tokenStream =
    1516           0 :             pair.mValue.GetTokenStreamValue();
    1517           0 :           nsCSSParser parser(aDocument->CSSLoader());
    1518           0 :           if (!parser.IsValueValidForProperty(pair.mProperty,
    1519             :                                               tokenStream->mTokenStream)) {
    1520           0 :             continue;
    1521             :           }
    1522             :         }
    1523             : 
    1524           0 :         MOZ_ASSERT(styleBackend != StyleBackendType::Servo ||
    1525             :                    pair.mServoDeclarationBlock);
    1526           0 :         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
    1527             :             prop, pair.mProperty, CSSEnabledState::eForAllContent) {
    1528           0 :           addToPropertySets(*prop, offsetToUse);
    1529             :         }
    1530             :       } else {
    1531           0 :         addToPropertySets(pair.mProperty, offsetToUse);
    1532             :       }
    1533             :     }
    1534             :   }
    1535             : 
    1536           0 :   return !propertiesWithFromValue.Equals(properties) ||
    1537           0 :          !propertiesWithToValue.Equals(properties);
    1538             : }
    1539             : 
    1540             : /**
    1541             :  * Distribute the offsets of all keyframes in between the endpoints of the
    1542             :  * given range.
    1543             :  *
    1544             :  * @param aRange The sequence of keyframes between whose endpoints we should
    1545             :  * distribute offsets.
    1546             :  */
    1547             : static void
    1548           2 : DistributeRange(const Range<Keyframe>& aRange)
    1549             : {
    1550           4 :   const Range<Keyframe> rangeToAdjust = Range<Keyframe>(aRange.begin() + 1,
    1551           6 :                                                         aRange.end() - 1);
    1552           2 :   const size_t n = aRange.length() - 1;
    1553           2 :   const double startOffset = aRange[0].mComputedOffset;
    1554           2 :   const double diffOffset = aRange[n].mComputedOffset - startOffset;
    1555           2 :   for (auto iter = rangeToAdjust.begin(); iter != rangeToAdjust.end(); ++iter) {
    1556           0 :     size_t index = iter - aRange.begin();
    1557           0 :     iter->mComputedOffset = startOffset + double(index) / n * diffOffset;
    1558             :   }
    1559           2 : }
    1560             : 
    1561             : } // namespace mozilla

Generated by: LCOV version 1.13