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