tor-browser

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

AnimationInfo.cpp (41980B)


      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 "AnimationInfo.h"
      8 #include "mozilla/LayerAnimationInfo.h"
      9 #include "mozilla/layers/WebRenderLayerManager.h"
     10 #include "mozilla/layers/AnimationHelper.h"
     11 #include "mozilla/layers/CompositorThread.h"
     12 #include "mozilla/dom/Animation.h"
     13 #include "mozilla/dom/CSSTransition.h"
     14 #include "mozilla/dom/KeyframeEffect.h"
     15 #include "mozilla/EffectSet.h"
     16 #include "mozilla/MotionPathUtils.h"
     17 #include "mozilla/PresShell.h"
     18 #include "mozilla/ScrollContainerFrame.h"
     19 #include "nsIContent.h"
     20 #include "nsLayoutUtils.h"
     21 #include "nsRefreshDriver.h"
     22 #include "nsStyleTransformMatrix.h"
     23 #include "PuppetWidget.h"
     24 
     25 namespace mozilla::layers {
     26 
     27 using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox;
     28 
     29 AnimationInfo::AnimationInfo() : mCompositorAnimationsId(0), mMutated(false) {}
     30 
     31 AnimationInfo::~AnimationInfo() = default;
     32 
     33 void AnimationInfo::EnsureAnimationsId() {
     34  if (!mCompositorAnimationsId) {
     35    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
     36  }
     37 }
     38 
     39 Animation* AnimationInfo::AddAnimation() {
     40  MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
     41  // Here generates a new id when the first animation is added and
     42  // this id is used to represent the animations in this layer.
     43  EnsureAnimationsId();
     44 
     45  MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
     46 
     47  Animation* anim = mAnimations.AppendElement();
     48 
     49  mMutated = true;
     50 
     51  return anim;
     52 }
     53 
     54 Animation* AnimationInfo::AddAnimationForNextTransaction() {
     55  MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
     56  MOZ_ASSERT(mPendingAnimations,
     57             "should have called ClearAnimationsForNextTransaction first");
     58 
     59  Animation* anim = mPendingAnimations->AppendElement();
     60 
     61  return anim;
     62 }
     63 
     64 void AnimationInfo::ClearAnimations() {
     65  mPendingAnimations = nullptr;
     66 
     67  if (mAnimations.IsEmpty() && mStorageData.IsEmpty()) {
     68    return;
     69  }
     70 
     71  mAnimations.Clear();
     72  mStorageData.Clear();
     73 
     74  mMutated = true;
     75 }
     76 
     77 void AnimationInfo::ClearAnimationsForNextTransaction() {
     78  // Ensure we have a non-null mPendingAnimations to mark a future clear.
     79  if (!mPendingAnimations) {
     80    mPendingAnimations = MakeUnique<AnimationArray>();
     81  }
     82 
     83  mPendingAnimations->Clear();
     84 }
     85 
     86 void AnimationInfo::MaybeStartPendingAnimation(Animation& aAnimation,
     87                                               const TimeStamp& aReadyTime) {
     88  // If the animation is doing an async update of its playback rate, then we
     89  // want to match whatever its current time would be at *aReadyTime*.
     90  if (!std::isnan(aAnimation.previousPlaybackRate()) &&
     91      aAnimation.startTime().isSome() && !aAnimation.originTime().IsNull() &&
     92      !aAnimation.isNotPlaying()) {
     93    TimeDuration readyTime = aReadyTime - aAnimation.originTime();
     94    aAnimation.holdTime() = dom::Animation::CurrentTimeFromTimelineTime(
     95        readyTime, aAnimation.startTime().ref(),
     96        aAnimation.previousPlaybackRate());
     97    // Make start time null so that we know to update it below.
     98    aAnimation.startTime() = Nothing();
     99  }
    100 
    101  // If the aAnimationation is play-pending, resolve the start time.
    102  if (aAnimation.startTime().isNothing() && !aAnimation.originTime().IsNull() &&
    103      !aAnimation.isNotPlaying()) {
    104    const TimeDuration readyTime = aReadyTime - aAnimation.originTime();
    105    aAnimation.startTime() = Some(dom::Animation::StartTimeFromTimelineTime(
    106        readyTime, aAnimation.holdTime(), aAnimation.playbackRate()));
    107  }
    108 }
    109 
    110 bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() {
    111  if (mPendingAnimations) {
    112    mAnimations = std::move(*mPendingAnimations);
    113    mPendingAnimations = nullptr;
    114    return true;
    115  }
    116 
    117  return false;
    118 }
    119 
    120 bool AnimationInfo::HasTransformAnimation() const {
    121  const nsCSSPropertyIDSet& transformSet =
    122      LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM);
    123  for (const auto& animation : mAnimations) {
    124    if (transformSet.HasProperty(animation.property())) {
    125      return true;
    126    }
    127  }
    128  return false;
    129 }
    130 
    131 /* static */
    132 Maybe<uint64_t> AnimationInfo::GetGenerationFromFrame(
    133    nsIFrame* aFrame, DisplayItemType aDisplayItemKey) {
    134  MOZ_ASSERT(aFrame->IsPrimaryFrame() ||
    135             nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame));
    136 
    137  // In case of continuation, KeyframeEffectReadOnly uses its first frame,
    138  // whereas nsDisplayItem uses its last continuation, so we have to use the
    139  // last continuation frame here.
    140  if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) {
    141    aFrame = nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame);
    142  }
    143  RefPtr<WebRenderAnimationData> animationData =
    144      GetWebRenderUserData<WebRenderAnimationData>(aFrame,
    145                                                   (uint32_t)aDisplayItemKey);
    146  if (animationData) {
    147    return animationData->GetAnimationInfo().GetAnimationGeneration();
    148  }
    149 
    150  return Nothing();
    151 }
    152 
    153 /* static */
    154 void AnimationInfo::EnumerateGenerationOnFrame(
    155    const nsIFrame* aFrame, const nsIContent* aContent,
    156    const CompositorAnimatableDisplayItemTypes& aDisplayItemTypes,
    157    AnimationGenerationCallback aCallback) {
    158  nsIWidget* widget = nsContentUtils::WidgetForContent(aContent);
    159  if (!widget) {
    160    return;
    161  }
    162  // If we haven't created a window renderer there's no animation generation
    163  // that we can have, thus we call the callback function with |Nothing()| for
    164  // the generation.
    165  if (!widget->HasWindowRenderer()) {
    166    for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) {
    167      aCallback(Nothing(), displayItem);
    168    }
    169    return;
    170  }
    171  WindowRenderer* renderer = widget->GetWindowRenderer();
    172  MOZ_ASSERT(renderer);
    173  if (!renderer->AsWebRender()) {
    174    return;
    175  }
    176 
    177  // In case of continuation, nsDisplayItem uses its last continuation, so we
    178  // have to use the last continuation frame here.
    179  if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) {
    180    aFrame = nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame);
    181  }
    182 
    183  for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) {
    184    // For transform animations, the animation is on the primary frame but
    185    // |aFrame| is the style frame.
    186    const nsIFrame* frameToQuery =
    187        displayItem == DisplayItemType::TYPE_TRANSFORM
    188            ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame)
    189            : aFrame;
    190    RefPtr<WebRenderAnimationData> animationData =
    191        GetWebRenderUserData<WebRenderAnimationData>(frameToQuery,
    192                                                     (uint32_t)displayItem);
    193    Maybe<uint64_t> generation;
    194    if (animationData) {
    195      generation = animationData->GetAnimationInfo().GetAnimationGeneration();
    196    }
    197    aCallback(generation, displayItem);
    198  }
    199 }
    200 
    201 static StyleTransformOperation ResolveTranslate(
    202    TransformReferenceBox& aRefBox, const LengthPercentage& aX,
    203    const LengthPercentage& aY = LengthPercentage::Zero(),
    204    const Length& aZ = Length{0}) {
    205  float x = nsStyleTransformMatrix::ProcessTranslatePart(
    206      aX, &aRefBox, &TransformReferenceBox::Width);
    207  float y = nsStyleTransformMatrix::ProcessTranslatePart(
    208      aY, &aRefBox, &TransformReferenceBox::Height);
    209  return StyleTransformOperation::Translate3D(
    210      LengthPercentage::FromPixels(x), LengthPercentage::FromPixels(y), aZ);
    211 }
    212 
    213 static StyleTranslate ResolveTranslate(const StyleTranslate& aValue,
    214                                       TransformReferenceBox& aRefBox) {
    215  if (aValue.IsTranslate()) {
    216    const auto& t = aValue.AsTranslate();
    217    float x = nsStyleTransformMatrix::ProcessTranslatePart(
    218        t._0, &aRefBox, &TransformReferenceBox::Width);
    219    float y = nsStyleTransformMatrix::ProcessTranslatePart(
    220        t._1, &aRefBox, &TransformReferenceBox::Height);
    221    return StyleTranslate::Translate(LengthPercentage::FromPixels(x),
    222                                     LengthPercentage::FromPixels(y), t._2);
    223  }
    224 
    225  MOZ_ASSERT(aValue.IsNone());
    226  return StyleTranslate::None();
    227 }
    228 
    229 static StyleTransform ResolveTransformOperations(
    230    const StyleTransform& aTransform, TransformReferenceBox& aRefBox) {
    231  auto convertMatrix = [](const gfx::Matrix4x4& aM) {
    232    return StyleTransformOperation::Matrix3D(StyleGenericMatrix3D<StyleNumber>{
    233        aM._11, aM._12, aM._13, aM._14, aM._21, aM._22, aM._23, aM._24, aM._31,
    234        aM._32, aM._33, aM._34, aM._41, aM._42, aM._43, aM._44});
    235  };
    236 
    237  Vector<StyleTransformOperation> result;
    238  MOZ_RELEASE_ASSERT(
    239      result.initCapacity(aTransform.Operations().Length()),
    240      "Allocating vector of transform operations should be successful.");
    241 
    242  for (const StyleTransformOperation& op : aTransform.Operations()) {
    243    switch (op.tag) {
    244      case StyleTransformOperation::Tag::TranslateX:
    245        result.infallibleAppend(ResolveTranslate(aRefBox, op.AsTranslateX()));
    246        break;
    247      case StyleTransformOperation::Tag::TranslateY:
    248        result.infallibleAppend(ResolveTranslate(
    249            aRefBox, LengthPercentage::Zero(), op.AsTranslateY()));
    250        break;
    251      case StyleTransformOperation::Tag::TranslateZ:
    252        result.infallibleAppend(
    253            ResolveTranslate(aRefBox, LengthPercentage::Zero(),
    254                             LengthPercentage::Zero(), op.AsTranslateZ()));
    255        break;
    256      case StyleTransformOperation::Tag::Translate: {
    257        const auto& translate = op.AsTranslate();
    258        result.infallibleAppend(
    259            ResolveTranslate(aRefBox, translate._0, translate._1));
    260        break;
    261      }
    262      case StyleTransformOperation::Tag::Translate3D: {
    263        const auto& translate = op.AsTranslate3D();
    264        result.infallibleAppend(ResolveTranslate(aRefBox, translate._0,
    265                                                 translate._1, translate._2));
    266        break;
    267      }
    268      case StyleTransformOperation::Tag::InterpolateMatrix: {
    269        gfx::Matrix4x4 matrix;
    270        nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, op, aRefBox);
    271        result.infallibleAppend(convertMatrix(matrix));
    272        break;
    273      }
    274      case StyleTransformOperation::Tag::AccumulateMatrix: {
    275        gfx::Matrix4x4 matrix;
    276        nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix, op, aRefBox);
    277        result.infallibleAppend(convertMatrix(matrix));
    278        break;
    279      }
    280      case StyleTransformOperation::Tag::RotateX:
    281      case StyleTransformOperation::Tag::RotateY:
    282      case StyleTransformOperation::Tag::RotateZ:
    283      case StyleTransformOperation::Tag::Rotate:
    284      case StyleTransformOperation::Tag::Rotate3D:
    285      case StyleTransformOperation::Tag::ScaleX:
    286      case StyleTransformOperation::Tag::ScaleY:
    287      case StyleTransformOperation::Tag::ScaleZ:
    288      case StyleTransformOperation::Tag::Scale:
    289      case StyleTransformOperation::Tag::Scale3D:
    290      case StyleTransformOperation::Tag::SkewX:
    291      case StyleTransformOperation::Tag::SkewY:
    292      case StyleTransformOperation::Tag::Skew:
    293      case StyleTransformOperation::Tag::Matrix:
    294      case StyleTransformOperation::Tag::Matrix3D:
    295      case StyleTransformOperation::Tag::Perspective:
    296        result.infallibleAppend(op);
    297        break;
    298      default:
    299        MOZ_ASSERT_UNREACHABLE("Function not handled yet!");
    300    }
    301  }
    302 
    303  auto transform = StyleTransform{
    304      StyleOwnedSlice<StyleTransformOperation>(std::move(result))};
    305  MOZ_ASSERT(!transform.HasPercent());
    306  MOZ_ASSERT(transform.Operations().Length() ==
    307             aTransform.Operations().Length());
    308  return transform;
    309 }
    310 
    311 static Maybe<ScrollTimelineOptions> GetScrollTimelineOptions(
    312    dom::AnimationTimeline* aTimeline) {
    313  if (!aTimeline || !aTimeline->IsScrollTimeline()) {
    314    return Nothing();
    315  }
    316 
    317  const dom::ScrollTimeline* timeline = aTimeline->AsScrollTimeline();
    318  MOZ_ASSERT(timeline->IsActive(),
    319             "We send scroll animation to the compositor only if its timeline "
    320             "is active");
    321 
    322  ScrollableLayerGuid::ViewID source = ScrollableLayerGuid::NULL_SCROLL_ID;
    323  DebugOnly<bool> success =
    324      nsLayoutUtils::FindIDFor(timeline->SourceElement(), &source);
    325  MOZ_ASSERT(success, "We should have a valid ViewID for the scroller");
    326 
    327  return Some(ScrollTimelineOptions(source, timeline->Axis()));
    328 }
    329 
    330 static void SetAnimatable(NonCustomCSSPropertyId aProperty,
    331                          const AnimationValue& aAnimationValue,
    332                          nsIFrame* aFrame, TransformReferenceBox& aRefBox,
    333                          layers::Animatable& aAnimatable) {
    334  MOZ_ASSERT(aFrame);
    335 
    336  if (aAnimationValue.IsNull()) {
    337    aAnimatable = null_t();
    338    return;
    339  }
    340 
    341  switch (aProperty) {
    342    case eCSSProperty_background_color: {
    343      // We don't support color animation on the compositor yet so that we can
    344      // resolve currentColor at this moment.
    345      nscolor foreground =
    346          aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor);
    347      aAnimatable = aAnimationValue.GetColor(foreground);
    348      break;
    349    }
    350    case eCSSProperty_opacity:
    351      aAnimatable = aAnimationValue.GetOpacity();
    352      break;
    353    case eCSSProperty_rotate:
    354      aAnimatable = aAnimationValue.GetRotateProperty();
    355      break;
    356    case eCSSProperty_scale:
    357      aAnimatable = aAnimationValue.GetScaleProperty();
    358      break;
    359    case eCSSProperty_translate:
    360      aAnimatable =
    361          ResolveTranslate(aAnimationValue.GetTranslateProperty(), aRefBox);
    362      break;
    363    case eCSSProperty_transform:
    364      aAnimatable = ResolveTransformOperations(
    365          aAnimationValue.GetTransformProperty(), aRefBox);
    366      break;
    367    case eCSSProperty_offset_path:
    368      aAnimatable = StyleOffsetPath::None();
    369      aAnimationValue.GetOffsetPathProperty(aAnimatable.get_StyleOffsetPath());
    370      break;
    371    case eCSSProperty_offset_distance:
    372      aAnimatable = aAnimationValue.GetOffsetDistanceProperty();
    373      break;
    374    case eCSSProperty_offset_rotate:
    375      aAnimatable = aAnimationValue.GetOffsetRotateProperty();
    376      break;
    377    case eCSSProperty_offset_anchor:
    378      aAnimatable = aAnimationValue.GetOffsetAnchorProperty();
    379      break;
    380    case eCSSProperty_offset_position:
    381      aAnimatable = aAnimationValue.GetOffsetPositionProperty();
    382      break;
    383    default:
    384      MOZ_ASSERT_UNREACHABLE("Unsupported property");
    385  }
    386 }
    387 
    388 void AnimationInfo::AddAnimationForProperty(
    389    nsIFrame* aFrame, const AnimationProperty& aProperty,
    390    dom::Animation* aAnimation, const Maybe<TransformData>& aTransformData,
    391    Send aSendFlag) {
    392  MOZ_ASSERT(aAnimation->GetEffect(),
    393             "Should not be adding an animation without an effect");
    394  MOZ_ASSERT(!aAnimation->GetStartTime().IsNull() || !aAnimation->IsPlaying() ||
    395                 (aAnimation->GetTimeline() &&
    396                  aAnimation->GetTimeline()->TracksWallclockTime()),
    397             "If the animation has an unresolved start time it should either"
    398             " be static (so we don't need a start time) or else have a"
    399             " timeline capable of converting TimeStamps (so we can calculate"
    400             " one later");
    401 
    402  Animation* animation = (aSendFlag == Send::NextTransaction)
    403                             ? AddAnimationForNextTransaction()
    404                             : AddAnimation();
    405 
    406  const TimingParams& timing = aAnimation->GetEffect()->NormalizedTiming();
    407 
    408  // If we are starting a new transition that replaces an existing transition
    409  // running on the compositor, it is possible that the animation on the
    410  // compositor will have advanced ahead of the main thread. If we use as
    411  // the starting point of the new transition, the current value of the
    412  // replaced transition as calculated on the main thread using the refresh
    413  // driver time, the new transition will jump when it starts. Instead, we
    414  // re-calculate the starting point of the new transition by applying the
    415  // current TimeStamp to the parameters of the replaced transition.
    416  //
    417  // We need to do this here, rather than when we generate the new transition,
    418  // since after generating the new transition other requestAnimationFrame
    419  // callbacks may run that introduce further lag between the main thread and
    420  // the compositor.
    421  //
    422  // Note that we will replace the start value with the last sampled animation
    423  // value on the compositor.
    424  // The computation here is for updating the keyframe values, to make sure the
    425  // computed values on the main thread don't behind the rendering result on the
    426  // compositor too much.
    427  bool needReplaceTransition = false;
    428  if (dom::CSSTransition* cssTransition = aAnimation->AsCSSTransition()) {
    429    needReplaceTransition =
    430        cssTransition->UpdateStartValueFromReplacedTransition();
    431  }
    432 
    433  animation->originTime() =
    434      !aAnimation->GetTimeline()
    435          ? TimeStamp()
    436          : aAnimation->GetTimeline()->ToTimeStamp(TimeDuration());
    437 
    438  dom::Nullable<TimeDuration> startTime = aAnimation->GetStartTime();
    439  if (startTime.IsNull()) {
    440    animation->startTime() = Nothing();
    441  } else {
    442    animation->startTime() = Some(startTime.Value());
    443  }
    444 
    445  animation->holdTime() = aAnimation->GetCurrentTimeAsDuration().Value();
    446 
    447  const ComputedTiming computedTiming =
    448      aAnimation->GetEffect()->GetComputedTiming();
    449  animation->delay() = timing.Delay();
    450  animation->endDelay() = timing.EndDelay();
    451  animation->duration() = computedTiming.mDuration;
    452  animation->iterations() = static_cast<float>(computedTiming.mIterations);
    453  animation->iterationStart() =
    454      static_cast<float>(computedTiming.mIterationStart);
    455  animation->direction() = static_cast<uint8_t>(timing.Direction());
    456  animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill);
    457  MOZ_ASSERT(!aProperty.mProperty.IsCustom(),
    458             "We don't animate custom properties in the compositor");
    459  animation->property() = aProperty.mProperty.mId;
    460  animation->playbackRate() =
    461      static_cast<float>(aAnimation->CurrentOrPendingPlaybackRate());
    462  animation->previousPlaybackRate() =
    463      aAnimation->HasPendingPlaybackRate()
    464          ? static_cast<float>(aAnimation->PlaybackRate())
    465          : std::numeric_limits<float>::quiet_NaN();
    466  animation->transformData() = aTransformData;
    467  animation->easingFunction() = timing.TimingFunction();
    468  animation->iterationComposite() = static_cast<uint8_t>(
    469      aAnimation->GetEffect()->AsKeyframeEffect()->IterationComposite());
    470  animation->isNotPlaying() = !aAnimation->IsPlaying();
    471  animation->isNotAnimating() = false;
    472  animation->scrollTimelineOptions() =
    473      GetScrollTimelineOptions(aAnimation->GetTimeline());
    474  // We set this flag to let the compositor know that the start value of this
    475  // transition is replaced. The compositor may replace the start value with its
    476  // last sampled animation value, instead of using the segment.mFromValue we
    477  // send to the compositor, to avoid any potential lag.
    478  animation->replacedTransitionId() =
    479      needReplaceTransition ? Some(GetCompositorAnimationsId()) : Nothing();
    480 
    481  TransformReferenceBox refBox(aFrame);
    482 
    483  // If the animation is additive or accumulates, we need to pass its base value
    484  // to the compositor.
    485 
    486  AnimationValue baseStyle =
    487      aAnimation->GetEffect()->AsKeyframeEffect()->BaseStyle(
    488          aProperty.mProperty);
    489  if (!baseStyle.IsNull()) {
    490    SetAnimatable(aProperty.mProperty.mId, baseStyle, aFrame, refBox,
    491                  animation->baseStyle());
    492  } else {
    493    animation->baseStyle() = null_t();
    494  }
    495 
    496  for (const AnimationPropertySegment& segment : aProperty.mSegments) {
    497    AnimationSegment* animSegment = animation->segments().AppendElement();
    498    SetAnimatable(aProperty.mProperty.mId, segment.mFromValue, aFrame, refBox,
    499                  animSegment->startState());
    500    SetAnimatable(aProperty.mProperty.mId, segment.mToValue, aFrame, refBox,
    501                  animSegment->endState());
    502 
    503    animSegment->startPortion() = segment.mFromKey;
    504    animSegment->endPortion() = segment.mToKey;
    505    animSegment->startComposite() =
    506        static_cast<uint8_t>(segment.mFromComposite);
    507    animSegment->endComposite() = static_cast<uint8_t>(segment.mToComposite);
    508    animSegment->sampleFn() = segment.mTimingFunction;
    509  }
    510 
    511  if (aAnimation->Pending()) {
    512    TimeStamp readyTime = aAnimation->GetPendingReadyTime();
    513    if (readyTime.IsNull()) {
    514      // TODO(emilio): This should generally not happen anymore, can we remove
    515      // this SetPendingReadyTime call?
    516      readyTime = aFrame->PresContext()->RefreshDriver()->MostRecentRefresh();
    517      MOZ_ASSERT(!readyTime.IsNull());
    518      aAnimation->SetPendingReadyTime(readyTime);
    519    }
    520    MaybeStartPendingAnimation(*animation, readyTime);
    521  }
    522 }
    523 
    524 // Let's use an example to explain this function:
    525 //
    526 // We have 4 playing animations (without any !important rule or transition):
    527 // Animation A: [ transform, rotate ].
    528 // Animation B: [ rotate, scale ].
    529 // Animation C: [ transform, margin-left ].
    530 // Animation D: [ opacity, margin-left ].
    531 //
    532 // Normally, GetAnimationsForCompositor(|transform-like properties|) returns:
    533 // [ Animation A, Animation B, Animation C ], which is the first argument of
    534 // this function.
    535 //
    536 // In this function, we want to re-organize the list as (Note: don't care
    537 // the order of properties):
    538 // [
    539 //   { rotate:    [ Animation A, Animation B ] },
    540 //   { scale:     [ Animation B ] },
    541 //   { transform: [ Animation A, Animation C ] },
    542 // ]
    543 //
    544 // Therefore, AddAnimationsForProperty() will append each animation property
    545 // into AnimationInfo,  as a final list of layers::Animation:
    546 // [
    547 //   { rotate: Animation A },
    548 //   { rotate: Animation B },
    549 //   { scale: Animation B },
    550 //   { transform: Animation A },
    551 //   { transform: Animation C },
    552 // ]
    553 //
    554 // And then, for each transaction, we send this list to the compositor thread.
    555 static HashMap<NonCustomCSSPropertyId, nsTArray<RefPtr<dom::Animation>>>
    556 GroupAnimationsByProperty(const nsTArray<RefPtr<dom::Animation>>& aAnimations,
    557                          const nsCSSPropertyIDSet& aPropertySet) {
    558  HashMap<NonCustomCSSPropertyId, nsTArray<RefPtr<dom::Animation>>>
    559      groupedAnims;
    560  for (const RefPtr<dom::Animation>& anim : aAnimations) {
    561    const dom::KeyframeEffect* effect = anim->GetEffect()->AsKeyframeEffect();
    562    MOZ_ASSERT(effect);
    563    for (const AnimationProperty& property : effect->Properties()) {
    564      // TODO(zrhoffman, bug 1869475): Handle custom properties
    565      if (!aPropertySet.HasProperty(property.mProperty)) {
    566        continue;
    567      }
    568 
    569      auto animsForPropertyPtr =
    570          groupedAnims.lookupForAdd(property.mProperty.mId);
    571      if (!animsForPropertyPtr) {
    572        DebugOnly<bool> rv =
    573            groupedAnims.add(animsForPropertyPtr, property.mProperty.mId,
    574                             nsTArray<RefPtr<dom::Animation>>());
    575        MOZ_ASSERT(rv, "Should have enough memory");
    576      }
    577      animsForPropertyPtr->value().AppendElement(anim);
    578    }
    579  }
    580  return groupedAnims;
    581 }
    582 
    583 bool AnimationInfo::AddAnimationsForProperty(
    584    nsIFrame* aFrame, const EffectSet* aEffects,
    585    const nsTArray<RefPtr<dom::Animation>>& aCompositorAnimations,
    586    const Maybe<TransformData>& aTransformData,
    587    NonCustomCSSPropertyId aProperty, Send aSendFlag,
    588    WebRenderLayerManager* aLayerManager) {
    589  bool addedAny = false;
    590  // Add from first to last (since last overrides)
    591  for (dom::Animation* anim : aCompositorAnimations) {
    592    if (!anim->IsRelevant()) {
    593      continue;
    594    }
    595 
    596    MOZ_ASSERT(anim->GetEffect() && anim->GetEffect()->AsKeyframeEffect(),
    597               "A playing animation should have a keyframe effect");
    598    dom::KeyframeEffect* keyframeEffect = anim->GetEffect()->AsKeyframeEffect();
    599    const AnimationProperty* property =
    600        keyframeEffect->GetEffectiveAnimationOfProperty(
    601            CSSPropertyId(aProperty), *aEffects);
    602    if (!property) {
    603      continue;
    604    }
    605 
    606    // Note that if the property is overridden by !important rules,
    607    // GetEffectiveAnimationOfProperty returns null instead.
    608    // This is what we want, since if we have animations overridden by
    609    // !important rules, we don't want to send them to the compositor.
    610    MOZ_ASSERT(
    611        anim->CascadeLevel() != EffectCompositor::CascadeLevel::Animations ||
    612            !aEffects->PropertiesWithImportantRules().HasProperty(aProperty),
    613        "GetEffectiveAnimationOfProperty already tested the property "
    614        "is not overridden by !important rules");
    615 
    616    // Don't add animations that are pending if their timeline does not track
    617    // wallclock time. This is because any pending animations on layers will
    618    // have their start time updated with the current wallclock time.
    619    //
    620    // If we can't convert that wallclock time back to an equivalent timeline
    621    // time, we won't be able to update the content animation and it will end
    622    // up being out of sync with the layer animation.
    623    //
    624    // Currently this only happens when the timeline is driven by a refresh
    625    // driver under test control. In this case, the next time the refresh
    626    // driver is advanced it will trigger any pending animations.
    627    if (anim->Pending() && anim->GetTimeline() &&
    628        !anim->GetTimeline()->TracksWallclockTime()) {
    629      continue;
    630    }
    631 
    632    AddAnimationForProperty(aFrame, *property, anim, aTransformData, aSendFlag);
    633    keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
    634    addedAny = true;
    635    if (aTransformData && aTransformData->partialPrerenderData() &&
    636        aLayerManager) {
    637      aLayerManager->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(),
    638                                                    anim);
    639    }
    640  }
    641  return addedAny;
    642 }
    643 
    644 // Returns which pre-rendered area's sides are overflowed from the pre-rendered
    645 // rect.
    646 //
    647 // We don't need to make jank animations when we are going to composite the
    648 // area where there is no overflowed area even if it's outside of the
    649 // pre-rendered area.
    650 static SideBits GetOverflowedSides(const nsRect& aOverflow,
    651                                   const nsRect& aPartialPrerenderArea) {
    652  SideBits sides = SideBits::eNone;
    653  if (aOverflow.X() < aPartialPrerenderArea.X()) {
    654    sides |= SideBits::eLeft;
    655  }
    656  if (aOverflow.Y() < aPartialPrerenderArea.Y()) {
    657    sides |= SideBits::eTop;
    658  }
    659  if (aOverflow.XMost() > aPartialPrerenderArea.XMost()) {
    660    sides |= SideBits::eRight;
    661  }
    662  if (aOverflow.YMost() > aPartialPrerenderArea.YMost()) {
    663    sides |= SideBits::eBottom;
    664  }
    665  return sides;
    666 }
    667 
    668 static std::pair<ParentLayerRect, gfx::Matrix4x4>
    669 GetClipRectAndTransformForPartialPrerender(
    670    const nsIFrame* aFrame, int32_t aDevPixelsToAppUnits,
    671    const nsIFrame* aClipFrame,
    672    const ScrollContainerFrame* aScrollContainerFrame) {
    673  MOZ_ASSERT(aClipFrame);
    674 
    675  gfx::Matrix4x4 transformInClip =
    676      nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame->GetParent()},
    677                                            RelativeTo{aClipFrame})
    678          .GetMatrix();
    679  if (aScrollContainerFrame) {
    680    transformInClip.PostTranslate(
    681        LayoutDevicePoint::FromAppUnits(
    682            aScrollContainerFrame->GetScrollPosition(), aDevPixelsToAppUnits)
    683            .ToUnknownPoint());
    684  }
    685 
    686  // We don't necessarily use nsLayoutUtils::CalculateCompositionSizeForFrame
    687  // since this is a case where we don't use APZ at all.
    688  return std::make_pair(
    689      LayoutDeviceRect::FromAppUnits(
    690          aScrollContainerFrame ? aScrollContainerFrame->GetScrollPortRect()
    691                                : aClipFrame->GetRectRelativeToSelf(),
    692          aDevPixelsToAppUnits) *
    693          LayoutDeviceToLayerScale2D() * LayerToParentLayerScale(),
    694      transformInClip);
    695 }
    696 
    697 static PartialPrerenderData GetPartialPrerenderData(
    698    const nsIFrame* aFrame, const nsDisplayItem* aItem) {
    699  const nsRect& partialPrerenderedRect = aItem->GetUntransformedPaintRect();
    700  nsRect overflow = aFrame->InkOverflowRectRelativeToSelf();
    701 
    702  ScrollableLayerGuid::ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
    703 
    704  const nsIFrame* clipFrame =
    705      nsLayoutUtils::GetNearestOverflowClipFrame(aFrame->GetParent());
    706  const ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(clipFrame);
    707 
    708  if (!clipFrame) {
    709    // If there is no suitable clip frame in the same document, use the
    710    // root one.
    711    scrollContainerFrame = aFrame->PresShell()->GetRootScrollContainerFrame();
    712    if (scrollContainerFrame) {
    713      clipFrame = scrollContainerFrame;
    714    } else {
    715      // If there is no root scroll frame, use the viewport frame.
    716      clipFrame = aFrame->PresShell()->GetRootFrame();
    717    }
    718  }
    719 
    720  // If the scroll frame is asyncronously scrollable, try to find the scroll id.
    721  if (scrollContainerFrame &&
    722      !scrollContainerFrame->GetScrollStyles().IsHiddenInBothDirections() &&
    723      nsLayoutUtils::AsyncPanZoomEnabled(aFrame)) {
    724    const bool isInPositionFixed =
    725        nsLayoutUtils::IsInPositionFixedSubtree(aFrame);
    726    // We need to find asynchronously scrollable ASRs, therefore we should
    727    // ignore ASRs for pos:sticky display items.
    728    const ActiveScrolledRoot* asr = aItem->GetNearestScrollASR();
    729    if (!isInPositionFixed && asr &&
    730        aFrame->PresContext() == asr->ScrollFrame()->PresContext()) {
    731      scrollId = asr->GetViewId();
    732      MOZ_ASSERT(clipFrame == asr->ScrollFrame());
    733    } else {
    734      // Use the root scroll id in the same document if the target frame is in
    735      // position:fixed subtree or there is no ASR or the ASR is in a different
    736      // ancestor document.
    737      scrollId =
    738          nsLayoutUtils::ScrollIdForRootScrollFrame(aFrame->PresContext());
    739      MOZ_ASSERT(clipFrame ==
    740                 aFrame->PresShell()->GetRootScrollContainerFrame());
    741    }
    742  }
    743 
    744  int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
    745 
    746  auto [clipRect, transformInClip] = GetClipRectAndTransformForPartialPrerender(
    747      aFrame, devPixelsToAppUnits, clipFrame, scrollContainerFrame);
    748 
    749  return PartialPrerenderData{
    750      LayoutDeviceRect::FromAppUnits(partialPrerenderedRect,
    751                                     devPixelsToAppUnits),
    752      GetOverflowedSides(overflow, partialPrerenderedRect),
    753      scrollId,
    754      clipRect,
    755      transformInClip,
    756      LayoutDevicePoint()};  // will be set by caller.
    757 }
    758 
    759 enum class AnimationDataType {
    760  WithMotionPath,
    761  WithoutMotionPath,
    762 };
    763 static Maybe<TransformData> CreateAnimationData(
    764    nsIFrame* aFrame, nsDisplayItem* aItem, DisplayItemType aType,
    765    layers::LayersBackend aLayersBackend, AnimationDataType aDataType,
    766    const Maybe<LayoutDevicePoint>& aPosition) {
    767  if (aType != DisplayItemType::TYPE_TRANSFORM) {
    768    return Nothing();
    769  }
    770 
    771  // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
    772  // the dimensions of refBox. That said, we only get here if there are CSS
    773  // animations or transitions on this element, and that is likely to be a
    774  // lot rarer than transforms on SVG (the frequency of which drives the need
    775  // for TransformReferenceBox).
    776  TransformReferenceBox refBox(aFrame);
    777  const nsRect bounds(0, 0, refBox.Width(), refBox.Height());
    778 
    779  // all data passed directly to the compositor should be in dev pixels
    780  int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
    781  float scale = devPixelsToAppUnits;
    782  gfx::Point3D offsetToTransformOrigin =
    783      nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, refBox, scale);
    784  nsPoint origin;
    785  if (aLayersBackend == layers::LayersBackend::LAYERS_WR) {
    786    // leave origin empty, because we are sending it separately on the
    787    // stacking context that we are pushing to WR, and WR will automatically
    788    // include it when picking up the animated transform values
    789  } else if (aItem) {
    790    // This branch is for display items to leverage the cache of
    791    // nsDisplayListBuilder.
    792    origin = aItem->ToReferenceFrame();
    793  } else {
    794    // This branch is running for restyling.
    795    // Animations are animated at the coordination of the reference
    796    // frame outside, not the given frame itself.  The given frame
    797    // is also reference frame too, so the parent's reference frame
    798    // are used.
    799    nsIFrame* referenceFrame = nsLayoutUtils::GetReferenceFrame(
    800        nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame));
    801    origin = aFrame->GetOffsetToCrossDoc(referenceFrame);
    802  }
    803 
    804  Maybe<MotionPathData> motionPathData;
    805  if (aDataType == AnimationDataType::WithMotionPath) {
    806    const StyleTransformOrigin& styleOrigin =
    807        aFrame->StyleDisplay()->mTransformOrigin;
    808    CSSPoint motionPathOrigin = nsStyleTransformMatrix::Convert2DPosition(
    809        styleOrigin.horizontal, styleOrigin.vertical, refBox);
    810    CSSPoint anchorAdjustment =
    811        MotionPathUtils::ComputeAnchorPointAdjustment(*aFrame);
    812    // Note: If there is no containing block or coord-box is empty, we still
    813    // pass it to the compositor. Just render them as no path on the compositor
    814    // thread.
    815    nsRect coordBox;
    816    const nsIFrame* containingBlockFrame =
    817        MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
    818    nsTArray<nscoord> radii;
    819    if (containingBlockFrame) {
    820      radii = MotionPathUtils::ComputeBorderRadii(
    821          containingBlockFrame->StyleBorder()->mBorderRadius, coordBox);
    822    }
    823    motionPathData.emplace(
    824        std::move(motionPathOrigin), std::move(anchorAdjustment),
    825        std::move(coordBox),
    826        containingBlockFrame ? aFrame->GetOffsetTo(containingBlockFrame)
    827                             : aFrame->GetPosition(),
    828        MotionPathUtils::GetRayContainReferenceSize(aFrame), std::move(radii));
    829  }
    830 
    831  Maybe<PartialPrerenderData> partialPrerenderData;
    832  if (aItem && static_cast<nsDisplayTransform*>(aItem)->IsPartialPrerender()) {
    833    partialPrerenderData = Some(GetPartialPrerenderData(aFrame, aItem));
    834 
    835    if (aLayersBackend == layers::LayersBackend::LAYERS_WR) {
    836      MOZ_ASSERT(aPosition);
    837      partialPrerenderData->position() = *aPosition;
    838    }
    839  }
    840 
    841  return Some(TransformData(origin, offsetToTransformOrigin, bounds,
    842                            devPixelsToAppUnits, motionPathData,
    843                            partialPrerenderData));
    844 }
    845 
    846 void AnimationInfo::AddNonAnimatingTransformLikePropertiesStyles(
    847    const nsCSSPropertyIDSet& aNonAnimatingProperties, nsIFrame* aFrame,
    848    Send aSendFlag) {
    849  auto appendFakeAnimation = [this, aSendFlag](NonCustomCSSPropertyId aProperty,
    850                                               Animatable&& aBaseStyle) {
    851    layers::Animation* animation = (aSendFlag == Send::NextTransaction)
    852                                       ? AddAnimationForNextTransaction()
    853                                       : AddAnimation();
    854    animation->property() = aProperty;
    855    animation->baseStyle() = std::move(aBaseStyle);
    856    animation->easingFunction() = Nothing();
    857    animation->isNotAnimating() = true;
    858  };
    859 
    860  const nsStyleDisplay* display = aFrame->StyleDisplay();
    861  // A simple optimization. We don't need to send offset-* properties if we
    862  // don't have offset-path and offset-position.
    863  bool hasMotion =
    864      !display->mOffsetPath.IsNone() ||
    865      !aNonAnimatingProperties.HasProperty(eCSSProperty_offset_path);
    866 
    867  for (NonCustomCSSPropertyId id : aNonAnimatingProperties) {
    868    switch (id) {
    869      case eCSSProperty_transform:
    870        if (!display->mTransform.IsNone()) {
    871          TransformReferenceBox refBox(aFrame);
    872          appendFakeAnimation(
    873              id, ResolveTransformOperations(display->mTransform, refBox));
    874        }
    875        break;
    876      case eCSSProperty_translate:
    877        if (!display->mTranslate.IsNone()) {
    878          TransformReferenceBox refBox(aFrame);
    879          appendFakeAnimation(id,
    880                              ResolveTranslate(display->mTranslate, refBox));
    881        }
    882        break;
    883      case eCSSProperty_rotate:
    884        if (!display->mRotate.IsNone()) {
    885          appendFakeAnimation(id, display->mRotate);
    886        }
    887        break;
    888      case eCSSProperty_scale:
    889        if (!display->mScale.IsNone()) {
    890          appendFakeAnimation(id, display->mScale);
    891        }
    892        break;
    893      case eCSSProperty_offset_path:
    894        if (!display->mOffsetPath.IsNone()) {
    895          appendFakeAnimation(id, display->mOffsetPath);
    896        }
    897        break;
    898      case eCSSProperty_offset_distance:
    899        if (hasMotion && !display->mOffsetDistance.IsDefinitelyZero()) {
    900          appendFakeAnimation(id, display->mOffsetDistance);
    901        }
    902        break;
    903      case eCSSProperty_offset_rotate:
    904        if (hasMotion && (!display->mOffsetRotate.auto_ ||
    905                          display->mOffsetRotate.angle.ToDegrees() != 0.0)) {
    906          appendFakeAnimation(id, display->mOffsetRotate);
    907        }
    908        break;
    909      case eCSSProperty_offset_anchor:
    910        if (hasMotion && !display->mOffsetAnchor.IsAuto()) {
    911          appendFakeAnimation(id, display->mOffsetAnchor);
    912        }
    913        break;
    914      case eCSSProperty_offset_position:
    915        if (hasMotion && !display->mOffsetPosition.IsAuto()) {
    916          appendFakeAnimation(id, display->mOffsetPosition);
    917        }
    918        break;
    919      default:
    920        MOZ_ASSERT_UNREACHABLE("Unsupported transform-like properties");
    921    }
    922  }
    923 }
    924 
    925 void AnimationInfo::AddAnimationsForDisplayItem(
    926    nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
    927    DisplayItemType aType, WebRenderLayerManager* aLayerManager,
    928    const Maybe<LayoutDevicePoint>& aPosition) {
    929  Send sendFlag = !aBuilder ? Send::NextTransaction : Send::Immediate;
    930  if (sendFlag == Send::NextTransaction) {
    931    ClearAnimationsForNextTransaction();
    932  } else {
    933    ClearAnimations();
    934  }
    935 
    936  // Update the animation generation on the layer. We need to do this before
    937  // any early returns since even if we don't add any animations to the
    938  // layer, we still need to mark it as up-to-date with regards to animations.
    939  // Otherwise, in RestyleManager we'll notice the discrepancy between the
    940  // animation generation numbers and update the layer indefinitely.
    941  EffectSet* effects = EffectSet::GetForFrame(aFrame, aType);
    942  uint64_t animationGeneration =
    943      effects ? effects->GetAnimationGeneration() : 0;
    944  SetAnimationGeneration(animationGeneration);
    945  if (!effects || effects->IsEmpty()) {
    946    return;
    947  }
    948 
    949  EffectCompositor::ClearIsRunningOnCompositor(aFrame, aType);
    950  const nsCSSPropertyIDSet& propertySet =
    951      LayerAnimationInfo::GetCSSPropertiesFor(aType);
    952  const nsTArray<RefPtr<dom::Animation>> matchedAnimations =
    953      EffectCompositor::GetAnimationsForCompositor(aFrame, propertySet);
    954  if (matchedAnimations.IsEmpty()) {
    955    return;
    956  }
    957 
    958  // If the frame is not prerendered, bail out.
    959  // Do this check only during layer construction; during updating the
    960  // caller is required to check it appropriately.
    961  if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) {
    962    // EffectCompositor needs to know that we refused to run this animation
    963    // asynchronously so that it will not throttle the main thread
    964    // animation.
    965    aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
    966    return;
    967  }
    968 
    969  const HashMap<NonCustomCSSPropertyId, nsTArray<RefPtr<dom::Animation>>>
    970      compositorAnimations =
    971          GroupAnimationsByProperty(matchedAnimations, propertySet);
    972  Maybe<TransformData> transformData =
    973      CreateAnimationData(aFrame, aItem, aType, aLayerManager->GetBackendType(),
    974                          compositorAnimations.has(eCSSProperty_offset_path) ||
    975                                  !aFrame->StyleDisplay()->mOffsetPath.IsNone()
    976                              ? AnimationDataType::WithMotionPath
    977                              : AnimationDataType::WithoutMotionPath,
    978                          aPosition);
    979  const bool hasMultipleTransformLikeProperties =
    980      aType == DisplayItemType::TYPE_TRANSFORM;
    981  nsCSSPropertyIDSet nonAnimatingProperties =
    982      nsCSSPropertyIDSet::TransformLikeProperties();
    983  for (auto iter = compositorAnimations.iter(); !iter.done(); iter.next()) {
    984    // Note: We can skip offset-* if there is no offset-path/offset-position
    985    // animations and styles. However, it should be fine and may be better to
    986    // send these information to the compositor because 1) they are simple data
    987    // structure, 2) AddAnimationsForProperty() marks these animations as
    988    // running on the composiror, so CanThrottle() returns true for them, and
    989    // we avoid running these animations on the main thread.
    990    bool added = AddAnimationsForProperty(aFrame, effects, iter.get().value(),
    991                                          transformData, iter.get().key(),
    992                                          sendFlag, aLayerManager);
    993    if (added && transformData) {
    994      // Only copy TransformLikeMetaData in the first animation property.
    995      transformData.reset();
    996    }
    997 
    998    if (hasMultipleTransformLikeProperties && added) {
    999      nonAnimatingProperties.RemoveProperty(iter.get().key());
   1000    }
   1001  }
   1002 
   1003  // If some transform-like properties have animations, but others not, and
   1004  // those non-animating transform-like properties have non-none
   1005  // transform/translate/rotate/scale styles or non-initial value for motion
   1006  // path properties, we also pass their styles into the compositor, so the
   1007  // final transform matrix (on the compositor) could take them into account.
   1008  if (hasMultipleTransformLikeProperties &&
   1009      // For these cases we don't need to send the property style values to
   1010      // the compositor:
   1011      // 1. No property has running animations on the compositor. (i.e. All
   1012      //    properties should be handled by main thread)
   1013      // 2. All properties have running animations on the compositor.
   1014      //    (i.e. Those running animations should override the styles.)
   1015      !nonAnimatingProperties.Equals(
   1016          nsCSSPropertyIDSet::TransformLikeProperties()) &&
   1017      !nonAnimatingProperties.IsEmpty()) {
   1018    AddNonAnimatingTransformLikePropertiesStyles(nonAnimatingProperties, aFrame,
   1019                                                 sendFlag);
   1020  }
   1021 }
   1022 
   1023 }  // namespace mozilla::layers