tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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