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