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