Bug 1827491 [wpt PR 39482] - Update html5lib-tests generated tests, a=testonly
[gecko.git] / gfx / layers / AnimationInfo.cpp
blobbe3d47b3789445a3ba9efccec3f7aad5e4a7d305
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"
24 namespace mozilla {
25 namespace layers {
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.
43 EnsureAnimationsId();
45 MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
47 Animation* anim = mAnimations.AppendElement();
49 mMutated = true;
51 return anim;
54 Animation* AnimationInfo::AddAnimationForNextTransaction() {
55 MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
56 MOZ_ASSERT(mPendingAnimations,
57 "should have called ClearAnimationsForNextTransaction first");
59 Animation* anim = mPendingAnimations->AppendElement();
61 return anim;
64 void AnimationInfo::ClearAnimations() {
65 mPendingAnimations = nullptr;
67 if (mAnimations.IsEmpty() && mStorageData.IsEmpty()) {
68 return;
71 mAnimations.Clear();
72 mStorageData.Clear();
74 mMutated = true;
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) {
87 bool updated = false;
88 for (size_t animIdx = 0, animEnd = mAnimations.Length(); animIdx < animEnd;
89 animIdx++) {
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()));
109 updated = true;
112 return updated;
115 bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() {
116 if (mPendingAnimations) {
117 mAnimations = std::move(*mPendingAnimations);
118 mPendingAnimations = nullptr;
119 return true;
122 return false;
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())) {
130 return true;
133 return false;
136 /* static */
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);
151 if (animationData) {
152 return animationData->GetAnimationInfo().GetAnimationGeneration();
155 return Nothing();
158 /* static */
159 void AnimationInfo::EnumerateGenerationOnFrame(
160 const nsIFrame* aFrame, const nsIContent* aContent,
161 const CompositorAnimatableDisplayItemTypes& aDisplayItemTypes,
162 AnimationGenerationCallback aCallback) {
163 nsIWidget* widget = nsContentUtils::WidgetForContent(aContent);
164 if (!widget) {
165 return;
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
169 // the generation.
170 if (!widget->HasWindowRenderer()) {
171 for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) {
172 aCallback(Nothing(), displayItem);
174 return;
176 WindowRenderer* renderer = widget->GetWindowRenderer();
177 MOZ_ASSERT(renderer);
178 if (!renderer->AsWebRender()) {
179 return;
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)
194 : aFrame;
195 RefPtr<WebRenderAnimationData> animationData =
196 GetWebRenderUserData<WebRenderAnimationData>(frameToQuery,
197 (uint32_t)displayItem);
198 Maybe<uint64_t> generation;
199 if (animationData) {
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;
243 MOZ_RELEASE_ASSERT(
244 result.initCapacity(aTransform.Operations().Length()),
245 "Allocating vector of transform operations should be successful.");
247 for (const StyleTransformOperation& op : aTransform.Operations()) {
248 switch (op.tag) {
249 case StyleTransformOperation::Tag::TranslateX:
250 result.infallibleAppend(ResolveTranslate(aRefBox, op.AsTranslateX()));
251 break;
252 case StyleTransformOperation::Tag::TranslateY:
253 result.infallibleAppend(ResolveTranslate(
254 aRefBox, LengthPercentage::Zero(), op.AsTranslateY()));
255 break;
256 case StyleTransformOperation::Tag::TranslateZ:
257 result.infallibleAppend(
258 ResolveTranslate(aRefBox, LengthPercentage::Zero(),
259 LengthPercentage::Zero(), op.AsTranslateZ()));
260 break;
261 case StyleTransformOperation::Tag::Translate: {
262 const auto& translate = op.AsTranslate();
263 result.infallibleAppend(
264 ResolveTranslate(aRefBox, translate._0, translate._1));
265 break;
267 case StyleTransformOperation::Tag::Translate3D: {
268 const auto& translate = op.AsTranslate3D();
269 result.infallibleAppend(ResolveTranslate(aRefBox, translate._0,
270 translate._1, translate._2));
271 break;
273 case StyleTransformOperation::Tag::InterpolateMatrix: {
274 gfx::Matrix4x4 matrix;
275 nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, op, aRefBox);
276 result.infallibleAppend(convertMatrix(matrix));
277 break;
279 case StyleTransformOperation::Tag::AccumulateMatrix: {
280 gfx::Matrix4x4 matrix;
281 nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix, op, aRefBox);
282 result.infallibleAppend(convertMatrix(matrix));
283 break;
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);
302 break;
303 default:
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());
313 return transform;
316 static Maybe<ScrollTimelineOptions> GetScrollTimelineOptions(
317 dom::AnimationTimeline* aTimeline) {
318 if (!aTimeline || !aTimeline->IsScrollTimeline()) {
319 return Nothing();
322 const dom::ScrollTimeline* timeline = aTimeline->AsScrollTimeline();
323 MOZ_ASSERT(timeline->IsActive(),
324 "We send scroll animation to the compositor only if its timeline "
325 "is active");
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) {
349 MOZ_ASSERT(aFrame);
351 if (aAnimationValue.IsNull()) {
352 aAnimatable = null_t();
353 return;
356 switch (aProperty) {
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.
360 nscolor foreground =
361 aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor);
362 aAnimatable = aAnimationValue.GetColor(foreground);
363 break;
365 case eCSSProperty_opacity:
366 aAnimatable = aAnimationValue.GetOpacity();
367 break;
368 case eCSSProperty_rotate:
369 aAnimatable = aAnimationValue.GetRotateProperty();
370 break;
371 case eCSSProperty_scale:
372 aAnimatable = aAnimationValue.GetScaleProperty();
373 break;
374 case eCSSProperty_translate:
375 aAnimatable =
376 ResolveTranslate(aAnimationValue.GetTranslateProperty(), aRefBox);
377 break;
378 case eCSSProperty_transform:
379 aAnimatable = ResolveTransformOperations(
380 aAnimationValue.GetTransformProperty(), aRefBox);
381 break;
382 case eCSSProperty_offset_path:
383 aAnimatable =
384 NormalizeOffsetPath(aAnimationValue.GetOffsetPathProperty());
385 break;
386 case eCSSProperty_offset_distance:
387 aAnimatable = aAnimationValue.GetOffsetDistanceProperty();
388 break;
389 case eCSSProperty_offset_rotate:
390 aAnimatable = aAnimationValue.GetOffsetRotateProperty();
391 break;
392 case eCSSProperty_offset_anchor:
393 aAnimatable = aAnimationValue.GetOffsetAnchorProperty();
394 break;
395 default:
396 MOZ_ASSERT_UNREACHABLE("Unsupported property");
400 void AnimationInfo::AddAnimationForProperty(
401 nsIFrame* aFrame, const AnimationProperty& aProperty,
402 dom::Animation* aAnimation, const Maybe<TransformData>& aTransformData,
403 Send aSendFlag) {
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"
413 " one later");
415 layers::Animation* animation = (aSendFlag == Send::NextTransaction)
416 ? AddAnimationForNextTransaction()
417 : AddAnimation();
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
433 // the compositor.
434 dom::CSSTransition* cssTransition = aAnimation->AsCSSTransition();
435 if (cssTransition) {
436 cssTransition->UpdateStartValueFromReplacedTransition();
439 animation->originTime() =
440 !aAnimation->GetTimeline()
441 ? TimeStamp()
442 : aAnimation->GetTimeline()->ToTimeStamp(TimeDuration());
444 dom::Nullable<TimeDuration> startTime =
445 aAnimation->GetCurrentOrPendingStartTime();
446 if (startTime.IsNull()) {
447 animation->startTime() = Nothing();
448 } else {
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());
491 } else {
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
523 // this function.
525 // In this function, we want to re-organize the list as (Note: don't care
526 // the order of properties):
527 // [
528 // { rotate: [ Animation A, Animation B ] },
529 // { scale: [ Animation B ] },
530 // { transform: [ Animation A, Animation C ] },
531 // ]
533 // Therefore, AddAnimationsForProperty() will append each animation property
534 // into AnimationInfo, as a final list of layers::Animation:
535 // [
536 // { rotate: Animation A },
537 // { rotate: Animation B },
538 // { scale: Animation B },
539 // { transform: Animation A },
540 // { transform: Animation C },
541 // ]
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();
550 MOZ_ASSERT(effect);
551 for (const AnimationProperty& property : effect->Properties()) {
552 if (!aPropertySet.HasProperty(property.mProperty)) {
553 continue;
556 auto animsForPropertyPtr = groupedAnims.lookupForAdd(property.mProperty);
557 if (!animsForPropertyPtr) {
558 DebugOnly<bool> rv =
559 groupedAnims.add(animsForPropertyPtr, property.mProperty,
560 nsTArray<RefPtr<dom::Animation>>());
561 MOZ_ASSERT(rv, "Should have enough memory");
563 animsForPropertyPtr->value().AppendElement(anim);
566 return groupedAnims;
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()) {
578 continue;
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);
587 if (!property) {
588 continue;
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.
595 MOZ_ASSERT(
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())) {
613 continue;
616 AddAnimationForProperty(aFrame, *property, anim, aTransformData, aSendFlag);
617 keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
618 addedAny = true;
619 if (aTransformData && aTransformData->partialPrerenderData() &&
620 aLayerManager) {
621 aLayerManager->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(),
622 anim);
625 return addedAny;
628 // Returns which pre-rendered area's sides are overflowed from the pre-rendered
629 // rect.
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;
649 return sides;
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})
661 .GetMatrix();
662 if (aScrollFrame) {
663 transformInClip.PostTranslate(
664 LayoutDevicePoint::FromAppUnits(aScrollFrame->GetScrollPosition(),
665 aDevPixelsToAppUnits)
666 .ToUnknownPoint());
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(),
677 transformInClip);
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);
691 if (!clipFrame) {
692 // If there is no suitable clip frame in the same document, use the
693 // root one.
694 scrollFrame = aFrame->PresShell()->GetRootScrollFrameAsScrollable();
695 if (scrollFrame) {
696 clipFrame = do_QueryFrame(scrollFrame);
697 } else {
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.
704 if (scrollFrame &&
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);
716 } else {
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.
720 scrollId =
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),
735 scrollId,
736 clipRect,
737 transformInClip,
738 LayoutDevicePoint()}; // will be set by caller.
741 enum class AnimationDataType {
742 WithMotionPath,
743 WithoutMotionPath,
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) {
750 return Nothing();
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);
766 nsPoint origin;
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
771 } else if (aItem) {
772 // This branch is for display items to leverage the cache of
773 // nsDisplayListBuilder.
774 origin = aItem->ToReferenceFrame();
775 } else {
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
780 // are used.
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,
816 Send aSendFlag) {
817 auto appendFakeAnimation = [this, aSendFlag](nsCSSPropertyID aProperty,
818 Animatable&& aBaseStyle) {
819 layers::Animation* animation = (aSendFlag == Send::NextTransaction)
820 ? AddAnimationForNextTransaction()
821 : AddAnimation();
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.
832 bool hasMotion =
833 !display->mOffsetPath.IsNone() ||
834 !aNonAnimatingProperties.HasProperty(eCSSProperty_offset_path);
836 for (nsCSSPropertyID id : aNonAnimatingProperties) {
837 switch (id) {
838 case eCSSProperty_transform:
839 if (!display->mTransform.IsNone()) {
840 TransformReferenceBox refBox(aFrame);
841 appendFakeAnimation(
842 id, ResolveTransformOperations(display->mTransform, refBox));
844 break;
845 case eCSSProperty_translate:
846 if (!display->mTranslate.IsNone()) {
847 TransformReferenceBox refBox(aFrame);
848 appendFakeAnimation(id,
849 ResolveTranslate(display->mTranslate, refBox));
851 break;
852 case eCSSProperty_rotate:
853 if (!display->mRotate.IsNone()) {
854 appendFakeAnimation(id, display->mRotate);
856 break;
857 case eCSSProperty_scale:
858 if (!display->mScale.IsNone()) {
859 appendFakeAnimation(id, display->mScale);
861 break;
862 case eCSSProperty_offset_path:
863 if (!display->mOffsetPath.IsNone()) {
864 appendFakeAnimation(id, NormalizeOffsetPath(display->mOffsetPath));
866 break;
867 case eCSSProperty_offset_distance:
868 if (hasMotion && !display->mOffsetDistance.IsDefinitelyZero()) {
869 appendFakeAnimation(id, display->mOffsetDistance);
871 break;
872 case eCSSProperty_offset_rotate:
873 if (hasMotion && (!display->mOffsetRotate.auto_ ||
874 display->mOffsetRotate.angle.ToDegrees() != 0.0)) {
875 appendFakeAnimation(id, display->mOffsetRotate);
877 break;
878 case eCSSProperty_offset_anchor:
879 if (hasMotion && !display->mOffsetAnchor.IsAuto()) {
880 appendFakeAnimation(id, display->mOffsetAnchor);
882 break;
883 default:
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();
896 } else {
897 ClearAnimations();
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()) {
910 return;
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()) {
919 return;
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
928 // animation.
929 aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
930 return;
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,
942 aPosition);
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
978 // the compositor:
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,
987 sendFlag);
991 } // namespace layers
992 } // namespace mozilla