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/. */
7 #include "AnimationInfo.h"
8 #include "mozilla/LayerAnimationInfo.h"
9 #include "mozilla/layers/WebRenderLayerManager.h"
10 #include "mozilla/layers/AnimationHelper.h"
11 #include "mozilla/layers/CompositorThread.h"
12 #include "mozilla/dom/Animation.h"
13 #include "mozilla/dom/CSSTransition.h"
14 #include "mozilla/dom/KeyframeEffect.h"
15 #include "mozilla/EffectSet.h"
16 #include "mozilla/MotionPathUtils.h"
17 #include "mozilla/PresShell.h"
18 #include "nsIContent.h"
19 #include "nsLayoutUtils.h"
20 #include "nsRefreshDriver.h"
21 #include "nsStyleTransformMatrix.h"
22 #include "PuppetWidget.h"
24 namespace mozilla::layers
{
26 using TransformReferenceBox
= nsStyleTransformMatrix::TransformReferenceBox
;
28 AnimationInfo::AnimationInfo() : mCompositorAnimationsId(0), mMutated(false) {}
30 AnimationInfo::~AnimationInfo() = default;
32 void AnimationInfo::EnsureAnimationsId() {
33 if (!mCompositorAnimationsId
) {
34 mCompositorAnimationsId
= AnimationHelper::GetNextCompositorAnimationsId();
38 Animation
* AnimationInfo::AddAnimation() {
39 MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
40 // Here generates a new id when the first animation is added and
41 // this id is used to represent the animations in this layer.
44 MOZ_ASSERT(!mPendingAnimations
, "should have called ClearAnimations first");
46 Animation
* anim
= mAnimations
.AppendElement();
53 Animation
* AnimationInfo::AddAnimationForNextTransaction() {
54 MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
55 MOZ_ASSERT(mPendingAnimations
,
56 "should have called ClearAnimationsForNextTransaction first");
58 Animation
* anim
= mPendingAnimations
->AppendElement();
63 void AnimationInfo::ClearAnimations() {
64 mPendingAnimations
= nullptr;
66 if (mAnimations
.IsEmpty() && mStorageData
.IsEmpty()) {
76 void AnimationInfo::ClearAnimationsForNextTransaction() {
77 // Ensure we have a non-null mPendingAnimations to mark a future clear.
78 if (!mPendingAnimations
) {
79 mPendingAnimations
= MakeUnique
<AnimationArray
>();
82 mPendingAnimations
->Clear();
85 void AnimationInfo::MaybeStartPendingAnimation(Animation
& aAnimation
,
86 const TimeStamp
& aReadyTime
) {
87 // If the animation is doing an async update of its playback rate, then we
88 // want to match whatever its current time would be at *aReadyTime*.
89 if (!std::isnan(aAnimation
.previousPlaybackRate()) &&
90 aAnimation
.startTime().isSome() && !aAnimation
.originTime().IsNull() &&
91 !aAnimation
.isNotPlaying()) {
92 TimeDuration readyTime
= aReadyTime
- aAnimation
.originTime();
93 aAnimation
.holdTime() = dom::Animation::CurrentTimeFromTimelineTime(
94 readyTime
, aAnimation
.startTime().ref(),
95 aAnimation
.previousPlaybackRate());
96 // Make start time null so that we know to update it below.
97 aAnimation
.startTime() = Nothing();
100 // If the aAnimationation is play-pending, resolve the start time.
101 if (aAnimation
.startTime().isNothing() && !aAnimation
.originTime().IsNull() &&
102 !aAnimation
.isNotPlaying()) {
103 const TimeDuration readyTime
= aReadyTime
- aAnimation
.originTime();
104 aAnimation
.startTime() = Some(dom::Animation::StartTimeFromTimelineTime(
105 readyTime
, aAnimation
.holdTime(), aAnimation
.playbackRate()));
109 bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() {
110 if (mPendingAnimations
) {
111 mAnimations
= std::move(*mPendingAnimations
);
112 mPendingAnimations
= nullptr;
119 bool AnimationInfo::HasTransformAnimation() const {
120 const nsCSSPropertyIDSet
& transformSet
=
121 LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM
);
122 for (const auto& animation
: mAnimations
) {
123 if (transformSet
.HasProperty(animation
.property())) {
131 Maybe
<uint64_t> AnimationInfo::GetGenerationFromFrame(
132 nsIFrame
* aFrame
, DisplayItemType aDisplayItemKey
) {
133 MOZ_ASSERT(aFrame
->IsPrimaryFrame() ||
134 nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
));
136 // In case of continuation, KeyframeEffectReadOnly uses its first frame,
137 // whereas nsDisplayItem uses its last continuation, so we have to use the
138 // last continuation frame here.
139 if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
)) {
140 aFrame
= nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame
);
142 RefPtr
<WebRenderAnimationData
> animationData
=
143 GetWebRenderUserData
<WebRenderAnimationData
>(aFrame
,
144 (uint32_t)aDisplayItemKey
);
146 return animationData
->GetAnimationInfo().GetAnimationGeneration();
153 void AnimationInfo::EnumerateGenerationOnFrame(
154 const nsIFrame
* aFrame
, const nsIContent
* aContent
,
155 const CompositorAnimatableDisplayItemTypes
& aDisplayItemTypes
,
156 AnimationGenerationCallback aCallback
) {
157 nsIWidget
* widget
= nsContentUtils::WidgetForContent(aContent
);
161 // If we haven't created a window renderer there's no animation generation
162 // that we can have, thus we call the callback function with |Nothing()| for
164 if (!widget
->HasWindowRenderer()) {
165 for (auto displayItem
: LayerAnimationInfo::sDisplayItemTypes
) {
166 aCallback(Nothing(), displayItem
);
170 WindowRenderer
* renderer
= widget
->GetWindowRenderer();
171 MOZ_ASSERT(renderer
);
172 if (!renderer
->AsWebRender()) {
176 // In case of continuation, nsDisplayItem uses its last continuation, so we
177 // have to use the last continuation frame here.
178 if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
)) {
179 aFrame
= nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame
);
182 for (auto displayItem
: LayerAnimationInfo::sDisplayItemTypes
) {
183 // For transform animations, the animation is on the primary frame but
184 // |aFrame| is the style frame.
185 const nsIFrame
* frameToQuery
=
186 displayItem
== DisplayItemType::TYPE_TRANSFORM
187 ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame
)
189 RefPtr
<WebRenderAnimationData
> animationData
=
190 GetWebRenderUserData
<WebRenderAnimationData
>(frameToQuery
,
191 (uint32_t)displayItem
);
192 Maybe
<uint64_t> generation
;
194 generation
= animationData
->GetAnimationInfo().GetAnimationGeneration();
196 aCallback(generation
, displayItem
);
200 static StyleTransformOperation
ResolveTranslate(
201 TransformReferenceBox
& aRefBox
, const LengthPercentage
& aX
,
202 const LengthPercentage
& aY
= LengthPercentage::Zero(),
203 const Length
& aZ
= Length
{0}) {
204 float x
= nsStyleTransformMatrix::ProcessTranslatePart(
205 aX
, &aRefBox
, &TransformReferenceBox::Width
);
206 float y
= nsStyleTransformMatrix::ProcessTranslatePart(
207 aY
, &aRefBox
, &TransformReferenceBox::Height
);
208 return StyleTransformOperation::Translate3D(
209 LengthPercentage::FromPixels(x
), LengthPercentage::FromPixels(y
), aZ
);
212 static StyleTranslate
ResolveTranslate(const StyleTranslate
& aValue
,
213 TransformReferenceBox
& aRefBox
) {
214 if (aValue
.IsTranslate()) {
215 const auto& t
= aValue
.AsTranslate();
216 float x
= nsStyleTransformMatrix::ProcessTranslatePart(
217 t
._0
, &aRefBox
, &TransformReferenceBox::Width
);
218 float y
= nsStyleTransformMatrix::ProcessTranslatePart(
219 t
._1
, &aRefBox
, &TransformReferenceBox::Height
);
220 return StyleTranslate::Translate(LengthPercentage::FromPixels(x
),
221 LengthPercentage::FromPixels(y
), t
._2
);
224 MOZ_ASSERT(aValue
.IsNone());
225 return StyleTranslate::None();
228 static StyleTransform
ResolveTransformOperations(
229 const StyleTransform
& aTransform
, TransformReferenceBox
& aRefBox
) {
230 auto convertMatrix
= [](const gfx::Matrix4x4
& aM
) {
231 return StyleTransformOperation::Matrix3D(StyleGenericMatrix3D
<StyleNumber
>{
232 aM
._11
, aM
._12
, aM
._13
, aM
._14
, aM
._21
, aM
._22
, aM
._23
, aM
._24
, aM
._31
,
233 aM
._32
, aM
._33
, aM
._34
, aM
._41
, aM
._42
, aM
._43
, aM
._44
});
236 Vector
<StyleTransformOperation
> result
;
238 result
.initCapacity(aTransform
.Operations().Length()),
239 "Allocating vector of transform operations should be successful.");
241 for (const StyleTransformOperation
& op
: aTransform
.Operations()) {
243 case StyleTransformOperation::Tag::TranslateX
:
244 result
.infallibleAppend(ResolveTranslate(aRefBox
, op
.AsTranslateX()));
246 case StyleTransformOperation::Tag::TranslateY
:
247 result
.infallibleAppend(ResolveTranslate(
248 aRefBox
, LengthPercentage::Zero(), op
.AsTranslateY()));
250 case StyleTransformOperation::Tag::TranslateZ
:
251 result
.infallibleAppend(
252 ResolveTranslate(aRefBox
, LengthPercentage::Zero(),
253 LengthPercentage::Zero(), op
.AsTranslateZ()));
255 case StyleTransformOperation::Tag::Translate
: {
256 const auto& translate
= op
.AsTranslate();
257 result
.infallibleAppend(
258 ResolveTranslate(aRefBox
, translate
._0
, translate
._1
));
261 case StyleTransformOperation::Tag::Translate3D
: {
262 const auto& translate
= op
.AsTranslate3D();
263 result
.infallibleAppend(ResolveTranslate(aRefBox
, translate
._0
,
264 translate
._1
, translate
._2
));
267 case StyleTransformOperation::Tag::InterpolateMatrix
: {
268 gfx::Matrix4x4 matrix
;
269 nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix
, op
, aRefBox
);
270 result
.infallibleAppend(convertMatrix(matrix
));
273 case StyleTransformOperation::Tag::AccumulateMatrix
: {
274 gfx::Matrix4x4 matrix
;
275 nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix
, op
, aRefBox
);
276 result
.infallibleAppend(convertMatrix(matrix
));
279 case StyleTransformOperation::Tag::RotateX
:
280 case StyleTransformOperation::Tag::RotateY
:
281 case StyleTransformOperation::Tag::RotateZ
:
282 case StyleTransformOperation::Tag::Rotate
:
283 case StyleTransformOperation::Tag::Rotate3D
:
284 case StyleTransformOperation::Tag::ScaleX
:
285 case StyleTransformOperation::Tag::ScaleY
:
286 case StyleTransformOperation::Tag::ScaleZ
:
287 case StyleTransformOperation::Tag::Scale
:
288 case StyleTransformOperation::Tag::Scale3D
:
289 case StyleTransformOperation::Tag::SkewX
:
290 case StyleTransformOperation::Tag::SkewY
:
291 case StyleTransformOperation::Tag::Skew
:
292 case StyleTransformOperation::Tag::Matrix
:
293 case StyleTransformOperation::Tag::Matrix3D
:
294 case StyleTransformOperation::Tag::Perspective
:
295 result
.infallibleAppend(op
);
298 MOZ_ASSERT_UNREACHABLE("Function not handled yet!");
302 auto transform
= StyleTransform
{
303 StyleOwnedSlice
<StyleTransformOperation
>(std::move(result
))};
304 MOZ_ASSERT(!transform
.HasPercent());
305 MOZ_ASSERT(transform
.Operations().Length() ==
306 aTransform
.Operations().Length());
310 static Maybe
<ScrollTimelineOptions
> GetScrollTimelineOptions(
311 dom::AnimationTimeline
* aTimeline
) {
312 if (!aTimeline
|| !aTimeline
->IsScrollTimeline()) {
316 const dom::ScrollTimeline
* timeline
= aTimeline
->AsScrollTimeline();
317 MOZ_ASSERT(timeline
->IsActive(),
318 "We send scroll animation to the compositor only if its timeline "
321 ScrollableLayerGuid::ViewID source
= ScrollableLayerGuid::NULL_SCROLL_ID
;
322 DebugOnly
<bool> success
=
323 nsLayoutUtils::FindIDFor(timeline
->SourceElement(), &source
);
324 MOZ_ASSERT(success
, "We should have a valid ViewID for the scroller");
326 return Some(ScrollTimelineOptions(source
, timeline
->Axis()));
329 static void SetAnimatable(nsCSSPropertyID aProperty
,
330 const AnimationValue
& aAnimationValue
,
331 nsIFrame
* aFrame
, TransformReferenceBox
& aRefBox
,
332 layers::Animatable
& aAnimatable
) {
335 if (aAnimationValue
.IsNull()) {
336 aAnimatable
= null_t();
341 case eCSSProperty_background_color
: {
342 // We don't support color animation on the compositor yet so that we can
343 // resolve currentColor at this moment.
345 aFrame
->Style()->GetVisitedDependentColor(&nsStyleText::mColor
);
346 aAnimatable
= aAnimationValue
.GetColor(foreground
);
349 case eCSSProperty_opacity
:
350 aAnimatable
= aAnimationValue
.GetOpacity();
352 case eCSSProperty_rotate
:
353 aAnimatable
= aAnimationValue
.GetRotateProperty();
355 case eCSSProperty_scale
:
356 aAnimatable
= aAnimationValue
.GetScaleProperty();
358 case eCSSProperty_translate
:
360 ResolveTranslate(aAnimationValue
.GetTranslateProperty(), aRefBox
);
362 case eCSSProperty_transform
:
363 aAnimatable
= ResolveTransformOperations(
364 aAnimationValue
.GetTransformProperty(), aRefBox
);
366 case eCSSProperty_offset_path
:
367 aAnimatable
= StyleOffsetPath::None();
368 aAnimationValue
.GetOffsetPathProperty(aAnimatable
.get_StyleOffsetPath());
370 case eCSSProperty_offset_distance
:
371 aAnimatable
= aAnimationValue
.GetOffsetDistanceProperty();
373 case eCSSProperty_offset_rotate
:
374 aAnimatable
= aAnimationValue
.GetOffsetRotateProperty();
376 case eCSSProperty_offset_anchor
:
377 aAnimatable
= aAnimationValue
.GetOffsetAnchorProperty();
379 case eCSSProperty_offset_position
:
380 aAnimatable
= aAnimationValue
.GetOffsetPositionProperty();
383 MOZ_ASSERT_UNREACHABLE("Unsupported property");
387 void AnimationInfo::AddAnimationForProperty(
388 nsIFrame
* aFrame
, const AnimationProperty
& aProperty
,
389 dom::Animation
* aAnimation
, const Maybe
<TransformData
>& aTransformData
,
391 MOZ_ASSERT(aAnimation
->GetEffect(),
392 "Should not be adding an animation without an effect");
393 MOZ_ASSERT(!aAnimation
->GetStartTime().IsNull() || !aAnimation
->IsPlaying() ||
394 (aAnimation
->GetTimeline() &&
395 aAnimation
->GetTimeline()->TracksWallclockTime()),
396 "If the animation has an unresolved start time it should either"
397 " be static (so we don't need a start time) or else have a"
398 " timeline capable of converting TimeStamps (so we can calculate"
401 Animation
* animation
= (aSendFlag
== Send::NextTransaction
)
402 ? AddAnimationForNextTransaction()
405 const TimingParams
& timing
= aAnimation
->GetEffect()->NormalizedTiming();
407 // If we are starting a new transition that replaces an existing transition
408 // running on the compositor, it is possible that the animation on the
409 // compositor will have advanced ahead of the main thread. If we use as
410 // the starting point of the new transition, the current value of the
411 // replaced transition as calculated on the main thread using the refresh
412 // driver time, the new transition will jump when it starts. Instead, we
413 // re-calculate the starting point of the new transition by applying the
414 // current TimeStamp to the parameters of the replaced transition.
416 // We need to do this here, rather than when we generate the new transition,
417 // since after generating the new transition other requestAnimationFrame
418 // callbacks may run that introduce further lag between the main thread and
420 if (dom::CSSTransition
* cssTransition
= aAnimation
->AsCSSTransition()) {
421 cssTransition
->UpdateStartValueFromReplacedTransition();
424 animation
->originTime() =
425 !aAnimation
->GetTimeline()
427 : aAnimation
->GetTimeline()->ToTimeStamp(TimeDuration());
429 dom::Nullable
<TimeDuration
> startTime
= aAnimation
->GetStartTime();
430 if (startTime
.IsNull()) {
431 animation
->startTime() = Nothing();
433 animation
->startTime() = Some(startTime
.Value());
436 animation
->holdTime() = aAnimation
->GetCurrentTimeAsDuration().Value();
438 const ComputedTiming computedTiming
=
439 aAnimation
->GetEffect()->GetComputedTiming();
440 animation
->delay() = timing
.Delay();
441 animation
->endDelay() = timing
.EndDelay();
442 animation
->duration() = computedTiming
.mDuration
;
443 animation
->iterations() = static_cast<float>(computedTiming
.mIterations
);
444 animation
->iterationStart() =
445 static_cast<float>(computedTiming
.mIterationStart
);
446 animation
->direction() = static_cast<uint8_t>(timing
.Direction());
447 animation
->fillMode() = static_cast<uint8_t>(computedTiming
.mFill
);
448 MOZ_ASSERT(!aProperty
.mProperty
.IsCustom(),
449 "We don't animate custom properties in the compositor");
450 animation
->property() = aProperty
.mProperty
.mID
;
451 animation
->playbackRate() =
452 static_cast<float>(aAnimation
->CurrentOrPendingPlaybackRate());
453 animation
->previousPlaybackRate() =
454 aAnimation
->HasPendingPlaybackRate()
455 ? static_cast<float>(aAnimation
->PlaybackRate())
456 : std::numeric_limits
<float>::quiet_NaN();
457 animation
->transformData() = aTransformData
;
458 animation
->easingFunction() = timing
.TimingFunction();
459 animation
->iterationComposite() = static_cast<uint8_t>(
460 aAnimation
->GetEffect()->AsKeyframeEffect()->IterationComposite());
461 animation
->isNotPlaying() = !aAnimation
->IsPlaying();
462 animation
->isNotAnimating() = false;
463 animation
->scrollTimelineOptions() =
464 GetScrollTimelineOptions(aAnimation
->GetTimeline());
466 TransformReferenceBox
refBox(aFrame
);
468 // If the animation is additive or accumulates, we need to pass its base value
469 // to the compositor.
471 AnimationValue baseStyle
=
472 aAnimation
->GetEffect()->AsKeyframeEffect()->BaseStyle(
473 aProperty
.mProperty
);
474 if (!baseStyle
.IsNull()) {
475 SetAnimatable(aProperty
.mProperty
.mID
, baseStyle
, aFrame
, refBox
,
476 animation
->baseStyle());
478 animation
->baseStyle() = null_t();
481 for (const AnimationPropertySegment
& segment
: aProperty
.mSegments
) {
482 AnimationSegment
* animSegment
= animation
->segments().AppendElement();
483 SetAnimatable(aProperty
.mProperty
.mID
, segment
.mFromValue
, aFrame
, refBox
,
484 animSegment
->startState());
485 SetAnimatable(aProperty
.mProperty
.mID
, segment
.mToValue
, aFrame
, refBox
,
486 animSegment
->endState());
488 animSegment
->startPortion() = segment
.mFromKey
;
489 animSegment
->endPortion() = segment
.mToKey
;
490 animSegment
->startComposite() =
491 static_cast<uint8_t>(segment
.mFromComposite
);
492 animSegment
->endComposite() = static_cast<uint8_t>(segment
.mToComposite
);
493 animSegment
->sampleFn() = segment
.mTimingFunction
;
496 if (aAnimation
->Pending()) {
497 const TimeStamp readyTime
=
498 aFrame
->PresContext()->RefreshDriver()->MostRecentRefresh(
499 /* aEnsureTimerStarted= */ false);
500 MOZ_ASSERT(!readyTime
.IsNull());
501 aAnimation
->SetPendingReadyTime(readyTime
);
502 MaybeStartPendingAnimation(*animation
, readyTime
);
506 // Let's use an example to explain this function:
508 // We have 4 playing animations (without any !important rule or transition):
509 // Animation A: [ transform, rotate ].
510 // Animation B: [ rotate, scale ].
511 // Animation C: [ transform, margin-left ].
512 // Animation D: [ opacity, margin-left ].
514 // Normally, GetAnimationsForCompositor(|transform-like properties|) returns:
515 // [ Animation A, Animation B, Animation C ], which is the first argument of
518 // In this function, we want to re-organize the list as (Note: don't care
519 // the order of properties):
521 // { rotate: [ Animation A, Animation B ] },
522 // { scale: [ Animation B ] },
523 // { transform: [ Animation A, Animation C ] },
526 // Therefore, AddAnimationsForProperty() will append each animation property
527 // into AnimationInfo, as a final list of layers::Animation:
529 // { rotate: Animation A },
530 // { rotate: Animation B },
531 // { scale: Animation B },
532 // { transform: Animation A },
533 // { transform: Animation C },
536 // And then, for each transaction, we send this list to the compositor thread.
537 static HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>>
538 GroupAnimationsByProperty(const nsTArray
<RefPtr
<dom::Animation
>>& aAnimations
,
539 const nsCSSPropertyIDSet
& aPropertySet
) {
540 HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>> groupedAnims
;
541 for (const RefPtr
<dom::Animation
>& anim
: aAnimations
) {
542 const dom::KeyframeEffect
* effect
= anim
->GetEffect()->AsKeyframeEffect();
544 for (const AnimationProperty
& property
: effect
->Properties()) {
545 // TODO(zrhoffman, bug 1869475): Handle custom properties
546 if (!aPropertySet
.HasProperty(property
.mProperty
)) {
550 auto animsForPropertyPtr
=
551 groupedAnims
.lookupForAdd(property
.mProperty
.mID
);
552 if (!animsForPropertyPtr
) {
554 groupedAnims
.add(animsForPropertyPtr
, property
.mProperty
.mID
,
555 nsTArray
<RefPtr
<dom::Animation
>>());
556 MOZ_ASSERT(rv
, "Should have enough memory");
558 animsForPropertyPtr
->value().AppendElement(anim
);
564 bool AnimationInfo::AddAnimationsForProperty(
565 nsIFrame
* aFrame
, const EffectSet
* aEffects
,
566 const nsTArray
<RefPtr
<dom::Animation
>>& aCompositorAnimations
,
567 const Maybe
<TransformData
>& aTransformData
, nsCSSPropertyID aProperty
,
568 Send aSendFlag
, WebRenderLayerManager
* aLayerManager
) {
569 bool addedAny
= false;
570 // Add from first to last (since last overrides)
571 for (dom::Animation
* anim
: aCompositorAnimations
) {
572 if (!anim
->IsRelevant()) {
576 MOZ_ASSERT(anim
->GetEffect() && anim
->GetEffect()->AsKeyframeEffect(),
577 "A playing animation should have a keyframe effect");
578 dom::KeyframeEffect
* keyframeEffect
= anim
->GetEffect()->AsKeyframeEffect();
579 const AnimationProperty
* property
=
580 keyframeEffect
->GetEffectiveAnimationOfProperty(
581 AnimatedPropertyID(aProperty
), *aEffects
);
586 // Note that if the property is overridden by !important rules,
587 // GetEffectiveAnimationOfProperty returns null instead.
588 // This is what we want, since if we have animations overridden by
589 // !important rules, we don't want to send them to the compositor.
591 anim
->CascadeLevel() != EffectCompositor::CascadeLevel::Animations
||
592 !aEffects
->PropertiesWithImportantRules().HasProperty(aProperty
),
593 "GetEffectiveAnimationOfProperty already tested the property "
594 "is not overridden by !important rules");
596 // Don't add animations that are pending if their timeline does not track
597 // wallclock time. This is because any pending animations on layers will
598 // have their start time updated with the current wallclock time.
600 // If we can't convert that wallclock time back to an equivalent timeline
601 // time, we won't be able to update the content animation and it will end
602 // up being out of sync with the layer animation.
604 // Currently this only happens when the timeline is driven by a refresh
605 // driver under test control. In this case, the next time the refresh
606 // driver is advanced it will trigger any pending animations.
607 if (anim
->Pending() && anim
->GetTimeline() &&
608 !anim
->GetTimeline()->TracksWallclockTime()) {
612 AddAnimationForProperty(aFrame
, *property
, anim
, aTransformData
, aSendFlag
);
613 keyframeEffect
->SetIsRunningOnCompositor(aProperty
, true);
615 if (aTransformData
&& aTransformData
->partialPrerenderData() &&
617 aLayerManager
->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(),
624 // Returns which pre-rendered area's sides are overflowed from the pre-rendered
627 // We don't need to make jank animations when we are going to composite the
628 // area where there is no overflowed area even if it's outside of the
629 // pre-rendered area.
630 static SideBits
GetOverflowedSides(const nsRect
& aOverflow
,
631 const nsRect
& aPartialPrerenderArea
) {
632 SideBits sides
= SideBits::eNone
;
633 if (aOverflow
.X() < aPartialPrerenderArea
.X()) {
634 sides
|= SideBits::eLeft
;
636 if (aOverflow
.Y() < aPartialPrerenderArea
.Y()) {
637 sides
|= SideBits::eTop
;
639 if (aOverflow
.XMost() > aPartialPrerenderArea
.XMost()) {
640 sides
|= SideBits::eRight
;
642 if (aOverflow
.YMost() > aPartialPrerenderArea
.YMost()) {
643 sides
|= SideBits::eBottom
;
648 static std::pair
<ParentLayerRect
, gfx::Matrix4x4
>
649 GetClipRectAndTransformForPartialPrerender(
650 const nsIFrame
* aFrame
, int32_t aDevPixelsToAppUnits
,
651 const nsIFrame
* aClipFrame
, const nsIScrollableFrame
* aScrollFrame
) {
652 MOZ_ASSERT(aClipFrame
);
654 gfx::Matrix4x4 transformInClip
=
655 nsLayoutUtils::GetTransformToAncestor(RelativeTo
{aFrame
->GetParent()},
656 RelativeTo
{aClipFrame
})
659 transformInClip
.PostTranslate(
660 LayoutDevicePoint::FromAppUnits(aScrollFrame
->GetScrollPosition(),
661 aDevPixelsToAppUnits
)
665 // We don't necessarily use nsLayoutUtils::CalculateCompositionSizeForFrame
666 // since this is a case where we don't use APZ at all.
667 return std::make_pair(
668 LayoutDeviceRect::FromAppUnits(aScrollFrame
669 ? aScrollFrame
->GetScrollPortRect()
670 : aClipFrame
->GetRectRelativeToSelf(),
671 aDevPixelsToAppUnits
) *
672 LayoutDeviceToLayerScale2D() * LayerToParentLayerScale(),
676 static PartialPrerenderData
GetPartialPrerenderData(
677 const nsIFrame
* aFrame
, const nsDisplayItem
* aItem
) {
678 const nsRect
& partialPrerenderedRect
= aItem
->GetUntransformedPaintRect();
679 nsRect overflow
= aFrame
->InkOverflowRectRelativeToSelf();
681 ScrollableLayerGuid::ViewID scrollId
= ScrollableLayerGuid::NULL_SCROLL_ID
;
683 const nsIFrame
* clipFrame
=
684 nsLayoutUtils::GetNearestOverflowClipFrame(aFrame
->GetParent());
685 const nsIScrollableFrame
* scrollFrame
= do_QueryFrame(clipFrame
);
688 // If there is no suitable clip frame in the same document, use the
690 scrollFrame
= aFrame
->PresShell()->GetRootScrollFrameAsScrollable();
692 clipFrame
= do_QueryFrame(scrollFrame
);
694 // If there is no root scroll frame, use the viewport frame.
695 clipFrame
= aFrame
->PresShell()->GetRootFrame();
699 // If the scroll frame is asyncronously scrollable, try to find the scroll id.
701 !scrollFrame
->GetScrollStyles().IsHiddenInBothDirections() &&
702 nsLayoutUtils::AsyncPanZoomEnabled(aFrame
)) {
703 const bool isInPositionFixed
=
704 nsLayoutUtils::IsInPositionFixedSubtree(aFrame
);
705 const ActiveScrolledRoot
* asr
= aItem
->GetActiveScrolledRoot();
706 const nsIFrame
* asrScrollableFrame
=
707 asr
? do_QueryFrame(asr
->mScrollableFrame
) : nullptr;
708 if (!isInPositionFixed
&& asr
&&
709 aFrame
->PresContext() == asrScrollableFrame
->PresContext()) {
710 scrollId
= asr
->GetViewId();
711 MOZ_ASSERT(clipFrame
== asrScrollableFrame
);
713 // Use the root scroll id in the same document if the target frame is in
714 // position:fixed subtree or there is no ASR or the ASR is in a different
715 // ancestor document.
717 nsLayoutUtils::ScrollIdForRootScrollFrame(aFrame
->PresContext());
718 MOZ_ASSERT(clipFrame
== aFrame
->PresShell()->GetRootScrollFrame());
722 int32_t devPixelsToAppUnits
= aFrame
->PresContext()->AppUnitsPerDevPixel();
724 auto [clipRect
, transformInClip
] = GetClipRectAndTransformForPartialPrerender(
725 aFrame
, devPixelsToAppUnits
, clipFrame
, scrollFrame
);
727 return PartialPrerenderData
{
728 LayoutDeviceRect::FromAppUnits(partialPrerenderedRect
,
729 devPixelsToAppUnits
),
730 GetOverflowedSides(overflow
, partialPrerenderedRect
),
734 LayoutDevicePoint()}; // will be set by caller.
737 enum class AnimationDataType
{
741 static Maybe
<TransformData
> CreateAnimationData(
742 nsIFrame
* aFrame
, nsDisplayItem
* aItem
, DisplayItemType aType
,
743 layers::LayersBackend aLayersBackend
, AnimationDataType aDataType
,
744 const Maybe
<LayoutDevicePoint
>& aPosition
) {
745 if (aType
!= DisplayItemType::TYPE_TRANSFORM
) {
749 // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
750 // the dimensions of refBox. That said, we only get here if there are CSS
751 // animations or transitions on this element, and that is likely to be a
752 // lot rarer than transforms on SVG (the frequency of which drives the need
753 // for TransformReferenceBox).
754 TransformReferenceBox
refBox(aFrame
);
755 const nsRect
bounds(0, 0, refBox
.Width(), refBox
.Height());
757 // all data passed directly to the compositor should be in dev pixels
758 int32_t devPixelsToAppUnits
= aFrame
->PresContext()->AppUnitsPerDevPixel();
759 float scale
= devPixelsToAppUnits
;
760 gfx::Point3D offsetToTransformOrigin
=
761 nsDisplayTransform::GetDeltaToTransformOrigin(aFrame
, refBox
, scale
);
763 if (aLayersBackend
== layers::LayersBackend::LAYERS_WR
) {
764 // leave origin empty, because we are sending it separately on the
765 // stacking context that we are pushing to WR, and WR will automatically
766 // include it when picking up the animated transform values
768 // This branch is for display items to leverage the cache of
769 // nsDisplayListBuilder.
770 origin
= aItem
->ToReferenceFrame();
772 // This branch is running for restyling.
773 // Animations are animated at the coordination of the reference
774 // frame outside, not the given frame itself. The given frame
775 // is also reference frame too, so the parent's reference frame
777 nsIFrame
* referenceFrame
= nsLayoutUtils::GetReferenceFrame(
778 nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame
));
779 origin
= aFrame
->GetOffsetToCrossDoc(referenceFrame
);
782 Maybe
<MotionPathData
> motionPathData
;
783 if (aDataType
== AnimationDataType::WithMotionPath
) {
784 const StyleTransformOrigin
& styleOrigin
=
785 aFrame
->StyleDisplay()->mTransformOrigin
;
786 CSSPoint motionPathOrigin
= nsStyleTransformMatrix::Convert2DPosition(
787 styleOrigin
.horizontal
, styleOrigin
.vertical
, refBox
);
788 CSSPoint anchorAdjustment
=
789 MotionPathUtils::ComputeAnchorPointAdjustment(*aFrame
);
790 // Note: If there is no containing block or coord-box is empty, we still
791 // pass it to the compositor. Just render them as no path on the compositor
794 const nsIFrame
* containingBlockFrame
=
795 MotionPathUtils::GetOffsetPathReferenceBox(aFrame
, coordBox
);
796 nsTArray
<nscoord
> radii
;
797 if (containingBlockFrame
) {
798 radii
= MotionPathUtils::ComputeBorderRadii(
799 containingBlockFrame
->StyleBorder()->mBorderRadius
, coordBox
);
801 motionPathData
.emplace(
802 std::move(motionPathOrigin
), std::move(anchorAdjustment
),
804 containingBlockFrame
? aFrame
->GetOffsetTo(containingBlockFrame
)
805 : aFrame
->GetPosition(),
806 MotionPathUtils::GetRayContainReferenceSize(aFrame
), std::move(radii
));
809 Maybe
<PartialPrerenderData
> partialPrerenderData
;
810 if (aItem
&& static_cast<nsDisplayTransform
*>(aItem
)->IsPartialPrerender()) {
811 partialPrerenderData
= Some(GetPartialPrerenderData(aFrame
, aItem
));
813 if (aLayersBackend
== layers::LayersBackend::LAYERS_WR
) {
814 MOZ_ASSERT(aPosition
);
815 partialPrerenderData
->position() = *aPosition
;
819 return Some(TransformData(origin
, offsetToTransformOrigin
, bounds
,
820 devPixelsToAppUnits
, motionPathData
,
821 partialPrerenderData
));
824 void AnimationInfo::AddNonAnimatingTransformLikePropertiesStyles(
825 const nsCSSPropertyIDSet
& aNonAnimatingProperties
, nsIFrame
* aFrame
,
827 auto appendFakeAnimation
= [this, aSendFlag
](nsCSSPropertyID aProperty
,
828 Animatable
&& aBaseStyle
) {
829 layers::Animation
* animation
= (aSendFlag
== Send::NextTransaction
)
830 ? AddAnimationForNextTransaction()
832 animation
->property() = aProperty
;
833 animation
->baseStyle() = std::move(aBaseStyle
);
834 animation
->easingFunction() = Nothing();
835 animation
->isNotAnimating() = true;
838 const nsStyleDisplay
* display
= aFrame
->StyleDisplay();
839 // A simple optimization. We don't need to send offset-* properties if we
840 // don't have offset-path and offset-position.
842 !display
->mOffsetPath
.IsNone() ||
843 !aNonAnimatingProperties
.HasProperty(eCSSProperty_offset_path
);
845 for (nsCSSPropertyID id
: aNonAnimatingProperties
) {
847 case eCSSProperty_transform
:
848 if (!display
->mTransform
.IsNone()) {
849 TransformReferenceBox
refBox(aFrame
);
851 id
, ResolveTransformOperations(display
->mTransform
, refBox
));
854 case eCSSProperty_translate
:
855 if (!display
->mTranslate
.IsNone()) {
856 TransformReferenceBox
refBox(aFrame
);
857 appendFakeAnimation(id
,
858 ResolveTranslate(display
->mTranslate
, refBox
));
861 case eCSSProperty_rotate
:
862 if (!display
->mRotate
.IsNone()) {
863 appendFakeAnimation(id
, display
->mRotate
);
866 case eCSSProperty_scale
:
867 if (!display
->mScale
.IsNone()) {
868 appendFakeAnimation(id
, display
->mScale
);
871 case eCSSProperty_offset_path
:
872 if (!display
->mOffsetPath
.IsNone()) {
873 appendFakeAnimation(id
, display
->mOffsetPath
);
876 case eCSSProperty_offset_distance
:
877 if (hasMotion
&& !display
->mOffsetDistance
.IsDefinitelyZero()) {
878 appendFakeAnimation(id
, display
->mOffsetDistance
);
881 case eCSSProperty_offset_rotate
:
882 if (hasMotion
&& (!display
->mOffsetRotate
.auto_
||
883 display
->mOffsetRotate
.angle
.ToDegrees() != 0.0)) {
884 appendFakeAnimation(id
, display
->mOffsetRotate
);
887 case eCSSProperty_offset_anchor
:
888 if (hasMotion
&& !display
->mOffsetAnchor
.IsAuto()) {
889 appendFakeAnimation(id
, display
->mOffsetAnchor
);
892 case eCSSProperty_offset_position
:
893 if (hasMotion
&& !display
->mOffsetPosition
.IsAuto()) {
894 appendFakeAnimation(id
, display
->mOffsetPosition
);
898 MOZ_ASSERT_UNREACHABLE("Unsupported transform-like properties");
903 void AnimationInfo::AddAnimationsForDisplayItem(
904 nsIFrame
* aFrame
, nsDisplayListBuilder
* aBuilder
, nsDisplayItem
* aItem
,
905 DisplayItemType aType
, WebRenderLayerManager
* aLayerManager
,
906 const Maybe
<LayoutDevicePoint
>& aPosition
) {
907 Send sendFlag
= !aBuilder
? Send::NextTransaction
: Send::Immediate
;
908 if (sendFlag
== Send::NextTransaction
) {
909 ClearAnimationsForNextTransaction();
914 // Update the animation generation on the layer. We need to do this before
915 // any early returns since even if we don't add any animations to the
916 // layer, we still need to mark it as up-to-date with regards to animations.
917 // Otherwise, in RestyleManager we'll notice the discrepancy between the
918 // animation generation numbers and update the layer indefinitely.
919 EffectSet
* effects
= EffectSet::GetForFrame(aFrame
, aType
);
920 uint64_t animationGeneration
=
921 effects
? effects
->GetAnimationGeneration() : 0;
922 SetAnimationGeneration(animationGeneration
);
923 if (!effects
|| effects
->IsEmpty()) {
927 EffectCompositor::ClearIsRunningOnCompositor(aFrame
, aType
);
928 const nsCSSPropertyIDSet
& propertySet
=
929 LayerAnimationInfo::GetCSSPropertiesFor(aType
);
930 const nsTArray
<RefPtr
<dom::Animation
>> matchedAnimations
=
931 EffectCompositor::GetAnimationsForCompositor(aFrame
, propertySet
);
932 if (matchedAnimations
.IsEmpty()) {
936 // If the frame is not prerendered, bail out.
937 // Do this check only during layer construction; during updating the
938 // caller is required to check it appropriately.
939 if (aItem
&& !aItem
->CanUseAsyncAnimations(aBuilder
)) {
940 // EffectCompositor needs to know that we refused to run this animation
941 // asynchronously so that it will not throttle the main thread
943 aFrame
->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
947 const HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>>
948 compositorAnimations
=
949 GroupAnimationsByProperty(matchedAnimations
, propertySet
);
950 Maybe
<TransformData
> transformData
=
951 CreateAnimationData(aFrame
, aItem
, aType
, aLayerManager
->GetBackendType(),
952 compositorAnimations
.has(eCSSProperty_offset_path
) ||
953 !aFrame
->StyleDisplay()->mOffsetPath
.IsNone()
954 ? AnimationDataType::WithMotionPath
955 : AnimationDataType::WithoutMotionPath
,
957 const bool hasMultipleTransformLikeProperties
=
958 aType
== DisplayItemType::TYPE_TRANSFORM
;
959 nsCSSPropertyIDSet nonAnimatingProperties
=
960 nsCSSPropertyIDSet::TransformLikeProperties();
961 for (auto iter
= compositorAnimations
.iter(); !iter
.done(); iter
.next()) {
962 // Note: We can skip offset-* if there is no offset-path/offset-position
963 // animations and styles. However, it should be fine and may be better to
964 // send these information to the compositor because 1) they are simple data
965 // structure, 2) AddAnimationsForProperty() marks these animations as
966 // running on the composiror, so CanThrottle() returns true for them, and
967 // we avoid running these animations on the main thread.
968 bool added
= AddAnimationsForProperty(aFrame
, effects
, iter
.get().value(),
969 transformData
, iter
.get().key(),
970 sendFlag
, aLayerManager
);
971 if (added
&& transformData
) {
972 // Only copy TransformLikeMetaData in the first animation property.
973 transformData
.reset();
976 if (hasMultipleTransformLikeProperties
&& added
) {
977 nonAnimatingProperties
.RemoveProperty(iter
.get().key());
981 // If some transform-like properties have animations, but others not, and
982 // those non-animating transform-like properties have non-none
983 // transform/translate/rotate/scale styles or non-initial value for motion
984 // path properties, we also pass their styles into the compositor, so the
985 // final transform matrix (on the compositor) could take them into account.
986 if (hasMultipleTransformLikeProperties
&&
987 // For these cases we don't need to send the property style values to
989 // 1. No property has running animations on the compositor. (i.e. All
990 // properties should be handled by main thread)
991 // 2. All properties have running animations on the compositor.
992 // (i.e. Those running animations should override the styles.)
993 !nonAnimatingProperties
.Equals(
994 nsCSSPropertyIDSet::TransformLikeProperties()) &&
995 !nonAnimatingProperties
.IsEmpty()) {
996 AddNonAnimatingTransformLikePropertiesStyles(nonAnimatingProperties
, aFrame
,
1001 } // namespace mozilla::layers