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