tor-browser

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

nsAnimationManager.cpp (18819B)


      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 "nsAnimationManager.h"
      8 
      9 #include <math.h>
     10 
     11 #include <algorithm>  // std::stable_sort
     12 
     13 #include "mozilla/AnimationEventDispatcher.h"
     14 #include "mozilla/AnimationUtils.h"
     15 #include "mozilla/EffectCompositor.h"
     16 #include "mozilla/ElementAnimationData.h"
     17 #include "mozilla/ServoStyleSet.h"
     18 #include "mozilla/TimelineCollection.h"
     19 #include "mozilla/dom/AnimationEffect.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/DocumentTimeline.h"
     22 #include "mozilla/dom/KeyframeEffect.h"
     23 #include "mozilla/dom/MutationObservers.h"
     24 #include "mozilla/dom/ScrollTimeline.h"
     25 #include "mozilla/dom/ViewTimeline.h"
     26 #include "nsDOMMutationObserver.h"
     27 #include "nsIFrame.h"
     28 #include "nsINode.h"
     29 #include "nsLayoutUtils.h"
     30 #include "nsPresContext.h"
     31 #include "nsPresContextInlines.h"
     32 #include "nsRFPService.h"
     33 #include "nsStyleChangeList.h"
     34 #include "nsTransitionManager.h"
     35 
     36 using namespace mozilla;
     37 using namespace mozilla::css;
     38 using mozilla::dom::Animation;
     39 using mozilla::dom::AnimationPlayState;
     40 using mozilla::dom::CSSAnimation;
     41 using mozilla::dom::Element;
     42 using mozilla::dom::KeyframeEffect;
     43 using mozilla::dom::MutationObservers;
     44 using mozilla::dom::ScrollTimeline;
     45 using mozilla::dom::ViewTimeline;
     46 
     47 ////////////////////////// nsAnimationManager ////////////////////////////
     48 
     49 // Find the matching animation by |aName| in the old list
     50 // of animations and remove the matched animation from the list.
     51 static already_AddRefed<CSSAnimation> PopExistingAnimation(
     52    const nsAtom* aName,
     53    nsAnimationManager::CSSAnimationCollection* aCollection) {
     54  if (!aCollection) {
     55    return nullptr;
     56  }
     57 
     58  // Animations are stored in reverse order to how they appear in the
     59  // animation-name property. However, we want to match animations beginning
     60  // from the end of the animation-name list, so we iterate *forwards*
     61  // through the collection.
     62  for (size_t idx = 0, length = aCollection->mAnimations.Length();
     63       idx != length; ++idx) {
     64    CSSAnimation* cssAnim = aCollection->mAnimations[idx];
     65    if (cssAnim->AnimationName() == aName) {
     66      RefPtr<CSSAnimation> match = cssAnim;
     67      aCollection->mAnimations.RemoveElementAt(idx);
     68      return match.forget();
     69    }
     70  }
     71 
     72  return nullptr;
     73 }
     74 
     75 class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
     76 public:
     77  explicit ServoCSSAnimationBuilder(const ComputedStyle* aComputedStyle)
     78      : mComputedStyle(aComputedStyle) {
     79    MOZ_ASSERT(aComputedStyle);
     80  }
     81 
     82  bool BuildKeyframes(const Element& aElement, nsPresContext* aPresContext,
     83                      nsAtom* aName,
     84                      const StyleComputedTimingFunction& aTimingFunction,
     85                      nsTArray<Keyframe>& aKeyframes) {
     86    return aPresContext->StyleSet()->GetKeyframesForName(
     87        aElement, *mComputedStyle, aName, aTimingFunction, aKeyframes);
     88  }
     89  void SetKeyframes(KeyframeEffect& aEffect, nsTArray<Keyframe>&& aKeyframes,
     90                    const dom::AnimationTimeline* aTimeline) {
     91    aEffect.SetKeyframes(std::move(aKeyframes), mComputedStyle, aTimeline);
     92  }
     93 
     94  // Currently all the animation building code in this file is based on
     95  // assumption that creating and removing animations should *not* trigger
     96  // additional restyles since those changes will be handled within the same
     97  // restyle.
     98  //
     99  // While that is true for the Gecko style backend, it is not true for the
    100  // Servo style backend where we want restyles to be triggered so that we
    101  // perform a second animation restyle where we will incorporate the changes
    102  // arising from creating and removing animations.
    103  //
    104  // Fortunately, our attempts to avoid posting extra restyles as part of the
    105  // processing here are imperfect and most of the time we happen to post
    106  // them anyway. Occasionally, however, we don't. For example, we don't post
    107  // a restyle when we create a new animation whose an animation index matches
    108  // the default value it was given already (which is typically only true when
    109  // the CSSAnimation we create is the first Animation created in a particular
    110  // content process).
    111  //
    112  // As a result, when we are using the Servo backend, whenever we have an added
    113  // or removed animation we need to explicitly trigger a restyle.
    114  //
    115  // This code should eventually disappear along with the Gecko style backend
    116  // and we should simply call Play() / Pause() / Cancel() etc. which will
    117  // post the required restyles.
    118  void NotifyNewOrRemovedAnimation(const Animation& aAnimation) {
    119    dom::AnimationEffect* effect = aAnimation.GetEffect();
    120    if (!effect) {
    121      return;
    122    }
    123 
    124    KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
    125    if (!keyframeEffect) {
    126      return;
    127    }
    128 
    129    keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
    130  }
    131 
    132 private:
    133  const ComputedStyle* mComputedStyle;
    134 };
    135 
    136 static void UpdateOldAnimationPropertiesWithNew(
    137    CSSAnimation& aOld, TimingParams&& aNewTiming,
    138    nsTArray<Keyframe>&& aNewKeyframes, bool aNewIsStylePaused,
    139    CSSAnimationProperties aOverriddenProperties,
    140    ServoCSSAnimationBuilder& aBuilder, dom::AnimationTimeline* aTimeline,
    141    dom::CompositeOperation aNewComposite) {
    142  bool animationChanged = false;
    143 
    144  // Update the old from the new so we can keep the original object
    145  // identity (and any expando properties attached to it).
    146  if (aOld.GetEffect()) {
    147    dom::AnimationEffect* oldEffect = aOld.GetEffect();
    148 
    149    // Copy across the changes that are not overridden
    150    TimingParams updatedTiming = oldEffect->SpecifiedTiming();
    151    if (~aOverriddenProperties & CSSAnimationProperties::Duration) {
    152      updatedTiming.SetDuration(aNewTiming.Duration());
    153    }
    154    if (~aOverriddenProperties & CSSAnimationProperties::IterationCount) {
    155      updatedTiming.SetIterations(aNewTiming.Iterations());
    156    }
    157    if (~aOverriddenProperties & CSSAnimationProperties::Direction) {
    158      updatedTiming.SetDirection(aNewTiming.Direction());
    159    }
    160    if (~aOverriddenProperties & CSSAnimationProperties::Delay) {
    161      updatedTiming.SetDelay(aNewTiming.Delay());
    162    }
    163    if (~aOverriddenProperties & CSSAnimationProperties::FillMode) {
    164      updatedTiming.SetFill(aNewTiming.Fill());
    165    }
    166 
    167    animationChanged = oldEffect->SpecifiedTiming() != updatedTiming;
    168    oldEffect->SetSpecifiedTiming(std::move(updatedTiming));
    169 
    170    if (KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect()) {
    171      if (~aOverriddenProperties & CSSAnimationProperties::Keyframes) {
    172        aBuilder.SetKeyframes(*oldKeyframeEffect, std::move(aNewKeyframes),
    173                              aTimeline);
    174      }
    175 
    176      if (~aOverriddenProperties & CSSAnimationProperties::Composition) {
    177        animationChanged = oldKeyframeEffect->Composite() != aNewComposite;
    178        oldKeyframeEffect->SetCompositeFromStyle(aNewComposite);
    179      }
    180    }
    181  }
    182 
    183  // Checking pointers should be enough. If both are scroll-timeline, we reuse
    184  // the scroll-timeline object if their scrollers and axes are the same.
    185  if (aOld.GetTimeline() != aTimeline) {
    186    aOld.SetTimeline(aTimeline);
    187    animationChanged = true;
    188  }
    189 
    190  // Handle changes in play state. If the animation is idle, however,
    191  // changes to animation-play-state should *not* restart it.
    192  if (aOld.PlayState() != AnimationPlayState::Idle &&
    193      ~aOverriddenProperties & CSSAnimationProperties::PlayState) {
    194    bool wasPaused = aOld.PlayState() == AnimationPlayState::Paused;
    195    if (!wasPaused && aNewIsStylePaused) {
    196      aOld.PauseFromStyle();
    197      animationChanged = true;
    198    } else if (wasPaused && !aNewIsStylePaused) {
    199      aOld.PlayFromStyle();
    200      animationChanged = true;
    201    }
    202  }
    203 
    204  // Updating the effect timing above might already have caused the
    205  // animation to become irrelevant so only add a changed record if
    206  // the animation is still relevant.
    207  if (animationChanged && aOld.IsRelevant()) {
    208    MutationObservers::NotifyAnimationChanged(&aOld);
    209  }
    210 }
    211 
    212 static already_AddRefed<dom::AnimationTimeline> GetNamedProgressTimeline(
    213    dom::Document* aDocument, const NonOwningAnimationTarget& aTarget,
    214    nsAtom* aName) {
    215  // A named progress timeline is referenceable in animation-timeline by:
    216  // 1. the declaring element itself
    217  // 2. that element’s descendants
    218  // 3. that element’s following siblings and their descendants
    219  // https://drafts.csswg.org/scroll-animations-1/#timeline-scope
    220  // FIXME: Bug 1823500. Reduce default scoping to ancestors only.
    221  for (Element* curr =
    222           aTarget.mElement->GetPseudoElement(aTarget.mPseudoRequest);
    223       curr; curr = curr->GetParentElement()) {
    224    // If multiple elements have declared the same timeline name, the matching
    225    // timeline is the one declared on the nearest element in tree order, which
    226    // considers siblings closer than parents.
    227    // Note: This is fine for parallel traversal because we update animations by
    228    // SequentialTask.
    229    for (Element* e = curr; e; e = e->GetPreviousElementSibling()) {
    230      // In case of a name conflict on the same element, scroll progress
    231      // timelines take precedence over view progress timelines.
    232      const auto [element, pseudo] = AnimationUtils::GetElementPseudoPair(e);
    233      if (auto* collection =
    234              TimelineCollection<ScrollTimeline>::Get(element, pseudo)) {
    235        if (RefPtr<ScrollTimeline> timeline = collection->Lookup(aName)) {
    236          return timeline.forget();
    237        }
    238      }
    239 
    240      if (auto* collection =
    241              TimelineCollection<ViewTimeline>::Get(element, pseudo)) {
    242        if (RefPtr<ViewTimeline> timeline = collection->Lookup(aName)) {
    243          return timeline.forget();
    244        }
    245      }
    246    }
    247  }
    248 
    249  // If we cannot find a matched scroll-timeline-name, this animation is not
    250  // associated with a timeline.
    251  // https://drafts.csswg.org/css-animations-2/#valdef-animation-timeline-custom-ident
    252  return nullptr;
    253 }
    254 
    255 static already_AddRefed<dom::AnimationTimeline> GetTimeline(
    256    const StyleAnimationTimeline& aStyleTimeline, nsPresContext* aPresContext,
    257    const NonOwningAnimationTarget& aTarget) {
    258  switch (aStyleTimeline.tag) {
    259    case StyleAnimationTimeline::Tag::Timeline: {
    260      // Check scroll-timeline-name property or view-timeline-property.
    261      nsAtom* name = aStyleTimeline.AsTimeline().AsAtom();
    262      return name != nsGkAtoms::_empty
    263                 ? GetNamedProgressTimeline(aPresContext->Document(), aTarget,
    264                                            name)
    265                 : nullptr;
    266    }
    267    case StyleAnimationTimeline::Tag::Scroll: {
    268      const auto& scroll = aStyleTimeline.AsScroll();
    269      return ScrollTimeline::MakeAnonymous(aPresContext->Document(), aTarget,
    270                                           scroll.axis, scroll.scroller);
    271    }
    272    case StyleAnimationTimeline::Tag::View: {
    273      const auto& view = aStyleTimeline.AsView();
    274      return ViewTimeline::MakeAnonymous(aPresContext->Document(), aTarget,
    275                                         view.axis, view.inset);
    276    }
    277    case StyleAnimationTimeline::Tag::Auto:
    278      return do_AddRef(aTarget.mElement->OwnerDoc()->Timeline());
    279  }
    280  MOZ_ASSERT_UNREACHABLE("Unknown animation-timeline value?");
    281  return nullptr;
    282 }
    283 
    284 // Returns a new animation set up with given StyleAnimation.
    285 // Or returns an existing animation matching StyleAnimation's name updated
    286 // with the new StyleAnimation.
    287 static already_AddRefed<CSSAnimation> BuildAnimation(
    288    nsPresContext* aPresContext, const NonOwningAnimationTarget& aTarget,
    289    const nsStyleUIReset& aStyle, uint32_t animIdx,
    290    ServoCSSAnimationBuilder& aBuilder,
    291    nsAnimationManager::CSSAnimationCollection* aCollection) {
    292  MOZ_ASSERT(aPresContext);
    293 
    294  nsAtom* animationName = aStyle.GetAnimationName(animIdx);
    295  nsTArray<Keyframe> keyframes;
    296  if (!aBuilder.BuildKeyframes(*aTarget.mElement, aPresContext, animationName,
    297                               aStyle.GetAnimationTimingFunction(animIdx),
    298                               keyframes)) {
    299    return nullptr;
    300  }
    301 
    302  const StyleAnimationDuration& duration = aStyle.GetAnimationDuration(animIdx);
    303  TimingParams timing = TimingParamsFromCSSParams(
    304      duration.IsAuto() ? Nothing() : Some(duration.AsTime().ToMilliseconds()),
    305      aStyle.GetAnimationDelay(animIdx).ToMilliseconds(),
    306      aStyle.GetAnimationIterationCount(animIdx),
    307      aStyle.GetAnimationDirection(animIdx),
    308      aStyle.GetAnimationFillMode(animIdx));
    309 
    310  bool isStylePaused =
    311      aStyle.GetAnimationPlayState(animIdx) == StyleAnimationPlayState::Paused;
    312 
    313  RefPtr<dom::AnimationTimeline> timeline =
    314      GetTimeline(aStyle.GetTimeline(animIdx), aPresContext, aTarget);
    315 
    316  // Find the matching animation with animation name in the old list
    317  // of animations and remove the matched animation from the list.
    318  RefPtr<CSSAnimation> oldAnim =
    319      PopExistingAnimation(animationName, aCollection);
    320 
    321  const auto composition = StyleToDom(aStyle.GetAnimationComposition(animIdx));
    322  if (oldAnim) {
    323    // Copy over the start times and (if still paused) pause starts
    324    // for each animation (matching on name only) that was also in the
    325    // old list of animations.
    326    // This means that we honor dynamic changes, which isn't what the
    327    // spec says to do, but WebKit seems to honor at least some of
    328    // them.  See
    329    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
    330    // In order to honor what the spec said, we'd copy more data over.
    331    UpdateOldAnimationPropertiesWithNew(
    332        *oldAnim, std::move(timing), std::move(keyframes), isStylePaused,
    333        oldAnim->GetOverriddenProperties(), aBuilder, timeline, composition);
    334    return oldAnim.forget();
    335  }
    336 
    337  KeyframeEffectParams effectOptions(composition);
    338  auto effect = MakeRefPtr<dom::CSSAnimationKeyframeEffect>(
    339      aPresContext->Document(),
    340      OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoRequest),
    341      std::move(timing), effectOptions);
    342 
    343  aBuilder.SetKeyframes(*effect, std::move(keyframes), timeline);
    344 
    345  auto animation = MakeRefPtr<CSSAnimation>(
    346      aPresContext->Document()->GetScopeObject(), animationName);
    347  animation->SetOwningElement(
    348      OwningElementRef(*aTarget.mElement, aTarget.mPseudoRequest));
    349 
    350  animation->SetTimelineNoUpdate(timeline);
    351  animation->SetEffectNoUpdate(effect);
    352 
    353  if (isStylePaused) {
    354    animation->PauseFromStyle();
    355  } else {
    356    animation->PlayFromStyle();
    357  }
    358 
    359  aBuilder.NotifyNewOrRemovedAnimation(*animation);
    360 
    361  return animation.forget();
    362 }
    363 
    364 static nsAnimationManager::OwningCSSAnimationPtrArray BuildAnimations(
    365    nsPresContext* aPresContext, const NonOwningAnimationTarget& aTarget,
    366    const nsStyleUIReset& aStyle, ServoCSSAnimationBuilder& aBuilder,
    367    nsAnimationManager::CSSAnimationCollection* aCollection,
    368    nsTHashSet<RefPtr<nsAtom>>& aReferencedAnimations) {
    369  nsAnimationManager::OwningCSSAnimationPtrArray result;
    370 
    371  for (size_t animIdx = aStyle.mAnimationNameCount; animIdx-- != 0;) {
    372    nsAtom* name = aStyle.GetAnimationName(animIdx);
    373    // CSS Animations whose animation-name does not match a @keyframes rule do
    374    // not generate animation events. This includes when the animation-name is
    375    // "none" which is represented by an empty name in the StyleAnimation.
    376    // Since such animations neither affect style nor dispatch events, we do
    377    // not generate a corresponding CSSAnimation for them.
    378    if (name == nsGkAtoms::_empty) {
    379      continue;
    380    }
    381 
    382    aReferencedAnimations.Insert(name);
    383    RefPtr<CSSAnimation> dest = BuildAnimation(aPresContext, aTarget, aStyle,
    384                                               animIdx, aBuilder, aCollection);
    385    if (!dest) {
    386      continue;
    387    }
    388 
    389    dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
    390    result.AppendElement(dest);
    391  }
    392  return result;
    393 }
    394 
    395 void nsAnimationManager::UpdateAnimations(
    396    dom::Element* aElement, const PseudoStyleRequest& aPseudoRequest,
    397    const ComputedStyle* aComputedStyle) {
    398  MOZ_ASSERT(mPresContext->IsDynamic(),
    399             "Should not update animations for print or print preview");
    400  MOZ_ASSERT(aElement->IsInComposedDoc(),
    401             "Should not update animations that are not attached to the "
    402             "document tree");
    403 
    404  if (!aComputedStyle ||
    405      aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None) {
    406    // If we are in a display:none subtree we will have no computed values.
    407    // However, if we are on the root of display:none subtree, the computed
    408    // values might not have been cleared yet.
    409    // In either case, since CSS animations should not run in display:none
    410    // subtrees we should stop (actually, destroy) any animations on this
    411    // element here.
    412    StopAnimationsForElement(aElement, aPseudoRequest);
    413    return;
    414  }
    415 
    416  NonOwningAnimationTarget target(aElement, aPseudoRequest);
    417  ServoCSSAnimationBuilder builder(aComputedStyle);
    418 
    419  DoUpdateAnimations(target, *aComputedStyle->StyleUIReset(), builder);
    420 }
    421 
    422 void nsAnimationManager::DoUpdateAnimations(
    423    const NonOwningAnimationTarget& aTarget, const nsStyleUIReset& aStyle,
    424    ServoCSSAnimationBuilder& aBuilder) {
    425  // Everything that causes our animation data to change triggers a
    426  // style change, which in turn triggers a non-animation restyle.
    427  // Likewise, when we initially construct frames, we're not in a
    428  // style change, but also not in an animation restyle.
    429 
    430  auto* collection =
    431      CSSAnimationCollection::Get(aTarget.mElement, aTarget.mPseudoRequest);
    432  if (!collection && aStyle.mAnimationNameCount == 1 &&
    433      aStyle.mAnimations[0].GetName() == nsGkAtoms::_empty) {
    434    return;
    435  }
    436 
    437  nsAutoAnimationMutationBatch mb(aTarget.mElement->OwnerDoc());
    438 
    439  // Build the updated animations list, extracting matching animations from
    440  // the existing collection as we go.
    441  OwningCSSAnimationPtrArray newAnimations =
    442      BuildAnimations(mPresContext, aTarget, aStyle, aBuilder, collection,
    443                      mMaybeReferencedAnimations);
    444 
    445  if (newAnimations.IsEmpty()) {
    446    if (collection) {
    447      collection->Destroy();
    448    }
    449    return;
    450  }
    451 
    452  if (!collection) {
    453    collection =
    454        &aTarget.mElement->EnsureAnimationData().EnsureAnimationCollection(
    455            *aTarget.mElement, aTarget.mPseudoRequest);
    456    if (!collection->isInList()) {
    457      AddElementCollection(collection);
    458    }
    459  }
    460  collection->mAnimations.SwapElements(newAnimations);
    461 
    462  // Cancel removed animations
    463  for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0;) {
    464    aBuilder.NotifyNewOrRemovedAnimation(*newAnimations[newAnimIdx]);
    465    newAnimations[newAnimIdx]->CancelFromStyle(PostRestyleMode::IfNeeded);
    466  }
    467 }