SMILCSSValueType.cpp (19853B)
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 "SMILCSSValueType.h" 10 11 #include "mozilla/DeclarationBlock.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/PresShellInlines.h" 14 #include "mozilla/SMILParserUtils.h" 15 #include "mozilla/SMILValue.h" 16 #include "mozilla/ServoBindings.h" 17 #include "mozilla/ServoCSSParser.h" 18 #include "mozilla/ServoStyleSet.h" 19 #include "mozilla/StyleAnimationValue.h" 20 #include "mozilla/dom/BaseKeyframeTypesBinding.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/Element.h" 23 #include "nsCSSProps.h" 24 #include "nsCSSValue.h" 25 #include "nsColor.h" 26 #include "nsComputedDOMStyle.h" 27 #include "nsDebug.h" 28 #include "nsPresContext.h" 29 #include "nsPresContextInlines.h" 30 #include "nsString.h" 31 #include "nsStyleUtil.h" 32 33 using namespace mozilla::dom; 34 35 namespace mozilla { 36 37 using ServoAnimationValues = CopyableAutoTArray<RefPtr<StyleAnimationValue>, 1>; 38 39 /*static*/ 40 SMILCSSValueType SMILCSSValueType::sSingleton; 41 42 struct ValueWrapper { 43 ValueWrapper(NonCustomCSSPropertyId aPropId, const AnimationValue& aValue) 44 : mPropId(aPropId) { 45 MOZ_ASSERT(!aValue.IsNull()); 46 mServoValues.AppendElement(aValue.mServo); 47 } 48 ValueWrapper(NonCustomCSSPropertyId aPropId, 49 const RefPtr<StyleAnimationValue>& aValue) 50 : mPropId(aPropId), mServoValues{(aValue)} {} 51 ValueWrapper(NonCustomCSSPropertyId aPropId, ServoAnimationValues&& aValues) 52 : mPropId(aPropId), mServoValues{std::move(aValues)} {} 53 54 bool operator==(const ValueWrapper& aOther) const { 55 if (mPropId != aOther.mPropId) { 56 return false; 57 } 58 59 MOZ_ASSERT(!mServoValues.IsEmpty()); 60 size_t len = mServoValues.Length(); 61 if (len != aOther.mServoValues.Length()) { 62 return false; 63 } 64 for (size_t i = 0; i < len; i++) { 65 if (!Servo_AnimationValue_DeepEqual(mServoValues[i], 66 aOther.mServoValues[i])) { 67 return false; 68 } 69 } 70 return true; 71 } 72 73 bool operator!=(const ValueWrapper& aOther) const { 74 return !(*this == aOther); 75 } 76 77 NonCustomCSSPropertyId mPropId; 78 ServoAnimationValues mServoValues; 79 }; 80 81 // Helper Methods 82 // -------------- 83 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). 86 // 87 // If neither argument is null, this method simply returns true. 88 // 89 // If both arguments are null, this method returns false. 90 // 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. 94 // 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; 102 } 103 104 // Are we missing either val? (If so, it's an implied 0 in other val's units) 105 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; 112 } 113 return *aValue1 && *aValue2; 114 } 115 116 static ValueWrapper* ExtractValueWrapper(SMILValue& aValue) { 117 return static_cast<ValueWrapper*>(aValue.mU.mPtr); 118 } 119 120 static const ValueWrapper* ExtractValueWrapper(const SMILValue& aValue) { 121 return static_cast<const ValueWrapper*>(aValue.mU.mPtr); 122 } 123 124 // Class methods 125 // ------------- 126 void SMILCSSValueType::InitValue(SMILValue& aValue) const { 127 MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL value type"); 128 129 aValue.mU.mPtr = nullptr; 130 aValue.mType = this; 131 } 132 133 void SMILCSSValueType::DestroyValue(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(); 137 } 138 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); 145 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; 153 } 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. 159 160 return NS_OK; 161 } 162 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); 169 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; 176 } 177 // Left non-null, right null 178 return false; 179 } 180 if (rightWrapper) { 181 // Left null, right non-null 182 return false; 183 } 184 // Both null 185 return true; 186 } 187 188 static bool AddOrAccumulate(SMILValue& aDest, const SMILValue& aValueToAdd, 189 CompositeOperation aCompositeOp, uint64_t aCount) { 190 MOZ_ASSERT(aValueToAdd.mType == aDest.mType, 191 "Trying to add mismatching types"); 192 MOZ_ASSERT(aValueToAdd.mType == &SMILCSSValueType::sSingleton, 193 "Unexpected SMIL value type"); 194 MOZ_ASSERT(aCompositeOp == CompositeOperation::Add || 195 aCompositeOp == CompositeOperation::Accumulate, 196 "Composite operation should be add or accumulate"); 197 MOZ_ASSERT(aCompositeOp != CompositeOperation::Add || aCount == 1, 198 "Count should be 1 if composite operation is add"); 199 200 ValueWrapper* destWrapper = ExtractValueWrapper(aDest); 201 const ValueWrapper* valueToAddWrapper = ExtractValueWrapper(aValueToAdd); 202 203 // If both of the values are empty just fail. This can happen in rare cases 204 // such as when the underlying animation produced an empty value. 205 // 206 // Technically, it doesn't matter what we return here since in either case it 207 // will produce the same result: an empty value. 208 if (!destWrapper && !valueToAddWrapper) { 209 return false; 210 } 211 212 NonCustomCSSPropertyId property = 213 valueToAddWrapper ? valueToAddWrapper->mPropId : destWrapper->mPropId; 214 // Special case: font-size-adjust and stroke-dasharray are explicitly 215 // non-additive (even though StyleAnimationValue *could* support adding them) 216 if (property == eCSSProperty_font_size_adjust || 217 property == eCSSProperty_stroke_dasharray) { 218 return false; 219 } 220 // Skip font shorthand since it includes font-size-adjust. 221 if (property == eCSSProperty_font) { 222 return false; 223 } 224 225 size_t len = valueToAddWrapper ? valueToAddWrapper->mServoValues.Length() 226 : destWrapper->mServoValues.Length(); 227 228 MOZ_ASSERT(!valueToAddWrapper || !destWrapper || 229 valueToAddWrapper->mServoValues.Length() == 230 destWrapper->mServoValues.Length(), 231 "Both of values' length in the wrappers should be the same if " 232 "both of them exist"); 233 234 for (size_t i = 0; i < len; i++) { 235 const RefPtr<StyleAnimationValue>* valueToAdd = 236 valueToAddWrapper ? &valueToAddWrapper->mServoValues[i] : nullptr; 237 const RefPtr<StyleAnimationValue>* destValue = 238 destWrapper ? &destWrapper->mServoValues[i] : nullptr; 239 RefPtr<StyleAnimationValue> zeroValueStorage; 240 if (!FinalizeServoAnimationValues(valueToAdd, destValue, 241 zeroValueStorage)) { 242 return false; 243 } 244 245 // FinalizeServoAnimationValues may have updated destValue so we should make 246 // sure the aDest and aDestWrapper outparams are up-to-date. 247 if (destWrapper) { 248 destWrapper->mServoValues[i] = *destValue; 249 } else { 250 // aDest may be a barely-initialized "zero" destination. 251 aDest.mU.mPtr = destWrapper = new ValueWrapper(property, *destValue); 252 destWrapper->mServoValues.SetLength(len); 253 } 254 255 RefPtr<StyleAnimationValue> result; 256 if (aCompositeOp == CompositeOperation::Add) { 257 result = Servo_AnimationValues_Add(*destValue, *valueToAdd).Consume(); 258 } else { 259 result = Servo_AnimationValues_Accumulate(*destValue, *valueToAdd, aCount) 260 .Consume(); 261 } 262 263 if (!result) { 264 return false; 265 } 266 destWrapper->mServoValues[i] = result; 267 } 268 269 return true; 270 } 271 272 nsresult SMILCSSValueType::SandwichAdd(SMILValue& aDest, 273 const SMILValue& aValueToAdd) const { 274 return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Add, 1) 275 ? NS_OK 276 : NS_ERROR_FAILURE; 277 } 278 279 nsresult SMILCSSValueType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, 280 uint32_t aCount) const { 281 return AddOrAccumulate(aDest, aValueToAdd, CompositeOperation::Accumulate, 282 aCount) 283 ? NS_OK 284 : NS_ERROR_FAILURE; 285 } 286 287 nsresult SMILCSSValueType::ComputeDistance(const SMILValue& aFrom, 288 const SMILValue& aTo, 289 double& aDistance) const { 290 MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); 291 MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); 292 293 const ValueWrapper* fromWrapper = ExtractValueWrapper(aFrom); 294 const ValueWrapper* toWrapper = ExtractValueWrapper(aTo); 295 MOZ_ASSERT(toWrapper, "expecting non-null endpoint"); 296 297 size_t len = toWrapper->mServoValues.Length(); 298 MOZ_ASSERT(!fromWrapper || fromWrapper->mServoValues.Length() == len, 299 "From and to values length should be the same if " 300 "The start value exists"); 301 302 double squareDistance = 0; 303 304 for (size_t i = 0; i < len; i++) { 305 const RefPtr<StyleAnimationValue>* fromValue = 306 fromWrapper ? &fromWrapper->mServoValues[i] : nullptr; 307 const RefPtr<StyleAnimationValue>* toValue = &toWrapper->mServoValues[i]; 308 RefPtr<StyleAnimationValue> zeroValueStorage; 309 if (!FinalizeServoAnimationValues(fromValue, toValue, zeroValueStorage)) { 310 return NS_ERROR_FAILURE; 311 } 312 313 double distance = 314 Servo_AnimationValues_ComputeDistance(*fromValue, *toValue); 315 if (distance < 0.0) { 316 return NS_ERROR_FAILURE; 317 } 318 319 if (len == 1) { 320 aDistance = distance; 321 return NS_OK; 322 } 323 squareDistance += distance * distance; 324 } 325 326 aDistance = sqrt(squareDistance); 327 328 return NS_OK; 329 } 330 331 nsresult SMILCSSValueType::Interpolate(const SMILValue& aStartVal, 332 const SMILValue& aEndVal, 333 double aUnitDistance, 334 SMILValue& aResult) const { 335 MOZ_ASSERT(aStartVal.mType == aEndVal.mType, 336 "Trying to interpolate different types"); 337 MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); 338 MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); 339 MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, 340 "unit distance value out of bounds"); 341 MOZ_ASSERT(!aResult.mU.mPtr, "expecting barely-initialized outparam"); 342 343 const ValueWrapper* startWrapper = ExtractValueWrapper(aStartVal); 344 const ValueWrapper* endWrapper = ExtractValueWrapper(aEndVal); 345 MOZ_ASSERT(endWrapper, "expecting non-null endpoint"); 346 347 // For discretely-animated properties Servo_AnimationValues_Interpolate will 348 // perform the discrete animation (i.e. 50% flip) and return a success result. 349 // However, SMIL has its own special discrete animation behavior that it uses 350 // when keyTimes are specified, but we won't run that unless that this method 351 // returns a failure to indicate that the property cannot be smoothly 352 // interpolated, i.e. that we need to use a discrete calcMode. 353 // 354 // For shorthands, Servo_Property_IsDiscreteAnimatable will always return 355 // false. That's fine since most shorthands (like 'font' and 356 // 'text-decoration') include non-discrete components. If authors want to 357 // treat all components as discrete then they should use calcMode="discrete". 358 if (Servo_Property_IsDiscreteAnimatable(endWrapper->mPropId)) { 359 return NS_ERROR_FAILURE; 360 } 361 362 ServoAnimationValues results; 363 size_t len = endWrapper->mServoValues.Length(); 364 results.SetCapacity(len); 365 MOZ_ASSERT(!startWrapper || startWrapper->mServoValues.Length() == len, 366 "Start and end values length should be the same if " 367 "the start value exists"); 368 for (size_t i = 0; i < len; i++) { 369 const RefPtr<StyleAnimationValue>* startValue = 370 startWrapper ? &startWrapper->mServoValues[i] : nullptr; 371 const RefPtr<StyleAnimationValue>* endValue = &endWrapper->mServoValues[i]; 372 RefPtr<StyleAnimationValue> zeroValueStorage; 373 if (!FinalizeServoAnimationValues(startValue, endValue, zeroValueStorage)) { 374 return NS_ERROR_FAILURE; 375 } 376 377 RefPtr<StyleAnimationValue> result = 378 Servo_AnimationValues_Interpolate(*startValue, *endValue, aUnitDistance) 379 .Consume(); 380 if (!result) { 381 return NS_ERROR_FAILURE; 382 } 383 results.AppendElement(result); 384 } 385 aResult.mU.mPtr = new ValueWrapper(endWrapper->mPropId, std::move(results)); 386 387 return NS_OK; 388 } 389 390 static ServoAnimationValues ValueFromStringHelper( 391 NonCustomCSSPropertyId aPropId, Element* aTargetElement, 392 nsPresContext* aPresContext, const ComputedStyle* aComputedStyle, 393 const nsAString& aString) { 394 ServoAnimationValues result; 395 396 Document* doc = aTargetElement->GetComposedDoc(); 397 if (!doc) { 398 return result; 399 } 400 401 // Parse property 402 ServoCSSParser::ParsingEnvironment env = 403 ServoCSSParser::GetParsingEnvironment(doc); 404 RefPtr<StyleLockedDeclarationBlock> servoDeclarationBlock = 405 ServoCSSParser::ParseProperty( 406 aPropId, NS_ConvertUTF16toUTF8(aString), env, 407 StyleParsingMode::ALLOW_UNITLESS_LENGTH | 408 StyleParsingMode::ALLOW_ALL_NUMERIC_VALUES); 409 if (!servoDeclarationBlock) { 410 return result; 411 } 412 413 // Compute value 414 aPresContext->StyleSet()->GetAnimationValues( 415 servoDeclarationBlock, aTargetElement, aComputedStyle, result); 416 417 return result; 418 } 419 420 // static 421 void SMILCSSValueType::ValueFromString(NonCustomCSSPropertyId aPropId, 422 Element* aTargetElement, 423 const nsAString& aString, 424 SMILValue& aValue, 425 bool* aIsContextSensitive) { 426 MOZ_ASSERT(aValue.IsNull(), "Outparam should be null-typed"); 427 nsPresContext* presContext = 428 nsContentUtils::GetContextForContent(aTargetElement); 429 if (!presContext) { 430 NS_WARNING("Not parsing animation value; unable to get PresContext"); 431 return; 432 } 433 434 Document* doc = aTargetElement->GetComposedDoc(); 435 if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc, nullptr, 0, 1, 436 aString, nullptr)) { 437 return; 438 } 439 440 RefPtr<const ComputedStyle> computedStyle = 441 nsComputedDOMStyle::GetComputedStyle(aTargetElement); 442 if (!computedStyle) { 443 return; 444 } 445 446 ServoAnimationValues parsedValues = ValueFromStringHelper( 447 aPropId, aTargetElement, presContext, computedStyle, aString); 448 if (aIsContextSensitive) { 449 // FIXME: Bug 1358955 - detect context-sensitive values and set this value 450 // appropriately. 451 *aIsContextSensitive = false; 452 } 453 454 if (!parsedValues.IsEmpty()) { 455 sSingleton.InitValue(aValue); 456 aValue.mU.mPtr = new ValueWrapper(aPropId, std::move(parsedValues)); 457 } 458 } 459 460 // static 461 SMILValue SMILCSSValueType::ValueFromAnimationValue( 462 NonCustomCSSPropertyId aPropId, Element* aTargetElement, 463 const AnimationValue& aValue) { 464 SMILValue result; 465 466 Document* doc = aTargetElement->GetComposedDoc(); 467 // We'd like to avoid serializing |aValue| if possible, and since the 468 // string passed to CSPAllowsInlineStyle is only used for reporting violations 469 // and an intermediate CSS value is not likely to be particularly useful 470 // in that case, we just use a generic placeholder string instead. 471 static const nsLiteralString kPlaceholderText = u"[SVG animation of CSS]"_ns; 472 if (doc && !nsStyleUtil::CSPAllowsInlineStyle(nullptr, doc, nullptr, 0, 1, 473 kPlaceholderText, nullptr)) { 474 return result; 475 } 476 477 sSingleton.InitValue(result); 478 result.mU.mPtr = new ValueWrapper(aPropId, aValue); 479 480 return result; 481 } 482 483 // static 484 bool SMILCSSValueType::SetPropertyValues(NonCustomCSSPropertyId aPropertyId, 485 const SMILValue& aValue, 486 DeclarationBlock& aDecl) { 487 MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton, 488 "Unexpected SMIL value type"); 489 const ValueWrapper* wrapper = ExtractValueWrapper(aValue); 490 if (!wrapper) { 491 return Servo_DeclarationBlock_RemovePropertyById(aDecl.Raw(), aPropertyId, 492 {}); 493 } 494 495 bool changed = false; 496 for (const auto& value : wrapper->mServoValues) { 497 changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(aDecl.Raw(), 498 value, {}); 499 } 500 501 return changed; 502 } 503 504 // static 505 NonCustomCSSPropertyId SMILCSSValueType::PropertyFromValue( 506 const SMILValue& aValue) { 507 if (aValue.mType != &SMILCSSValueType::sSingleton) { 508 return eCSSProperty_UNKNOWN; 509 } 510 511 const ValueWrapper* wrapper = ExtractValueWrapper(aValue); 512 if (!wrapper) { 513 return eCSSProperty_UNKNOWN; 514 } 515 516 return wrapper->mPropId; 517 } 518 519 // static 520 void SMILCSSValueType::FinalizeValue(SMILValue& aValue, 521 const SMILValue& aValueToMatch) { 522 MOZ_ASSERT(aValue.mType == aValueToMatch.mType, "Incompatible SMIL types"); 523 MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton, 524 "Unexpected SMIL value type"); 525 526 ValueWrapper* valueWrapper = ExtractValueWrapper(aValue); 527 // If |aValue| already has a value, there's nothing to do here. 528 if (valueWrapper) { 529 return; 530 } 531 532 const ValueWrapper* valueToMatchWrapper = ExtractValueWrapper(aValueToMatch); 533 if (!valueToMatchWrapper) { 534 MOZ_ASSERT_UNREACHABLE("Value to match is empty"); 535 return; 536 } 537 538 ServoAnimationValues zeroValues; 539 zeroValues.SetCapacity(valueToMatchWrapper->mServoValues.Length()); 540 541 for (const auto& valueToMatch : valueToMatchWrapper->mServoValues) { 542 RefPtr<StyleAnimationValue> zeroValue = 543 Servo_AnimationValues_GetZeroValue(valueToMatch).Consume(); 544 if (!zeroValue) { 545 return; 546 } 547 zeroValues.AppendElement(std::move(zeroValue)); 548 } 549 aValue.mU.mPtr = 550 new ValueWrapper(valueToMatchWrapper->mPropId, std::move(zeroValues)); 551 } 552 553 } // namespace mozilla