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/PresShell.h"
17 #include "mozilla/StaticPrefs_layout.h"
18 #include "nsIContent.h"
19 #include "nsLayoutUtils.h"
20 #include "nsPresContextInlines.h"
21 #include "nsStyleTransformMatrix.h"
22 #include "PuppetWidget.h"
27 using TransformReferenceBox
= nsStyleTransformMatrix::TransformReferenceBox
;
29 AnimationInfo::AnimationInfo() : mCompositorAnimationsId(0), mMutated(false) {}
31 AnimationInfo::~AnimationInfo() = default;
33 void AnimationInfo::EnsureAnimationsId() {
34 if (!mCompositorAnimationsId
) {
35 mCompositorAnimationsId
= AnimationHelper::GetNextCompositorAnimationsId();
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.
45 MOZ_ASSERT(!mPendingAnimations
, "should have called ClearAnimations first");
47 Animation
* anim
= mAnimations
.AppendElement();
54 Animation
* AnimationInfo::AddAnimationForNextTransaction() {
55 MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
56 MOZ_ASSERT(mPendingAnimations
,
57 "should have called ClearAnimationsForNextTransaction first");
59 Animation
* anim
= mPendingAnimations
->AppendElement();
64 void AnimationInfo::ClearAnimations() {
65 mPendingAnimations
= nullptr;
67 if (mAnimations
.IsEmpty() && mStorageData
.IsEmpty()) {
77 void AnimationInfo::ClearAnimationsForNextTransaction() {
78 // Ensure we have a non-null mPendingAnimations to mark a future clear.
79 if (!mPendingAnimations
) {
80 mPendingAnimations
= MakeUnique
<AnimationArray
>();
83 mPendingAnimations
->Clear();
86 bool AnimationInfo::StartPendingAnimations(const TimeStamp
& aReadyTime
) {
88 for (size_t animIdx
= 0, animEnd
= mAnimations
.Length(); animIdx
< animEnd
;
90 Animation
& anim
= mAnimations
[animIdx
];
92 // If the animation is doing an async update of its playback rate, then we
93 // want to match whatever its current time would be at *aReadyTime*.
94 if (!std::isnan(anim
.previousPlaybackRate()) && anim
.startTime().isSome() &&
95 !anim
.originTime().IsNull() && !anim
.isNotPlaying()) {
96 TimeDuration readyTime
= aReadyTime
- anim
.originTime();
97 anim
.holdTime() = dom::Animation::CurrentTimeFromTimelineTime(
98 readyTime
, anim
.startTime().ref(), anim
.previousPlaybackRate());
99 // Make start time null so that we know to update it below.
100 anim
.startTime() = Nothing();
103 // If the animation is play-pending, resolve the start time.
104 if (anim
.startTime().isNothing() && !anim
.originTime().IsNull() &&
105 !anim
.isNotPlaying()) {
106 TimeDuration readyTime
= aReadyTime
- anim
.originTime();
107 anim
.startTime() = Some(dom::Animation::StartTimeFromTimelineTime(
108 readyTime
, anim
.holdTime(), anim
.playbackRate()));
115 bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() {
116 if (mPendingAnimations
) {
117 mAnimations
= std::move(*mPendingAnimations
);
118 mPendingAnimations
= nullptr;
125 bool AnimationInfo::HasTransformAnimation() const {
126 const nsCSSPropertyIDSet
& transformSet
=
127 LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM
);
128 for (uint32_t i
= 0; i
< mAnimations
.Length(); i
++) {
129 if (transformSet
.HasProperty(mAnimations
[i
].property())) {
137 Maybe
<uint64_t> AnimationInfo::GetGenerationFromFrame(
138 nsIFrame
* aFrame
, DisplayItemType aDisplayItemKey
) {
139 MOZ_ASSERT(aFrame
->IsPrimaryFrame() ||
140 nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
));
142 // In case of continuation, KeyframeEffectReadOnly uses its first frame,
143 // whereas nsDisplayItem uses its last continuation, so we have to use the
144 // last continuation frame here.
145 if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
)) {
146 aFrame
= nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame
);
148 RefPtr
<WebRenderAnimationData
> animationData
=
149 GetWebRenderUserData
<WebRenderAnimationData
>(aFrame
,
150 (uint32_t)aDisplayItemKey
);
152 return animationData
->GetAnimationInfo().GetAnimationGeneration();
159 void AnimationInfo::EnumerateGenerationOnFrame(
160 const nsIFrame
* aFrame
, const nsIContent
* aContent
,
161 const CompositorAnimatableDisplayItemTypes
& aDisplayItemTypes
,
162 AnimationGenerationCallback aCallback
) {
163 nsIWidget
* widget
= nsContentUtils::WidgetForContent(aContent
);
167 // If we haven't created a window renderer there's no animation generation
168 // that we can have, thus we call the callback function with |Nothing()| for
170 if (!widget
->HasWindowRenderer()) {
171 for (auto displayItem
: LayerAnimationInfo::sDisplayItemTypes
) {
172 aCallback(Nothing(), displayItem
);
176 WindowRenderer
* renderer
= widget
->GetWindowRenderer();
177 MOZ_ASSERT(renderer
);
178 if (!renderer
->AsWebRender()) {
182 // In case of continuation, nsDisplayItem uses its last continuation, so we
183 // have to use the last continuation frame here.
184 if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame
)) {
185 aFrame
= nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame
);
188 for (auto displayItem
: LayerAnimationInfo::sDisplayItemTypes
) {
189 // For transform animations, the animation is on the primary frame but
190 // |aFrame| is the style frame.
191 const nsIFrame
* frameToQuery
=
192 displayItem
== DisplayItemType::TYPE_TRANSFORM
193 ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame
)
195 RefPtr
<WebRenderAnimationData
> animationData
=
196 GetWebRenderUserData
<WebRenderAnimationData
>(frameToQuery
,
197 (uint32_t)displayItem
);
198 Maybe
<uint64_t> generation
;
200 generation
= animationData
->GetAnimationInfo().GetAnimationGeneration();
202 aCallback(generation
, displayItem
);
206 static StyleTransformOperation
ResolveTranslate(
207 TransformReferenceBox
& aRefBox
, const LengthPercentage
& aX
,
208 const LengthPercentage
& aY
= LengthPercentage::Zero(),
209 const Length
& aZ
= Length
{0}) {
210 float x
= nsStyleTransformMatrix::ProcessTranslatePart(
211 aX
, &aRefBox
, &TransformReferenceBox::Width
);
212 float y
= nsStyleTransformMatrix::ProcessTranslatePart(
213 aY
, &aRefBox
, &TransformReferenceBox::Height
);
214 return StyleTransformOperation::Translate3D(
215 LengthPercentage::FromPixels(x
), LengthPercentage::FromPixels(y
), aZ
);
218 static StyleTranslate
ResolveTranslate(const StyleTranslate
& aValue
,
219 TransformReferenceBox
& aRefBox
) {
220 if (aValue
.IsTranslate()) {
221 const auto& t
= aValue
.AsTranslate();
222 float x
= nsStyleTransformMatrix::ProcessTranslatePart(
223 t
._0
, &aRefBox
, &TransformReferenceBox::Width
);
224 float y
= nsStyleTransformMatrix::ProcessTranslatePart(
225 t
._1
, &aRefBox
, &TransformReferenceBox::Height
);
226 return StyleTranslate::Translate(LengthPercentage::FromPixels(x
),
227 LengthPercentage::FromPixels(y
), t
._2
);
230 MOZ_ASSERT(aValue
.IsNone());
231 return StyleTranslate::None();
234 static StyleTransform
ResolveTransformOperations(
235 const StyleTransform
& aTransform
, TransformReferenceBox
& aRefBox
) {
236 auto convertMatrix
= [](const gfx::Matrix4x4
& aM
) {
237 return StyleTransformOperation::Matrix3D(StyleGenericMatrix3D
<StyleNumber
>{
238 aM
._11
, aM
._12
, aM
._13
, aM
._14
, aM
._21
, aM
._22
, aM
._23
, aM
._24
, aM
._31
,
239 aM
._32
, aM
._33
, aM
._34
, aM
._41
, aM
._42
, aM
._43
, aM
._44
});
242 Vector
<StyleTransformOperation
> result
;
244 result
.initCapacity(aTransform
.Operations().Length()),
245 "Allocating vector of transform operations should be successful.");
247 for (const StyleTransformOperation
& op
: aTransform
.Operations()) {
249 case StyleTransformOperation::Tag::TranslateX
:
250 result
.infallibleAppend(ResolveTranslate(aRefBox
, op
.AsTranslateX()));
252 case StyleTransformOperation::Tag::TranslateY
:
253 result
.infallibleAppend(ResolveTranslate(
254 aRefBox
, LengthPercentage::Zero(), op
.AsTranslateY()));
256 case StyleTransformOperation::Tag::TranslateZ
:
257 result
.infallibleAppend(
258 ResolveTranslate(aRefBox
, LengthPercentage::Zero(),
259 LengthPercentage::Zero(), op
.AsTranslateZ()));
261 case StyleTransformOperation::Tag::Translate
: {
262 const auto& translate
= op
.AsTranslate();
263 result
.infallibleAppend(
264 ResolveTranslate(aRefBox
, translate
._0
, translate
._1
));
267 case StyleTransformOperation::Tag::Translate3D
: {
268 const auto& translate
= op
.AsTranslate3D();
269 result
.infallibleAppend(ResolveTranslate(aRefBox
, translate
._0
,
270 translate
._1
, translate
._2
));
273 case StyleTransformOperation::Tag::InterpolateMatrix
: {
274 gfx::Matrix4x4 matrix
;
275 nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix
, op
, aRefBox
);
276 result
.infallibleAppend(convertMatrix(matrix
));
279 case StyleTransformOperation::Tag::AccumulateMatrix
: {
280 gfx::Matrix4x4 matrix
;
281 nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix
, op
, aRefBox
);
282 result
.infallibleAppend(convertMatrix(matrix
));
285 case StyleTransformOperation::Tag::RotateX
:
286 case StyleTransformOperation::Tag::RotateY
:
287 case StyleTransformOperation::Tag::RotateZ
:
288 case StyleTransformOperation::Tag::Rotate
:
289 case StyleTransformOperation::Tag::Rotate3D
:
290 case StyleTransformOperation::Tag::ScaleX
:
291 case StyleTransformOperation::Tag::ScaleY
:
292 case StyleTransformOperation::Tag::ScaleZ
:
293 case StyleTransformOperation::Tag::Scale
:
294 case StyleTransformOperation::Tag::Scale3D
:
295 case StyleTransformOperation::Tag::SkewX
:
296 case StyleTransformOperation::Tag::SkewY
:
297 case StyleTransformOperation::Tag::Skew
:
298 case StyleTransformOperation::Tag::Matrix
:
299 case StyleTransformOperation::Tag::Matrix3D
:
300 case StyleTransformOperation::Tag::Perspective
:
301 result
.infallibleAppend(op
);
304 MOZ_ASSERT_UNREACHABLE("Function not handled yet!");
308 auto transform
= StyleTransform
{
309 StyleOwnedSlice
<StyleTransformOperation
>(std::move(result
))};
310 MOZ_ASSERT(!transform
.HasPercent());
311 MOZ_ASSERT(transform
.Operations().Length() ==
312 aTransform
.Operations().Length());
316 static Maybe
<ScrollTimelineOptions
> GetScrollTimelineOptions(
317 dom::AnimationTimeline
* aTimeline
) {
318 if (!aTimeline
|| !aTimeline
->IsScrollTimeline()) {
322 const dom::ScrollTimeline
* timeline
= aTimeline
->AsScrollTimeline();
323 MOZ_ASSERT(timeline
->IsActive(),
324 "We send scroll animation to the compositor only if its timeline "
327 ScrollableLayerGuid::ViewID source
= ScrollableLayerGuid::NULL_SCROLL_ID
;
328 DebugOnly
<bool> success
=
329 nsLayoutUtils::FindIDFor(timeline
->SourceElement(), &source
);
330 MOZ_ASSERT(success
, "We should have a valid ViewID for the scroller");
332 return Some(ScrollTimelineOptions(source
, timeline
->Axis()));
335 // FIXME: Bug 1489392: We don't have to normalize the path here if we accept
336 // the spec issue which would like to normalize svg paths at computed time.
337 static StyleOffsetPath
NormalizeOffsetPath(const StyleOffsetPath
& aOffsetPath
) {
338 if (aOffsetPath
.IsPath()) {
339 return StyleOffsetPath::Path(
340 MotionPathUtils::NormalizeSVGPathData(aOffsetPath
.AsPath()));
342 return StyleOffsetPath(aOffsetPath
);
345 static void SetAnimatable(nsCSSPropertyID aProperty
,
346 const AnimationValue
& aAnimationValue
,
347 nsIFrame
* aFrame
, TransformReferenceBox
& aRefBox
,
348 layers::Animatable
& aAnimatable
) {
351 if (aAnimationValue
.IsNull()) {
352 aAnimatable
= null_t();
357 case eCSSProperty_background_color
: {
358 // We don't support color animation on the compositor yet so that we can
359 // resolve currentColor at this moment.
361 aFrame
->Style()->GetVisitedDependentColor(&nsStyleText::mColor
);
362 aAnimatable
= aAnimationValue
.GetColor(foreground
);
365 case eCSSProperty_opacity
:
366 aAnimatable
= aAnimationValue
.GetOpacity();
368 case eCSSProperty_rotate
:
369 aAnimatable
= aAnimationValue
.GetRotateProperty();
371 case eCSSProperty_scale
:
372 aAnimatable
= aAnimationValue
.GetScaleProperty();
374 case eCSSProperty_translate
:
376 ResolveTranslate(aAnimationValue
.GetTranslateProperty(), aRefBox
);
378 case eCSSProperty_transform
:
379 aAnimatable
= ResolveTransformOperations(
380 aAnimationValue
.GetTransformProperty(), aRefBox
);
382 case eCSSProperty_offset_path
:
384 NormalizeOffsetPath(aAnimationValue
.GetOffsetPathProperty());
386 case eCSSProperty_offset_distance
:
387 aAnimatable
= aAnimationValue
.GetOffsetDistanceProperty();
389 case eCSSProperty_offset_rotate
:
390 aAnimatable
= aAnimationValue
.GetOffsetRotateProperty();
392 case eCSSProperty_offset_anchor
:
393 aAnimatable
= aAnimationValue
.GetOffsetAnchorProperty();
396 MOZ_ASSERT_UNREACHABLE("Unsupported property");
400 void AnimationInfo::AddAnimationForProperty(
401 nsIFrame
* aFrame
, const AnimationProperty
& aProperty
,
402 dom::Animation
* aAnimation
, const Maybe
<TransformData
>& aTransformData
,
404 MOZ_ASSERT(aAnimation
->GetEffect(),
405 "Should not be adding an animation without an effect");
406 MOZ_ASSERT(!aAnimation
->GetCurrentOrPendingStartTime().IsNull() ||
407 !aAnimation
->IsPlaying() ||
408 (aAnimation
->GetTimeline() &&
409 aAnimation
->GetTimeline()->TracksWallclockTime()),
410 "If the animation has an unresolved start time it should either"
411 " be static (so we don't need a start time) or else have a"
412 " timeline capable of converting TimeStamps (so we can calculate"
415 layers::Animation
* animation
= (aSendFlag
== Send::NextTransaction
)
416 ? AddAnimationForNextTransaction()
419 const TimingParams
& timing
= aAnimation
->GetEffect()->NormalizedTiming();
421 // If we are starting a new transition that replaces an existing transition
422 // running on the compositor, it is possible that the animation on the
423 // compositor will have advanced ahead of the main thread. If we use as
424 // the starting point of the new transition, the current value of the
425 // replaced transition as calculated on the main thread using the refresh
426 // driver time, the new transition will jump when it starts. Instead, we
427 // re-calculate the starting point of the new transition by applying the
428 // current TimeStamp to the parameters of the replaced transition.
430 // We need to do this here, rather than when we generate the new transition,
431 // since after generating the new transition other requestAnimationFrame
432 // callbacks may run that introduce further lag between the main thread and
434 dom::CSSTransition
* cssTransition
= aAnimation
->AsCSSTransition();
436 cssTransition
->UpdateStartValueFromReplacedTransition();
439 animation
->originTime() =
440 !aAnimation
->GetTimeline()
442 : aAnimation
->GetTimeline()->ToTimeStamp(TimeDuration());
444 dom::Nullable
<TimeDuration
> startTime
=
445 aAnimation
->GetCurrentOrPendingStartTime();
446 if (startTime
.IsNull()) {
447 animation
->startTime() = Nothing();
449 animation
->startTime() = Some(startTime
.Value());
452 animation
->holdTime() = aAnimation
->GetCurrentTimeAsDuration().Value();
454 const ComputedTiming computedTiming
=
455 aAnimation
->GetEffect()->GetComputedTiming();
456 animation
->delay() = timing
.Delay();
457 animation
->endDelay() = timing
.EndDelay();
458 animation
->duration() = computedTiming
.mDuration
;
459 animation
->iterations() = static_cast<float>(computedTiming
.mIterations
);
460 animation
->iterationStart() =
461 static_cast<float>(computedTiming
.mIterationStart
);
462 animation
->direction() = static_cast<uint8_t>(timing
.Direction());
463 animation
->fillMode() = static_cast<uint8_t>(computedTiming
.mFill
);
464 animation
->property() = aProperty
.mProperty
;
465 animation
->playbackRate() =
466 static_cast<float>(aAnimation
->CurrentOrPendingPlaybackRate());
467 animation
->previousPlaybackRate() =
468 aAnimation
->HasPendingPlaybackRate()
469 ? static_cast<float>(aAnimation
->PlaybackRate())
470 : std::numeric_limits
<float>::quiet_NaN();
471 animation
->transformData() = aTransformData
;
472 animation
->easingFunction() = timing
.TimingFunction();
473 animation
->iterationComposite() = static_cast<uint8_t>(
474 aAnimation
->GetEffect()->AsKeyframeEffect()->IterationComposite());
475 animation
->isNotPlaying() = !aAnimation
->IsPlaying();
476 animation
->isNotAnimating() = false;
477 animation
->scrollTimelineOptions() =
478 GetScrollTimelineOptions(aAnimation
->GetTimeline());
480 TransformReferenceBox
refBox(aFrame
);
482 // If the animation is additive or accumulates, we need to pass its base value
483 // to the compositor.
485 AnimationValue baseStyle
=
486 aAnimation
->GetEffect()->AsKeyframeEffect()->BaseStyle(
487 aProperty
.mProperty
);
488 if (!baseStyle
.IsNull()) {
489 SetAnimatable(aProperty
.mProperty
, baseStyle
, aFrame
, refBox
,
490 animation
->baseStyle());
492 animation
->baseStyle() = null_t();
495 for (uint32_t segIdx
= 0; segIdx
< aProperty
.mSegments
.Length(); segIdx
++) {
496 const AnimationPropertySegment
& segment
= aProperty
.mSegments
[segIdx
];
498 AnimationSegment
* animSegment
= animation
->segments().AppendElement();
499 SetAnimatable(aProperty
.mProperty
, segment
.mFromValue
, aFrame
, refBox
,
500 animSegment
->startState());
501 SetAnimatable(aProperty
.mProperty
, segment
.mToValue
, aFrame
, refBox
,
502 animSegment
->endState());
504 animSegment
->startPortion() = segment
.mFromKey
;
505 animSegment
->endPortion() = segment
.mToKey
;
506 animSegment
->startComposite() =
507 static_cast<uint8_t>(segment
.mFromComposite
);
508 animSegment
->endComposite() = static_cast<uint8_t>(segment
.mToComposite
);
509 animSegment
->sampleFn() = segment
.mTimingFunction
;
513 // Let's use an example to explain this function:
515 // We have 4 playing animations (without any !important rule or transition):
516 // Animation A: [ transform, rotate ].
517 // Animation B: [ rotate, scale ].
518 // Animation C: [ transform, margin-left ].
519 // Animation D: [ opacity, margin-left ].
521 // Normally, GetAnimationsForCompositor(|transform-like properties|) returns:
522 // [ Animation A, Animation B, Animation C ], which is the first argument of
525 // In this function, we want to re-organize the list as (Note: don't care
526 // the order of properties):
528 // { rotate: [ Animation A, Animation B ] },
529 // { scale: [ Animation B ] },
530 // { transform: [ Animation A, Animation C ] },
533 // Therefore, AddAnimationsForProperty() will append each animation property
534 // into AnimationInfo, as a final list of layers::Animation:
536 // { rotate: Animation A },
537 // { rotate: Animation B },
538 // { scale: Animation B },
539 // { transform: Animation A },
540 // { transform: Animation C },
543 // And then, for each transaction, we send this list to the compositor thread.
544 static HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>>
545 GroupAnimationsByProperty(const nsTArray
<RefPtr
<dom::Animation
>>& aAnimations
,
546 const nsCSSPropertyIDSet
& aPropertySet
) {
547 HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>> groupedAnims
;
548 for (const RefPtr
<dom::Animation
>& anim
: aAnimations
) {
549 const dom::KeyframeEffect
* effect
= anim
->GetEffect()->AsKeyframeEffect();
551 for (const AnimationProperty
& property
: effect
->Properties()) {
552 if (!aPropertySet
.HasProperty(property
.mProperty
)) {
556 auto animsForPropertyPtr
= groupedAnims
.lookupForAdd(property
.mProperty
);
557 if (!animsForPropertyPtr
) {
559 groupedAnims
.add(animsForPropertyPtr
, property
.mProperty
,
560 nsTArray
<RefPtr
<dom::Animation
>>());
561 MOZ_ASSERT(rv
, "Should have enough memory");
563 animsForPropertyPtr
->value().AppendElement(anim
);
569 bool AnimationInfo::AddAnimationsForProperty(
570 nsIFrame
* aFrame
, const EffectSet
* aEffects
,
571 const nsTArray
<RefPtr
<dom::Animation
>>& aCompositorAnimations
,
572 const Maybe
<TransformData
>& aTransformData
, nsCSSPropertyID aProperty
,
573 Send aSendFlag
, WebRenderLayerManager
* aLayerManager
) {
574 bool addedAny
= false;
575 // Add from first to last (since last overrides)
576 for (dom::Animation
* anim
: aCompositorAnimations
) {
577 if (!anim
->IsRelevant()) {
581 dom::KeyframeEffect
* keyframeEffect
=
582 anim
->GetEffect() ? anim
->GetEffect()->AsKeyframeEffect() : nullptr;
583 MOZ_ASSERT(keyframeEffect
,
584 "A playing animation should have a keyframe effect");
585 const AnimationProperty
* property
=
586 keyframeEffect
->GetEffectiveAnimationOfProperty(aProperty
, *aEffects
);
591 // Note that if the property is overridden by !important rules,
592 // GetEffectiveAnimationOfProperty returns null instead.
593 // This is what we want, since if we have animations overridden by
594 // !important rules, we don't want to send them to the compositor.
596 anim
->CascadeLevel() != EffectCompositor::CascadeLevel::Animations
||
597 !aEffects
->PropertiesWithImportantRules().HasProperty(aProperty
),
598 "GetEffectiveAnimationOfProperty already tested the property "
599 "is not overridden by !important rules");
601 // Don't add animations that are pending if their timeline does not
602 // track wallclock time. This is because any pending animations on layers
603 // will have their start time updated with the current wallclock time.
604 // If we can't convert that wallclock time back to an equivalent timeline
605 // time, we won't be able to update the content animation and it will end
606 // up being out of sync with the layer animation.
608 // Currently this only happens when the timeline is driven by a refresh
609 // driver under test control. In this case, the next time the refresh
610 // driver is advanced it will trigger any pending animations.
611 if (anim
->Pending() &&
612 (anim
->GetTimeline() && !anim
->GetTimeline()->TracksWallclockTime())) {
616 AddAnimationForProperty(aFrame
, *property
, anim
, aTransformData
, aSendFlag
);
617 keyframeEffect
->SetIsRunningOnCompositor(aProperty
, true);
619 if (aTransformData
&& aTransformData
->partialPrerenderData() &&
621 aLayerManager
->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(),
628 // Returns which pre-rendered area's sides are overflowed from the pre-rendered
631 // We don't need to make jank animations when we are going to composite the
632 // area where there is no overflowed area even if it's outside of the
633 // pre-rendered area.
634 static SideBits
GetOverflowedSides(const nsRect
& aOverflow
,
635 const nsRect
& aPartialPrerenderArea
) {
636 SideBits sides
= SideBits::eNone
;
637 if (aOverflow
.X() < aPartialPrerenderArea
.X()) {
638 sides
|= SideBits::eLeft
;
640 if (aOverflow
.Y() < aPartialPrerenderArea
.Y()) {
641 sides
|= SideBits::eTop
;
643 if (aOverflow
.XMost() > aPartialPrerenderArea
.XMost()) {
644 sides
|= SideBits::eRight
;
646 if (aOverflow
.YMost() > aPartialPrerenderArea
.YMost()) {
647 sides
|= SideBits::eBottom
;
652 static std::pair
<ParentLayerRect
, gfx::Matrix4x4
>
653 GetClipRectAndTransformForPartialPrerender(
654 const nsIFrame
* aFrame
, int32_t aDevPixelsToAppUnits
,
655 const nsIFrame
* aClipFrame
, const nsIScrollableFrame
* aScrollFrame
) {
656 MOZ_ASSERT(aClipFrame
);
658 gfx::Matrix4x4 transformInClip
=
659 nsLayoutUtils::GetTransformToAncestor(RelativeTo
{aFrame
->GetParent()},
660 RelativeTo
{aClipFrame
})
663 transformInClip
.PostTranslate(
664 LayoutDevicePoint::FromAppUnits(aScrollFrame
->GetScrollPosition(),
665 aDevPixelsToAppUnits
)
669 // We don't necessarily use nsLayoutUtils::CalculateCompositionSizeForFrame
670 // since this is a case where we don't use APZ at all.
671 return std::make_pair(
672 LayoutDeviceRect::FromAppUnits(aScrollFrame
673 ? aScrollFrame
->GetScrollPortRect()
674 : aClipFrame
->GetRectRelativeToSelf(),
675 aDevPixelsToAppUnits
) *
676 LayoutDeviceToLayerScale2D() * LayerToParentLayerScale(),
680 static PartialPrerenderData
GetPartialPrerenderData(
681 const nsIFrame
* aFrame
, const nsDisplayItem
* aItem
) {
682 const nsRect
& partialPrerenderedRect
= aItem
->GetUntransformedPaintRect();
683 nsRect overflow
= aFrame
->InkOverflowRectRelativeToSelf();
685 ScrollableLayerGuid::ViewID scrollId
= ScrollableLayerGuid::NULL_SCROLL_ID
;
687 const nsIFrame
* clipFrame
=
688 nsLayoutUtils::GetNearestOverflowClipFrame(aFrame
->GetParent());
689 const nsIScrollableFrame
* scrollFrame
= do_QueryFrame(clipFrame
);
692 // If there is no suitable clip frame in the same document, use the
694 scrollFrame
= aFrame
->PresShell()->GetRootScrollFrameAsScrollable();
696 clipFrame
= do_QueryFrame(scrollFrame
);
698 // If there is no root scroll frame, use the viewport frame.
699 clipFrame
= aFrame
->PresShell()->GetRootFrame();
703 // If the scroll frame is asyncronously scrollable, try to find the scroll id.
705 !scrollFrame
->GetScrollStyles().IsHiddenInBothDirections() &&
706 nsLayoutUtils::AsyncPanZoomEnabled(aFrame
)) {
707 const bool isInPositionFixed
=
708 nsLayoutUtils::IsInPositionFixedSubtree(aFrame
);
709 const ActiveScrolledRoot
* asr
= aItem
->GetActiveScrolledRoot();
710 const nsIFrame
* asrScrollableFrame
=
711 asr
? do_QueryFrame(asr
->mScrollableFrame
) : nullptr;
712 if (!isInPositionFixed
&& asr
&&
713 aFrame
->PresContext() == asrScrollableFrame
->PresContext()) {
714 scrollId
= asr
->GetViewId();
715 MOZ_ASSERT(clipFrame
== asrScrollableFrame
);
717 // Use the root scroll id in the same document if the target frame is in
718 // position:fixed subtree or there is no ASR or the ASR is in a different
719 // ancestor document.
721 nsLayoutUtils::ScrollIdForRootScrollFrame(aFrame
->PresContext());
722 MOZ_ASSERT(clipFrame
== aFrame
->PresShell()->GetRootScrollFrame());
726 int32_t devPixelsToAppUnits
= aFrame
->PresContext()->AppUnitsPerDevPixel();
728 auto [clipRect
, transformInClip
] = GetClipRectAndTransformForPartialPrerender(
729 aFrame
, devPixelsToAppUnits
, clipFrame
, scrollFrame
);
731 return PartialPrerenderData
{
732 LayoutDeviceRect::FromAppUnits(partialPrerenderedRect
,
733 devPixelsToAppUnits
),
734 GetOverflowedSides(overflow
, partialPrerenderedRect
),
738 LayoutDevicePoint()}; // will be set by caller.
741 enum class AnimationDataType
{
745 static Maybe
<TransformData
> CreateAnimationData(
746 nsIFrame
* aFrame
, nsDisplayItem
* aItem
, DisplayItemType aType
,
747 layers::LayersBackend aLayersBackend
, AnimationDataType aDataType
,
748 const Maybe
<LayoutDevicePoint
>& aPosition
) {
749 if (aType
!= DisplayItemType::TYPE_TRANSFORM
) {
753 // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
754 // the dimensions of refBox. That said, we only get here if there are CSS
755 // animations or transitions on this element, and that is likely to be a
756 // lot rarer than transforms on SVG (the frequency of which drives the need
757 // for TransformReferenceBox).
758 TransformReferenceBox
refBox(aFrame
);
759 const nsRect
bounds(0, 0, refBox
.Width(), refBox
.Height());
761 // all data passed directly to the compositor should be in dev pixels
762 int32_t devPixelsToAppUnits
= aFrame
->PresContext()->AppUnitsPerDevPixel();
763 float scale
= devPixelsToAppUnits
;
764 gfx::Point3D offsetToTransformOrigin
=
765 nsDisplayTransform::GetDeltaToTransformOrigin(aFrame
, refBox
, scale
);
767 if (aLayersBackend
== layers::LayersBackend::LAYERS_WR
) {
768 // leave origin empty, because we are sending it separately on the
769 // stacking context that we are pushing to WR, and WR will automatically
770 // include it when picking up the animated transform values
772 // This branch is for display items to leverage the cache of
773 // nsDisplayListBuilder.
774 origin
= aItem
->ToReferenceFrame();
776 // This branch is running for restyling.
777 // Animations are animated at the coordination of the reference
778 // frame outside, not the given frame itself. The given frame
779 // is also reference frame too, so the parent's reference frame
781 nsIFrame
* referenceFrame
= nsLayoutUtils::GetReferenceFrame(
782 nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame
));
783 origin
= aFrame
->GetOffsetToCrossDoc(referenceFrame
);
786 Maybe
<MotionPathData
> motionPathData
;
787 if (aDataType
== AnimationDataType::WithMotionPath
) {
788 const StyleTransformOrigin
& styleOrigin
=
789 aFrame
->StyleDisplay()->mTransformOrigin
;
790 CSSPoint motionPathOrigin
= nsStyleTransformMatrix::Convert2DPosition(
791 styleOrigin
.horizontal
, styleOrigin
.vertical
, refBox
);
792 CSSPoint anchorAdjustment
=
793 MotionPathUtils::ComputeAnchorPointAdjustment(*aFrame
);
795 motionPathData
= Some(layers::MotionPathData(
796 motionPathOrigin
, anchorAdjustment
, RayReferenceData(aFrame
)));
799 Maybe
<PartialPrerenderData
> partialPrerenderData
;
800 if (aItem
&& static_cast<nsDisplayTransform
*>(aItem
)->IsPartialPrerender()) {
801 partialPrerenderData
= Some(GetPartialPrerenderData(aFrame
, aItem
));
803 if (aLayersBackend
== layers::LayersBackend::LAYERS_WR
) {
804 MOZ_ASSERT(aPosition
);
805 partialPrerenderData
->position() = *aPosition
;
809 return Some(TransformData(origin
, offsetToTransformOrigin
, bounds
,
810 devPixelsToAppUnits
, motionPathData
,
811 partialPrerenderData
));
814 void AnimationInfo::AddNonAnimatingTransformLikePropertiesStyles(
815 const nsCSSPropertyIDSet
& aNonAnimatingProperties
, nsIFrame
* aFrame
,
817 auto appendFakeAnimation
= [this, aSendFlag
](nsCSSPropertyID aProperty
,
818 Animatable
&& aBaseStyle
) {
819 layers::Animation
* animation
= (aSendFlag
== Send::NextTransaction
)
820 ? AddAnimationForNextTransaction()
822 animation
->property() = aProperty
;
823 animation
->baseStyle() = std::move(aBaseStyle
);
824 animation
->easingFunction() = Nothing();
825 animation
->isNotAnimating() = true;
828 const nsStyleDisplay
* display
= aFrame
->StyleDisplay();
829 // A simple optimization. We don't need to send offset-* properties if we
830 // don't have offset-path and offset-position.
831 // FIXME: Bug 1559232: Add offset-position here.
833 !display
->mOffsetPath
.IsNone() ||
834 !aNonAnimatingProperties
.HasProperty(eCSSProperty_offset_path
);
836 for (nsCSSPropertyID id
: aNonAnimatingProperties
) {
838 case eCSSProperty_transform
:
839 if (!display
->mTransform
.IsNone()) {
840 TransformReferenceBox
refBox(aFrame
);
842 id
, ResolveTransformOperations(display
->mTransform
, refBox
));
845 case eCSSProperty_translate
:
846 if (!display
->mTranslate
.IsNone()) {
847 TransformReferenceBox
refBox(aFrame
);
848 appendFakeAnimation(id
,
849 ResolveTranslate(display
->mTranslate
, refBox
));
852 case eCSSProperty_rotate
:
853 if (!display
->mRotate
.IsNone()) {
854 appendFakeAnimation(id
, display
->mRotate
);
857 case eCSSProperty_scale
:
858 if (!display
->mScale
.IsNone()) {
859 appendFakeAnimation(id
, display
->mScale
);
862 case eCSSProperty_offset_path
:
863 if (!display
->mOffsetPath
.IsNone()) {
864 appendFakeAnimation(id
, NormalizeOffsetPath(display
->mOffsetPath
));
867 case eCSSProperty_offset_distance
:
868 if (hasMotion
&& !display
->mOffsetDistance
.IsDefinitelyZero()) {
869 appendFakeAnimation(id
, display
->mOffsetDistance
);
872 case eCSSProperty_offset_rotate
:
873 if (hasMotion
&& (!display
->mOffsetRotate
.auto_
||
874 display
->mOffsetRotate
.angle
.ToDegrees() != 0.0)) {
875 appendFakeAnimation(id
, display
->mOffsetRotate
);
878 case eCSSProperty_offset_anchor
:
879 if (hasMotion
&& !display
->mOffsetAnchor
.IsAuto()) {
880 appendFakeAnimation(id
, display
->mOffsetAnchor
);
884 MOZ_ASSERT_UNREACHABLE("Unsupported transform-like properties");
889 void AnimationInfo::AddAnimationsForDisplayItem(
890 nsIFrame
* aFrame
, nsDisplayListBuilder
* aBuilder
, nsDisplayItem
* aItem
,
891 DisplayItemType aType
, WebRenderLayerManager
* aLayerManager
,
892 const Maybe
<LayoutDevicePoint
>& aPosition
) {
893 Send sendFlag
= !aBuilder
? Send::NextTransaction
: Send::Immediate
;
894 if (sendFlag
== Send::NextTransaction
) {
895 ClearAnimationsForNextTransaction();
900 // Update the animation generation on the layer. We need to do this before
901 // any early returns since even if we don't add any animations to the
902 // layer, we still need to mark it as up-to-date with regards to animations.
903 // Otherwise, in RestyleManager we'll notice the discrepancy between the
904 // animation generation numbers and update the layer indefinitely.
905 EffectSet
* effects
= EffectSet::GetForFrame(aFrame
, aType
);
906 uint64_t animationGeneration
=
907 effects
? effects
->GetAnimationGeneration() : 0;
908 SetAnimationGeneration(animationGeneration
);
909 if (!effects
|| effects
->IsEmpty()) {
913 EffectCompositor::ClearIsRunningOnCompositor(aFrame
, aType
);
914 const nsCSSPropertyIDSet
& propertySet
=
915 LayerAnimationInfo::GetCSSPropertiesFor(aType
);
916 const nsTArray
<RefPtr
<dom::Animation
>> matchedAnimations
=
917 EffectCompositor::GetAnimationsForCompositor(aFrame
, propertySet
);
918 if (matchedAnimations
.IsEmpty()) {
922 // If the frame is not prerendered, bail out.
923 // Do this check only during layer construction; during updating the
924 // caller is required to check it appropriately.
925 if (aItem
&& !aItem
->CanUseAsyncAnimations(aBuilder
)) {
926 // EffectCompositor needs to know that we refused to run this animation
927 // asynchronously so that it will not throttle the main thread
929 aFrame
->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
933 const HashMap
<nsCSSPropertyID
, nsTArray
<RefPtr
<dom::Animation
>>>
934 compositorAnimations
=
935 GroupAnimationsByProperty(matchedAnimations
, propertySet
);
936 Maybe
<TransformData
> transformData
=
937 CreateAnimationData(aFrame
, aItem
, aType
, aLayerManager
->GetBackendType(),
938 compositorAnimations
.has(eCSSProperty_offset_path
) ||
939 !aFrame
->StyleDisplay()->mOffsetPath
.IsNone()
940 ? AnimationDataType::WithMotionPath
941 : AnimationDataType::WithoutMotionPath
,
943 // Bug 1424900: Drop this pref check after shipping individual transforms.
944 // Bug 1582554: Drop this pref check after shipping motion path.
945 const bool hasMultipleTransformLikeProperties
=
946 (StaticPrefs::layout_css_individual_transform_enabled() ||
947 StaticPrefs::layout_css_motion_path_enabled()) &&
948 aType
== DisplayItemType::TYPE_TRANSFORM
;
949 nsCSSPropertyIDSet nonAnimatingProperties
=
950 nsCSSPropertyIDSet::TransformLikeProperties();
951 for (auto iter
= compositorAnimations
.iter(); !iter
.done(); iter
.next()) {
952 // Note: We can skip offset-* if there is no offset-path/offset-position
953 // animations and styles. However, it should be fine and may be better to
954 // send these information to the compositor because 1) they are simple data
955 // structure, 2) AddAnimationsForProperty() marks these animations as
956 // running on the composiror, so CanThrottle() returns true for them, and
957 // we avoid running these animations on the main thread.
958 bool added
= AddAnimationsForProperty(aFrame
, effects
, iter
.get().value(),
959 transformData
, iter
.get().key(),
960 sendFlag
, aLayerManager
);
961 if (added
&& transformData
) {
962 // Only copy TransformLikeMetaData in the first animation property.
963 transformData
.reset();
966 if (hasMultipleTransformLikeProperties
&& added
) {
967 nonAnimatingProperties
.RemoveProperty(iter
.get().key());
971 // If some transform-like properties have animations, but others not, and
972 // those non-animating transform-like properties have non-none
973 // transform/translate/rotate/scale styles or non-initial value for motion
974 // path properties, we also pass their styles into the compositor, so the
975 // final transform matrix (on the compositor) could take them into account.
976 if (hasMultipleTransformLikeProperties
&&
977 // For these cases we don't need to send the property style values to
979 // 1. No property has running animations on the compositor. (i.e. All
980 // properties should be handled by main thread)
981 // 2. All properties have running animations on the compositor.
982 // (i.e. Those running animations should override the styles.)
983 !nonAnimatingProperties
.Equals(
984 nsCSSPropertyIDSet::TransformLikeProperties()) &&
985 !nonAnimatingProperties
.IsEmpty()) {
986 AddNonAnimatingTransformLikePropertiesStyles(nonAnimatingProperties
, aFrame
,
991 } // namespace layers
992 } // namespace mozilla