1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* representation of a value for a SMIL-animated CSS property */
8 
9 #include "nsSMILCSSValueType.h"
10 
11 #include "nsComputedDOMStyle.h"
12 #include "nsString.h"
13 #include "nsSMILParserUtils.h"
14 #include "nsSMILValue.h"
15 #include "nsCSSProps.h"
16 #include "nsCSSValue.h"
17 #include "nsColor.h"
18 #include "nsPresContext.h"
19 #include "mozilla/ServoBindings.h"
20 #include "mozilla/StyleAnimationValue.h"  // For AnimationValue
21 #include "mozilla/ServoCSSParser.h"
22 #include "mozilla/StyleSetHandleInlines.h"
23 #include "mozilla/dom/BaseKeyframeTypesBinding.h"  // For CompositeOperation
24 #include "mozilla/dom/Element.h"
25 #include "nsDebug.h"
26 #include "nsStyleUtil.h"
27 #include "nsIDocument.h"
28 
29 using namespace mozilla::dom;
30 using mozilla::StyleAnimationValue;
31 
32 typedef AutoTArray<RefPtr<RawServoAnimationValue>, 1> ServoAnimationValues;
33 
34 /*static*/ nsSMILCSSValueType nsSMILCSSValueType::sSingleton;
35 
36 struct ValueWrapper {
ValueWrapperValueWrapper37   ValueWrapper(nsCSSPropertyID aPropID, const AnimationValue& aValue)
38       : mPropID(aPropID) {
39     if (aValue.mServo) {
40       mServoValues.AppendElement(aValue.mServo);
41       return;
42     }
43 #ifdef MOZ_OLD_STYLE
44     mGeckoValue = aValue.mGecko;
45 #else
46     MOZ_CRASH("old style system disabled");
47 #endif
48   }
49 #ifdef MOZ_OLD_STYLE
ValueWrapperValueWrapper50   ValueWrapper(nsCSSPropertyID aPropID, const StyleAnimationValue& aValue)
51       : mPropID(aPropID), mGeckoValue(aValue) {}
52 #endif
ValueWrapperValueWrapper53   ValueWrapper(nsCSSPropertyID aPropID,
54                const RefPtr<RawServoAnimationValue>& aValue)
55       : mPropID(aPropID), mServoValues{(aValue)} {}
ValueWrapperValueWrapper56   ValueWrapper(nsCSSPropertyID aPropID, ServoAnimationValues&& aValues)
57       : mPropID(aPropID), mServoValues{aValues} {}
58 
operator ==ValueWrapper59   bool operator==(const ValueWrapper& aOther) const {
60     if (mPropID != aOther.mPropID) {
61       return false;
62     }
63 
64     if (!mServoValues.IsEmpty()) {
65       size_t len = mServoValues.Length();
66       if (len != aOther.mServoValues.Length()) {
67         return false;
68       }
69       for (size_t i = 0; i < len; i++) {
70         if (!Servo_AnimationValue_DeepEqual(mServoValues[i],
71                                             aOther.mServoValues[i])) {
72           return false;
73         }
74       }
75       return true;
76     }
77 
78 #ifdef MOZ_OLD_STYLE
79     return mGeckoValue == aOther.mGeckoValue;
80 #else
81     MOZ_CRASH("old style system disabled");
82 #endif
83   }
84 
operator !=ValueWrapper85   bool operator!=(const ValueWrapper& aOther) const {
86     return !(*this == aOther);
87   }
88 
89   nsCSSPropertyID mPropID;
90   ServoAnimationValues mServoValues;
91 #ifdef MOZ_OLD_STYLE
92   StyleAnimationValue mGeckoValue;
93 #endif
94 };
95 
96 // Helper Methods
97 // --------------
98 #ifdef MOZ_OLD_STYLE
GetZeroValueForUnit(StyleAnimationValue::Unit aUnit)99 static const StyleAnimationValue* GetZeroValueForUnit(
100     StyleAnimationValue::Unit aUnit) {
101   static const StyleAnimationValue sZeroCoord(
102       0, StyleAnimationValue::CoordConstructor);
103   static const StyleAnimationValue sZeroPercent(
104       0.0f, StyleAnimationValue::PercentConstructor);
105   static const StyleAnimationValue sZeroFloat(
106       0.0f, StyleAnimationValue::FloatConstructor);
107   static const StyleAnimationValue sZeroColor(
108       NS_RGB(0, 0, 0), StyleAnimationValue::ColorConstructor);
109 
110   MOZ_ASSERT(aUnit != StyleAnimationValue::eUnit_Null,
111              "Need non-null unit for a zero value");
112   switch (aUnit) {
113     case StyleAnimationValue::eUnit_Coord:
114       return &sZeroCoord;
115     case StyleAnimationValue::eUnit_Percent:
116       return &sZeroPercent;
117     case StyleAnimationValue::eUnit_Float:
118       return &sZeroFloat;
119     case StyleAnimationValue::eUnit_Color:
120       return &sZeroColor;
121     default:
122       return nullptr;
123   }
124 }
125 #endif
126 
127 // If one argument is null, this method updates it to point to "zero"
128 // for the other argument's Unit (if applicable; otherwise, we return false).
129 //
130 // If neither argument is null, this method simply returns true.
131 //
132 // If both arguments are null, this method returns false.
133 //
134 // |aZeroValueStorage| should be a reference to a
135 // RefPtr<RawServoAnimationValue>. This is used where we may need to allocate a
136 // new ServoAnimationValue to represent the appropriate zero value.
137 //
138 // Returns true on success, or otherwise.
FinalizeServoAnimationValues(const RefPtr<RawServoAnimationValue> * & aValue1,const RefPtr<RawServoAnimationValue> * & aValue2,RefPtr<RawServoAnimationValue> & aZeroValueStorage)139 static bool FinalizeServoAnimationValues(
140     const RefPtr<RawServoAnimationValue>*& aValue1,
141     const RefPtr<RawServoAnimationValue>*& aValue2,
142     RefPtr<RawServoAnimationValue>& aZeroValueStorage) {
143   if (!aValue1 && !aValue2) {
144     return false;
145   }
146 
147   // Are we missing either val? (If so, it's an implied 0 in other val's units)
148 
149   if (!aValue1) {
150     aZeroValueStorage = Servo_AnimationValues_GetZeroValue(*aValue2).Consume();
151     aValue1 = &aZeroValueStorage;
152   } else if (!aValue2) {
153     aZeroValueStorage = Servo_AnimationValues_GetZeroValue(*aValue1).Consume();
154     aValue2 = &aZeroValueStorage;
155   }
156   return *aValue1 && *aValue2;
157 }
158 
159 #ifdef MOZ_OLD_STYLE
FinalizeStyleAnimationValues(const StyleAnimationValue * & aValue1,const StyleAnimationValue * & aValue2)160 static bool FinalizeStyleAnimationValues(const StyleAnimationValue*& aValue1,
161                                          const StyleAnimationValue*& aValue2) {
162   if (!aValue1 && !aValue2) {
163     return false;
164   }
165 
166   if (!aValue1) {
167     aValue1 = GetZeroValueForUnit(aValue2->GetUnit());
168     return !!aValue1;  // Fail if we have no zero value for this unit.
169   }
170   if (!aValue2) {
171     aValue2 = GetZeroValueForUnit(aValue1->GetUnit());
172     return !!aValue2;  // Fail if we have no zero value for this unit.
173   }
174 
175   // Ok, both values were specified.
176   // Need to handle a special-case, though: unitless nonzero length (parsed as
177   // eUnit_Float) mixed with unitless 0 length (parsed as eUnit_Coord).  These
178   // won't interoperate in StyleAnimationValue, since their Units don't match.
179   // In this case, we replace the eUnit_Coord 0 value with eUnit_Float 0 value.
180   const StyleAnimationValue& zeroCoord =
181       *GetZeroValueForUnit(StyleAnimationValue::eUnit_Coord);
182   if (*aValue1 == zeroCoord &&
183       aValue2->GetUnit() == StyleAnimationValue::eUnit_Float) {
184     aValue1 = GetZeroValueForUnit(StyleAnimationValue::eUnit_Float);
185   } else if (*aValue2 == zeroCoord &&
186              aValue1->GetUnit() == StyleAnimationValue::eUnit_Float) {
187     aValue2 = GetZeroValueForUnit(StyleAnimationValue::eUnit_Float);
188   }
189 
190   return true;
191 }
192 
InvertSign(StyleAnimationValue & aValue)193 static void InvertSign(StyleAnimationValue& aValue) {
194   switch (aValue.GetUnit()) {
195     case StyleAnimationValue::eUnit_Coord:
196       aValue.SetCoordValue(-aValue.GetCoordValue());
197       break;
198     case StyleAnimationValue::eUnit_Percent:
199       aValue.SetPercentValue(-aValue.GetPercentValue());
200       break;
201     case StyleAnimationValue::eUnit_Float:
202       aValue.SetFloatValue(-aValue.GetFloatValue());
203       break;
204     default:
205       NS_NOTREACHED("Calling InvertSign with an unsupported unit");
206       break;
207   }
208 }
209 #endif
210 
ExtractValueWrapper(nsSMILValue & aValue)211 static ValueWrapper* ExtractValueWrapper(nsSMILValue& aValue) {
212   return static_cast<ValueWrapper*>(aValue.mU.mPtr);
213 }
214 
ExtractValueWrapper(const nsSMILValue & aValue)215 static const ValueWrapper* ExtractValueWrapper(const nsSMILValue& aValue) {
216   return static_cast<const ValueWrapper*>(aValue.mU.mPtr);
217 }
218 
219 // Class methods
220 // -------------
Init(nsSMILValue & aValue) const221 void nsSMILCSSValueType::Init(nsSMILValue& aValue) const {
222   MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL value type");
223 
224   aValue.mU.mPtr = nullptr;
225   aValue.mType = this;
226 }
227 
Destroy(nsSMILValue & aValue) const228 void nsSMILCSSValueType::Destroy(nsSMILValue& aValue) const {
229   MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type");
230   delete static_cast<ValueWrapper*>(aValue.mU.mPtr);
231   aValue.mType = nsSMILNullType::Singleton();
232 }
233 
Assign(nsSMILValue & aDest,const nsSMILValue & aSrc) const234 nsresult nsSMILCSSValueType::Assign(nsSMILValue& aDest,
235                                     const nsSMILValue& aSrc) const {
236   MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types");
237   MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value type");
238   const ValueWrapper* srcWrapper = ExtractValueWrapper(aSrc);
239   ValueWrapper* destWrapper = ExtractValueWrapper(aDest);
240 
241   if (srcWrapper) {
242     if (!destWrapper) {
243       // barely-initialized dest -- need to alloc & copy
244       aDest.mU.mPtr = new ValueWrapper(*srcWrapper);
245     } else {
246       // both already fully-initialized -- just copy straight across
247       *destWrapper = *srcWrapper;
248     }
249   } else if (destWrapper) {
250     // fully-initialized dest, barely-initialized src -- clear dest
251     delete destWrapper;
252     aDest.mU.mPtr = destWrapper = nullptr;
253   }  // else, both are barely-initialized -- nothing to do.
254 
255   return NS_OK;
256 }
257 
IsEqual(const nsSMILValue & aLeft,const nsSMILValue & aRight) const258 bool nsSMILCSSValueType::IsEqual(const nsSMILValue& aLeft,
259                                  const nsSMILValue& aRight) const {
260   MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types");
261   MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL value");
262   const ValueWrapper* leftWrapper = ExtractValueWrapper(aLeft);
263   const ValueWrapper* rightWrapper = ExtractValueWrapper(aRight);
264 
265   if (leftWrapper) {
266     if (rightWrapper) {
267       // Both non-null
268       NS_WARNING_ASSERTION(leftWrapper != rightWrapper,
269                            "Two nsSMILValues with matching ValueWrapper ptr");
270       return *leftWrapper == *rightWrapper;
271     }
272     // Left non-null, right null
273     return false;
274   }
275   if (rightWrapper) {
276     // Left null, right non-null
277     return false;
278   }
279   // Both null
280   return true;
281 }
282 
AddOrAccumulateForServo(nsSMILValue & aDest,const ValueWrapper * aValueToAddWrapper,ValueWrapper * aDestWrapper,CompositeOperation aCompositeOp,uint64_t aCount)283 static bool AddOrAccumulateForServo(nsSMILValue& aDest,
284                                     const ValueWrapper* aValueToAddWrapper,
285                                     ValueWrapper* aDestWrapper,
286                                     CompositeOperation aCompositeOp,
287                                     uint64_t aCount) {
288   nsCSSPropertyID property =
289       aValueToAddWrapper ? aValueToAddWrapper->mPropID : aDestWrapper->mPropID;
290   size_t len = aValueToAddWrapper ? aValueToAddWrapper->mServoValues.Length()
291                                   : aDestWrapper->mServoValues.Length();
292 
293   MOZ_ASSERT(!aValueToAddWrapper || !aDestWrapper ||
294                  aValueToAddWrapper->mServoValues.Length() ==
295                      aDestWrapper->mServoValues.Length(),
296              "Both of values' length in the wrappers should be the same if "
297              "both of them exist");
298 
299   for (size_t i = 0; i < len; i++) {
300     const RefPtr<RawServoAnimationValue>* valueToAdd =
301         aValueToAddWrapper ? &aValueToAddWrapper->mServoValues[i] : nullptr;
302     const RefPtr<RawServoAnimationValue>* destValue =
303         aDestWrapper ? &aDestWrapper->mServoValues[i] : nullptr;
304     RefPtr<RawServoAnimationValue> zeroValueStorage;
305     if (!FinalizeServoAnimationValues(valueToAdd, destValue,
306                                       zeroValueStorage)) {
307       return false;
308     }
309 
310     // FinalizeServoAnimationValues may have updated destValue so we should make
311     // sure the aDest and aDestWrapper outparams are up-to-date.
312     if (aDestWrapper) {
313       aDestWrapper->mServoValues[i] = *destValue;
314     } else {
315       // aDest may be a barely-initialized "zero" destination.
316       aDest.mU.mPtr = aDestWrapper = new ValueWrapper(property, *destValue);
317       aDestWrapper->mServoValues.SetLength(len);
318     }
319 
320     RefPtr<RawServoAnimationValue> result;
321     if (aCompositeOp == CompositeOperation::Add) {
322       result = Servo_AnimationValues_Add(*destValue, *valueToAdd).Consume();
323     } else {
324       result = Servo_AnimationValues_Accumulate(*destValue, *valueToAdd, aCount)
325                    .Consume();
326     }
327 
328     if (!result) {
329       return false;
330     }
331     aDestWrapper->mServoValues[i] = result;
332   }
333 
334   return true;
335 }
336 
AddOrAccumulate(nsSMILValue & aDest,const nsSMILValue & aValueToAdd,CompositeOperation aCompositeOp,uint64_t aCount)337 static bool AddOrAccumulate(nsSMILValue& aDest, const nsSMILValue& aValueToAdd,
338                             CompositeOperation aCompositeOp, uint64_t aCount) {
339   MOZ_ASSERT(aValueToAdd.mType == aDest.mType,
340              "Trying to add mismatching types");
341   MOZ_ASSERT(aValueToAdd.mType == &nsSMILCSSValueType::sSingleton,
342              "Unexpected SMIL value type");
343   MOZ_ASSERT(aCompositeOp == CompositeOperation::Add ||
344                  aCompositeOp == CompositeOperation::Accumulate,
345              "Composite operation should be add or accumulate");
346   MOZ_ASSERT(aCompositeOp != CompositeOperation::Add || aCount == 1,
347              "Count should be 1 if composite operation is add");
348 
349   ValueWrapper* destWrapper = ExtractValueWrapper(aDest);
350   const ValueWrapper* valueToAddWrapper = ExtractValueWrapper(aValueToAdd);
351 
352   // If both of the values are empty just fail. This can happen in rare cases
353   // such as when the underlying animation produced an empty value.
354   //
355   // Technically, it doesn't matter what we return here since in either case it
356   // will produce the same result: an empty value.
357   if (!destWrapper && !valueToAddWrapper) {
358     return false;
359   }
360 
361   nsCSSPropertyID property =
362       valueToAddWrapper ? valueToAddWrapper->mPropID : destWrapper->mPropID;
363   // Special case: font-size-adjust and stroke-dasharray are explicitly
364   // non-additive (even though StyleAnimationValue *could* support adding them)
365   if (property == eCSSProperty_font_size_adjust ||
366       property == eCSSProperty_stroke_dasharray) {
367     return false;
368   }
369   // Skip font shorthand since it includes font-size-adjust.
370   if (property == eCSSProperty_font) {
371     return false;
372   }
373 
374   bool isServo = valueToAddWrapper ? !valueToAddWrapper->mServoValues.IsEmpty()
375                                    : !destWrapper->mServoValues.IsEmpty();
376   if (isServo) {
377     return AddOrAccumulateForServo(aDest, valueToAddWrapper, destWrapper,
378                                    aCompositeOp, aCount);
379   }
380 
381 #ifdef MOZ_OLD_STYLE
382   const StyleAnimationValue* valueToAdd =
383       valueToAddWrapper ? &valueToAddWrapper->mGeckoValue : nullptr;
384   const StyleAnimationValue* destValue =
385       destWrapper ? &destWrapper->mGeckoValue : nullptr;
386   if (!FinalizeStyleAnimationValues(valueToAdd, destValue)) {
387     return false;
388   }
389   // Did FinalizeStyleAnimationValues change destValue?
390   // If so, update outparam to use the new value.
391   if (destWrapper && &destWrapper->mGeckoValue != destValue) {
392     destWrapper->mGeckoValue = *destValue;
393   }
394 
395   // Handle barely-initialized "zero" destination.
396   if (!destWrapper) {
397     aDest.mU.mPtr = destWrapper = new ValueWrapper(property, *destValue);
398   }
399 
400   // For Gecko, we currently call Add for either composite mode.
401   //
402   // This is not ideal, but it doesn't make any difference for the set of
403   // properties we currently allow adding in SMIL and this code path will
404   // hopefully become obsolete before we expand that set.
405   return StyleAnimationValue::Add(property, destWrapper->mGeckoValue,
406                                   *valueToAdd, aCount);
407 #else
408   MOZ_CRASH("old style system disabled");
409 #endif
410 }
411 
SandwichAdd(nsSMILValue & aDest,const nsSMILValue & aValueToAdd) const412 nsresult nsSMILCSSValueType::SandwichAdd(nsSMILValue& aDest,
413                                          const nsSMILValue& aValueToAdd) const {
414   return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Add, 1)
415              ? NS_OK
416              : NS_ERROR_FAILURE;
417 }
418 
Add(nsSMILValue & aDest,const nsSMILValue & aValueToAdd,uint32_t aCount) const419 nsresult nsSMILCSSValueType::Add(nsSMILValue& aDest,
420                                  const nsSMILValue& aValueToAdd,
421                                  uint32_t aCount) const {
422   return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Accumulate,
423                          aCount)
424              ? NS_OK
425              : NS_ERROR_FAILURE;
426 }
427 
ComputeDistanceForServo(const ValueWrapper * aFromWrapper,const ValueWrapper & aToWrapper,double & aDistance)428 static nsresult ComputeDistanceForServo(const ValueWrapper* aFromWrapper,
429                                         const ValueWrapper& aToWrapper,
430                                         double& aDistance) {
431   size_t len = aToWrapper.mServoValues.Length();
432   MOZ_ASSERT(!aFromWrapper || aFromWrapper->mServoValues.Length() == len,
433              "From and to values length should be the same if "
434              "The start value exists");
435 
436   double squareDistance = 0;
437 
438   for (size_t i = 0; i < len; i++) {
439     const RefPtr<RawServoAnimationValue>* fromValue =
440         aFromWrapper ? &aFromWrapper->mServoValues[0] : nullptr;
441     const RefPtr<RawServoAnimationValue>* toValue = &aToWrapper.mServoValues[0];
442     RefPtr<RawServoAnimationValue> zeroValueStorage;
443     if (!FinalizeServoAnimationValues(fromValue, toValue, zeroValueStorage)) {
444       return NS_ERROR_FAILURE;
445     }
446 
447     double distance =
448         Servo_AnimationValues_ComputeDistance(*fromValue, *toValue);
449     if (distance < 0.0) {
450       return NS_ERROR_FAILURE;
451     }
452 
453     if (len == 1) {
454       aDistance = distance;
455       return NS_OK;
456     }
457     squareDistance += distance * distance;
458   }
459 
460   aDistance = sqrt(squareDistance);
461 
462   return NS_OK;
463 }
464 
ComputeDistance(const nsSMILValue & aFrom,const nsSMILValue & aTo,double & aDistance) const465 nsresult nsSMILCSSValueType::ComputeDistance(const nsSMILValue& aFrom,
466                                              const nsSMILValue& aTo,
467                                              double& aDistance) const {
468   MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types");
469   MOZ_ASSERT(aFrom.mType == this, "Unexpected source type");
470 
471   const ValueWrapper* fromWrapper = ExtractValueWrapper(aFrom);
472   const ValueWrapper* toWrapper = ExtractValueWrapper(aTo);
473   MOZ_ASSERT(toWrapper, "expecting non-null endpoint");
474 
475   if (!toWrapper->mServoValues.IsEmpty()) {
476     return ComputeDistanceForServo(fromWrapper, *toWrapper, aDistance);
477   }
478 
479 #ifdef MOZ_OLD_STYLE
480   const StyleAnimationValue* fromCSSValue =
481       fromWrapper ? &fromWrapper->mGeckoValue : nullptr;
482   const StyleAnimationValue* toCSSValue = &toWrapper->mGeckoValue;
483   if (!FinalizeStyleAnimationValues(fromCSSValue, toCSSValue)) {
484     return NS_ERROR_FAILURE;
485   }
486 
487   return StyleAnimationValue::ComputeDistance(
488              toWrapper->mPropID, fromWrapper->mGeckoValue,
489              toWrapper->mGeckoValue, nullptr, aDistance)
490              ? NS_OK
491              : NS_ERROR_FAILURE;
492 #else
493   MOZ_CRASH("old style system disabled");
494 #endif
495 }
496 
497 #ifdef MOZ_OLD_STYLE
InterpolateForGecko(const ValueWrapper * aStartWrapper,const ValueWrapper & aEndWrapper,double aUnitDistance,nsSMILValue & aResult)498 static nsresult InterpolateForGecko(const ValueWrapper* aStartWrapper,
499                                     const ValueWrapper& aEndWrapper,
500                                     double aUnitDistance,
501                                     nsSMILValue& aResult) {
502   const StyleAnimationValue* startCSSValue =
503       aStartWrapper ? &aStartWrapper->mGeckoValue : nullptr;
504   const StyleAnimationValue* endCSSValue = &aEndWrapper.mGeckoValue;
505   if (!FinalizeStyleAnimationValues(startCSSValue, endCSSValue)) {
506     return NS_ERROR_FAILURE;
507   }
508 
509   StyleAnimationValue resultValue;
510   if (StyleAnimationValue::Interpolate(aEndWrapper.mPropID, *startCSSValue,
511                                        *endCSSValue, aUnitDistance,
512                                        resultValue)) {
513     aResult.mU.mPtr = new ValueWrapper(aEndWrapper.mPropID, resultValue);
514     return NS_OK;
515   }
516   return NS_ERROR_FAILURE;
517 }
518 #endif
519 
InterpolateForServo(const ValueWrapper * aStartWrapper,const ValueWrapper & aEndWrapper,double aUnitDistance,nsSMILValue & aResult)520 static nsresult InterpolateForServo(const ValueWrapper* aStartWrapper,
521                                     const ValueWrapper& aEndWrapper,
522                                     double aUnitDistance,
523                                     nsSMILValue& aResult) {
524   // For discretely-animated properties Servo_AnimationValues_Interpolate will
525   // perform the discrete animation (i.e. 50% flip) and return a success result.
526   // However, SMIL has its own special discrete animation behavior that it uses
527   // when keyTimes are specified, but we won't run that unless that this method
528   // returns a failure to indicate that the property cannot be smoothly
529   // interpolated, i.e. that we need to use a discrete calcMode.
530   //
531   // For shorthands, Servo_Property_IsDiscreteAnimatable will always return
532   // false. That's fine since most shorthands (like 'font' and
533   // 'text-decoration') include non-discrete components. If authors want to
534   // treat all components as discrete then they should use calcMode="discrete".
535   if (Servo_Property_IsDiscreteAnimatable(aEndWrapper.mPropID)) {
536     return NS_ERROR_FAILURE;
537   }
538 
539   ServoAnimationValues results;
540   size_t len = aEndWrapper.mServoValues.Length();
541   results.SetCapacity(len);
542   MOZ_ASSERT(!aStartWrapper || aStartWrapper->mServoValues.Length() == len,
543              "Start and end values length should be the same if "
544              "the start value exists");
545   for (size_t i = 0; i < len; i++) {
546     const RefPtr<RawServoAnimationValue>* startValue =
547         aStartWrapper ? &aStartWrapper->mServoValues[i] : nullptr;
548     const RefPtr<RawServoAnimationValue>* endValue =
549         &aEndWrapper.mServoValues[i];
550     RefPtr<RawServoAnimationValue> zeroValueStorage;
551     if (!FinalizeServoAnimationValues(startValue, endValue, zeroValueStorage)) {
552       return NS_ERROR_FAILURE;
553     }
554 
555     RefPtr<RawServoAnimationValue> result =
556         Servo_AnimationValues_Interpolate(*startValue, *endValue, aUnitDistance)
557             .Consume();
558     if (!result) {
559       return NS_ERROR_FAILURE;
560     }
561     results.AppendElement(result);
562   }
563   aResult.mU.mPtr = new ValueWrapper(aEndWrapper.mPropID, Move(results));
564 
565   return NS_OK;
566 }
567 
Interpolate(const nsSMILValue & aStartVal,const nsSMILValue & aEndVal,double aUnitDistance,nsSMILValue & aResult) const568 nsresult nsSMILCSSValueType::Interpolate(const nsSMILValue& aStartVal,
569                                          const nsSMILValue& aEndVal,
570                                          double aUnitDistance,
571                                          nsSMILValue& aResult) const {
572   MOZ_ASSERT(aStartVal.mType == aEndVal.mType,
573              "Trying to interpolate different types");
574   MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation");
575   MOZ_ASSERT(aResult.mType == this, "Unexpected result type");
576   MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
577              "unit distance value out of bounds");
578   MOZ_ASSERT(!aResult.mU.mPtr, "expecting barely-initialized outparam");
579 
580   const ValueWrapper* startWrapper = ExtractValueWrapper(aStartVal);
581   const ValueWrapper* endWrapper = ExtractValueWrapper(aEndVal);
582   MOZ_ASSERT(endWrapper, "expecting non-null endpoint");
583 
584   if (!endWrapper->mServoValues.IsEmpty()) {
585     return InterpolateForServo(startWrapper, *endWrapper, aUnitDistance,
586                                aResult);
587   }
588 
589 #ifdef MOZ_OLD_STYLE
590   return InterpolateForGecko(startWrapper, *endWrapper, aUnitDistance, aResult);
591 #else
592   MOZ_CRASH("old style system disabled");
593 #endif
594 }
595 
596 // Helper function to extract presContext
GetPresContextForElement(Element * aElem)597 static nsPresContext* GetPresContextForElement(Element* aElem) {
598   nsIDocument* doc = aElem->GetUncomposedDoc();
599   if (!doc) {
600     // This can happen if we process certain types of restyles mid-sample
601     // and remove anonymous animated content from the document as a result.
602     // See bug 534975.
603     return nullptr;
604   }
605   return doc->GetPresContext();
606 }
607 
608 #ifdef MOZ_OLD_STYLE
GetNonNegativePropValue(const nsAString & aString,nsCSSPropertyID aPropID,bool & aIsNegative)609 static const nsDependentSubstring GetNonNegativePropValue(
610     const nsAString& aString, nsCSSPropertyID aPropID, bool& aIsNegative) {
611   // If value is negative, we'll strip off the "-" so the CSS parser won't
612   // barf, and then manually make the parsed value negative.
613   // (This is a partial solution to let us accept some otherwise out-of-bounds
614   // CSS values. Bug 501188 will provide a more complete fix.)
615   aIsNegative = false;
616   uint32_t subStringBegin = 0;
617 
618   // NOTE: We need to opt-out 'stroke-dasharray' from the negative-number
619   // check.  Its values might look negative (e.g. by starting with "-1"), but
620   // they're more complicated than our simple negation logic here can handle.
621   if (aPropID != eCSSProperty_stroke_dasharray) {
622     int32_t absValuePos = nsSMILParserUtils::CheckForNegativeNumber(aString);
623     if (absValuePos > 0) {
624       aIsNegative = true;
625       subStringBegin = (uint32_t)absValuePos;  // Start parsing after '-' sign
626     }
627   }
628 
629   return Substring(aString, subStringBegin);
630 }
631 
632 // Helper function to parse a string into a StyleAnimationValue
ValueFromStringHelper(nsCSSPropertyID aPropID,Element * aTargetElement,nsPresContext * aPresContext,mozilla::GeckoStyleContext * aStyleContext,const nsAString & aString,StyleAnimationValue & aStyleAnimValue,bool * aIsContextSensitive)633 static bool ValueFromStringHelper(nsCSSPropertyID aPropID,
634                                   Element* aTargetElement,
635                                   nsPresContext* aPresContext,
636                                   mozilla::GeckoStyleContext* aStyleContext,
637                                   const nsAString& aString,
638                                   StyleAnimationValue& aStyleAnimValue,
639                                   bool* aIsContextSensitive) {
640   bool isNegative = false;
641   const nsDependentSubstring subString =
642       GetNonNegativePropValue(aString, aPropID, isNegative);
643 
644   if (!StyleAnimationValue::ComputeValue(aPropID, aTargetElement, aStyleContext,
645                                          subString, true, aStyleAnimValue,
646                                          aIsContextSensitive)) {
647     return false;
648   }
649   if (isNegative) {
650     InvertSign(aStyleAnimValue);
651   }
652 
653   if (aPropID == eCSSProperty_font_size) {
654     // Divide out text-zoom, since SVG is supposed to ignore it
655     MOZ_ASSERT(aStyleAnimValue.GetUnit() == StyleAnimationValue::eUnit_Coord,
656                "'font-size' value with unexpected style unit");
657     aStyleAnimValue.SetCoordValue(aStyleAnimValue.GetCoordValue() /
658                                   aPresContext->EffectiveTextZoom());
659   }
660   return true;
661 }
662 #endif
663 
ValueFromStringHelper(nsCSSPropertyID aPropID,Element * aTargetElement,nsPresContext * aPresContext,nsStyleContext * aStyleContext,const nsAString & aString)664 static ServoAnimationValues ValueFromStringHelper(nsCSSPropertyID aPropID,
665                                                   Element* aTargetElement,
666                                                   nsPresContext* aPresContext,
667                                                   nsStyleContext* aStyleContext,
668                                                   const nsAString& aString) {
669   ServoAnimationValues result;
670 
671   nsIDocument* doc = aTargetElement->GetUncomposedDoc();
672   if (!doc) {
673     return result;
674   }
675 
676   // Parse property
677   ServoCSSParser::ParsingEnvironment env =
678       ServoCSSParser::GetParsingEnvironment(doc);
679   RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
680       ServoCSSParser::ParseProperty(aPropID, aString, env,
681                                     ParsingMode::AllowUnitlessLength |
682                                         ParsingMode::AllowAllNumericValues);
683   if (!servoDeclarationBlock) {
684     return result;
685   }
686 
687   // Compute value
688   aPresContext->StyleSet()->AsServo()->GetAnimationValues(
689       servoDeclarationBlock, aTargetElement, aStyleContext->AsServo(), result);
690 
691   return result;
692 }
693 
694 // static
ValueFromString(nsCSSPropertyID aPropID,Element * aTargetElement,const nsAString & aString,nsSMILValue & aValue,bool * aIsContextSensitive)695 void nsSMILCSSValueType::ValueFromString(nsCSSPropertyID aPropID,
696                                          Element* aTargetElement,
697                                          const nsAString& aString,
698                                          nsSMILValue& aValue,
699                                          bool* aIsContextSensitive) {
700   MOZ_ASSERT(aValue.IsNull(), "Outparam should be null-typed");
701   nsPresContext* presContext = GetPresContextForElement(aTargetElement);
702   if (!presContext) {
703     NS_WARNING("Not parsing animation value; unable to get PresContext");
704     return;
705   }
706 
707   nsIDocument* doc = aTargetElement->GetUncomposedDoc();
708   if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc->NodePrincipal(),
709                                                 nullptr, doc->GetDocumentURI(),
710                                                 0, aString, nullptr)) {
711     return;
712   }
713 
714   RefPtr<nsStyleContext> styleContext =
715       nsComputedDOMStyle::GetStyleContext(aTargetElement, nullptr);
716   if (!styleContext) {
717     return;
718   }
719 
720   if (styleContext->IsServo()) {
721     ServoAnimationValues parsedValues = ValueFromStringHelper(
722         aPropID, aTargetElement, presContext, styleContext, aString);
723     if (aIsContextSensitive) {
724       // FIXME: Bug 1358955 - detect context-sensitive values and set this value
725       // appropriately.
726       *aIsContextSensitive = false;
727     }
728 
729     if (!parsedValues.IsEmpty()) {
730       sSingleton.Init(aValue);
731       aValue.mU.mPtr = new ValueWrapper(aPropID, Move(parsedValues));
732     }
733     return;
734   }
735 
736 #ifdef MOZ_OLD_STYLE
737   StyleAnimationValue parsedValue;
738   if (ValueFromStringHelper(aPropID, aTargetElement, presContext,
739                             styleContext->AsGecko(), aString, parsedValue,
740                             aIsContextSensitive)) {
741     sSingleton.Init(aValue);
742     aValue.mU.mPtr = new ValueWrapper(aPropID, parsedValue);
743   }
744 #else
745   MOZ_CRASH("old style system disabled");
746 #endif
747 }
748 
749 // static
ValueFromAnimationValue(nsCSSPropertyID aPropID,Element * aTargetElement,const AnimationValue & aValue)750 nsSMILValue nsSMILCSSValueType::ValueFromAnimationValue(
751     nsCSSPropertyID aPropID, Element* aTargetElement,
752     const AnimationValue& aValue) {
753   nsSMILValue result;
754 
755   nsIDocument* doc = aTargetElement->GetUncomposedDoc();
756   // We'd like to avoid serializing |aValue| if possible, and since the
757   // string passed to CSPAllowsInlineStyle is only used for reporting violations
758   // and an intermediate CSS value is not likely to be particularly useful
759   // in that case, we just use a generic placeholder string instead.
760   static const nsLiteralString kPlaceholderText =
761       NS_LITERAL_STRING("[SVG animation of CSS]");
762   if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc->NodePrincipal(),
763                                                 nullptr, doc->GetDocumentURI(),
764                                                 0, kPlaceholderText, nullptr)) {
765     return result;
766   }
767 
768   sSingleton.Init(result);
769   result.mU.mPtr = new ValueWrapper(aPropID, aValue);
770 
771   return result;
772 }
773 
774 // static
ValueToString(const nsSMILValue & aValue,nsAString & aString)775 void nsSMILCSSValueType::ValueToString(const nsSMILValue& aValue,
776                                        nsAString& aString) {
777   MOZ_ASSERT(aValue.mType == &nsSMILCSSValueType::sSingleton,
778              "Unexpected SMIL value type");
779   const ValueWrapper* wrapper = ExtractValueWrapper(aValue);
780   if (!wrapper) {
781     return;
782   }
783 
784   if (wrapper->mServoValues.IsEmpty()) {
785 #ifdef MOZ_OLD_STYLE
786     DebugOnly<bool> uncomputeResult = StyleAnimationValue::UncomputeValue(
787         wrapper->mPropID, wrapper->mGeckoValue, aString);
788     return;
789 #else
790     MOZ_CRASH("old style system disabled");
791 #endif
792   }
793 
794   if (nsCSSProps::IsShorthand(wrapper->mPropID)) {
795     // In case of shorthand on servo, we iterate over all mServoValues array
796     // since we have multiple AnimationValues in the array for each longhand
797     // component.
798     Servo_Shorthand_AnimationValues_Serialize(wrapper->mPropID,
799                                               &wrapper->mServoValues, &aString);
800     return;
801   }
802 
803   Servo_AnimationValue_Serialize(wrapper->mServoValues[0], wrapper->mPropID,
804                                  &aString);
805 }
806 
807 // static
PropertyFromValue(const nsSMILValue & aValue)808 nsCSSPropertyID nsSMILCSSValueType::PropertyFromValue(
809     const nsSMILValue& aValue) {
810   if (aValue.mType != &nsSMILCSSValueType::sSingleton) {
811     return eCSSProperty_UNKNOWN;
812   }
813 
814   const ValueWrapper* wrapper = ExtractValueWrapper(aValue);
815   if (!wrapper) {
816     return eCSSProperty_UNKNOWN;
817   }
818 
819   return wrapper->mPropID;
820 }
821 
822 // static
FinalizeValue(nsSMILValue & aValue,const nsSMILValue & aValueToMatch)823 void nsSMILCSSValueType::FinalizeValue(nsSMILValue& aValue,
824                                        const nsSMILValue& aValueToMatch) {
825   MOZ_ASSERT(aValue.mType == aValueToMatch.mType, "Incompatible SMIL types");
826   MOZ_ASSERT(aValue.mType == &nsSMILCSSValueType::sSingleton,
827              "Unexpected SMIL value type");
828 
829   ValueWrapper* valueWrapper = ExtractValueWrapper(aValue);
830   // If |aValue| already has a value, there's nothing to do here.
831   if (valueWrapper) {
832     return;
833   }
834 
835   const ValueWrapper* valueToMatchWrapper = ExtractValueWrapper(aValueToMatch);
836   if (!valueToMatchWrapper) {
837     MOZ_ASSERT_UNREACHABLE("Value to match is empty");
838     return;
839   }
840 
841   bool isServo = !valueToMatchWrapper->mServoValues.IsEmpty();
842 
843   if (isServo) {
844     ServoAnimationValues zeroValues;
845     zeroValues.SetCapacity(valueToMatchWrapper->mServoValues.Length());
846 
847     for (auto& valueToMatch : valueToMatchWrapper->mServoValues) {
848       RefPtr<RawServoAnimationValue> zeroValue =
849           Servo_AnimationValues_GetZeroValue(valueToMatch).Consume();
850       if (!zeroValue) {
851         return;
852       }
853       zeroValues.AppendElement(Move(zeroValue));
854     }
855     aValue.mU.mPtr =
856         new ValueWrapper(valueToMatchWrapper->mPropID, Move(zeroValues));
857   } else {
858 #ifdef MOZ_OLD_STYLE
859     const StyleAnimationValue* zeroValue =
860         GetZeroValueForUnit(valueToMatchWrapper->mGeckoValue.GetUnit());
861     if (!zeroValue) {
862       return;
863     }
864     aValue.mU.mPtr = new ValueWrapper(valueToMatchWrapper->mPropID, *zeroValue);
865 #else
866     MOZ_CRASH("old style system disabled");
867 #endif
868   }
869 }
870