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