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