Bug 1835710 - Cancel off-thread JIT compilation before changing nursery allocation...
[gecko.git] / dom / smil / SMILCSSValueType.cpp
bloba93aeffffa0a77268d8921f515d339e7842e864c
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"
12 #include "nsColor.h"
13 #include "nsCSSProps.h"
14 #include "nsCSSValue.h"
15 #include "nsDebug.h"
16 #include "nsPresContextInlines.h"
17 #include "nsPresContext.h"
18 #include "nsString.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;
35 namespace mozilla {
37 using ServoAnimationValues = CopyableAutoTArray<RefPtr<StyleAnimationValue>, 1>;
39 /*static*/
40 SMILCSSValueType SMILCSSValueType::sSingleton;
42 struct ValueWrapper {
43 ValueWrapper(nsCSSPropertyID aPropID, const AnimationValue& aValue)
44 : mPropID(aPropID) {
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) {
56 return false;
59 MOZ_ASSERT(!mServoValues.IsEmpty());
60 size_t len = mServoValues.Length();
61 if (len != aOther.mServoValues.Length()) {
62 return false;
64 for (size_t i = 0; i < len; i++) {
65 if (!Servo_AnimationValue_DeepEqual(mServoValues[i],
66 aOther.mServoValues[i])) {
67 return false;
70 return true;
73 bool operator!=(const ValueWrapper& aOther) const {
74 return !(*this == aOther);
77 nsCSSPropertyID mPropID;
78 ServoAnimationValues mServoValues;
81 // Helper Methods
82 // --------------
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) {
101 return false;
104 // Are we missing either val? (If so, it's an implied 0 in other val's units)
106 if (!aValue1) {
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);
124 // Class methods
125 // -------------
126 void SMILCSSValueType::Init(SMILValue& aValue) const {
127 MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL value type");
129 aValue.mU.mPtr = nullptr;
130 aValue.mType = this;
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);
146 if (srcWrapper) {
147 if (!destWrapper) {
148 // barely-initialized dest -- need to alloc & copy
149 aDest.mU.mPtr = new ValueWrapper(*srcWrapper);
150 } else {
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
156 delete destWrapper;
157 aDest.mU.mPtr = destWrapper = nullptr;
158 } // else, both are barely-initialized -- nothing to do.
160 return NS_OK;
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);
170 if (leftWrapper) {
171 if (rightWrapper) {
172 // Both non-null
173 NS_WARNING_ASSERTION(leftWrapper != rightWrapper,
174 "Two SMILValues with matching ValueWrapper ptr");
175 return *leftWrapper == *rightWrapper;
177 // Left non-null, right null
178 return false;
180 if (rightWrapper) {
181 // Left null, right non-null
182 return false;
184 // Both null
185 return true;
188 static bool AddOrAccumulateForServo(SMILValue& aDest,
189 const ValueWrapper* aValueToAddWrapper,
190 ValueWrapper* aDestWrapper,
191 CompositeOperation aCompositeOp,
192 uint64_t aCount) {
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,
211 zeroValueStorage)) {
212 return false;
215 // FinalizeServoAnimationValues may have updated destValue so we should make
216 // sure the aDest and aDestWrapper outparams are up-to-date.
217 if (aDestWrapper) {
218 aDestWrapper->mServoValues[i] = *destValue;
219 } else {
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();
228 } else {
229 result = Servo_AnimationValues_Accumulate(*destValue, *valueToAdd, aCount)
230 .Consume();
233 if (!result) {
234 return false;
236 aDestWrapper->mServoValues[i] = result;
239 return true;
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) {
263 return false;
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) {
272 return false;
274 // Skip font shorthand since it includes font-size-adjust.
275 if (property == eCSSProperty_font) {
276 return false;
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)
286 ? NS_OK
287 : NS_ERROR_FAILURE;
290 nsresult SMILCSSValueType::Add(SMILValue& aDest, const SMILValue& aValueToAdd,
291 uint32_t aCount) const {
292 return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Accumulate,
293 aCount)
294 ? NS_OK
295 : NS_ERROR_FAILURE;
298 static nsresult ComputeDistanceForServo(const ValueWrapper* aFromWrapper,
299 const ValueWrapper& aToWrapper,
300 double& aDistance) {
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;
317 double distance =
318 Servo_AnimationValues_ComputeDistance(*fromValue, *toValue);
319 if (distance < 0.0) {
320 return NS_ERROR_FAILURE;
323 if (len == 1) {
324 aDistance = distance;
325 return NS_OK;
327 squareDistance += distance * distance;
330 aDistance = sqrt(squareDistance);
332 return NS_OK;
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)
382 .Consume();
383 if (!result) {
384 return NS_ERROR_FAILURE;
386 results.AppendElement(result);
388 aResult.mU.mPtr = new ValueWrapper(aEndWrapper.mPropID, std::move(results));
390 return NS_OK;
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();
418 if (!doc) {
419 return result;
422 // Parse property
423 ServoCSSParser::ParsingEnvironment env =
424 ServoCSSParser::GetParsingEnvironment(doc);
425 RefPtr<StyleLockedDeclarationBlock> servoDeclarationBlock =
426 ServoCSSParser::ParseProperty(aPropID, NS_ConvertUTF16toUTF8(aString),
427 env,
428 ParsingMode::AllowUnitlessLength |
429 ParsingMode::AllowAllNumericValues);
430 if (!servoDeclarationBlock) {
431 return result;
434 // Compute value
435 aPresContext->StyleSet()->GetAnimationValues(
436 servoDeclarationBlock, aTargetElement, aComputedStyle, result);
438 return result;
441 // static
442 void SMILCSSValueType::ValueFromString(nsCSSPropertyID aPropID,
443 Element* aTargetElement,
444 const nsAString& aString,
445 SMILValue& aValue,
446 bool* aIsContextSensitive) {
447 MOZ_ASSERT(aValue.IsNull(), "Outparam should be null-typed");
448 nsPresContext* presContext =
449 nsContentUtils::GetContextForContent(aTargetElement);
450 if (!presContext) {
451 NS_WARNING("Not parsing animation value; unable to get PresContext");
452 return;
455 Document* doc = aTargetElement->GetComposedDoc();
456 if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc, nullptr, 0, 0,
457 aString, nullptr)) {
458 return;
461 RefPtr<const ComputedStyle> computedStyle =
462 nsComputedDOMStyle::GetComputedStyle(aTargetElement);
463 if (!computedStyle) {
464 return;
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
471 // appropriately.
472 *aIsContextSensitive = false;
475 if (!parsedValues.IsEmpty()) {
476 sSingleton.Init(aValue);
477 aValue.mU.mPtr = new ValueWrapper(aPropID, std::move(parsedValues));
481 // static
482 SMILValue SMILCSSValueType::ValueFromAnimationValue(
483 nsCSSPropertyID aPropID, Element* aTargetElement,
484 const AnimationValue& aValue) {
485 SMILValue result;
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)) {
495 return result;
498 sSingleton.Init(result);
499 result.mU.mPtr = new ValueWrapper(aPropID, aValue);
501 return result;
504 // static
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);
510 if (!wrapper) {
511 return false;
514 bool changed = false;
515 for (const auto& value : wrapper->mServoValues) {
516 changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl.Raw(),
517 value, {});
520 return changed;
523 // static
524 nsCSSPropertyID SMILCSSValueType::PropertyFromValue(const SMILValue& aValue) {
525 if (aValue.mType != &SMILCSSValueType::sSingleton) {
526 return eCSSProperty_UNKNOWN;
529 const ValueWrapper* wrapper = ExtractValueWrapper(aValue);
530 if (!wrapper) {
531 return eCSSProperty_UNKNOWN;
534 return wrapper->mPropID;
537 // static
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.
546 if (valueWrapper) {
547 return;
550 const ValueWrapper* valueToMatchWrapper = ExtractValueWrapper(aValueToMatch);
551 if (!valueToMatchWrapper) {
552 MOZ_ASSERT_UNREACHABLE("Value to match is empty");
553 return;
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();
562 if (!zeroValue) {
563 return;
565 zeroValues.AppendElement(std::move(zeroValue));
567 aValue.mU.mPtr =
568 new ValueWrapper(valueToMatchWrapper->mPropID, std::move(zeroValues));
571 } // namespace mozilla