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/. */
7 /* representation of a value for a SMIL-animated CSS property */
9 #include "SMILCSSValueType.h"
11 #include "nsComputedDOMStyle.h"
13 #include "nsCSSProps.h"
14 #include "nsCSSValue.h"
16 #include "nsPresContextInlines.h"
17 #include "nsPresContext.h"
19 #include "nsStyleUtil.h"
20 #include "mozilla/DeclarationBlock.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/PresShellInlines.h"
23 #include "mozilla/ServoBindings.h"
24 #include "mozilla/StyleAnimationValue.h"
25 #include "mozilla/ServoCSSParser.h"
26 #include "mozilla/ServoStyleSet.h"
27 #include "mozilla/SMILParserUtils.h"
28 #include "mozilla/SMILValue.h"
29 #include "mozilla/dom/BaseKeyframeTypesBinding.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/Element.h"
33 using namespace mozilla::dom
;
37 using ServoAnimationValues
= CopyableAutoTArray
<RefPtr
<StyleAnimationValue
>, 1>;
40 SMILCSSValueType
SMILCSSValueType::sSingleton
;
43 ValueWrapper(nsCSSPropertyID aPropID
, const AnimationValue
& aValue
)
45 MOZ_ASSERT(!aValue
.IsNull());
46 mServoValues
.AppendElement(aValue
.mServo
);
48 ValueWrapper(nsCSSPropertyID aPropID
,
49 const RefPtr
<StyleAnimationValue
>& aValue
)
50 : mPropID(aPropID
), mServoValues
{(aValue
)} {}
51 ValueWrapper(nsCSSPropertyID aPropID
, ServoAnimationValues
&& aValues
)
52 : mPropID(aPropID
), mServoValues
{std::move(aValues
)} {}
54 bool operator==(const ValueWrapper
& aOther
) const {
55 if (mPropID
!= aOther
.mPropID
) {
59 MOZ_ASSERT(!mServoValues
.IsEmpty());
60 size_t len
= mServoValues
.Length();
61 if (len
!= aOther
.mServoValues
.Length()) {
64 for (size_t i
= 0; i
< len
; i
++) {
65 if (!Servo_AnimationValue_DeepEqual(mServoValues
[i
],
66 aOther
.mServoValues
[i
])) {
73 bool operator!=(const ValueWrapper
& aOther
) const {
74 return !(*this == aOther
);
77 nsCSSPropertyID mPropID
;
78 ServoAnimationValues mServoValues
;
84 // If one argument is null, this method updates it to point to "zero"
85 // for the other argument's Unit (if applicable; otherwise, we return false).
87 // If neither argument is null, this method simply returns true.
89 // If both arguments are null, this method returns false.
91 // |aZeroValueStorage| should be a reference to a
92 // RefPtr<StyleAnimationValue>. This is used where we may need to allocate a
93 // new ServoAnimationValue to represent the appropriate zero value.
95 // Returns true on success, or otherwise.
96 static bool FinalizeServoAnimationValues(
97 const RefPtr
<StyleAnimationValue
>*& aValue1
,
98 const RefPtr
<StyleAnimationValue
>*& aValue2
,
99 RefPtr
<StyleAnimationValue
>& aZeroValueStorage
) {
100 if (!aValue1
&& !aValue2
) {
104 // Are we missing either val? (If so, it's an implied 0 in other val's units)
107 aZeroValueStorage
= Servo_AnimationValues_GetZeroValue(*aValue2
).Consume();
108 aValue1
= &aZeroValueStorage
;
109 } else if (!aValue2
) {
110 aZeroValueStorage
= Servo_AnimationValues_GetZeroValue(*aValue1
).Consume();
111 aValue2
= &aZeroValueStorage
;
113 return *aValue1
&& *aValue2
;
116 static ValueWrapper
* ExtractValueWrapper(SMILValue
& aValue
) {
117 return static_cast<ValueWrapper
*>(aValue
.mU
.mPtr
);
120 static const ValueWrapper
* ExtractValueWrapper(const SMILValue
& aValue
) {
121 return static_cast<const ValueWrapper
*>(aValue
.mU
.mPtr
);
126 void SMILCSSValueType::Init(SMILValue
& aValue
) const {
127 MOZ_ASSERT(aValue
.IsNull(), "Unexpected SMIL value type");
129 aValue
.mU
.mPtr
= nullptr;
133 void SMILCSSValueType::Destroy(SMILValue
& aValue
) const {
134 MOZ_ASSERT(aValue
.mType
== this, "Unexpected SMIL value type");
135 delete static_cast<ValueWrapper
*>(aValue
.mU
.mPtr
);
136 aValue
.mType
= SMILNullType::Singleton();
139 nsresult
SMILCSSValueType::Assign(SMILValue
& aDest
,
140 const SMILValue
& aSrc
) const {
141 MOZ_ASSERT(aDest
.mType
== aSrc
.mType
, "Incompatible SMIL types");
142 MOZ_ASSERT(aDest
.mType
== this, "Unexpected SMIL value type");
143 const ValueWrapper
* srcWrapper
= ExtractValueWrapper(aSrc
);
144 ValueWrapper
* destWrapper
= ExtractValueWrapper(aDest
);
148 // barely-initialized dest -- need to alloc & copy
149 aDest
.mU
.mPtr
= new ValueWrapper(*srcWrapper
);
151 // both already fully-initialized -- just copy straight across
152 *destWrapper
= *srcWrapper
;
154 } else if (destWrapper
) {
155 // fully-initialized dest, barely-initialized src -- clear dest
157 aDest
.mU
.mPtr
= destWrapper
= nullptr;
158 } // else, both are barely-initialized -- nothing to do.
163 bool SMILCSSValueType::IsEqual(const SMILValue
& aLeft
,
164 const SMILValue
& aRight
) const {
165 MOZ_ASSERT(aLeft
.mType
== aRight
.mType
, "Incompatible SMIL types");
166 MOZ_ASSERT(aLeft
.mType
== this, "Unexpected SMIL value");
167 const ValueWrapper
* leftWrapper
= ExtractValueWrapper(aLeft
);
168 const ValueWrapper
* rightWrapper
= ExtractValueWrapper(aRight
);
173 NS_WARNING_ASSERTION(leftWrapper
!= rightWrapper
,
174 "Two SMILValues with matching ValueWrapper ptr");
175 return *leftWrapper
== *rightWrapper
;
177 // Left non-null, right null
181 // Left null, right non-null
188 static bool AddOrAccumulateForServo(SMILValue
& aDest
,
189 const ValueWrapper
* aValueToAddWrapper
,
190 ValueWrapper
* aDestWrapper
,
191 CompositeOperation aCompositeOp
,
193 nsCSSPropertyID property
=
194 aValueToAddWrapper
? aValueToAddWrapper
->mPropID
: aDestWrapper
->mPropID
;
195 size_t len
= aValueToAddWrapper
? aValueToAddWrapper
->mServoValues
.Length()
196 : aDestWrapper
->mServoValues
.Length();
198 MOZ_ASSERT(!aValueToAddWrapper
|| !aDestWrapper
||
199 aValueToAddWrapper
->mServoValues
.Length() ==
200 aDestWrapper
->mServoValues
.Length(),
201 "Both of values' length in the wrappers should be the same if "
202 "both of them exist");
204 for (size_t i
= 0; i
< len
; i
++) {
205 const RefPtr
<StyleAnimationValue
>* valueToAdd
=
206 aValueToAddWrapper
? &aValueToAddWrapper
->mServoValues
[i
] : nullptr;
207 const RefPtr
<StyleAnimationValue
>* destValue
=
208 aDestWrapper
? &aDestWrapper
->mServoValues
[i
] : nullptr;
209 RefPtr
<StyleAnimationValue
> zeroValueStorage
;
210 if (!FinalizeServoAnimationValues(valueToAdd
, destValue
,
215 // FinalizeServoAnimationValues may have updated destValue so we should make
216 // sure the aDest and aDestWrapper outparams are up-to-date.
218 aDestWrapper
->mServoValues
[i
] = *destValue
;
220 // aDest may be a barely-initialized "zero" destination.
221 aDest
.mU
.mPtr
= aDestWrapper
= new ValueWrapper(property
, *destValue
);
222 aDestWrapper
->mServoValues
.SetLength(len
);
225 RefPtr
<StyleAnimationValue
> result
;
226 if (aCompositeOp
== CompositeOperation::Add
) {
227 result
= Servo_AnimationValues_Add(*destValue
, *valueToAdd
).Consume();
229 result
= Servo_AnimationValues_Accumulate(*destValue
, *valueToAdd
, aCount
)
236 aDestWrapper
->mServoValues
[i
] = result
;
242 static bool AddOrAccumulate(SMILValue
& aDest
, const SMILValue
& aValueToAdd
,
243 CompositeOperation aCompositeOp
, uint64_t aCount
) {
244 MOZ_ASSERT(aValueToAdd
.mType
== aDest
.mType
,
245 "Trying to add mismatching types");
246 MOZ_ASSERT(aValueToAdd
.mType
== &SMILCSSValueType::sSingleton
,
247 "Unexpected SMIL value type");
248 MOZ_ASSERT(aCompositeOp
== CompositeOperation::Add
||
249 aCompositeOp
== CompositeOperation::Accumulate
,
250 "Composite operation should be add or accumulate");
251 MOZ_ASSERT(aCompositeOp
!= CompositeOperation::Add
|| aCount
== 1,
252 "Count should be 1 if composite operation is add");
254 ValueWrapper
* destWrapper
= ExtractValueWrapper(aDest
);
255 const ValueWrapper
* valueToAddWrapper
= ExtractValueWrapper(aValueToAdd
);
257 // If both of the values are empty just fail. This can happen in rare cases
258 // such as when the underlying animation produced an empty value.
260 // Technically, it doesn't matter what we return here since in either case it
261 // will produce the same result: an empty value.
262 if (!destWrapper
&& !valueToAddWrapper
) {
266 nsCSSPropertyID property
=
267 valueToAddWrapper
? valueToAddWrapper
->mPropID
: destWrapper
->mPropID
;
268 // Special case: font-size-adjust and stroke-dasharray are explicitly
269 // non-additive (even though StyleAnimationValue *could* support adding them)
270 if (property
== eCSSProperty_font_size_adjust
||
271 property
== eCSSProperty_stroke_dasharray
) {
274 // Skip font shorthand since it includes font-size-adjust.
275 if (property
== eCSSProperty_font
) {
279 return AddOrAccumulateForServo(aDest
, valueToAddWrapper
, destWrapper
,
280 aCompositeOp
, aCount
);
283 nsresult
SMILCSSValueType::SandwichAdd(SMILValue
& aDest
,
284 const SMILValue
& aValueToAdd
) const {
285 return AddOrAccumulate(aDest
, aValueToAdd
, CompositeOperation::Add
, 1)
290 nsresult
SMILCSSValueType::Add(SMILValue
& aDest
, const SMILValue
& aValueToAdd
,
291 uint32_t aCount
) const {
292 return AddOrAccumulate(aDest
, aValueToAdd
, CompositeOperation::Accumulate
,
298 static nsresult
ComputeDistanceForServo(const ValueWrapper
* aFromWrapper
,
299 const ValueWrapper
& aToWrapper
,
301 size_t len
= aToWrapper
.mServoValues
.Length();
302 MOZ_ASSERT(!aFromWrapper
|| aFromWrapper
->mServoValues
.Length() == len
,
303 "From and to values length should be the same if "
304 "The start value exists");
306 double squareDistance
= 0;
308 for (size_t i
= 0; i
< len
; i
++) {
309 const RefPtr
<StyleAnimationValue
>* fromValue
=
310 aFromWrapper
? &aFromWrapper
->mServoValues
[0] : nullptr;
311 const RefPtr
<StyleAnimationValue
>* toValue
= &aToWrapper
.mServoValues
[0];
312 RefPtr
<StyleAnimationValue
> zeroValueStorage
;
313 if (!FinalizeServoAnimationValues(fromValue
, toValue
, zeroValueStorage
)) {
314 return NS_ERROR_FAILURE
;
318 Servo_AnimationValues_ComputeDistance(*fromValue
, *toValue
);
319 if (distance
< 0.0) {
320 return NS_ERROR_FAILURE
;
324 aDistance
= distance
;
327 squareDistance
+= distance
* distance
;
330 aDistance
= sqrt(squareDistance
);
335 nsresult
SMILCSSValueType::ComputeDistance(const SMILValue
& aFrom
,
336 const SMILValue
& aTo
,
337 double& aDistance
) const {
338 MOZ_ASSERT(aFrom
.mType
== aTo
.mType
, "Trying to compare different types");
339 MOZ_ASSERT(aFrom
.mType
== this, "Unexpected source type");
341 const ValueWrapper
* fromWrapper
= ExtractValueWrapper(aFrom
);
342 const ValueWrapper
* toWrapper
= ExtractValueWrapper(aTo
);
343 MOZ_ASSERT(toWrapper
, "expecting non-null endpoint");
344 return ComputeDistanceForServo(fromWrapper
, *toWrapper
, aDistance
);
347 static nsresult
InterpolateForServo(const ValueWrapper
* aStartWrapper
,
348 const ValueWrapper
& aEndWrapper
,
349 double aUnitDistance
, SMILValue
& aResult
) {
350 // For discretely-animated properties Servo_AnimationValues_Interpolate will
351 // perform the discrete animation (i.e. 50% flip) and return a success result.
352 // However, SMIL has its own special discrete animation behavior that it uses
353 // when keyTimes are specified, but we won't run that unless that this method
354 // returns a failure to indicate that the property cannot be smoothly
355 // interpolated, i.e. that we need to use a discrete calcMode.
357 // For shorthands, Servo_Property_IsDiscreteAnimatable will always return
358 // false. That's fine since most shorthands (like 'font' and
359 // 'text-decoration') include non-discrete components. If authors want to
360 // treat all components as discrete then they should use calcMode="discrete".
361 if (Servo_Property_IsDiscreteAnimatable(aEndWrapper
.mPropID
)) {
362 return NS_ERROR_FAILURE
;
365 ServoAnimationValues results
;
366 size_t len
= aEndWrapper
.mServoValues
.Length();
367 results
.SetCapacity(len
);
368 MOZ_ASSERT(!aStartWrapper
|| aStartWrapper
->mServoValues
.Length() == len
,
369 "Start and end values length should be the same if "
370 "the start value exists");
371 for (size_t i
= 0; i
< len
; i
++) {
372 const RefPtr
<StyleAnimationValue
>* startValue
=
373 aStartWrapper
? &aStartWrapper
->mServoValues
[i
] : nullptr;
374 const RefPtr
<StyleAnimationValue
>* endValue
= &aEndWrapper
.mServoValues
[i
];
375 RefPtr
<StyleAnimationValue
> zeroValueStorage
;
376 if (!FinalizeServoAnimationValues(startValue
, endValue
, zeroValueStorage
)) {
377 return NS_ERROR_FAILURE
;
380 RefPtr
<StyleAnimationValue
> result
=
381 Servo_AnimationValues_Interpolate(*startValue
, *endValue
, aUnitDistance
)
384 return NS_ERROR_FAILURE
;
386 results
.AppendElement(result
);
388 aResult
.mU
.mPtr
= new ValueWrapper(aEndWrapper
.mPropID
, std::move(results
));
393 nsresult
SMILCSSValueType::Interpolate(const SMILValue
& aStartVal
,
394 const SMILValue
& aEndVal
,
395 double aUnitDistance
,
396 SMILValue
& aResult
) const {
397 MOZ_ASSERT(aStartVal
.mType
== aEndVal
.mType
,
398 "Trying to interpolate different types");
399 MOZ_ASSERT(aStartVal
.mType
== this, "Unexpected types for interpolation");
400 MOZ_ASSERT(aResult
.mType
== this, "Unexpected result type");
401 MOZ_ASSERT(aUnitDistance
>= 0.0 && aUnitDistance
<= 1.0,
402 "unit distance value out of bounds");
403 MOZ_ASSERT(!aResult
.mU
.mPtr
, "expecting barely-initialized outparam");
405 const ValueWrapper
* startWrapper
= ExtractValueWrapper(aStartVal
);
406 const ValueWrapper
* endWrapper
= ExtractValueWrapper(aEndVal
);
407 MOZ_ASSERT(endWrapper
, "expecting non-null endpoint");
408 return InterpolateForServo(startWrapper
, *endWrapper
, aUnitDistance
, aResult
);
411 static ServoAnimationValues
ValueFromStringHelper(
412 nsCSSPropertyID aPropID
, Element
* aTargetElement
,
413 nsPresContext
* aPresContext
, const ComputedStyle
* aComputedStyle
,
414 const nsAString
& aString
) {
415 ServoAnimationValues result
;
417 Document
* doc
= aTargetElement
->GetComposedDoc();
423 ServoCSSParser::ParsingEnvironment env
=
424 ServoCSSParser::GetParsingEnvironment(doc
);
425 RefPtr
<StyleLockedDeclarationBlock
> servoDeclarationBlock
=
426 ServoCSSParser::ParseProperty(aPropID
, NS_ConvertUTF16toUTF8(aString
),
428 ParsingMode::AllowUnitlessLength
|
429 ParsingMode::AllowAllNumericValues
);
430 if (!servoDeclarationBlock
) {
435 aPresContext
->StyleSet()->GetAnimationValues(
436 servoDeclarationBlock
, aTargetElement
, aComputedStyle
, result
);
442 void SMILCSSValueType::ValueFromString(nsCSSPropertyID aPropID
,
443 Element
* aTargetElement
,
444 const nsAString
& aString
,
446 bool* aIsContextSensitive
) {
447 MOZ_ASSERT(aValue
.IsNull(), "Outparam should be null-typed");
448 nsPresContext
* presContext
=
449 nsContentUtils::GetContextForContent(aTargetElement
);
451 NS_WARNING("Not parsing animation value; unable to get PresContext");
455 Document
* doc
= aTargetElement
->GetComposedDoc();
456 if (doc
&& !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc
, nullptr, 0, 0,
461 RefPtr
<const ComputedStyle
> computedStyle
=
462 nsComputedDOMStyle::GetComputedStyle(aTargetElement
);
463 if (!computedStyle
) {
467 ServoAnimationValues parsedValues
= ValueFromStringHelper(
468 aPropID
, aTargetElement
, presContext
, computedStyle
, aString
);
469 if (aIsContextSensitive
) {
470 // FIXME: Bug 1358955 - detect context-sensitive values and set this value
472 *aIsContextSensitive
= false;
475 if (!parsedValues
.IsEmpty()) {
476 sSingleton
.Init(aValue
);
477 aValue
.mU
.mPtr
= new ValueWrapper(aPropID
, std::move(parsedValues
));
482 SMILValue
SMILCSSValueType::ValueFromAnimationValue(
483 nsCSSPropertyID aPropID
, Element
* aTargetElement
,
484 const AnimationValue
& aValue
) {
487 Document
* doc
= aTargetElement
->GetComposedDoc();
488 // We'd like to avoid serializing |aValue| if possible, and since the
489 // string passed to CSPAllowsInlineStyle is only used for reporting violations
490 // and an intermediate CSS value is not likely to be particularly useful
491 // in that case, we just use a generic placeholder string instead.
492 static const nsLiteralString kPlaceholderText
= u
"[SVG animation of CSS]"_ns
;
493 if (doc
&& !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc
, nullptr, 0, 0,
494 kPlaceholderText
, nullptr)) {
498 sSingleton
.Init(result
);
499 result
.mU
.mPtr
= new ValueWrapper(aPropID
, aValue
);
505 bool SMILCSSValueType::SetPropertyValues(const SMILValue
& aValue
,
506 DeclarationBlock
& aDecl
) {
507 MOZ_ASSERT(aValue
.mType
== &SMILCSSValueType::sSingleton
,
508 "Unexpected SMIL value type");
509 const ValueWrapper
* wrapper
= ExtractValueWrapper(aValue
);
514 bool changed
= false;
515 for (const auto& value
: wrapper
->mServoValues
) {
516 changed
|= Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl
.Raw(),
524 nsCSSPropertyID
SMILCSSValueType::PropertyFromValue(const SMILValue
& aValue
) {
525 if (aValue
.mType
!= &SMILCSSValueType::sSingleton
) {
526 return eCSSProperty_UNKNOWN
;
529 const ValueWrapper
* wrapper
= ExtractValueWrapper(aValue
);
531 return eCSSProperty_UNKNOWN
;
534 return wrapper
->mPropID
;
538 void SMILCSSValueType::FinalizeValue(SMILValue
& aValue
,
539 const SMILValue
& aValueToMatch
) {
540 MOZ_ASSERT(aValue
.mType
== aValueToMatch
.mType
, "Incompatible SMIL types");
541 MOZ_ASSERT(aValue
.mType
== &SMILCSSValueType::sSingleton
,
542 "Unexpected SMIL value type");
544 ValueWrapper
* valueWrapper
= ExtractValueWrapper(aValue
);
545 // If |aValue| already has a value, there's nothing to do here.
550 const ValueWrapper
* valueToMatchWrapper
= ExtractValueWrapper(aValueToMatch
);
551 if (!valueToMatchWrapper
) {
552 MOZ_ASSERT_UNREACHABLE("Value to match is empty");
556 ServoAnimationValues zeroValues
;
557 zeroValues
.SetCapacity(valueToMatchWrapper
->mServoValues
.Length());
559 for (const auto& valueToMatch
: valueToMatchWrapper
->mServoValues
) {
560 RefPtr
<StyleAnimationValue
> zeroValue
=
561 Servo_AnimationValues_GetZeroValue(valueToMatch
).Consume();
565 zeroValues
.AppendElement(std::move(zeroValue
));
568 new ValueWrapper(valueToMatchWrapper
->mPropID
, std::move(zeroValues
));
571 } // namespace mozilla