tor-browser

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

KeyframeEffect.cpp (73782B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/KeyframeEffect.h"
      8 
      9 #include "mozilla/dom/Animation.h"
     10 #include "mozilla/dom/DocumentInlines.h"
     11 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
     12 // For UnrestrictedDoubleOrKeyframeAnimationOptions;
     13 #include "NonCustomCSSPropertyId.h"
     14 #include "WindowRenderer.h"
     15 #include "js/PropertyAndElement.h"  // JS_DefineProperty
     16 #include "mozilla/AnimationUtils.h"
     17 #include "mozilla/AutoRestore.h"
     18 #include "mozilla/ComputedStyleInlines.h"
     19 #include "mozilla/EffectSet.h"
     20 #include "mozilla/KeyframeUtils.h"
     21 #include "mozilla/LayerAnimationInfo.h"
     22 #include "mozilla/LookAndFeel.h"  // For LookAndFeel::GetInt
     23 #include "mozilla/PresShell.h"
     24 #include "mozilla/PresShellInlines.h"
     25 #include "mozilla/SVGObserverUtils.h"
     26 #include "mozilla/ScrollContainerFrame.h"
     27 #include "mozilla/ServoBindings.h"
     28 #include "mozilla/StaticPrefs_dom.h"
     29 #include "mozilla/StaticPrefs_gfx.h"
     30 #include "mozilla/StaticPrefs_layers.h"
     31 #include "mozilla/dom/KeyframeEffectBinding.h"
     32 #include "mozilla/dom/MutationObservers.h"
     33 #include "mozilla/layers/AnimationInfo.h"
     34 #include "nsCSSPropertyIDSet.h"
     35 #include "nsCSSProps.h"           // For nsCSSProps::PropHasFlags
     36 #include "nsCSSPseudoElements.h"  // For PseudoStyleType
     37 #include "nsComputedDOMStyle.h"   // nsComputedDOMStyle::GetComputedStyle
     38 #include "nsContentUtils.h"
     39 #include "nsDOMMutationObserver.h"  // For nsAutoAnimationMutationBatch
     40 #include "nsIFrame.h"
     41 #include "nsIFrameInlines.h"
     42 #include "nsPresContextInlines.h"
     43 #include "nsRefreshDriver.h"
     44 
     45 namespace mozilla {
     46 
     47 void AnimationProperty::SetPerformanceWarning(
     48    const AnimationPerformanceWarning& aWarning, const dom::Element* aElement) {
     49  if (mPerformanceWarning && *mPerformanceWarning == aWarning) {
     50    return;
     51  }
     52 
     53  mPerformanceWarning = Some(aWarning);
     54 
     55  nsAutoString localizedString;
     56  if (StaticPrefs::layers_offmainthreadcomposition_log_animations() &&
     57      mPerformanceWarning->ToLocalizedString(localizedString)) {
     58    nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString);
     59    AnimationUtils::LogAsyncAnimationFailure(logMessage, aElement);
     60  }
     61 }
     62 
     63 bool PropertyValuePair::operator==(const PropertyValuePair& aOther) const {
     64  if (mProperty != aOther.mProperty) {
     65    return false;
     66  }
     67  if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) {
     68    return true;
     69  }
     70  if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) {
     71    return false;
     72  }
     73  return Servo_DeclarationBlock_Equals(mServoDeclarationBlock,
     74                                       aOther.mServoDeclarationBlock);
     75 }
     76 
     77 namespace dom {
     78 
     79 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect, AnimationEffect,
     80                                   mTarget.mElement)
     81 
     82 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect, AnimationEffect)
     83 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     84 
     85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect)
     86 NS_INTERFACE_MAP_END_INHERITING(AnimationEffect)
     87 
     88 NS_IMPL_ADDREF_INHERITED(KeyframeEffect, AnimationEffect)
     89 NS_IMPL_RELEASE_INHERITED(KeyframeEffect, AnimationEffect)
     90 
     91 KeyframeEffect::KeyframeEffect(Document* aDocument,
     92                               OwningAnimationTarget&& aTarget,
     93                               TimingParams&& aTiming,
     94                               const KeyframeEffectParams& aOptions)
     95    : AnimationEffect(aDocument, std::move(aTiming)),
     96      mTarget(std::move(aTarget)),
     97      mEffectOptions(aOptions) {}
     98 
     99 KeyframeEffect::KeyframeEffect(Document* aDocument,
    100                               OwningAnimationTarget&& aTarget,
    101                               const KeyframeEffect& aOther)
    102    : AnimationEffect(aDocument, TimingParams{aOther.SpecifiedTiming()}),
    103      mTarget(std::move(aTarget)),
    104      mEffectOptions{aOther.IterationComposite(), aOther.Composite(),
    105                     mTarget.mPseudoRequest},
    106      mKeyframes(aOther.mKeyframes.Clone()),
    107      mProperties(aOther.mProperties.Clone()),
    108      mBaseValues(aOther.mBaseValues.Clone()) {}
    109 
    110 JSObject* KeyframeEffect::WrapObject(JSContext* aCx,
    111                                     JS::Handle<JSObject*> aGivenProto) {
    112  return KeyframeEffect_Binding::Wrap(aCx, this, aGivenProto);
    113 }
    114 
    115 IterationCompositeOperation KeyframeEffect::IterationComposite() const {
    116  return mEffectOptions.mIterationComposite;
    117 }
    118 
    119 void KeyframeEffect::SetIterationComposite(
    120    const IterationCompositeOperation& aIterationComposite) {
    121  if (mEffectOptions.mIterationComposite == aIterationComposite) {
    122    return;
    123  }
    124 
    125  if (mAnimation && mAnimation->IsRelevant()) {
    126    MutationObservers::NotifyAnimationChanged(mAnimation);
    127  }
    128 
    129  mEffectOptions.mIterationComposite = aIterationComposite;
    130  RequestRestyle(EffectCompositor::RestyleType::Layer);
    131 }
    132 
    133 CompositeOperation KeyframeEffect::Composite() const {
    134  return mEffectOptions.mComposite;
    135 }
    136 
    137 void KeyframeEffect::SetComposite(const CompositeOperation& aComposite) {
    138  if (mEffectOptions.mComposite == aComposite) {
    139    return;
    140  }
    141 
    142  mEffectOptions.mComposite = aComposite;
    143 
    144  if (mAnimation && mAnimation->IsRelevant()) {
    145    MutationObservers::NotifyAnimationChanged(mAnimation);
    146  }
    147 
    148  if (mTarget) {
    149    RefPtr<const ComputedStyle> computedStyle =
    150        GetTargetComputedStyle(Flush::None);
    151    if (computedStyle) {
    152      UpdateProperties(computedStyle);
    153    }
    154  }
    155 }
    156 
    157 void KeyframeEffect::NotifySpecifiedTimingUpdated() {
    158  // Use the same document for a pseudo element and its parent element.
    159  // Use nullptr if we don't have mTarget, so disable the mutation batch.
    160  nsAutoAnimationMutationBatch mb(mTarget ? mTarget.mElement->OwnerDoc()
    161                                          : nullptr);
    162 
    163  if (mAnimation) {
    164    mAnimation->NotifyEffectTimingUpdated();
    165 
    166    if (mAnimation->IsRelevant()) {
    167      MutationObservers::NotifyAnimationChanged(mAnimation);
    168    }
    169 
    170    RequestRestyle(EffectCompositor::RestyleType::Layer);
    171  }
    172 }
    173 
    174 void KeyframeEffect::NotifyAnimationTimingUpdated(
    175    PostRestyleMode aPostRestyle) {
    176  UpdateTargetRegistration();
    177 
    178  // If the effect is not relevant it will be removed from the target
    179  // element's effect set. However, effects not in the effect set
    180  // will not be included in the set of candidate effects for running on
    181  // the compositor and hence they won't have their compositor status
    182  // updated. As a result, we need to make sure we clear their compositor
    183  // status here.
    184  bool isRelevant = mAnimation && mAnimation->IsRelevant();
    185  if (!isRelevant) {
    186    ResetIsRunningOnCompositor();
    187  }
    188 
    189  // Request restyle if necessary.
    190  if (aPostRestyle == PostRestyleMode::IfNeeded && mAnimation &&
    191      !mProperties.IsEmpty() && HasComputedTimingChanged()) {
    192    EffectCompositor::RestyleType restyleType =
    193        CanThrottle() ? EffectCompositor::RestyleType::Throttled
    194                      : EffectCompositor::RestyleType::Standard;
    195    RequestRestyle(restyleType);
    196  }
    197 
    198  // Detect changes to "in effect" status since we need to recalculate the
    199  // animation cascade for this element whenever that changes.
    200  // Note that updating mInEffectOnLastAnimationTimingUpdate has to be done
    201  // after above CanThrottle() call since the function uses the flag inside it.
    202  bool inEffect = IsInEffect();
    203  if (inEffect != mInEffectOnLastAnimationTimingUpdate) {
    204    MarkCascadeNeedsUpdate();
    205    mInEffectOnLastAnimationTimingUpdate = inEffect;
    206  }
    207 
    208  // If we're no longer "in effect", our ComposeStyle method will never be
    209  // called and we will never have a chance to update mProgressOnLastCompose
    210  // and mCurrentIterationOnLastCompose.
    211  // We clear them here to ensure that if we later become "in effect" we will
    212  // request a restyle (above).
    213  if (!inEffect) {
    214    mProgressOnLastCompose.SetNull();
    215    mCurrentIterationOnLastCompose = 0;
    216  }
    217 }
    218 
    219 static bool KeyframesEqualIgnoringComputedOffsets(
    220    const nsTArray<Keyframe>& aLhs, const nsTArray<Keyframe>& aRhs) {
    221  if (aLhs.Length() != aRhs.Length()) {
    222    return false;
    223  }
    224 
    225  for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
    226    const Keyframe& a = aLhs[i];
    227    const Keyframe& b = aRhs[i];
    228    if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
    229        a.mPropertyValues != b.mPropertyValues) {
    230      return false;
    231    }
    232  }
    233  return true;
    234 }
    235 
    236 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
    237 void KeyframeEffect::SetKeyframes(JSContext* aContext,
    238                                  JS::Handle<JSObject*> aKeyframes,
    239                                  ErrorResult& aRv) {
    240  nsTArray<Keyframe> keyframes = KeyframeUtils::GetKeyframesFromObject(
    241      aContext, mDocument, aKeyframes, "KeyframeEffect.setKeyframes", aRv);
    242  if (aRv.Failed()) {
    243    return;
    244  }
    245 
    246  RefPtr<const ComputedStyle> style = GetTargetComputedStyle(Flush::None);
    247  SetKeyframes(std::move(keyframes), style, nullptr /* AnimationTimeline */);
    248 }
    249 
    250 void KeyframeEffect::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
    251                                  const ComputedStyle* aStyle,
    252                                  const AnimationTimeline* aTimeline) {
    253  if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
    254    return;
    255  }
    256 
    257  mKeyframes = std::move(aKeyframes);
    258  KeyframeUtils::DistributeKeyframes(mKeyframes);
    259 
    260  if (mAnimation && mAnimation->IsRelevant()) {
    261    MutationObservers::NotifyAnimationChanged(mAnimation);
    262  }
    263 
    264  // We need to call UpdateProperties() unless the target element doesn't have
    265  // style (e.g. the target element is not associated with any document).
    266  if (aStyle) {
    267    UpdateProperties(aStyle, aTimeline);
    268  }
    269 }
    270 
    271 void KeyframeEffect::ReplaceTransitionStartValue(AnimationValue&& aStartValue) {
    272  if (!aStartValue.mServo) {
    273    return;
    274  }
    275 
    276  // A typical transition should have a single property and a single segment.
    277  //
    278  // (And for atypical transitions, that is, those updated by script, we don't
    279  // apply the replacing behavior.)
    280  if (mProperties.Length() != 1 || mProperties[0].mSegments.Length() != 1) {
    281    return;
    282  }
    283 
    284  // Likewise, check that the keyframes are of the expected shape.
    285  if (mKeyframes.Length() != 2 || mKeyframes[0].mPropertyValues.Length() != 1) {
    286    return;
    287  }
    288 
    289  // Check that the value we are about to substitute in is actually for the
    290  // same property.
    291  CSSPropertyId property(eCSSProperty_UNKNOWN);
    292  Servo_AnimationValue_GetPropertyId(aStartValue.mServo, &property);
    293  if (property != mProperties[0].mProperty) {
    294    return;
    295  }
    296 
    297  mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
    298      Servo_AnimationValue_Uncompute(aStartValue.mServo).Consume();
    299  mProperties[0].mSegments[0].mFromValue = std::move(aStartValue);
    300 }
    301 
    302 static bool IsEffectiveProperty(const EffectSet& aEffects,
    303                                const CSSPropertyId& aProperty) {
    304  return !aEffects.PropertiesWithImportantRules().HasProperty(aProperty) ||
    305         !aEffects.PropertiesForAnimationsLevel().HasProperty(aProperty);
    306 }
    307 
    308 const AnimationProperty* KeyframeEffect::GetEffectiveAnimationOfProperty(
    309    const CSSPropertyId& aProperty, const EffectSet& aEffects) const {
    310  MOZ_ASSERT(mTarget && &aEffects == EffectSet::Get(mTarget));
    311 
    312  for (const AnimationProperty& property : mProperties) {
    313    if (aProperty != property.mProperty) {
    314      continue;
    315    }
    316 
    317    // Only include the property if it is not overridden by !important rules in
    318    // the transitions level.
    319    return IsEffectiveProperty(aEffects, property.mProperty) ? &property
    320                                                             : nullptr;
    321  }
    322  return nullptr;
    323 }
    324 
    325 bool KeyframeEffect::HasEffectiveAnimationOfPropertySet(
    326    const nsCSSPropertyIDSet& aPropertySet, const EffectSet& aEffectSet) const {
    327  for (const AnimationProperty& property : mProperties) {
    328    if (aPropertySet.HasProperty(property.mProperty) &&
    329        IsEffectiveProperty(aEffectSet, property.mProperty)) {
    330      return true;
    331    }
    332  }
    333  return false;
    334 }
    335 
    336 nsCSSPropertyIDSet KeyframeEffect::GetPropertiesForCompositor(
    337    EffectSet& aEffects, const nsIFrame* aFrame) const {
    338  MOZ_ASSERT(&aEffects == EffectSet::Get(mTarget));
    339 
    340  nsCSSPropertyIDSet properties;
    341 
    342  if (!mAnimation || !mAnimation->IsRelevant()) {
    343    return properties;
    344  }
    345 
    346  static constexpr nsCSSPropertyIDSet compositorAnimatables =
    347      nsCSSPropertyIDSet::CompositorAnimatables();
    348  static constexpr nsCSSPropertyIDSet transformLikeProperties =
    349      nsCSSPropertyIDSet::TransformLikeProperties();
    350 
    351  nsCSSPropertyIDSet transformSet;
    352  AnimationPerformanceWarning::Type dummyWarning;
    353 
    354  for (const AnimationProperty& property : mProperties) {
    355    if (!compositorAnimatables.HasProperty(property.mProperty)) {
    356      continue;
    357    }
    358 
    359    // Transform-like properties are combined together on the compositor so we
    360    // need to evaluate them as a group. We build up a separate set here then
    361    // evaluate it as a separate step below.
    362    if (transformLikeProperties.HasProperty(property.mProperty)) {
    363      transformSet.AddProperty(property.mProperty.mId);
    364      continue;
    365    }
    366 
    367    KeyframeEffect::MatchForCompositor matchResult =
    368        IsMatchForCompositor(nsCSSPropertyIDSet{property.mProperty.mId}, aFrame,
    369                             aEffects, dummyWarning);
    370    if (matchResult ==
    371            KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty ||
    372        matchResult == KeyframeEffect::MatchForCompositor::No) {
    373      continue;
    374    }
    375    properties.AddProperty(property.mProperty.mId);
    376  }
    377 
    378  if (!transformSet.IsEmpty()) {
    379    KeyframeEffect::MatchForCompositor matchResult =
    380        IsMatchForCompositor(transformSet, aFrame, aEffects, dummyWarning);
    381    if (matchResult == KeyframeEffect::MatchForCompositor::Yes ||
    382        matchResult == KeyframeEffect::MatchForCompositor::IfNeeded) {
    383      properties |= transformSet;
    384    }
    385  }
    386 
    387  return properties;
    388 }
    389 
    390 AnimatedPropertyIDSet KeyframeEffect::GetPropertySet() const {
    391  AnimatedPropertyIDSet result;
    392 
    393  for (const AnimationProperty& property : mProperties) {
    394    result.AddProperty(property.mProperty);
    395  }
    396 
    397  return result;
    398 }
    399 
    400 #ifdef DEBUG
    401 bool SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA,
    402                                     const nsTArray<Keyframe>& aB) {
    403  if (aA.Length() != aB.Length()) {
    404    return false;
    405  }
    406 
    407  for (size_t i = 0; i < aA.Length(); i++) {
    408    const Keyframe& a = aA[i];
    409    const Keyframe& b = aB[i];
    410    if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
    411        a.mPropertyValues != b.mPropertyValues) {
    412      return false;
    413    }
    414  }
    415 
    416  return true;
    417 }
    418 #endif
    419 
    420 static bool HasCurrentColor(
    421    const nsTArray<AnimationPropertySegment>& aSegments) {
    422  for (const AnimationPropertySegment& segment : aSegments) {
    423    if ((!segment.mFromValue.IsNull() && segment.mFromValue.IsCurrentColor()) ||
    424        (!segment.mToValue.IsNull() && segment.mToValue.IsCurrentColor())) {
    425      return true;
    426    }
    427  }
    428  return false;
    429 }
    430 void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle,
    431                                      const AnimationTimeline* aTimeline) {
    432  MOZ_ASSERT(aStyle);
    433 
    434  nsTArray<AnimationProperty> properties = BuildProperties(aStyle);
    435 
    436  bool propertiesChanged = mProperties != properties;
    437 
    438  // We need to update base styles even if any properties are not changed at all
    439  // since base styles might have been changed due to parent style changes, etc.
    440  bool baseStylesChanged = false;
    441  EnsureBaseStyles(aStyle, properties, aTimeline,
    442                   !propertiesChanged ? &baseStylesChanged : nullptr);
    443 
    444  if (!propertiesChanged) {
    445    if (baseStylesChanged) {
    446      RequestRestyle(EffectCompositor::RestyleType::Layer);
    447    }
    448    return;
    449  }
    450 
    451  // Preserve the state of the mIsRunningOnCompositor flag.
    452  nsCSSPropertyIDSet runningOnCompositorProperties;
    453 
    454  for (const AnimationProperty& property : mProperties) {
    455    if (property.mIsRunningOnCompositor) {
    456      runningOnCompositorProperties.AddProperty(property.mProperty.mId);
    457    }
    458  }
    459 
    460  mProperties = std::move(properties);
    461  UpdateEffectSet();
    462 
    463  mCumulativeChanges = {};
    464  for (AnimationProperty& property : mProperties) {
    465    property.mIsRunningOnCompositor =
    466        runningOnCompositorProperties.HasProperty(property.mProperty);
    467    CalculateCumulativeChangesForProperty(property);
    468  }
    469 
    470  MarkCascadeNeedsUpdate();
    471 
    472  if (mAnimation) {
    473    mAnimation->NotifyEffectPropertiesUpdated();
    474  }
    475 
    476  RequestRestyle(EffectCompositor::RestyleType::Layer);
    477 }
    478 
    479 void KeyframeEffect::UpdateBaseStyle(const ComputedStyle* aStyle) {
    480  EnsureBaseStyles(aStyle, BuildProperties(aStyle), nullptr, nullptr);
    481 }
    482 
    483 void KeyframeEffect::EnsureBaseStyles(
    484    const ComputedStyle* aComputedValues,
    485    const nsTArray<AnimationProperty>& aProperties,
    486    const AnimationTimeline* aTimeline, bool* aBaseStylesChanged) {
    487  if (aBaseStylesChanged != nullptr) {
    488    *aBaseStylesChanged = false;
    489  }
    490 
    491  if (!mTarget) {
    492    return;
    493  }
    494 
    495  BaseValuesHashmap previousBaseStyles;
    496  if (aBaseStylesChanged != nullptr) {
    497    previousBaseStyles = std::move(mBaseValues);
    498  }
    499 
    500  mBaseValues.Clear();
    501 
    502  nsPresContext* presContext =
    503      nsContentUtils::GetContextForContent(mTarget.mElement);
    504  // If |aProperties| is empty we're not going to dereference |presContext| so
    505  // we don't care if it is nullptr.
    506  //
    507  // We could just return early when |aProperties| is empty and save looking up
    508  // the pres context, but that won't save any effort normally since we don't
    509  // call this function if we have no keyframes to begin with. Furthermore, the
    510  // case where |presContext| is nullptr is so rare (we've only ever seen in
    511  // fuzzing, and even then we've never been able to reproduce it reliably)
    512  // it's not worth the runtime cost of an extra branch.
    513  MOZ_ASSERT(presContext || aProperties.IsEmpty(),
    514             "Typically presContext should not be nullptr but if it is"
    515             " we should have also failed to calculate the computed values"
    516             " passed-in as aProperties");
    517 
    518  if (!aTimeline) {
    519    // If we pass a valid timeline, we use it (note: this happens when we create
    520    // a new animation or replace the old one, for CSS Animations and CSS
    521    // Transitions). Otherwise, we check the timeline from |mAnimation|.
    522    aTimeline = mAnimation ? mAnimation->GetTimeline() : nullptr;
    523  }
    524 
    525  RefPtr<const ComputedStyle> baseComputedStyle;
    526  for (const AnimationProperty& property : aProperties) {
    527    EnsureBaseStyle(property, presContext, aComputedValues, aTimeline,
    528                    baseComputedStyle);
    529  }
    530 
    531  if (aBaseStylesChanged != nullptr &&
    532      std::any_of(
    533          mBaseValues.cbegin(), mBaseValues.cend(), [&](const auto& entry) {
    534            return AnimationValue(entry.GetData()) !=
    535                   AnimationValue(previousBaseStyles.Get(entry.GetKey()));
    536          })) {
    537    *aBaseStylesChanged = true;
    538  }
    539 }
    540 
    541 void KeyframeEffect::EnsureBaseStyle(
    542    const AnimationProperty& aProperty, nsPresContext* aPresContext,
    543    const ComputedStyle* aComputedStyle, const AnimationTimeline* aTimeline,
    544    RefPtr<const ComputedStyle>& aBaseComputedStyle) {
    545  auto needBaseStyleForScrollTimeline =
    546      [this](const AnimationProperty& aProperty,
    547             const AnimationTimeline* aTimeline) {
    548        static constexpr TimeDuration zeroDuration;
    549        const TimingParams& timing = NormalizedTiming();
    550        // For scroll-timeline with a positive delay, it's possible to scroll
    551        // back and forth between delay phase and active phase, so we need to
    552        // keep its base style and maybe use it to override the animations in
    553        // delay on the compositor.
    554        return aTimeline && aTimeline->IsScrollTimeline() &&
    555               nsCSSPropertyIDSet::CompositorAnimatables().HasProperty(
    556                   aProperty.mProperty) &&
    557               (timing.Delay() > zeroDuration ||
    558                timing.EndDelay() > zeroDuration);
    559      };
    560  auto hasAdditiveValues = [](const AnimationProperty& aProperty) {
    561    for (const AnimationPropertySegment& segment : aProperty.mSegments) {
    562      if (!segment.HasReplaceableValues()) {
    563        return true;
    564      }
    565    }
    566    return false;
    567  };
    568 
    569  // Note: Check base style for compositor (i.e. for scroll-driven animations)
    570  // first because it is much cleaper.
    571  const bool needBaseStyle =
    572      needBaseStyleForScrollTimeline(aProperty, aTimeline) ||
    573      hasAdditiveValues(aProperty);
    574  if (!needBaseStyle) {
    575    return;
    576  }
    577 
    578  if (!aBaseComputedStyle) {
    579    MOZ_ASSERT(mTarget, "Should have a valid target");
    580 
    581    Element* animatingElement =
    582        mTarget.mElement->GetPseudoElement(mTarget.mPseudoRequest);
    583    if (!animatingElement) {
    584      return;
    585    }
    586    aBaseComputedStyle = aPresContext->StyleSet()->GetBaseContextForElement(
    587        animatingElement, aComputedStyle);
    588  }
    589  RefPtr<StyleAnimationValue> baseValue =
    590      Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle,
    591                                                 &aProperty.mProperty)
    592          .Consume();
    593  mBaseValues.InsertOrUpdate(aProperty.mProperty, std::move(baseValue));
    594 }
    595 
    596 void KeyframeEffect::WillComposeStyle() {
    597  ComputedTiming computedTiming = GetComputedTiming();
    598  mProgressOnLastCompose = computedTiming.mProgress;
    599  mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
    600 }
    601 
    602 void KeyframeEffect::ComposeStyleRule(StyleAnimationValueMap& aAnimationValues,
    603                                      const AnimationProperty& aProperty,
    604                                      const AnimationPropertySegment& aSegment,
    605                                      const ComputedTiming& aComputedTiming) {
    606  auto* opaqueTable =
    607      reinterpret_cast<RawServoAnimationValueTable*>(&mBaseValues);
    608  Servo_AnimationCompose(&aAnimationValues, opaqueTable, &aProperty.mProperty,
    609                         &aSegment, &aProperty.mSegments.LastElement(),
    610                         &aComputedTiming, mEffectOptions.mIterationComposite);
    611 }
    612 
    613 void KeyframeEffect::ComposeStyle(
    614    StyleAnimationValueMap& aComposeResult,
    615    const InvertibleAnimatedPropertyIDSet& aPropertiesToSkip,
    616    EndpointBehavior aEndpointBehavior) {
    617  ComputedTiming computedTiming = GetComputedTiming(nullptr, aEndpointBehavior);
    618 
    619  // If the progress is null, we don't have fill data for the current
    620  // time so we shouldn't animate.
    621  if (computedTiming.mProgress.IsNull()) {
    622    return;
    623  }
    624 
    625  for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd;
    626       ++propIdx) {
    627    const AnimationProperty& prop = mProperties[propIdx];
    628 
    629    MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
    630    MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
    631               "incorrect last to key");
    632 
    633    if (aPropertiesToSkip.HasProperty(prop.mProperty)) {
    634      continue;
    635    }
    636 
    637    MOZ_ASSERT(prop.mSegments.Length() > 0,
    638               "property should not be in animations if it has no segments");
    639 
    640    // FIXME: Maybe cache the current segment?
    641    const AnimationPropertySegment *segment = prop.mSegments.Elements(),
    642                                   *segmentEnd =
    643                                       segment + prop.mSegments.Length();
    644    while (segment->mToKey <= computedTiming.mProgress.Value()) {
    645      MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
    646      if ((segment + 1) == segmentEnd) {
    647        break;
    648      }
    649      ++segment;
    650      MOZ_ASSERT(segment->mFromKey == (segment - 1)->mToKey, "incorrect keys");
    651    }
    652    MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
    653    MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
    654                   size_t(segment - prop.mSegments.Elements()) <
    655                       prop.mSegments.Length(),
    656               "out of array bounds");
    657 
    658    ComposeStyleRule(aComposeResult, prop, *segment, computedTiming);
    659  }
    660 
    661  // If the animation produces a change hint that affects the overflow region,
    662  // we need to record the current time to unthrottle the animation
    663  // periodically when the animation is being throttled because it's scrolled
    664  // out of view.
    665  if (HasPropertiesThatMightAffectOverflow()) {
    666    nsPresContext* presContext =
    667        nsContentUtils::GetContextForContent(mTarget.mElement);
    668    EffectSet* effectSet = EffectSet::Get(mTarget);
    669    if (presContext && effectSet) {
    670      TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh();
    671      effectSet->UpdateLastOverflowAnimationSyncTime(now);
    672    }
    673  }
    674 }
    675 
    676 bool KeyframeEffect::IsRunningOnCompositor() const {
    677  // We consider animation is running on compositor if there is at least
    678  // one property running on compositor.
    679  // Animation.IsRunningOnCompotitor will return more fine grained
    680  // information in bug 1196114.
    681  for (const AnimationProperty& property : mProperties) {
    682    if (property.mIsRunningOnCompositor) {
    683      return true;
    684    }
    685  }
    686  return false;
    687 }
    688 
    689 void KeyframeEffect::SetIsRunningOnCompositor(NonCustomCSSPropertyId aProperty,
    690                                              bool aIsRunning) {
    691  MOZ_ASSERT(aProperty != eCSSPropertyExtra_variable,
    692             "Can't animate variables on compositor");
    693  MOZ_ASSERT(
    694      nsCSSProps::PropHasFlags(aProperty, CSSPropFlags::CanAnimateOnCompositor),
    695      "Property being animated on compositor is not a recognized "
    696      "compositor-animatable property");
    697 
    698  for (AnimationProperty& property : mProperties) {
    699    if (property.mProperty.mId == aProperty) {
    700      property.mIsRunningOnCompositor = aIsRunning;
    701      // We currently only set a performance warning message when animations
    702      // cannot be run on the compositor, so if this animation is running
    703      // on the compositor we don't need a message.
    704      if (aIsRunning) {
    705        property.mPerformanceWarning.reset();
    706      } else if (mAnimation && mAnimation->IsPartialPrerendered()) {
    707        ResetPartialPrerendered();
    708      }
    709      return;
    710    }
    711  }
    712 }
    713 
    714 void KeyframeEffect::SetIsRunningOnCompositor(
    715    const nsCSSPropertyIDSet& aPropertySet, bool aIsRunning) {
    716  for (AnimationProperty& property : mProperties) {
    717    if (aPropertySet.HasProperty(property.mProperty)) {
    718      MOZ_ASSERT(nsCSSProps::PropHasFlags(property.mProperty.mId,
    719                                          CSSPropFlags::CanAnimateOnCompositor),
    720                 "Property being animated on compositor is a recognized "
    721                 "compositor-animatable property");
    722      property.mIsRunningOnCompositor = aIsRunning;
    723      // We currently only set a performance warning message when animations
    724      // cannot be run on the compositor, so if this animation is running
    725      // on the compositor we don't need a message.
    726      if (aIsRunning) {
    727        property.mPerformanceWarning.reset();
    728      }
    729    }
    730  }
    731 
    732  if (!aIsRunning && mAnimation && mAnimation->IsPartialPrerendered()) {
    733    ResetPartialPrerendered();
    734  }
    735 }
    736 
    737 void KeyframeEffect::ResetIsRunningOnCompositor() {
    738  for (AnimationProperty& property : mProperties) {
    739    property.mIsRunningOnCompositor = false;
    740  }
    741 
    742  if (mAnimation && mAnimation->IsPartialPrerendered()) {
    743    ResetPartialPrerendered();
    744  }
    745 }
    746 
    747 void KeyframeEffect::ResetPartialPrerendered() {
    748  MOZ_ASSERT(mAnimation && mAnimation->IsPartialPrerendered());
    749 
    750  nsIFrame* frame = GetPrimaryFrame();
    751  if (!frame) {
    752    return;
    753  }
    754 
    755  nsIWidget* widget = frame->GetNearestWidget();
    756  if (!widget) {
    757    return;
    758  }
    759 
    760  if (WindowRenderer* windowRenderer = widget->GetWindowRenderer()) {
    761    windowRenderer->RemovePartialPrerenderedAnimation(
    762        mAnimation->IdOnCompositor(), mAnimation);
    763  }
    764 }
    765 
    766 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
    767    const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) {
    768  MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
    769  return aOptions.GetAsKeyframeEffectOptions();
    770 }
    771 
    772 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
    773    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) {
    774  MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
    775  return aOptions.GetAsKeyframeAnimationOptions();
    776 }
    777 
    778 template <class OptionsType>
    779 static KeyframeEffectParams KeyframeEffectParamsFromUnion(
    780    const OptionsType& aOptions, CallerType aCallerType, ErrorResult& aRv) {
    781  KeyframeEffectParams result;
    782  if (aOptions.IsUnrestrictedDouble()) {
    783    return result;
    784  }
    785 
    786  const KeyframeEffectOptions& options =
    787      KeyframeEffectOptionsFromUnion(aOptions);
    788 
    789  // |result.mPseudoRequest| uses the default value, i.e. NotPseudo.
    790  result.mIterationComposite = options.mIterationComposite;
    791  result.mComposite = options.mComposite;
    792 
    793  if (DOMStringIsNull(options.mPseudoElement)) {
    794    return result;
    795  }
    796 
    797  Maybe<PseudoStyleRequest> pseudoRequest =
    798      nsCSSPseudoElements::ParsePseudoElement(options.mPseudoElement);
    799  if (!pseudoRequest) {
    800    // Per the spec, we throw SyntaxError for syntactically invalid pseudos.
    801    aRv.ThrowSyntaxError(
    802        nsPrintfCString("'%s' is a syntactically invalid pseudo-element.",
    803                        NS_ConvertUTF16toUTF8(options.mPseudoElement).get()));
    804    return result;
    805  }
    806 
    807  result.mPseudoRequest = std::move(*pseudoRequest);
    808  if (!AnimationUtils::IsSupportedPseudoForAnimations(result.mPseudoRequest)) {
    809    // Per the spec, we throw SyntaxError for unsupported pseudos.
    810    aRv.ThrowSyntaxError(
    811        nsPrintfCString("'%s' is an unsupported pseudo-element.",
    812                        NS_ConvertUTF16toUTF8(options.mPseudoElement).get()));
    813  }
    814 
    815  return result;
    816 }
    817 
    818 template <class OptionsType>
    819 /* static */
    820 already_AddRefed<KeyframeEffect> KeyframeEffect::ConstructKeyframeEffect(
    821    const GlobalObject& aGlobal, Element* aTarget,
    822    JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions,
    823    ErrorResult& aRv) {
    824  // We should get the document from `aGlobal` instead of the current Realm
    825  // to make this works in Xray case.
    826  //
    827  // In all non-Xray cases, `aGlobal` matches the current Realm, so this
    828  // matches the spec behavior.
    829  //
    830  // In Xray case, the new objects should be created using the document of
    831  // the target global, but the KeyframeEffect constructors are called in the
    832  // caller's compartment to access `aKeyframes` object.
    833  Document* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
    834  if (!doc) {
    835    aRv.Throw(NS_ERROR_FAILURE);
    836    return nullptr;
    837  }
    838 
    839  KeyframeEffectParams effectOptions =
    840      KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType(), aRv);
    841  // An invalid Pseudo-element aborts all further steps.
    842  if (aRv.Failed()) {
    843    return nullptr;
    844  }
    845 
    846  TimingParams timingParams = TimingParams::FromOptionsUnion(aOptions, aRv);
    847  if (aRv.Failed()) {
    848    return nullptr;
    849  }
    850 
    851  RefPtr<KeyframeEffect> effect = new KeyframeEffect(
    852      doc, OwningAnimationTarget(aTarget, effectOptions.mPseudoRequest),
    853      std::move(timingParams), effectOptions);
    854 
    855  effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
    856  if (aRv.Failed()) {
    857    return nullptr;
    858  }
    859 
    860  return effect.forget();
    861 }
    862 
    863 nsTArray<AnimationProperty> KeyframeEffect::BuildProperties(
    864    const ComputedStyle* aStyle) {
    865  MOZ_ASSERT(aStyle);
    866 
    867  nsTArray<AnimationProperty> result;
    868  // If mTarget is false (i.e. mTarget.mElement is null), return an empty
    869  // property array.
    870  if (!mTarget) {
    871    return result;
    872  }
    873 
    874  // When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes
    875  // calculate computed values from |mKeyframes|, they could possibly
    876  // trigger a subsequent restyle in which we rebuild animations. If that
    877  // happens we could find that |mKeyframes| is overwritten while it is
    878  // being iterated over. Normally that shouldn't happen but just in case we
    879  // make a copy of |mKeyframes| first and iterate over that instead.
    880  auto keyframesCopy(mKeyframes.Clone());
    881 
    882  result = KeyframeUtils::GetAnimationPropertiesFromKeyframes(
    883      keyframesCopy, mTarget.mElement, mTarget.mPseudoRequest, aStyle,
    884      mEffectOptions.mComposite);
    885 
    886 #ifdef DEBUG
    887  MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy),
    888             "Apart from the computed offset members, the keyframes array"
    889             " should not be modified");
    890 #endif
    891 
    892  mKeyframes = std::move(keyframesCopy);
    893  return result;
    894 }
    895 
    896 template <typename FrameEnumFunc>
    897 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame,
    898                                                    FrameEnumFunc&& aFunc) {
    899  while (aFrame) {
    900    aFunc(aFrame);
    901    aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
    902  }
    903 }
    904 
    905 void KeyframeEffect::UpdateTarget(Element* aElement,
    906                                  const PseudoStyleRequest& aPseudoRequest) {
    907  OwningAnimationTarget newTarget(aElement, aPseudoRequest);
    908 
    909  if (mTarget == newTarget) {
    910    // Assign the same target, skip it.
    911    return;
    912  }
    913 
    914  if (mTarget) {
    915    // Call ResetIsRunningOnCompositor() prior to UnregisterTarget() since
    916    // ResetIsRunningOnCompositor() might try to get the EffectSet associated
    917    // with this keyframe effect to remove partial pre-render animation from
    918    // the layer manager.
    919    ResetIsRunningOnCompositor();
    920    UnregisterTarget();
    921 
    922    RequestRestyle(EffectCompositor::RestyleType::Layer);
    923 
    924    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
    925    if (mAnimation) {
    926      MutationObservers::NotifyAnimationRemoved(mAnimation);
    927    }
    928  }
    929 
    930  mTarget = newTarget;
    931 
    932  if (mTarget) {
    933    UpdateTargetRegistration();
    934    RefPtr<const ComputedStyle> computedStyle =
    935        GetTargetComputedStyle(Flush::None);
    936    if (computedStyle) {
    937      UpdateProperties(computedStyle);
    938    }
    939 
    940    RequestRestyle(EffectCompositor::RestyleType::Layer);
    941 
    942    nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
    943    if (mAnimation) {
    944      MutationObservers::NotifyAnimationAdded(mAnimation);
    945    }
    946  }
    947 
    948  if (mAnimation) {
    949    mAnimation->NotifyEffectTargetUpdated();
    950  }
    951 }
    952 
    953 void KeyframeEffect::UpdateTargetRegistration() {
    954  if (!mTarget) {
    955    return;
    956  }
    957 
    958  bool isRelevant = mAnimation && mAnimation->IsRelevant();
    959 
    960  // Animation::IsRelevant() returns a cached value. It only updates when
    961  // something calls Animation::UpdateRelevance. Whenever our timing changes,
    962  // we should be notifying our Animation before calling this, so
    963  // Animation::IsRelevant() should be up-to-date by the time we get here.
    964  MOZ_ASSERT(isRelevant ==
    965                 ((IsCurrent() || IsInEffect()) && mAnimation &&
    966                  mAnimation->ReplaceState() != AnimationReplaceState::Removed),
    967             "Out of date Animation::IsRelevant value");
    968 
    969  if (isRelevant && !mInEffectSet) {
    970    EffectSet* effectSet = EffectSet::GetOrCreate(mTarget);
    971    effectSet->AddEffect(*this);
    972    mInEffectSet = true;
    973    UpdateEffectSet(effectSet);
    974    nsIFrame* frame = GetPrimaryFrame();
    975    EnumerateContinuationsOrIBSplitSiblings(
    976        frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); });
    977  } else if (!isRelevant && mInEffectSet) {
    978    UnregisterTarget();
    979  }
    980 }
    981 
    982 void KeyframeEffect::UnregisterTarget() {
    983  if (!mInEffectSet) {
    984    return;
    985  }
    986 
    987  EffectSet* effectSet = EffectSet::Get(mTarget);
    988  MOZ_ASSERT(effectSet,
    989             "If mInEffectSet is true, there must be an EffectSet"
    990             " on the target element");
    991  mInEffectSet = false;
    992  if (effectSet) {
    993    effectSet->RemoveEffect(*this);
    994 
    995    if (effectSet->IsEmpty()) {
    996      EffectSet::DestroyEffectSet(mTarget);
    997    }
    998  }
    999  nsIFrame* frame = GetPrimaryFrame();
   1000  EnumerateContinuationsOrIBSplitSiblings(
   1001      frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); });
   1002 }
   1003 
   1004 void KeyframeEffect::RequestRestyle(
   1005    EffectCompositor::RestyleType aRestyleType) {
   1006  if (!mTarget) {
   1007    return;
   1008  }
   1009  nsPresContext* presContext =
   1010      nsContentUtils::GetContextForContent(mTarget.mElement);
   1011  if (presContext && mAnimation) {
   1012    presContext->EffectCompositor()->RequestRestyle(
   1013        mTarget.mElement, mTarget.mPseudoRequest, aRestyleType,
   1014        mAnimation->CascadeLevel());
   1015  }
   1016 }
   1017 
   1018 already_AddRefed<const ComputedStyle> KeyframeEffect::GetTargetComputedStyle(
   1019    Flush aFlushType) const {
   1020  if (!GetRenderedDocument()) {
   1021    return nullptr;
   1022  }
   1023 
   1024  MOZ_ASSERT(mTarget,
   1025             "Should only have a document when we have a target element");
   1026 
   1027  OwningAnimationTarget kungfuDeathGrip(mTarget.mElement,
   1028                                        mTarget.mPseudoRequest);
   1029 
   1030  return aFlushType == Flush::Style
   1031             ? nsComputedDOMStyle::GetComputedStyle(mTarget.mElement,
   1032                                                    mTarget.mPseudoRequest)
   1033             : nsComputedDOMStyle::GetComputedStyleNoFlush(
   1034                   mTarget.mElement, mTarget.mPseudoRequest);
   1035 }
   1036 
   1037 #ifdef DEBUG
   1038 void DumpAnimationProperties(
   1039    const StylePerDocumentStyleData* aRawData,
   1040    nsTArray<AnimationProperty>& aAnimationProperties) {
   1041  for (auto& p : aAnimationProperties) {
   1042    printf("%s\n",
   1043           nsCString(nsCSSProps::GetStringValue(p.mProperty.mId)).get());
   1044    for (auto& s : p.mSegments) {
   1045      nsAutoCString fromValue, toValue;
   1046      s.mFromValue.SerializeSpecifiedValue(p.mProperty, aRawData, fromValue);
   1047      s.mToValue.SerializeSpecifiedValue(p.mProperty, aRawData, toValue);
   1048      printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey, fromValue.get(),
   1049             toValue.get());
   1050    }
   1051  }
   1052 }
   1053 #endif
   1054 
   1055 /* static */
   1056 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
   1057    const GlobalObject& aGlobal, Element* aTarget,
   1058    JS::Handle<JSObject*> aKeyframes,
   1059    const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
   1060    ErrorResult& aRv) {
   1061  return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
   1062 }
   1063 
   1064 /* static */
   1065 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
   1066    const GlobalObject& aGlobal, Element* aTarget,
   1067    JS::Handle<JSObject*> aKeyframes,
   1068    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
   1069    ErrorResult& aRv) {
   1070  return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
   1071 }
   1072 
   1073 /* static */
   1074 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
   1075    const GlobalObject& aGlobal, KeyframeEffect& aSource, ErrorResult& aRv) {
   1076  Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
   1077  if (!doc) {
   1078    aRv.Throw(NS_ERROR_FAILURE);
   1079    return nullptr;
   1080  }
   1081 
   1082  // Create a new KeyframeEffect object with aSource's target,
   1083  // iteration composite operation, composite operation, and spacing mode.
   1084  // The constructor creates a new AnimationEffect object by
   1085  // aSource's TimingParams.
   1086  // Note: we don't need to re-throw exceptions since the value specified on
   1087  //       aSource's timing object can be assumed valid.
   1088  RefPtr<KeyframeEffect> effect =
   1089      new KeyframeEffect(doc, OwningAnimationTarget{aSource.mTarget}, aSource);
   1090  // Copy cumulative changes. mCumulativeChangeHint should be the same as the
   1091  // source one because both of targets are the same.
   1092  effect->mCumulativeChanges = aSource.mCumulativeChanges;
   1093  return effect.forget();
   1094 }
   1095 
   1096 void KeyframeEffect::SetPseudoElement(const nsAString& aPseudoElement,
   1097                                      ErrorResult& aRv) {
   1098  if (DOMStringIsNull(aPseudoElement)) {
   1099    UpdateTarget(mTarget.mElement, PseudoStyleRequest::NotPseudo());
   1100    return;
   1101  }
   1102 
   1103  // Note: ParsePseudoELement() returns Some(NotPseudo) for the null string,
   1104  // so we handle null case before this.
   1105  Maybe<PseudoStyleRequest> pseudoRequest =
   1106      nsCSSPseudoElements::ParsePseudoElement(aPseudoElement);
   1107  if (!pseudoRequest || pseudoRequest->IsNotPseudo()) {
   1108    // Per the spec, we throw SyntaxError for syntactically invalid pseudos.
   1109    aRv.ThrowSyntaxError(
   1110        nsPrintfCString("'%s' is a syntactically invalid pseudo-element.",
   1111                        NS_ConvertUTF16toUTF8(aPseudoElement).get()));
   1112    return;
   1113  }
   1114 
   1115  if (!AnimationUtils::IsSupportedPseudoForAnimations(*pseudoRequest)) {
   1116    // Per the spec, we throw SyntaxError for unsupported pseudos.
   1117    aRv.ThrowSyntaxError(
   1118        nsPrintfCString("'%s' is an unsupported pseudo-element.",
   1119                        NS_ConvertUTF16toUTF8(aPseudoElement).get()));
   1120    return;
   1121  }
   1122 
   1123  UpdateTarget(mTarget.mElement, *pseudoRequest);
   1124 }
   1125 
   1126 static void CreatePropertyValue(
   1127    const CSSPropertyId& aProperty, float aOffset,
   1128    const Maybe<StyleComputedTimingFunction>& aTimingFunction,
   1129    const AnimationValue& aValue, dom::CompositeOperation aComposite,
   1130    const StylePerDocumentStyleData* aRawData,
   1131    AnimationPropertyValueDetails& aResult) {
   1132  aResult.mOffset = aOffset;
   1133 
   1134  if (!aValue.IsNull()) {
   1135    nsAutoCString stringValue;
   1136    aValue.SerializeSpecifiedValue(aProperty, aRawData, stringValue);
   1137    aResult.mValue.Construct(stringValue);
   1138  }
   1139 
   1140  if (aTimingFunction) {
   1141    aResult.mEasing.Construct();
   1142    aTimingFunction->AppendToString(aResult.mEasing.Value());
   1143  } else {
   1144    aResult.mEasing.Construct("linear"_ns);
   1145  }
   1146 
   1147  aResult.mComposite = aComposite;
   1148 }
   1149 
   1150 void KeyframeEffect::GetProperties(
   1151    nsTArray<AnimationPropertyDetails>& aProperties, ErrorResult& aRv) const {
   1152  const StylePerDocumentStyleData* rawData =
   1153      mDocument->EnsureStyleSet().RawData();
   1154 
   1155  for (const AnimationProperty& property : mProperties) {
   1156    AnimationPropertyDetails propertyDetails;
   1157    property.mProperty.ToString(propertyDetails.mProperty);
   1158    propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
   1159 
   1160    nsAutoString localizedString;
   1161    if (property.mPerformanceWarning &&
   1162        property.mPerformanceWarning->ToLocalizedString(localizedString)) {
   1163      propertyDetails.mWarning.Construct(localizedString);
   1164    }
   1165 
   1166    if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),
   1167                                             mozilla::fallible)) {
   1168      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   1169      return;
   1170    }
   1171 
   1172    for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
   1173         segmentIdx < segmentLen; segmentIdx++) {
   1174      const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
   1175 
   1176      binding_detail::FastAnimationPropertyValueDetails fromValue;
   1177      CreatePropertyValue(property.mProperty, segment.mFromKey,
   1178                          segment.mTimingFunction, segment.mFromValue,
   1179                          segment.mFromComposite, rawData, fromValue);
   1180      // We don't apply timing functions for zero-length segments, so
   1181      // don't return one here.
   1182      if (segment.mFromKey == segment.mToKey) {
   1183        fromValue.mEasing.Reset();
   1184      }
   1185      // Even though we called SetCapacity before, this could fail, since we
   1186      // might add multiple elements to propertyDetails.mValues for an element
   1187      // of property.mSegments in the cases mentioned below.
   1188      if (!propertyDetails.mValues.AppendElement(fromValue,
   1189                                                 mozilla::fallible)) {
   1190        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   1191        return;
   1192      }
   1193 
   1194      // Normally we can ignore the to-value for this segment since it is
   1195      // identical to the from-value from the next segment. However, we need
   1196      // to add it if either:
   1197      // a) this is the last segment, or
   1198      // b) the next segment's from-value differs.
   1199      if (segmentIdx == segmentLen - 1 ||
   1200          property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
   1201        binding_detail::FastAnimationPropertyValueDetails toValue;
   1202        CreatePropertyValue(property.mProperty, segment.mToKey, Nothing(),
   1203                            segment.mToValue, segment.mToComposite, rawData,
   1204                            toValue);
   1205        // It doesn't really make sense to have a timing function on the
   1206        // last property value or before a sudden jump so we just drop the
   1207        // easing property altogether.
   1208        toValue.mEasing.Reset();
   1209        if (!propertyDetails.mValues.AppendElement(toValue,
   1210                                                   mozilla::fallible)) {
   1211          aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   1212          return;
   1213        }
   1214      }
   1215    }
   1216 
   1217    aProperties.AppendElement(propertyDetails);
   1218  }
   1219 }
   1220 
   1221 void KeyframeEffect::GetKeyframes(JSContext* aCx, nsTArray<JSObject*>& aResult,
   1222                                  ErrorResult& aRv) const {
   1223  MOZ_ASSERT(aResult.IsEmpty());
   1224  MOZ_ASSERT(!aRv.Failed());
   1225 
   1226  if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) {
   1227    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   1228    return;
   1229  }
   1230 
   1231  bool isCSSAnimation = mAnimation && mAnimation->AsCSSAnimation();
   1232 
   1233  // For Servo, when we have CSS Animation @keyframes with variables, we convert
   1234  // shorthands to longhands if needed, and store a reference to the unparsed
   1235  // value. When it comes time to serialize, however, what do you serialize for
   1236  // a longhand that comes from a variable reference in a shorthand? Servo says,
   1237  // "an empty string" which is not particularly helpful.
   1238  //
   1239  // We should just store shorthands as-is (bug 1391537) and then return the
   1240  // variable references, but for now, since we don't do that, and in order to
   1241  // be consistent with Gecko, we just expand the variables (assuming we have
   1242  // enough context to do so). For that we need to grab the ComputedStyle so we
   1243  // know what custom property values to provide.
   1244  RefPtr<const ComputedStyle> computedStyle;
   1245  if (isCSSAnimation) {
   1246    // The following will flush style but that's ok since if you update a
   1247    // variable's computed value, you expect to see that updated value in the
   1248    // result of getKeyframes().
   1249    //
   1250    // If we don't have a target, the following will return null. In that case
   1251    // we might end up returning variables as-is or empty string. That should be
   1252    // acceptable however, since such a case is rare and this is only
   1253    // short-term (and unshipped) behavior until bug 1391537 is fixed.
   1254    computedStyle = GetTargetComputedStyle(Flush::Style);
   1255  }
   1256 
   1257  const StylePerDocumentStyleData* rawData =
   1258      mDocument->EnsureStyleSet().RawData();
   1259 
   1260  for (const Keyframe& keyframe : mKeyframes) {
   1261    // Set up a dictionary object for the explicit members
   1262    BaseComputedKeyframe keyframeDict;
   1263    if (keyframe.mOffset) {
   1264      keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
   1265    }
   1266    MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
   1267               "Invalid computed offset");
   1268    keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
   1269    if (keyframe.mTimingFunction) {
   1270      keyframeDict.mEasing.Truncate();
   1271      keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
   1272    }  // else if null, leave easing as its default "linear".
   1273 
   1274    keyframeDict.mComposite = keyframe.mComposite;
   1275 
   1276    JS::Rooted<JS::Value> keyframeJSValue(aCx);
   1277    if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
   1278      aRv.Throw(NS_ERROR_FAILURE);
   1279      return;
   1280    }
   1281 
   1282    JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
   1283    for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
   1284      nsAutoCString stringValue;
   1285      if (propertyValue.mServoDeclarationBlock) {
   1286        Servo_DeclarationBlock_SerializeOneValue(
   1287            propertyValue.mServoDeclarationBlock, &propertyValue.mProperty,
   1288            &stringValue, computedStyle, rawData);
   1289      } else if (auto* value = mBaseValues.GetWeak(propertyValue.mProperty)) {
   1290        Servo_AnimationValue_Serialize(value, &propertyValue.mProperty, rawData,
   1291                                       &stringValue);
   1292      }
   1293 
   1294      // Basically, we need to do the mapping:
   1295      // * eCSSProperty_offset => "cssOffset"
   1296      // * eCSSProperty_float => "cssFloat"
   1297      // This means if property refers to the CSS "offset"/"float" property,
   1298      // return the string "cssOffset"/"cssFloat". (So avoid overlapping
   1299      // "offset" property in BaseKeyframe.)
   1300      // https://drafts.csswg.org/web-animations/#property-name-conversion
   1301      const char* name = nullptr;
   1302      nsAutoCString customName;
   1303      switch (propertyValue.mProperty.mId) {
   1304        case NonCustomCSSPropertyId::eCSSPropertyExtra_variable:
   1305          customName.Append("--");
   1306          customName.Append(nsAtomCString(propertyValue.mProperty.mCustomName));
   1307          name = customName.get();
   1308          break;
   1309        case NonCustomCSSPropertyId::eCSSProperty_offset:
   1310          name = "cssOffset";
   1311          break;
   1312        case NonCustomCSSPropertyId::eCSSProperty_float:
   1313          // FIXME: Bug 1582314: Should handle cssFloat manually if we remove it
   1314          // from nsCSSProps::PropertyIDLName().
   1315        default:
   1316          name = nsCSSProps::PropertyIDLName(propertyValue.mProperty.mId);
   1317      }
   1318 
   1319      JS::Rooted<JS::Value> value(aCx);
   1320      if (!NonVoidUTF8StringToJsval(aCx, stringValue, &value) ||
   1321          !JS_DefineProperty(aCx, keyframeObject, name, value,
   1322                             JSPROP_ENUMERATE)) {
   1323        aRv.Throw(NS_ERROR_FAILURE);
   1324        return;
   1325      }
   1326    }
   1327 
   1328    aResult.AppendElement(keyframeObject);
   1329  }
   1330 }
   1331 
   1332 /* static */ const TimeDuration
   1333 KeyframeEffect::OverflowRegionRefreshInterval() {
   1334  // The amount of time we can wait between updating throttled animations
   1335  // on the main thread that influence the overflow region.
   1336  static const TimeDuration kOverflowRegionRefreshInterval =
   1337      TimeDuration::FromMilliseconds(200);
   1338 
   1339  return kOverflowRegionRefreshInterval;
   1340 }
   1341 
   1342 static bool CanOptimizeAwayDueToOpacity(const KeyframeEffect& aEffect,
   1343                                        const nsIFrame& aFrame) {
   1344  if (!aFrame.Style()->IsInOpacityZeroSubtree()) {
   1345    return false;
   1346  }
   1347 
   1348  // Find the root of the opacity: 0 subtree.
   1349  const nsIFrame* root = &aFrame;
   1350  while (true) {
   1351    auto* parent = root->GetInFlowParent();
   1352    if (!parent || !parent->Style()->IsInOpacityZeroSubtree()) {
   1353      break;
   1354    }
   1355    root = parent;
   1356  }
   1357 
   1358  MOZ_ASSERT(root && root->Style()->IsInOpacityZeroSubtree());
   1359 
   1360  // Even if we're in an opacity: zero subtree, if the root of the subtree may
   1361  // have an opacity animation, we can't optimize us away, as we may become
   1362  // visible ourselves.
   1363  return (root != &aFrame || !aEffect.HasOpacityChange()) &&
   1364         !root->HasAnimationOfOpacity();
   1365 }
   1366 
   1367 bool KeyframeEffect::CanThrottleIfNotVisible(nsIFrame& aFrame) const {
   1368  // Unless we are newly in-effect, we can throttle the animation if the
   1369  // animation is paint only and the target frame is out of view or the document
   1370  // is in background tabs.
   1371  if (!mInEffectOnLastAnimationTimingUpdate || !CanIgnoreIfNotVisible()) {
   1372    return false;
   1373  }
   1374 
   1375  PresShell* presShell = GetPresShell();
   1376  if (presShell && !presShell->IsActive()) {
   1377    return true;
   1378  }
   1379 
   1380  // The frame may be indirectly rendered as a mask or clipPath or via a use
   1381  // element.
   1382  if (SVGObserverUtils::SelfOrAncestorHasRenderingObservers(&aFrame)) {
   1383    return false;
   1384  }
   1385 
   1386  const bool isVisibilityHidden =
   1387      !aFrame.IsVisibleOrMayHaveVisibleDescendants();
   1388  const bool canOptimizeAwayVisibility =
   1389      isVisibilityHidden && !HasVisibilityChange();
   1390 
   1391  const bool invisible = canOptimizeAwayVisibility ||
   1392                         CanOptimizeAwayDueToOpacity(*this, aFrame) ||
   1393                         aFrame.IsScrolledOutOfView();
   1394  if (!invisible) {
   1395    return false;
   1396  }
   1397 
   1398  // If there are no overflow change hints, we don't need to worry about
   1399  // unthrottling the animation periodically to update scrollbar positions for
   1400  // the overflow region.
   1401  if (!HasPropertiesThatMightAffectOverflow()) {
   1402    return true;
   1403  }
   1404 
   1405  // Don't throttle finite animations since the animation might suddenly
   1406  // come into view and if it was throttled it will be out-of-sync.
   1407  if (HasFiniteActiveDuration()) {
   1408    return false;
   1409  }
   1410 
   1411  return isVisibilityHidden ? CanThrottleOverflowChangesInScrollable(aFrame)
   1412                            : CanThrottleOverflowChanges(aFrame);
   1413 }
   1414 
   1415 bool KeyframeEffect::CanThrottle() const {
   1416  // Unthrottle if we are not in effect or current. This will be the case when
   1417  // our owning animation has finished, is idle, or when we are in the delay
   1418  // phase (but without a backwards fill). In each case the computed progress
   1419  // value produced on each tick will be the same so we will skip requesting
   1420  // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get
   1421  // here will be because of a change in state (e.g. we are newly finished or
   1422  // newly no longer in effect) in which case we shouldn't throttle the sample.
   1423  if (!IsInEffect() || !IsCurrent()) {
   1424    return false;
   1425  }
   1426 
   1427  nsIFrame* const frame = GetStyleFrame();
   1428  if (!frame) {
   1429    // There are two possible cases here.
   1430    // a) No target element
   1431    // b) The target element has no frame, e.g. because it is in a display:none
   1432    //    subtree.
   1433    // In either case we can throttle the animation because there is no
   1434    // need to update on the main thread.
   1435    return true;
   1436  }
   1437 
   1438  // Do not throttle any animations during print preview.
   1439  if (frame->PresContext()->IsPrintingOrPrintPreview()) {
   1440    return false;
   1441  }
   1442 
   1443  if (CanThrottleIfNotVisible(*frame)) {
   1444    return true;
   1445  }
   1446 
   1447  EffectSet* effectSet = nullptr;
   1448  for (const AnimationProperty& property : mProperties) {
   1449    if (!property.mIsRunningOnCompositor) {
   1450      return false;
   1451    }
   1452 
   1453    MOZ_ASSERT(nsCSSPropertyIDSet::CompositorAnimatables().HasProperty(
   1454                   property.mProperty),
   1455               "The property should be able to run on the compositor");
   1456    if (!effectSet) {
   1457      effectSet = EffectSet::Get(mTarget);
   1458      MOZ_ASSERT(effectSet,
   1459                 "CanThrottle should be called on an effect "
   1460                 "associated with a target element");
   1461    }
   1462 
   1463    DisplayItemType displayItemType =
   1464        LayerAnimationInfo::GetDisplayItemTypeForProperty(
   1465            property.mProperty.mId);
   1466 
   1467    // Note that AnimationInfo::GetGenarationFromFrame() is supposed to work
   1468    // with the primary frame instead of the style frame.
   1469    Maybe<uint64_t> generation = layers::AnimationInfo::GetGenerationFromFrame(
   1470        GetPrimaryFrame(), displayItemType);
   1471    // Unthrottle if the animation needs to be brought up to date
   1472    if (!generation || effectSet->GetAnimationGeneration() != *generation) {
   1473      return false;
   1474    }
   1475 
   1476    MOZ_ASSERT(HasEffectiveAnimationOfProperty(property.mProperty, *effectSet),
   1477               "There should be an effective animation of the property while "
   1478               "it is marked as being run on the compositor");
   1479 
   1480    // If this is a transform animation that affects the overflow region,
   1481    // we should unthrottle the animation periodically.
   1482    if (HasPropertiesThatMightAffectOverflow() &&
   1483        !CanThrottleOverflowChangesInScrollable(*frame)) {
   1484      return false;
   1485    }
   1486  }
   1487 
   1488  return true;
   1489 }
   1490 
   1491 bool KeyframeEffect::CanThrottleOverflowChanges(const nsIFrame& aFrame) const {
   1492  TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh();
   1493 
   1494  EffectSet* effectSet = EffectSet::Get(mTarget);
   1495  MOZ_ASSERT(effectSet,
   1496             "CanOverflowTransformChanges is expected to be called"
   1497             " on an effect in an effect set");
   1498  MOZ_ASSERT(mAnimation,
   1499             "CanOverflowTransformChanges is expected to be called"
   1500             " on an effect with a parent animation");
   1501  TimeStamp lastSyncTime = effectSet->LastOverflowAnimationSyncTime();
   1502  // If this animation can cause overflow, we can throttle some of the ticks.
   1503  return (!lastSyncTime.IsNull() &&
   1504          (now - lastSyncTime) < OverflowRegionRefreshInterval());
   1505 }
   1506 
   1507 bool KeyframeEffect::CanThrottleOverflowChangesInScrollable(
   1508    nsIFrame& aFrame) const {
   1509  // If the target element is not associated with any documents, we don't care
   1510  // it.
   1511  Document* doc = GetRenderedDocument();
   1512  if (!doc) {
   1513    return true;
   1514  }
   1515 
   1516  // If we know that the animation cannot cause overflow,
   1517  // we can just disable flushes for this animation.
   1518 
   1519  // If we have no intersection observers, we don't care about overflow.
   1520  if (!doc->HasIntersectionObservers()) {
   1521    return true;
   1522  }
   1523 
   1524  if (CanThrottleOverflowChanges(aFrame)) {
   1525    return true;
   1526  }
   1527 
   1528  // If the nearest scroll container ancestor has overflow:hidden,
   1529  // we don't care about overflow.
   1530  ScrollContainerFrame* scrollContainerFrame =
   1531      nsLayoutUtils::GetNearestScrollContainerFrame(&aFrame);
   1532  if (!scrollContainerFrame) {
   1533    return true;
   1534  }
   1535 
   1536  ScrollStyles ss = scrollContainerFrame->GetScrollStyles();
   1537  if (ss.mVertical == StyleOverflow::Hidden &&
   1538      ss.mHorizontal == StyleOverflow::Hidden &&
   1539      scrollContainerFrame->GetLogicalScrollPosition() == nsPoint(0, 0)) {
   1540    return true;
   1541  }
   1542 
   1543  return false;
   1544 }
   1545 
   1546 nsIFrame* KeyframeEffect::GetStyleFrame() const {
   1547  nsIFrame* frame = GetPrimaryFrame();
   1548  if (!frame) {
   1549    return nullptr;
   1550  }
   1551 
   1552  return nsLayoutUtils::GetStyleFrame(frame);
   1553 }
   1554 
   1555 nsIFrame* KeyframeEffect::GetPrimaryFrame() const {
   1556  nsIFrame* frame = nullptr;
   1557  if (!mTarget) {
   1558    return frame;
   1559  }
   1560 
   1561  switch (mTarget.mPseudoRequest.mType) {
   1562    case PseudoStyleType::before:
   1563      frame = nsLayoutUtils::GetBeforeFrame(mTarget.mElement);
   1564      break;
   1565    case PseudoStyleType::after:
   1566      frame = nsLayoutUtils::GetAfterFrame(mTarget.mElement);
   1567      break;
   1568    case PseudoStyleType::marker:
   1569      frame = nsLayoutUtils::GetMarkerFrame(mTarget.mElement);
   1570      break;
   1571    case PseudoStyleType::backdrop:
   1572      frame = nsLayoutUtils::GetBackdropFrame(mTarget.mElement);
   1573      break;
   1574    case PseudoStyleType::viewTransition:
   1575    case PseudoStyleType::viewTransitionGroup:
   1576    case PseudoStyleType::viewTransitionImagePair:
   1577    case PseudoStyleType::viewTransitionOld:
   1578    case PseudoStyleType::viewTransitionNew:
   1579      if (Element* pseudoElement =
   1580              mTarget.mElement->GetPseudoElement(mTarget.mPseudoRequest)) {
   1581        frame = pseudoElement->GetPrimaryFrame();
   1582      }
   1583      break;
   1584    default:
   1585      frame = mTarget.mElement->GetPrimaryFrame();
   1586      MOZ_ASSERT(mTarget.mPseudoRequest.IsNotPseudo(),
   1587                 "unknown mTarget.mPseudoRequest");
   1588  }
   1589 
   1590  return frame;
   1591 }
   1592 
   1593 Document* KeyframeEffect::GetRenderedDocument() const {
   1594  if (!mTarget) {
   1595    return nullptr;
   1596  }
   1597  return mTarget.mElement->GetComposedDoc();
   1598 }
   1599 
   1600 PresShell* KeyframeEffect::GetPresShell() const {
   1601  Document* doc = GetRenderedDocument();
   1602  if (!doc) {
   1603    return nullptr;
   1604  }
   1605  return doc->GetPresShell();
   1606 }
   1607 
   1608 /* static */
   1609 bool KeyframeEffect::IsGeometricProperty(
   1610    const NonCustomCSSPropertyId aProperty) {
   1611  MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
   1612             "Property should be a longhand property");
   1613 
   1614  switch (aProperty) {
   1615    case eCSSProperty_bottom:
   1616    case eCSSProperty_height:
   1617    case eCSSProperty_left:
   1618    case eCSSProperty_margin_bottom:
   1619    case eCSSProperty_margin_left:
   1620    case eCSSProperty_margin_right:
   1621    case eCSSProperty_margin_top:
   1622    case eCSSProperty_padding_bottom:
   1623    case eCSSProperty_padding_left:
   1624    case eCSSProperty_padding_right:
   1625    case eCSSProperty_padding_top:
   1626    case eCSSProperty_right:
   1627    case eCSSProperty_top:
   1628    case eCSSProperty_width:
   1629      return true;
   1630    default:
   1631      return false;
   1632  }
   1633 }
   1634 
   1635 /* static */
   1636 bool KeyframeEffect::CanAnimateTransformOnCompositor(
   1637    const nsIFrame* aFrame,
   1638    AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) {
   1639  // In some cases, such as when we are simply collecting all the compositor
   1640  // animations regardless of the frame on which they run in order to calculate
   1641  // change hints, |aFrame| will be the style frame. However, even in that case
   1642  // we should look at the primary frame since that is where the transform will
   1643  // be applied.
   1644  const nsIFrame* primaryFrame =
   1645      nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame);
   1646 
   1647  // Async 'transform' animations of aFrames with SVG transforms is not
   1648  // supported.  See bug 779599.
   1649  if (primaryFrame->GetParentSVGTransforms()) {
   1650    aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
   1651    return false;
   1652  }
   1653 
   1654  // If there's any content that might have non-scaling stroke then we can't
   1655  // run in the compositor.
   1656  if (primaryFrame->IsSVGFrame() &&
   1657      primaryFrame->HasAnyStateBits(
   1658          NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE)) {
   1659    aPerformanceWarning = AnimationPerformanceWarning::Type::NonScalingStroke;
   1660    return false;
   1661  }
   1662 
   1663  return true;
   1664 }
   1665 
   1666 bool KeyframeEffect::ShouldBlockAsyncTransformAnimations(
   1667    const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
   1668    AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const {
   1669  // If we depend on the SVG url (no matter whether there are any offset-path
   1670  // animations), we cannot run any transform-like animations in the compositor
   1671  // because we cannot resolve the url in the compositor if its style uses url.
   1672  if (aFrame->StyleDisplay()->mOffsetPath.IsUrl()) {
   1673    return true;
   1674  }
   1675 
   1676  EffectSet* effectSet = EffectSet::Get(mTarget);
   1677  // The various transform properties ('transform', 'scale' etc.) get combined
   1678  // on the compositor.
   1679  //
   1680  // As a result, if we have an animation of 'scale' and 'translate', but the
   1681  // 'translate' property is covered by an !important rule, we will not be
   1682  // able to combine the result on the compositor since we won't have the
   1683  // !important rule to incorporate. In that case we should run all the
   1684  // transform-related animations on the main thread (where we have the
   1685  // !important rule).
   1686  nsCSSPropertyIDSet blockedProperties =
   1687      effectSet->PropertiesForAnimationsLevel().Intersect(
   1688          effectSet->PropertiesWithImportantRules());
   1689  if (blockedProperties.Intersects(aPropertySet)) {
   1690    aPerformanceWarning =
   1691        AnimationPerformanceWarning::Type::TransformIsBlockedByImportantRules;
   1692    return true;
   1693  }
   1694 
   1695  MOZ_ASSERT(mAnimation);
   1696  for (const AnimationProperty& property : mProperties) {
   1697    // If there is a property for animations level that is overridden by
   1698    // !important rules, it should not block other animations from running
   1699    // on the compositor.
   1700    // NOTE: We don't currently check for !important rules for properties that
   1701    // don't run on the compositor. As result such properties (e.g. margin-left)
   1702    // can still block async animations even if they are overridden by
   1703    // !important rules.
   1704    if (effectSet &&
   1705        effectSet->PropertiesWithImportantRules().HasProperty(
   1706            property.mProperty) &&
   1707        effectSet->PropertiesForAnimationsLevel().HasProperty(
   1708            property.mProperty)) {
   1709      continue;
   1710    }
   1711 
   1712    // Check for unsupported transform animations
   1713    if (LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM)
   1714            .HasProperty(property.mProperty)) {
   1715      if (!CanAnimateTransformOnCompositor(aFrame, aPerformanceWarning)) {
   1716        return true;
   1717      }
   1718    }
   1719 
   1720    // If there are any offset-path animations whose animation values are url(),
   1721    // we have to sync with the main thread when resolving it.
   1722    if (property.mProperty.mId == eCSSProperty_offset_path) {
   1723      for (const auto& seg : property.mSegments) {
   1724        if (seg.mFromValue.IsOffsetPathUrl() ||
   1725            seg.mToValue.IsOffsetPathUrl()) {
   1726          return true;
   1727        }
   1728      }
   1729    }
   1730  }
   1731 
   1732  return false;
   1733 }
   1734 
   1735 bool KeyframeEffect::HasGeometricProperties() const {
   1736  for (const AnimationProperty& property : mProperties) {
   1737    if (IsGeometricProperty(property.mProperty.mId)) {
   1738      return true;
   1739    }
   1740  }
   1741 
   1742  return false;
   1743 }
   1744 
   1745 void KeyframeEffect::SetPerformanceWarning(
   1746    const nsCSSPropertyIDSet& aPropertySet,
   1747    const AnimationPerformanceWarning& aWarning) {
   1748  nsCSSPropertyIDSet curr = aPropertySet;
   1749  for (AnimationProperty& property : mProperties) {
   1750    if (!curr.HasProperty(property.mProperty)) {
   1751      continue;
   1752    }
   1753    property.SetPerformanceWarning(aWarning, mTarget.mElement);
   1754    curr.RemoveProperty(property.mProperty.mId);
   1755    if (curr.IsEmpty()) {
   1756      return;
   1757    }
   1758  }
   1759 }
   1760 
   1761 void KeyframeEffect::CalculateCumulativeChangesForProperty(
   1762    const AnimationProperty& aProperty) {
   1763  if (aProperty.mProperty.IsCustom()) {
   1764    // Custom properties don't affect rendering on their own.
   1765    return;
   1766  }
   1767 
   1768  constexpr auto kInterestingFlags =
   1769      CSSPropFlags::AffectsLayout | CSSPropFlags::AffectsOverflow;
   1770  if (aProperty.mProperty.mId == eCSSProperty_opacity) {
   1771    mCumulativeChanges.mOpacity = true;
   1772    return;  // We know opacity is visual-only.
   1773  }
   1774 
   1775  if (aProperty.mProperty.mId == eCSSProperty_visibility) {
   1776    mCumulativeChanges.mVisibility = true;
   1777    return;  // We know visibility is visual-only.
   1778  }
   1779 
   1780  if (aProperty.mProperty.mId == eCSSProperty_background_color) {
   1781    if (!mCumulativeChanges.mHasBackgroundColorCurrentColor) {
   1782      mCumulativeChanges.mHasBackgroundColorCurrentColor =
   1783          HasCurrentColor(aProperty.mSegments);
   1784    }
   1785    return;  // We know background-color is visual-only.
   1786  }
   1787 
   1788  auto flags = nsCSSProps::PropFlags(aProperty.mProperty.mId);
   1789  if (!(flags & kInterestingFlags)) {
   1790    return;  // Property is visual-only.
   1791  }
   1792 
   1793  bool anyChange = false;
   1794  for (const AnimationPropertySegment& segment : aProperty.mSegments) {
   1795    if (!segment.HasReplaceableValues() ||
   1796        segment.mFromValue != segment.mToValue) {
   1797      // We can't know non-replaceable values until we compose the animation, so
   1798      // assume a change there.
   1799      anyChange = true;
   1800      break;
   1801    }
   1802  }
   1803 
   1804  if (!anyChange) {
   1805    return;
   1806  }
   1807 
   1808  mCumulativeChanges.mOverflow |= bool(flags & CSSPropFlags::AffectsOverflow);
   1809  mCumulativeChanges.mLayout |= bool(flags & CSSPropFlags::AffectsLayout);
   1810 }
   1811 
   1812 void KeyframeEffect::SetAnimation(Animation* aAnimation) {
   1813  if (mAnimation == aAnimation) {
   1814    return;
   1815  }
   1816 
   1817  // Restyle for the old animation.
   1818  RequestRestyle(EffectCompositor::RestyleType::Layer);
   1819 
   1820  mAnimation = aAnimation;
   1821 
   1822  UpdateNormalizedTiming();
   1823 
   1824  // The order of these function calls is important:
   1825  // NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check
   1826  // if it should create the effectSet or not, and MarkCascadeNeedsUpdate()
   1827  // needs a valid effectSet, so we should call them in this order.
   1828  if (mAnimation) {
   1829    mAnimation->UpdateRelevance();
   1830  }
   1831  NotifyAnimationTimingUpdated(PostRestyleMode::IfNeeded);
   1832  if (mAnimation) {
   1833    MarkCascadeNeedsUpdate();
   1834  }
   1835 }
   1836 
   1837 bool KeyframeEffect::CanIgnoreIfNotVisible() const {
   1838  if (!StaticPrefs::dom_animations_offscreen_throttling()) {
   1839    return false;
   1840  }
   1841  return !mCumulativeChanges.mLayout;
   1842 }
   1843 
   1844 void KeyframeEffect::MarkCascadeNeedsUpdate() {
   1845  if (!mTarget) {
   1846    return;
   1847  }
   1848 
   1849  EffectSet* effectSet = EffectSet::Get(mTarget);
   1850  if (!effectSet) {
   1851    return;
   1852  }
   1853  effectSet->MarkCascadeNeedsUpdate();
   1854 }
   1855 
   1856 /* static */
   1857 bool KeyframeEffect::HasComputedTimingChanged(
   1858    const ComputedTiming& aComputedTiming,
   1859    IterationCompositeOperation aIterationComposite,
   1860    const Nullable<double>& aProgressOnLastCompose,
   1861    uint64_t aCurrentIterationOnLastCompose) {
   1862  // Typically we don't need to request a restyle if the progress hasn't
   1863  // changed since the last call to ComposeStyle. The one exception is if the
   1864  // iteration composite mode is 'accumulate' and the current iteration has
   1865  // changed, since that will often produce a different result.
   1866  return aComputedTiming.mProgress != aProgressOnLastCompose ||
   1867         (aIterationComposite == IterationCompositeOperation::Accumulate &&
   1868          aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose);
   1869 }
   1870 
   1871 bool KeyframeEffect::HasComputedTimingChanged() const {
   1872  ComputedTiming computedTiming = GetComputedTiming();
   1873  return HasComputedTimingChanged(
   1874      computedTiming, mEffectOptions.mIterationComposite,
   1875      mProgressOnLastCompose, mCurrentIterationOnLastCompose);
   1876 }
   1877 
   1878 bool KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const {
   1879  // For display:table content, transform animations run on the table wrapper
   1880  // frame. If we are being passed a frame that doesn't support transforms
   1881  // (i.e. the inner table frame) we could just return false, but it possibly
   1882  // means we looked up the wrong EffectSet so for now we just assert instead.
   1883  MOZ_ASSERT(aFrame && aFrame->SupportsCSSTransforms(),
   1884             "We should be passed a frame that supports transforms");
   1885 
   1886  if (!IsCurrent()) {
   1887    return false;
   1888  }
   1889 
   1890  if (!mAnimation ||
   1891      mAnimation->ReplaceState() == AnimationReplaceState::Removed) {
   1892    return false;
   1893  }
   1894 
   1895  for (const AnimationProperty& prop : mProperties) {
   1896    if (prop.mProperty.mId != eCSSProperty_transform &&
   1897        prop.mProperty.mId != eCSSProperty_scale &&
   1898        prop.mProperty.mId != eCSSProperty_rotate) {
   1899      continue;
   1900    }
   1901 
   1902    AnimationValue baseStyle = BaseStyle(prop.mProperty);
   1903    if (!baseStyle.IsNull()) {
   1904      gfx::MatrixScales size = baseStyle.GetScaleValue(aFrame);
   1905      if (size != gfx::MatrixScales()) {
   1906        return true;
   1907      }
   1908    }
   1909 
   1910    // This is actually overestimate because there are some cases that combining
   1911    // the base value and from/to value produces 1:1 scale. But it doesn't
   1912    // really matter.
   1913    for (const AnimationPropertySegment& segment : prop.mSegments) {
   1914      if (!segment.mFromValue.IsNull()) {
   1915        gfx::MatrixScales from = segment.mFromValue.GetScaleValue(aFrame);
   1916        if (from != gfx::MatrixScales()) {
   1917          return true;
   1918        }
   1919      }
   1920      if (!segment.mToValue.IsNull()) {
   1921        gfx::MatrixScales to = segment.mToValue.GetScaleValue(aFrame);
   1922        if (to != gfx::MatrixScales()) {
   1923          return true;
   1924        }
   1925      }
   1926    }
   1927  }
   1928 
   1929  return false;
   1930 }
   1931 
   1932 void KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const {
   1933  if (!mInEffectSet) {
   1934    return;
   1935  }
   1936 
   1937  EffectSet* effectSet = aEffectSet ? aEffectSet : EffectSet::Get(mTarget);
   1938  if (!effectSet) {
   1939    return;
   1940  }
   1941 
   1942  nsIFrame* styleFrame = GetStyleFrame();
   1943  if (HasAnimationOfPropertySet(nsCSSPropertyIDSet::OpacityProperties())) {
   1944    effectSet->SetMayHaveOpacityAnimation();
   1945    EnumerateContinuationsOrIBSplitSiblings(styleFrame, [](nsIFrame* aFrame) {
   1946      aFrame->SetMayHaveOpacityAnimation();
   1947    });
   1948  }
   1949 
   1950  nsIFrame* primaryFrame = GetPrimaryFrame();
   1951  if (HasAnimationOfPropertySet(
   1952          nsCSSPropertyIDSet::TransformLikeProperties())) {
   1953    effectSet->SetMayHaveTransformAnimation();
   1954    // For table frames, it's not clear if we should iterate over the
   1955    // continuations of the table wrapper or the inner table frame.
   1956    //
   1957    // Fortunately, this is not currently an issue because we only split tables
   1958    // when printing (page breaks) where we don't run animations.
   1959    EnumerateContinuationsOrIBSplitSiblings(primaryFrame, [](nsIFrame* aFrame) {
   1960      aFrame->SetMayHaveTransformAnimation();
   1961    });
   1962  }
   1963 }
   1964 
   1965 KeyframeEffect::MatchForCompositor KeyframeEffect::IsMatchForCompositor(
   1966    const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame,
   1967    const EffectSet& aEffects,
   1968    AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const {
   1969  MOZ_ASSERT(mAnimation);
   1970 
   1971  if (!mAnimation->IsRelevant()) {
   1972    return KeyframeEffect::MatchForCompositor::No;
   1973  }
   1974 
   1975  if (mAnimation->ShouldBeSynchronizedWithMainThread(aPropertySet, aFrame,
   1976                                                     aPerformanceWarning)) {
   1977    // For a given |aFrame|, we don't want some animations of |aProperty| to
   1978    // run on the compositor and others to run on the main thread, so if any
   1979    // need to be synchronized with the main thread, run them all there.
   1980    return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
   1981  }
   1982 
   1983  if (mAnimation->UsingScrollTimeline()) {
   1984    const ScrollTimeline* scrollTimeline =
   1985        mAnimation->GetTimeline()->AsScrollTimeline();
   1986    // We don't send this animation to the compositor if
   1987    // 1. the APZ is disabled entirely or for the source, or
   1988    // 2. the associated scroll-timeline is inactive, or
   1989    // 3. the scrolling direction is not available (i.e. no scroll range).
   1990    // 4. the scroll style of the scroller is overflow:hidden.
   1991    if (!scrollTimeline->APZIsActiveForSource() ||
   1992        !scrollTimeline->IsActive() ||
   1993        !scrollTimeline->ScrollingDirectionIsAvailable() ||
   1994        scrollTimeline->SourceScrollStyle() == StyleOverflow::Hidden) {
   1995      return KeyframeEffect::MatchForCompositor::No;
   1996    }
   1997 
   1998    // FIXME: Bug 1818346. Support OMTA for view-timeline. We disable it for now
   1999    // because we need to make view-timeline-inset animations run on the OMTA as
   2000    // well before enable this.
   2001    if (scrollTimeline->IsViewTimeline()) {
   2002      return KeyframeEffect::MatchForCompositor::No;
   2003    }
   2004  }
   2005 
   2006  if (!HasEffectiveAnimationOfPropertySet(aPropertySet, aEffects)) {
   2007    return KeyframeEffect::MatchForCompositor::No;
   2008  }
   2009 
   2010  // If we know that the animation is not visible, we don't need to send the
   2011  // animation to the compositor.
   2012  if (!aFrame->IsVisibleOrMayHaveVisibleDescendants() ||
   2013      CanOptimizeAwayDueToOpacity(*this, *aFrame) ||
   2014      aFrame->IsScrolledOutOfView()) {
   2015    return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
   2016  }
   2017 
   2018  if (aPropertySet.HasProperty(eCSSProperty_background_color)) {
   2019    if (!StaticPrefs::gfx_omta_background_color()) {
   2020      return KeyframeEffect::MatchForCompositor::No;
   2021    }
   2022 
   2023    // We don't yet support off-main-thread background-color animations on
   2024    // canvas frame or on <html> or <body> which generate
   2025    // nsDisplayCanvasBackgroundColor or nsDisplaySolidColor display item.
   2026    if (aFrame->IsCanvasFrame() ||
   2027        (aFrame->GetContent() &&
   2028         (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::body) ||
   2029          aFrame->GetContent()->IsHTMLElement(nsGkAtoms::html)))) {
   2030      return KeyframeEffect::MatchForCompositor::No;
   2031    }
   2032  }
   2033 
   2034  // We can't run this background color animation on the compositor if there
   2035  // is any `current-color` keyframe.
   2036  if (mCumulativeChanges.mHasBackgroundColorCurrentColor) {
   2037    aPerformanceWarning = AnimationPerformanceWarning::Type::HasCurrentColor;
   2038    return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
   2039  }
   2040 
   2041  return mAnimation->IsPlaying() ? KeyframeEffect::MatchForCompositor::Yes
   2042                                 : KeyframeEffect::MatchForCompositor::IfNeeded;
   2043 }
   2044 
   2045 }  // namespace dom
   2046 }  // namespace mozilla