EffectCompositor.cpp (35863B)
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 #include "EffectCompositor.h" 8 9 #include "mozilla/AnimationComparator.h" 10 #include "mozilla/AnimationPerformanceWarning.h" 11 #include "mozilla/AnimationTarget.h" 12 #include "mozilla/AnimationUtils.h" 13 #include "mozilla/AutoRestore.h" 14 #include "mozilla/ComputedStyleInlines.h" 15 #include "mozilla/EffectSet.h" 16 #include "mozilla/LayerAnimationInfo.h" 17 #include "mozilla/PresShell.h" 18 #include "mozilla/PresShellInlines.h" 19 #include "mozilla/RestyleManager.h" 20 #include "mozilla/SVGObserverUtils.h" 21 #include "mozilla/ServoBindings.h" // Servo_GetProperties_Overriding_Animation 22 #include "mozilla/ServoStyleSet.h" 23 #include "mozilla/StaticPrefs_layers.h" 24 #include "mozilla/StyleAnimationValue.h" 25 #include "mozilla/dom/Animation.h" 26 #include "mozilla/dom/Element.h" 27 #include "mozilla/dom/KeyframeEffect.h" 28 #include "nsCSSPropertyIDSet.h" 29 #include "nsCSSProps.h" 30 #include "nsComputedDOMStyle.h" 31 #include "nsContentUtils.h" 32 #include "nsDisplayItemTypes.h" 33 #include "nsLayoutUtils.h" 34 #include "nsTArray.h" 35 36 using mozilla::dom::Animation; 37 using mozilla::dom::Element; 38 using mozilla::dom::KeyframeEffect; 39 40 namespace mozilla { 41 42 NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor) 43 44 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor) 45 for (auto& elementSet : tmp->mElementsToRestyle) { 46 elementSet.Clear(); 47 } 48 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 49 50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor) 51 for (const auto& elementSet : tmp->mElementsToRestyle) { 52 for (const auto& key : elementSet.Keys()) { 53 CycleCollectionNoteChild(cb, key.mElement, 54 "EffectCompositor::mElementsToRestyle[]", 55 cb.Flags()); 56 } 57 } 58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 59 60 /* static */ 61 bool EffectCompositor::AllowCompositorAnimationsOnFrame( 62 const nsIFrame* aFrame, 63 AnimationPerformanceWarning::Type& aWarning /* out */) { 64 if (aFrame->RefusedAsyncAnimation()) { 65 return false; 66 } 67 68 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { 69 if (StaticPrefs::layers_offmainthreadcomposition_log_animations()) { 70 nsCString message; 71 message.AppendLiteral( 72 "Performance warning: Async animations are " 73 "disabled"); 74 AnimationUtils::LogAsyncAnimationFailure(message); 75 } 76 return false; 77 } 78 79 // Disable async animations if we have a rendering observer that 80 // depends on our content (svg masking, -moz-element etc) so that 81 // it gets updated correctly. 82 if (SVGObserverUtils::SelfOrAncestorHasRenderingObservers(aFrame)) { 83 aWarning = AnimationPerformanceWarning::Type::HasRenderingObserver; 84 return false; 85 } 86 87 return true; 88 } 89 90 // Helper function to factor out the common logic from 91 // GetAnimationsForCompositor and HasAnimationsForCompositor. 92 // 93 // Takes an optional array to fill with eligible animations. 94 // 95 // Returns true if there are eligible animations, false otherwise. 96 bool FindAnimationsForCompositor( 97 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet, 98 nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/) { 99 // Do not process any animations on the compositor when in print or print 100 // preview. 101 if (aFrame->PresContext()->IsPrintingOrPrintPreview()) { 102 return false; 103 } 104 105 MOZ_ASSERT( 106 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( 107 DisplayItemType::TYPE_TRANSFORM)) || 108 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( 109 DisplayItemType::TYPE_OPACITY)) || 110 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( 111 DisplayItemType::TYPE_BACKGROUND_COLOR)), 112 "Should be the subset of transform-like properties, or opacity, " 113 "or background color"); 114 115 MOZ_ASSERT(!aMatches || aMatches->IsEmpty(), 116 "Matches array, if provided, should be empty"); 117 118 EffectSet* effects = EffectSet::GetForFrame(aFrame, aPropertySet); 119 if (!effects || effects->IsEmpty()) { 120 return false; 121 } 122 123 AnimationPerformanceWarning::Type warning = 124 AnimationPerformanceWarning::Type::None; 125 if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aFrame, warning)) { 126 if (warning != AnimationPerformanceWarning::Type::None) { 127 EffectCompositor::SetPerformanceWarning( 128 aFrame, aPropertySet, AnimationPerformanceWarning(warning)); 129 } 130 return false; 131 } 132 133 // The animation cascade will almost always be up-to-date by this point 134 // but there are some cases such as when we are restoring the refresh driver 135 // from test control after seeking where it might not be the case. 136 // 137 // Those cases are probably not important but just to be safe, let's make 138 // sure the cascade is up to date since if it *is* up to date, this is 139 // basically a no-op. 140 Maybe<NonOwningAnimationTarget> pseudoElement = 141 EffectCompositor::GetAnimationElementAndPseudoForFrame( 142 nsLayoutUtils::GetStyleFrame(aFrame)); 143 MOZ_ASSERT(pseudoElement, 144 "We have a valid element for the frame, if we don't we should " 145 "have bailed out at above the call to EffectSet::Get"); 146 EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->mElement, 147 pseudoElement->mPseudoRequest); 148 149 bool foundRunningAnimations = false; 150 for (KeyframeEffect* effect : *effects) { 151 auto effectWarning = AnimationPerformanceWarning::Type::None; 152 KeyframeEffect::MatchForCompositor matchResult = 153 effect->IsMatchForCompositor(aPropertySet, aFrame, *effects, 154 effectWarning); 155 if (effectWarning != AnimationPerformanceWarning::Type::None) { 156 EffectCompositor::SetPerformanceWarning( 157 aFrame, aPropertySet, AnimationPerformanceWarning(effectWarning)); 158 } 159 160 if (matchResult == 161 KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty) { 162 // For a given |aFrame|, we don't want some animations of |aPropertySet| 163 // to run on the compositor and others to run on the main thread, so if 164 // any need to be synchronized with the main thread, run them all there. 165 if (aMatches) { 166 aMatches->Clear(); 167 } 168 return false; 169 } 170 171 if (matchResult == KeyframeEffect::MatchForCompositor::No) { 172 continue; 173 } 174 175 if (aMatches) { 176 aMatches->AppendElement(effect->GetAnimation()); 177 } 178 179 if (matchResult == KeyframeEffect::MatchForCompositor::Yes) { 180 foundRunningAnimations = true; 181 } 182 } 183 184 // If all animations we added were not currently playing animations, don't 185 // send them to the compositor. 186 if (aMatches && !foundRunningAnimations) { 187 aMatches->Clear(); 188 } 189 190 MOZ_ASSERT(!foundRunningAnimations || !aMatches || !aMatches->IsEmpty(), 191 "If return value is true, matches array should be non-empty"); 192 193 if (aMatches && foundRunningAnimations) { 194 aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>()); 195 } 196 197 return foundRunningAnimations; 198 } 199 200 void EffectCompositor::RequestRestyle(dom::Element* aElement, 201 const PseudoStyleRequest& aPseudoRequest, 202 RestyleType aRestyleType, 203 CascadeLevel aCascadeLevel) { 204 if (!mPresContext) { 205 // Pres context will be null after the effect compositor is disconnected. 206 return; 207 } 208 209 // Ignore animations on orphaned elements and elements in documents without 210 // a pres shell (e.g. XMLHttpRequest responseXML documents). 211 if (!nsContentUtils::GetPresShellForContent(aElement)) { 212 return; 213 } 214 215 auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel]; 216 PseudoElementHashEntry::KeyType key = {aElement, aPseudoRequest}; 217 218 bool& restyleEntry = elementsToRestyle.LookupOrInsert(key, false); 219 if (aRestyleType == RestyleType::Throttled) { 220 mPresContext->PresShell()->SetNeedThrottledAnimationFlush(); 221 } else { 222 // Update hashtable first in case PostRestyleForAnimation mutates it 223 // and invalidates the restyleEntry reference. 224 // (It shouldn't, but just to be sure.) 225 bool skipRestyle = std::exchange(restyleEntry, true); 226 if (!skipRestyle) { 227 PostRestyleForAnimation(aElement, aPseudoRequest, aCascadeLevel); 228 } 229 } 230 231 if (aRestyleType == RestyleType::Layer) { 232 mPresContext->RestyleManager()->IncrementAnimationGeneration(); 233 if (auto* effectSet = EffectSet::Get(aElement, aPseudoRequest)) { 234 effectSet->UpdateAnimationGeneration(mPresContext); 235 } 236 } 237 } 238 239 void EffectCompositor::PostRestyleForAnimation( 240 dom::Element* aElement, const PseudoStyleRequest& aPseudoRequest, 241 CascadeLevel aCascadeLevel) { 242 if (!mPresContext) { 243 return; 244 } 245 246 // FIXME: Bug 1615083 KeyframeEffect::SetTarget() and 247 // KeyframeEffect::SetPseudoElement() may set a non-existing pseudo element, 248 // and we still have to update its style, based on the wpt. However, we don't 249 // have the generated element here, so we failed the wpt. 250 // 251 // See wpt for more info: web-animations/interfaces/KeyframeEffect/target.html 252 Element* element = aElement->GetPseudoElement(aPseudoRequest); 253 if (!element) { 254 return; 255 } 256 257 RestyleHint hint = aCascadeLevel == CascadeLevel::Transitions 258 ? RestyleHint::RESTYLE_CSS_TRANSITIONS 259 : RestyleHint::RESTYLE_CSS_ANIMATIONS; 260 261 MOZ_ASSERT(NS_IsMainThread(), 262 "Restyle request during restyling should be requested only on " 263 "the main-thread. e.g. after the parallel traversal"); 264 if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse) { 265 MOZ_ASSERT(hint == RestyleHint::RESTYLE_CSS_ANIMATIONS || 266 hint == RestyleHint::RESTYLE_CSS_TRANSITIONS); 267 268 // We can't call Servo_NoteExplicitHints here since AtomicRefCell does not 269 // allow us mutate ElementData of the |aElement| in SequentialTask. 270 // Instead we call Servo_NoteExplicitHints for the element in PreTraverse() 271 // which will be called right before the second traversal that we do for 272 // updating CSS animations. 273 // In that case PreTraverse() will return true so that we know to do the 274 // second traversal so we don't need to post any restyle requests to the 275 // PresShell. 276 return; 277 } 278 279 MOZ_ASSERT(!mPresContext->RestyleManager()->IsInStyleRefresh()); 280 281 mPresContext->PresShell()->RestyleForAnimation(element, hint); 282 } 283 284 void EffectCompositor::PostRestyleForThrottledAnimations() { 285 for (size_t i = 0; i < kCascadeLevelCount; i++) { 286 CascadeLevel cascadeLevel = CascadeLevel(i); 287 auto& elementSet = mElementsToRestyle[cascadeLevel]; 288 289 for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { 290 bool& postedRestyle = iter.Data(); 291 if (postedRestyle) { 292 continue; 293 } 294 295 PostRestyleForAnimation(iter.Key().mElement, iter.Key().mPseudoRequest, 296 cascadeLevel); 297 postedRestyle = true; 298 } 299 } 300 } 301 302 void EffectCompositor::UpdateEffectProperties( 303 const ComputedStyle* aStyle, Element* aElement, 304 const PseudoStyleRequest& aPseudoRequest) { 305 EffectSet* effectSet = EffectSet::Get(aElement, aPseudoRequest); 306 if (!effectSet) { 307 return; 308 } 309 310 // Style context (Gecko) or computed values (Stylo) change might cause CSS 311 // cascade level, e.g removing !important, so we should update the cascading 312 // result. 313 effectSet->MarkCascadeNeedsUpdate(); 314 315 for (KeyframeEffect* effect : *effectSet) { 316 effect->UpdateProperties(aStyle); 317 } 318 } 319 320 namespace { 321 class EffectCompositeOrderComparator { 322 mutable nsContentUtils::NodeIndexCache mCache; 323 324 public: 325 bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const { 326 return a == b; 327 } 328 329 bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const { 330 MOZ_ASSERT(a->GetAnimation()); 331 MOZ_ASSERT(b->GetAnimation()); 332 const int32_t cmp = 333 a->GetAnimation()->CompareCompositeOrder(*b->GetAnimation(), mCache); 334 MOZ_ASSERT(Equals(a, b) || cmp != 0); 335 return cmp < 0; 336 } 337 }; 338 } // namespace 339 340 static void ComposeSortedEffects( 341 const nsTArray<KeyframeEffect*>& aSortedEffects, 342 const EffectSet* aEffectSet, EffectCompositor::CascadeLevel aCascadeLevel, 343 StyleAnimationValueMap* aAnimationValues, 344 dom::EndpointBehavior aEndpointBehavior = 345 dom::EndpointBehavior::Exclusive) { 346 const bool isTransition = 347 aCascadeLevel == EffectCompositor::CascadeLevel::Transitions; 348 InvertibleAnimatedPropertyIDSet propertiesToSkip; 349 // Transitions should be overridden by running animations of the same 350 // property per https://drafts.csswg.org/css-transitions/#application: 351 // 352 // > Implementations must add this value to the cascade if and only if that 353 // > property is not currently undergoing a CSS Animation on the same element. 354 // 355 // FIXME(emilio, bug 1606176): This should assert that 356 // aEffectSet->PropertiesForAnimationsLevel() is up-to-date, and it may not 357 // follow the spec in those cases. There are various places where we get style 358 // without flushing that would trigger the below assertion. 359 // 360 // MOZ_ASSERT_IF(aEffectSet, !aEffectSet->CascadeNeedsUpdate()); 361 if (aEffectSet) { 362 // Note that we do invert the set on CascadeLevel::Animations because we 363 // don't want to skip those properties when composing the animation rule on 364 // CascadeLevel::Animations. 365 propertiesToSkip.Setup(&aEffectSet->PropertiesForAnimationsLevel(), 366 !isTransition); 367 } 368 369 for (KeyframeEffect* effect : aSortedEffects) { 370 auto* animation = effect->GetAnimation(); 371 MOZ_ASSERT(!isTransition || animation->CascadeLevel() == aCascadeLevel); 372 animation->ComposeStyle(*aAnimationValues, propertiesToSkip, 373 aEndpointBehavior); 374 } 375 } 376 377 bool EffectCompositor::GetServoAnimationRule( 378 const dom::Element* aElement, const PseudoStyleRequest& aPseudoRequest, 379 CascadeLevel aCascadeLevel, StyleAnimationValueMap* aAnimationValues) { 380 MOZ_ASSERT(aAnimationValues); 381 // Gecko_GetAnimationRule should have already checked this 382 MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement), 383 "Should not be trying to run animations on elements in documents" 384 " without a pres shell (e.g. XMLHttpRequest documents)"); 385 386 EffectSet* effectSet = EffectSet::Get(aElement, aPseudoRequest); 387 if (!effectSet) { 388 return false; 389 } 390 391 const bool isTransition = aCascadeLevel == CascadeLevel::Transitions; 392 393 // Get a list of effects sorted by composite order. 394 nsTArray<KeyframeEffect*> sortedEffectList(effectSet->Count()); 395 for (KeyframeEffect* effect : *effectSet) { 396 if (isTransition && 397 effect->GetAnimation()->CascadeLevel() != aCascadeLevel) { 398 // We may need to use transition rules for the animations level for the 399 // case of missing keyframes in animations, but we don't ever need to look 400 // at non-transition levels to build a transition rule. When the effect 401 // set information is out of date (see above), this avoids creating bogus 402 // transition rules, see bug 1605610. 403 continue; 404 } 405 sortedEffectList.AppendElement(effect); 406 } 407 408 if (sortedEffectList.IsEmpty()) { 409 return false; 410 } 411 412 sortedEffectList.Sort(EffectCompositeOrderComparator()); 413 414 ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel, 415 aAnimationValues); 416 417 MOZ_ASSERT(effectSet == EffectSet::Get(aElement, aPseudoRequest), 418 "EffectSet should not change while composing style"); 419 420 return true; 421 } 422 423 bool EffectCompositor::ComposeServoAnimationRuleForEffect( 424 KeyframeEffect& aEffect, CascadeLevel aCascadeLevel, 425 StyleAnimationValueMap* aAnimationValues, 426 dom::EndpointBehavior aEndpointBehavior) { 427 MOZ_ASSERT(aAnimationValues); 428 MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(), 429 "Should not be in print preview"); 430 431 NonOwningAnimationTarget target = aEffect.GetAnimationTarget(); 432 if (!target) { 433 return false; 434 } 435 436 // Don't try to compose animations for elements in documents without a pres 437 // shell (e.g. XMLHttpRequest documents). 438 if (!nsContentUtils::GetPresShellForContent(target.mElement)) { 439 return false; 440 } 441 442 // GetServoAnimationRule is called as part of the regular style resolution 443 // where the cascade results are updated in the pre-traversal as needed. 444 // This function, however, is only called when committing styles so we 445 // need to ensure the cascade results are up-to-date manually. 446 MaybeUpdateCascadeResults(target.mElement, target.mPseudoRequest); 447 448 // We may need to update the base styles cached on the keyframes for |aEffect| 449 // since they won't be updated as part of the regular animation processing if 450 // |aEffect| has finished but doesn't have an appropriate fill mode. 451 // We can get computed style without flush, because |CommitStyles| should have 452 // already flushed styles. 453 RefPtr<const ComputedStyle> style = 454 nsComputedDOMStyle::GetComputedStyleNoFlush(target.mElement, 455 target.mPseudoRequest); 456 aEffect.UpdateBaseStyle(style); 457 458 EffectSet* effectSet = EffectSet::Get(target); 459 460 // Get a list of effects sorted by composite order up to and including 461 // |aEffect|, even if it is not in the EffectSet. 462 auto comparator = EffectCompositeOrderComparator(); 463 nsTArray<KeyframeEffect*> sortedEffectList(effectSet ? effectSet->Count() + 1 464 : 1); 465 if (effectSet) { 466 for (KeyframeEffect* effect : *effectSet) { 467 if (comparator.LessThan(effect, &aEffect)) { 468 sortedEffectList.AppendElement(effect); 469 } 470 } 471 sortedEffectList.Sort(comparator); 472 } 473 sortedEffectList.AppendElement(&aEffect); 474 475 ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel, 476 aAnimationValues, aEndpointBehavior); 477 478 MOZ_ASSERT(effectSet == EffectSet::Get(target), 479 "EffectSet should not change while composing style"); 480 481 return true; 482 } 483 484 bool EffectCompositor::HasPendingStyleUpdates() const { 485 for (auto& elementSet : mElementsToRestyle) { 486 if (elementSet.Count()) { 487 return true; 488 } 489 } 490 491 return false; 492 } 493 494 /* static */ 495 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame, 496 DisplayItemType aType) { 497 return FindAnimationsForCompositor( 498 aFrame, LayerAnimationInfo::GetCSSPropertiesFor(aType), nullptr); 499 } 500 501 /* static */ 502 nsTArray<RefPtr<dom::Animation>> EffectCompositor::GetAnimationsForCompositor( 503 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) { 504 nsTArray<RefPtr<dom::Animation>> result; 505 506 #ifdef DEBUG 507 bool foundSome = 508 #endif 509 FindAnimationsForCompositor(aFrame, aPropertySet, &result); 510 MOZ_ASSERT(!foundSome || !result.IsEmpty(), 511 "If return value is true, matches array should be non-empty"); 512 513 return result; 514 } 515 516 /* static */ 517 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame* aFrame, 518 DisplayItemType aType) { 519 EffectSet* effects = EffectSet::GetForFrame(aFrame, aType); 520 if (!effects) { 521 return; 522 } 523 524 const nsCSSPropertyIDSet& propertySet = 525 LayerAnimationInfo::GetCSSPropertiesFor(aType); 526 for (KeyframeEffect* effect : *effects) { 527 effect->SetIsRunningOnCompositor(propertySet, false); 528 } 529 } 530 531 /* static */ 532 void EffectCompositor::MaybeUpdateCascadeResults( 533 Element* aElement, const PseudoStyleRequest& aPseudoRequest) { 534 EffectSet* effects = EffectSet::Get(aElement, aPseudoRequest); 535 if (!effects || !effects->CascadeNeedsUpdate()) { 536 return; 537 } 538 539 UpdateCascadeResults(*effects, aElement, aPseudoRequest); 540 541 MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state"); 542 } 543 544 /* static */ 545 Maybe<NonOwningAnimationTarget> 546 EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame) { 547 // Always return the same object to benefit from return-value optimization. 548 Maybe<NonOwningAnimationTarget> result; 549 550 auto request = PseudoStyleRequest(aFrame->Style()->GetPseudoType()); 551 const bool isSupportedPseudo = 552 AnimationUtils::IsSupportedPseudoForAnimations(request); 553 554 // If it is a pseudo element but we don't support animations for it, just 555 // return. 556 if (!request.IsNotPseudo() && !isSupportedPseudo) { 557 return result; 558 } 559 560 nsIContent* content = aFrame->GetContent(); 561 if (!content || !content->IsElement()) { 562 return result; 563 } 564 565 Element* element = content->AsElement(); 566 switch (request.mType) { 567 case PseudoStyleType::before: 568 case PseudoStyleType::after: 569 case PseudoStyleType::marker: 570 case PseudoStyleType::backdrop: { 571 nsIContent* parent = element->GetParent(); 572 if (!parent || !parent->IsElement()) { 573 return result; 574 } 575 element = parent->AsElement(); 576 break; 577 } 578 case PseudoStyleType::viewTransition: 579 case PseudoStyleType::viewTransitionGroup: 580 case PseudoStyleType::viewTransitionImagePair: 581 case PseudoStyleType::viewTransitionOld: 582 case PseudoStyleType::viewTransitionNew: { 583 request.mIdentifier = 584 element->HasName() 585 ? element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() 586 : nullptr; 587 element = element->OwnerDoc()->GetRootElement(); 588 break; 589 } 590 case PseudoStyleType::NotPseudo: 591 break; 592 default: 593 MOZ_ASSERT_UNREACHABLE("Unknown PseudoStyleType"); 594 } 595 596 result.emplace(element, request); 597 return result; 598 } 599 600 /* static */ 601 nsCSSPropertyIDSet EffectCompositor::GetOverriddenProperties( 602 EffectSet& aEffectSet, Element* aElement, 603 const PseudoStyleRequest& aPseudoRequest) { 604 MOZ_ASSERT(aElement, "Should have an element to get style data from"); 605 606 nsCSSPropertyIDSet result; 607 608 Element* elementForRestyle = aElement->GetPseudoElement(aPseudoRequest); 609 if (!elementForRestyle) { 610 return result; 611 } 612 613 static constexpr size_t compositorAnimatableCount = 614 nsCSSPropertyIDSet::CompositorAnimatableCount(); 615 AutoTArray<NonCustomCSSPropertyId, compositorAnimatableCount> 616 propertiesToTrack; 617 { 618 nsCSSPropertyIDSet propertiesToTrackAsSet; 619 for (KeyframeEffect* effect : aEffectSet) { 620 for (const AnimationProperty& property : effect->Properties()) { 621 // Custom properties don't run on the compositor. 622 if (property.mProperty.IsCustom()) { 623 continue; 624 } 625 626 if (nsCSSProps::PropHasFlags(property.mProperty.mId, 627 CSSPropFlags::CanAnimateOnCompositor) && 628 !propertiesToTrackAsSet.HasProperty(property.mProperty.mId)) { 629 propertiesToTrackAsSet.AddProperty(property.mProperty.mId); 630 propertiesToTrack.AppendElement(property.mProperty.mId); 631 } 632 } 633 // Skip iterating over the rest of the effects if we've already 634 // found all the compositor-animatable properties. 635 if (propertiesToTrack.Length() == compositorAnimatableCount) { 636 break; 637 } 638 } 639 } 640 641 if (propertiesToTrack.IsEmpty()) { 642 return result; 643 } 644 645 Servo_GetProperties_Overriding_Animation(elementForRestyle, 646 &propertiesToTrack, &result); 647 return result; 648 } 649 650 /* static */ 651 void EffectCompositor::UpdateCascadeResults( 652 EffectSet& aEffectSet, Element* aElement, 653 const PseudoStyleRequest& aPseudoRequest) { 654 MOZ_ASSERT(EffectSet::Get(aElement, aPseudoRequest) == &aEffectSet, 655 "Effect set should correspond to the specified (pseudo-)element"); 656 if (aEffectSet.IsEmpty()) { 657 aEffectSet.MarkCascadeUpdated(); 658 return; 659 } 660 661 // Get a list of effects sorted by composite order. 662 nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count()); 663 for (KeyframeEffect* effect : aEffectSet) { 664 sortedEffectList.AppendElement(effect); 665 } 666 sortedEffectList.Sort(EffectCompositeOrderComparator()); 667 668 // Get properties that override the *animations* level of the cascade. 669 // 670 // We only do this for properties that we can animate on the compositor 671 // since we will apply other properties on the main thread where the usual 672 // cascade applies. 673 nsCSSPropertyIDSet overriddenProperties = 674 GetOverriddenProperties(aEffectSet, aElement, aPseudoRequest); 675 676 nsCSSPropertyIDSet& propertiesWithImportantRules = 677 aEffectSet.PropertiesWithImportantRules(); 678 679 static constexpr nsCSSPropertyIDSet compositorAnimatables = 680 nsCSSPropertyIDSet::CompositorAnimatables(); 681 // Record which compositor-animatable properties were originally set so we can 682 // compare for changes later. 683 nsCSSPropertyIDSet prevCompositorPropertiesWithImportantRules = 684 propertiesWithImportantRules.Intersect(compositorAnimatables); 685 686 propertiesWithImportantRules.Empty(); 687 688 AnimatedPropertyIDSet propertiesForAnimationsLevel; 689 AnimatedPropertyIDSet propertiesForTransitionsLevel; 690 691 for (const KeyframeEffect* effect : sortedEffectList) { 692 MOZ_ASSERT(effect->GetAnimation(), 693 "Effects on a target element should have an Animation"); 694 CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel(); 695 696 for (const AnimationProperty& prop : effect->Properties()) { 697 // Note that nsCSSPropertyIDSet::HasProperty() returns false for custom 698 // properties. We don't support custom properties for compositor 699 // animations, so we are still using nsCSSPropertyIDSet to handle these 700 // properties. 701 // TODO: Bug 1869475. Support custom properties for compositor animations. 702 if (overriddenProperties.HasProperty(prop.mProperty)) { 703 propertiesWithImportantRules.AddProperty(prop.mProperty.mId); 704 } 705 706 switch (cascadeLevel) { 707 case EffectCompositor::CascadeLevel::Animations: 708 propertiesForAnimationsLevel.AddProperty(prop.mProperty); 709 break; 710 case EffectCompositor::CascadeLevel::Transitions: 711 propertiesForTransitionsLevel.AddProperty(prop.mProperty); 712 break; 713 } 714 } 715 } 716 717 aEffectSet.MarkCascadeUpdated(); 718 719 // Update EffectSet::mPropertiesForAnimationsLevel to the new set, after 720 // exiting this scope. 721 auto scopeExit = MakeScopeExit([&] { 722 aEffectSet.PropertiesForAnimationsLevel() = 723 std::move(propertiesForAnimationsLevel); 724 }); 725 726 nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement); 727 if (!presContext) { 728 return; 729 } 730 731 // If properties for compositor are newly overridden by !important rules, or 732 // released from being overridden by !important rules, we need to update 733 // layers for animations level because it's a trigger to send animations to 734 // the compositor or pull animations back from the compositor. 735 if (!prevCompositorPropertiesWithImportantRules.Equals( 736 propertiesWithImportantRules.Intersect(compositorAnimatables))) { 737 presContext->EffectCompositor()->RequestRestyle( 738 aElement, aPseudoRequest, EffectCompositor::RestyleType::Layer, 739 EffectCompositor::CascadeLevel::Animations); 740 } 741 742 // If we have transition properties and if the same propery for animations 743 // level is newly added or removed, we need to update the transition level 744 // rule since the it will be added/removed from the rule tree. 745 const AnimatedPropertyIDSet& prevPropertiesForAnimationsLevel = 746 aEffectSet.PropertiesForAnimationsLevel(); 747 const AnimatedPropertyIDSet& changedPropertiesForAnimationLevel = 748 prevPropertiesForAnimationsLevel.Xor(propertiesForAnimationsLevel); 749 const AnimatedPropertyIDSet& commonProperties = 750 propertiesForTransitionsLevel.Intersect( 751 changedPropertiesForAnimationLevel); 752 if (!commonProperties.IsEmpty()) { 753 EffectCompositor::RestyleType restyleType = 754 changedPropertiesForAnimationLevel.Intersects(compositorAnimatables) 755 ? EffectCompositor::RestyleType::Standard 756 : EffectCompositor::RestyleType::Layer; 757 presContext->EffectCompositor()->RequestRestyle( 758 aElement, aPseudoRequest, restyleType, 759 EffectCompositor::CascadeLevel::Transitions); 760 } 761 } 762 763 /* static */ 764 void EffectCompositor::SetPerformanceWarning( 765 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet, 766 const AnimationPerformanceWarning& aWarning) { 767 EffectSet* effects = EffectSet::GetForFrame(aFrame, aPropertySet); 768 if (!effects) { 769 return; 770 } 771 772 for (KeyframeEffect* effect : *effects) { 773 effect->SetPerformanceWarning(aPropertySet, aWarning); 774 } 775 } 776 777 bool EffectCompositor::PreTraverse(ServoTraversalFlags aFlags) { 778 return PreTraverseInSubtree(aFlags, nullptr); 779 } 780 781 bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags, 782 Element* aRoot) { 783 MOZ_ASSERT(NS_IsMainThread()); 784 MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot), 785 "Traversal root, if provided, should be bound to a display " 786 "document"); 787 788 // Convert the root element to the parent element if the root element is 789 // pseudo since we check each element in mElementsToRestyle is in the subtree 790 // of the root element later in this function, but for pseudo elements the 791 // element in mElementsToRestyle is the parent of the pseudo. 792 if (aRoot && (aRoot->IsGeneratedContentContainerForBefore() || 793 aRoot->IsGeneratedContentContainerForAfter() || 794 aRoot->IsGeneratedContentContainerForBackdrop() || 795 aRoot->IsGeneratedContentContainerForMarker())) { 796 aRoot = aRoot->GetParentElement(); 797 } 798 799 AutoRestore<bool> guard(mIsInPreTraverse); 800 mIsInPreTraverse = true; 801 802 // We need to force flush all throttled animations if we also have 803 // non-animation restyles (since we'll want the up-to-date animation style 804 // when we go to process them so we can trigger transitions correctly), and 805 // if we are currently flushing all throttled animation restyles. 806 bool flushThrottledRestyles = 807 (aRoot && aRoot->HasDirtyDescendantsForServo()) || 808 (aFlags & ServoTraversalFlags::FlushThrottledAnimations); 809 810 using ElementsToRestyleIterType = 811 nsTHashMap<PseudoElementHashEntry, bool>::ConstIterator; 812 auto getNeededRestyleTarget = 813 [&](const ElementsToRestyleIterType& aIter) -> NonOwningAnimationTarget { 814 NonOwningAnimationTarget returnTarget; 815 816 // If aIter.Data() is false, the element only requested a throttled 817 // (skippable) restyle, so we can skip it if flushThrottledRestyles is not 818 // true. 819 if (!flushThrottledRestyles && !aIter.Data()) { 820 return returnTarget; 821 } 822 823 const NonOwningAnimationTarget& target = aIter.Key(); 824 825 // Skip elements in documents without a pres shell. Normally we filter out 826 // such elements in RequestRestyle but it can happen that, after adding 827 // them to mElementsToRestyle, they are transferred to a different document. 828 // 829 // We will drop them from mElementsToRestyle at the end of the next full 830 // document restyle (at the end of this function) but for consistency with 831 // how we treat such elements in RequestRestyle, we just ignore them here. 832 if (!nsContentUtils::GetPresShellForContent(target.mElement)) { 833 return returnTarget; 834 } 835 836 // Ignore restyles that aren't in the flattened tree subtree rooted at 837 // aRoot. 838 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle( 839 target.mElement, aRoot)) { 840 return returnTarget; 841 } 842 843 returnTarget = target; 844 return returnTarget; 845 }; 846 847 bool foundElementsNeedingRestyle = false; 848 849 nsTArray<NonOwningAnimationTarget> elementsWithCascadeUpdates; 850 for (size_t i = 0; i < kCascadeLevelCount; ++i) { 851 CascadeLevel cascadeLevel = CascadeLevel(i); 852 auto& elementSet = mElementsToRestyle[cascadeLevel]; 853 for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) { 854 const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter); 855 if (!target.mElement) { 856 continue; 857 } 858 859 EffectSet* effects = EffectSet::Get(target); 860 if (!effects || !effects->CascadeNeedsUpdate()) { 861 continue; 862 } 863 864 elementsWithCascadeUpdates.AppendElement(target); 865 } 866 } 867 868 for (const NonOwningAnimationTarget& target : elementsWithCascadeUpdates) { 869 MaybeUpdateCascadeResults(target.mElement, target.mPseudoRequest); 870 } 871 elementsWithCascadeUpdates.Clear(); 872 873 for (size_t i = 0; i < kCascadeLevelCount; ++i) { 874 CascadeLevel cascadeLevel = CascadeLevel(i); 875 auto& elementSet = mElementsToRestyle[cascadeLevel]; 876 for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { 877 const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter); 878 if (!target.mElement) { 879 continue; 880 } 881 882 if (target.mElement->GetComposedDoc() != mPresContext->Document()) { 883 iter.Remove(); 884 continue; 885 } 886 887 // We need to post restyle hints even if the target is not in EffectSet to 888 // ensure the final restyling for removed animations. 889 // We can't call PostRestyleEvent directly here since we are still in the 890 // middle of the servo traversal. 891 mPresContext->RestyleManager()->PostRestyleEventForAnimations( 892 target.mElement, target.mPseudoRequest, 893 cascadeLevel == CascadeLevel::Transitions 894 ? RestyleHint::RESTYLE_CSS_TRANSITIONS 895 : RestyleHint::RESTYLE_CSS_ANIMATIONS); 896 897 foundElementsNeedingRestyle = true; 898 899 auto* effects = EffectSet::Get(target); 900 if (!effects) { 901 // Drop EffectSets that have been destroyed. 902 iter.Remove(); 903 continue; 904 } 905 906 for (KeyframeEffect* effect : *effects) { 907 effect->GetAnimation()->WillComposeStyle(); 908 } 909 910 // Remove the element from the list of elements to restyle since we are 911 // about to restyle it. 912 iter.Remove(); 913 } 914 915 // If this is a full document restyle, then unconditionally clear 916 // elementSet in case there are any elements that didn't match above 917 // because they were moved to a document without a pres shell after 918 // posting an animation restyle. 919 if (!aRoot && flushThrottledRestyles) { 920 elementSet.Clear(); 921 } 922 } 923 924 return foundElementsNeedingRestyle; 925 } 926 927 void EffectCompositor::NoteElementForReducing( 928 const NonOwningAnimationTarget& aTarget) { 929 (void)mElementsToReduce.put( 930 OwningAnimationTarget{aTarget.mElement, aTarget.mPseudoRequest}); 931 } 932 933 static void ReduceEffectSet(EffectSet& aEffectSet) { 934 // Get a list of effects sorted by composite order. 935 nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count()); 936 for (KeyframeEffect* effect : aEffectSet) { 937 sortedEffectList.AppendElement(effect); 938 } 939 sortedEffectList.Sort(EffectCompositeOrderComparator()); 940 941 AnimatedPropertyIDSet setProperties; 942 943 // Iterate in reverse 944 for (auto iter = sortedEffectList.rbegin(); iter != sortedEffectList.rend(); 945 ++iter) { 946 MOZ_ASSERT(*iter && (*iter)->GetAnimation(), 947 "Effect in an EffectSet should have an animation"); 948 KeyframeEffect& effect = **iter; 949 Animation& animation = *effect.GetAnimation(); 950 if (animation.IsRemovable() && 951 effect.GetPropertySet().IsSubsetOf(setProperties)) { 952 animation.Remove(); 953 } else if (animation.IsReplaceable()) { 954 setProperties.AddProperties(effect.GetPropertySet()); 955 } 956 } 957 } 958 959 void EffectCompositor::ReduceAnimations() { 960 for (auto iter = mElementsToReduce.iter(); !iter.done(); iter.next()) { 961 auto* effectSet = EffectSet::Get(iter.get()); 962 if (effectSet) { 963 ReduceEffectSet(*effectSet); 964 } 965 } 966 967 mElementsToReduce.clear(); 968 } 969 970 } // namespace mozilla