Bug 1764201 Part 3: Remove screen info stuff from gfxPlatform. r=jgilbert,geckoview...
[gecko.git] / gfx / layers / AnimationInfo.cpp
blob81d11f672c233a119ceb256c5ac84f991a87b7ee
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.
42 EnsureAnimationsId();
44 MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
46 Animation* anim = mAnimations.AppendElement();
48 mMutated = true;
50 return anim;
53 Animation* AnimationInfo::AddAnimationForNextTransaction() {
54 MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
55 MOZ_ASSERT(mPendingAnimations,
56 "should have called ClearAnimationsForNextTransaction first");
58 Animation* anim = mPendingAnimations->AppendElement();
60 return anim;
63 void AnimationInfo::ClearAnimations() {
64 mPendingAnimations = nullptr;
66 if (mAnimations.IsEmpty() && mStorageData.IsEmpty()) {
67 return;
70 mAnimations.Clear();
71 mStorageData.Clear();
73 mMutated = true;
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;
113 return true;
116 return false;
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())) {
124 return true;
127 return false;
130 /* static */
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);
145 if (animationData) {
146 return animationData->GetAnimationInfo().GetAnimationGeneration();
149 return Nothing();
152 /* static */
153 void AnimationInfo::EnumerateGenerationOnFrame(
154 const nsIFrame* aFrame, const nsIContent* aContent,
155 const CompositorAnimatableDisplayItemTypes& aDisplayItemTypes,
156 AnimationGenerationCallback aCallback) {
157 nsIWidget* widget = nsContentUtils::WidgetForContent(aContent);
158 if (!widget) {
159 return;
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
163 // the generation.
164 if (!widget->HasWindowRenderer()) {
165 for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) {
166 aCallback(Nothing(), displayItem);
168 return;
170 WindowRenderer* renderer = widget->GetWindowRenderer();
171 MOZ_ASSERT(renderer);
172 if (!renderer->AsWebRender()) {
173 return;
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)
188 : aFrame;
189 RefPtr<WebRenderAnimationData> animationData =
190 GetWebRenderUserData<WebRenderAnimationData>(frameToQuery,
191 (uint32_t)displayItem);
192 Maybe<uint64_t> generation;
193 if (animationData) {
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;
237 MOZ_RELEASE_ASSERT(
238 result.initCapacity(aTransform.Operations().Length()),
239 "Allocating vector of transform operations should be successful.");
241 for (const StyleTransformOperation& op : aTransform.Operations()) {
242 switch (op.tag) {
243 case StyleTransformOperation::Tag::TranslateX:
244 result.infallibleAppend(ResolveTranslate(aRefBox, op.AsTranslateX()));
245 break;
246 case StyleTransformOperation::Tag::TranslateY:
247 result.infallibleAppend(ResolveTranslate(
248 aRefBox, LengthPercentage::Zero(), op.AsTranslateY()));
249 break;
250 case StyleTransformOperation::Tag::TranslateZ:
251 result.infallibleAppend(
252 ResolveTranslate(aRefBox, LengthPercentage::Zero(),
253 LengthPercentage::Zero(), op.AsTranslateZ()));
254 break;
255 case StyleTransformOperation::Tag::Translate: {
256 const auto& translate = op.AsTranslate();
257 result.infallibleAppend(
258 ResolveTranslate(aRefBox, translate._0, translate._1));
259 break;
261 case StyleTransformOperation::Tag::Translate3D: {
262 const auto& translate = op.AsTranslate3D();
263 result.infallibleAppend(ResolveTranslate(aRefBox, translate._0,
264 translate._1, translate._2));
265 break;
267 case StyleTransformOperation::Tag::InterpolateMatrix: {
268 gfx::Matrix4x4 matrix;
269 nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, op, aRefBox);
270 result.infallibleAppend(convertMatrix(matrix));
271 break;
273 case StyleTransformOperation::Tag::AccumulateMatrix: {
274 gfx::Matrix4x4 matrix;
275 nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix, op, aRefBox);
276 result.infallibleAppend(convertMatrix(matrix));
277 break;
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);
296 break;
297 default:
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());
307 return transform;
310 static Maybe<ScrollTimelineOptions> GetScrollTimelineOptions(
311 dom::AnimationTimeline* aTimeline) {
312 if (!aTimeline || !aTimeline->IsScrollTimeline()) {
313 return Nothing();
316 const dom::ScrollTimeline* timeline = aTimeline->AsScrollTimeline();
317 MOZ_ASSERT(timeline->IsActive(),
318 "We send scroll animation to the compositor only if its timeline "
319 "is active");
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) {
333 MOZ_ASSERT(aFrame);
335 if (aAnimationValue.IsNull()) {
336 aAnimatable = null_t();
337 return;
340 switch (aProperty) {
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.
344 nscolor foreground =
345 aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor);
346 aAnimatable = aAnimationValue.GetColor(foreground);
347 break;
349 case eCSSProperty_opacity:
350 aAnimatable = aAnimationValue.GetOpacity();
351 break;
352 case eCSSProperty_rotate:
353 aAnimatable = aAnimationValue.GetRotateProperty();
354 break;
355 case eCSSProperty_scale:
356 aAnimatable = aAnimationValue.GetScaleProperty();
357 break;
358 case eCSSProperty_translate:
359 aAnimatable =
360 ResolveTranslate(aAnimationValue.GetTranslateProperty(), aRefBox);
361 break;
362 case eCSSProperty_transform:
363 aAnimatable = ResolveTransformOperations(
364 aAnimationValue.GetTransformProperty(), aRefBox);
365 break;
366 case eCSSProperty_offset_path:
367 aAnimatable = StyleOffsetPath::None();
368 aAnimationValue.GetOffsetPathProperty(aAnimatable.get_StyleOffsetPath());
369 break;
370 case eCSSProperty_offset_distance:
371 aAnimatable = aAnimationValue.GetOffsetDistanceProperty();
372 break;
373 case eCSSProperty_offset_rotate:
374 aAnimatable = aAnimationValue.GetOffsetRotateProperty();
375 break;
376 case eCSSProperty_offset_anchor:
377 aAnimatable = aAnimationValue.GetOffsetAnchorProperty();
378 break;
379 case eCSSProperty_offset_position:
380 aAnimatable = aAnimationValue.GetOffsetPositionProperty();
381 break;
382 default:
383 MOZ_ASSERT_UNREACHABLE("Unsupported property");
387 void AnimationInfo::AddAnimationForProperty(
388 nsIFrame* aFrame, const AnimationProperty& aProperty,
389 dom::Animation* aAnimation, const Maybe<TransformData>& aTransformData,
390 Send aSendFlag) {
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"
399 " one later");
401 Animation* animation = (aSendFlag == Send::NextTransaction)
402 ? AddAnimationForNextTransaction()
403 : AddAnimation();
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
419 // the compositor.
420 if (dom::CSSTransition* cssTransition = aAnimation->AsCSSTransition()) {
421 cssTransition->UpdateStartValueFromReplacedTransition();
424 animation->originTime() =
425 !aAnimation->GetTimeline()
426 ? TimeStamp()
427 : aAnimation->GetTimeline()->ToTimeStamp(TimeDuration());
429 dom::Nullable<TimeDuration> startTime = aAnimation->GetStartTime();
430 if (startTime.IsNull()) {
431 animation->startTime() = Nothing();
432 } else {
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());
477 } else {
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
516 // this function.
518 // In this function, we want to re-organize the list as (Note: don't care
519 // the order of properties):
520 // [
521 // { rotate: [ Animation A, Animation B ] },
522 // { scale: [ Animation B ] },
523 // { transform: [ Animation A, Animation C ] },
524 // ]
526 // Therefore, AddAnimationsForProperty() will append each animation property
527 // into AnimationInfo, as a final list of layers::Animation:
528 // [
529 // { rotate: Animation A },
530 // { rotate: Animation B },
531 // { scale: Animation B },
532 // { transform: Animation A },
533 // { transform: Animation C },
534 // ]
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();
543 MOZ_ASSERT(effect);
544 for (const AnimationProperty& property : effect->Properties()) {
545 // TODO(zrhoffman, bug 1869475): Handle custom properties
546 if (!aPropertySet.HasProperty(property.mProperty)) {
547 continue;
550 auto animsForPropertyPtr =
551 groupedAnims.lookupForAdd(property.mProperty.mID);
552 if (!animsForPropertyPtr) {
553 DebugOnly<bool> rv =
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);
561 return groupedAnims;
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()) {
573 continue;
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);
582 if (!property) {
583 continue;
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.
590 MOZ_ASSERT(
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()) {
609 continue;
612 AddAnimationForProperty(aFrame, *property, anim, aTransformData, aSendFlag);
613 keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
614 addedAny = true;
615 if (aTransformData && aTransformData->partialPrerenderData() &&
616 aLayerManager) {
617 aLayerManager->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(),
618 anim);
621 return addedAny;
624 // Returns which pre-rendered area's sides are overflowed from the pre-rendered
625 // rect.
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;
645 return sides;
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})
657 .GetMatrix();
658 if (aScrollFrame) {
659 transformInClip.PostTranslate(
660 LayoutDevicePoint::FromAppUnits(aScrollFrame->GetScrollPosition(),
661 aDevPixelsToAppUnits)
662 .ToUnknownPoint());
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(),
673 transformInClip);
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);
687 if (!clipFrame) {
688 // If there is no suitable clip frame in the same document, use the
689 // root one.
690 scrollFrame = aFrame->PresShell()->GetRootScrollFrameAsScrollable();
691 if (scrollFrame) {
692 clipFrame = do_QueryFrame(scrollFrame);
693 } else {
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.
700 if (scrollFrame &&
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);
712 } else {
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.
716 scrollId =
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),
731 scrollId,
732 clipRect,
733 transformInClip,
734 LayoutDevicePoint()}; // will be set by caller.
737 enum class AnimationDataType {
738 WithMotionPath,
739 WithoutMotionPath,
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) {
746 return Nothing();
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);
762 nsPoint origin;
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
767 } else if (aItem) {
768 // This branch is for display items to leverage the cache of
769 // nsDisplayListBuilder.
770 origin = aItem->ToReferenceFrame();
771 } else {
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
776 // are used.
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
792 // thread.
793 nsRect coordBox;
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),
803 std::move(coordBox),
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,
826 Send aSendFlag) {
827 auto appendFakeAnimation = [this, aSendFlag](nsCSSPropertyID aProperty,
828 Animatable&& aBaseStyle) {
829 layers::Animation* animation = (aSendFlag == Send::NextTransaction)
830 ? AddAnimationForNextTransaction()
831 : AddAnimation();
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.
841 bool hasMotion =
842 !display->mOffsetPath.IsNone() ||
843 !aNonAnimatingProperties.HasProperty(eCSSProperty_offset_path);
845 for (nsCSSPropertyID id : aNonAnimatingProperties) {
846 switch (id) {
847 case eCSSProperty_transform:
848 if (!display->mTransform.IsNone()) {
849 TransformReferenceBox refBox(aFrame);
850 appendFakeAnimation(
851 id, ResolveTransformOperations(display->mTransform, refBox));
853 break;
854 case eCSSProperty_translate:
855 if (!display->mTranslate.IsNone()) {
856 TransformReferenceBox refBox(aFrame);
857 appendFakeAnimation(id,
858 ResolveTranslate(display->mTranslate, refBox));
860 break;
861 case eCSSProperty_rotate:
862 if (!display->mRotate.IsNone()) {
863 appendFakeAnimation(id, display->mRotate);
865 break;
866 case eCSSProperty_scale:
867 if (!display->mScale.IsNone()) {
868 appendFakeAnimation(id, display->mScale);
870 break;
871 case eCSSProperty_offset_path:
872 if (!display->mOffsetPath.IsNone()) {
873 appendFakeAnimation(id, display->mOffsetPath);
875 break;
876 case eCSSProperty_offset_distance:
877 if (hasMotion && !display->mOffsetDistance.IsDefinitelyZero()) {
878 appendFakeAnimation(id, display->mOffsetDistance);
880 break;
881 case eCSSProperty_offset_rotate:
882 if (hasMotion && (!display->mOffsetRotate.auto_ ||
883 display->mOffsetRotate.angle.ToDegrees() != 0.0)) {
884 appendFakeAnimation(id, display->mOffsetRotate);
886 break;
887 case eCSSProperty_offset_anchor:
888 if (hasMotion && !display->mOffsetAnchor.IsAuto()) {
889 appendFakeAnimation(id, display->mOffsetAnchor);
891 break;
892 case eCSSProperty_offset_position:
893 if (hasMotion && !display->mOffsetPosition.IsAuto()) {
894 appendFakeAnimation(id, display->mOffsetPosition);
896 break;
897 default:
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();
910 } else {
911 ClearAnimations();
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()) {
924 return;
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()) {
933 return;
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
942 // animation.
943 aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
944 return;
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,
956 aPosition);
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
988 // the compositor:
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,
997 sendFlag);
1001 } // namespace mozilla::layers