KeyframeEffect.cpp (73782B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/KeyframeEffect.h" 8 9 #include "mozilla/dom/Animation.h" 10 #include "mozilla/dom/DocumentInlines.h" 11 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h" 12 // For UnrestrictedDoubleOrKeyframeAnimationOptions; 13 #include "NonCustomCSSPropertyId.h" 14 #include "WindowRenderer.h" 15 #include "js/PropertyAndElement.h" // JS_DefineProperty 16 #include "mozilla/AnimationUtils.h" 17 #include "mozilla/AutoRestore.h" 18 #include "mozilla/ComputedStyleInlines.h" 19 #include "mozilla/EffectSet.h" 20 #include "mozilla/KeyframeUtils.h" 21 #include "mozilla/LayerAnimationInfo.h" 22 #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt 23 #include "mozilla/PresShell.h" 24 #include "mozilla/PresShellInlines.h" 25 #include "mozilla/SVGObserverUtils.h" 26 #include "mozilla/ScrollContainerFrame.h" 27 #include "mozilla/ServoBindings.h" 28 #include "mozilla/StaticPrefs_dom.h" 29 #include "mozilla/StaticPrefs_gfx.h" 30 #include "mozilla/StaticPrefs_layers.h" 31 #include "mozilla/dom/KeyframeEffectBinding.h" 32 #include "mozilla/dom/MutationObservers.h" 33 #include "mozilla/layers/AnimationInfo.h" 34 #include "nsCSSPropertyIDSet.h" 35 #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags 36 #include "nsCSSPseudoElements.h" // For PseudoStyleType 37 #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetComputedStyle 38 #include "nsContentUtils.h" 39 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch 40 #include "nsIFrame.h" 41 #include "nsIFrameInlines.h" 42 #include "nsPresContextInlines.h" 43 #include "nsRefreshDriver.h" 44 45 namespace mozilla { 46 47 void AnimationProperty::SetPerformanceWarning( 48 const AnimationPerformanceWarning& aWarning, const dom::Element* aElement) { 49 if (mPerformanceWarning && *mPerformanceWarning == aWarning) { 50 return; 51 } 52 53 mPerformanceWarning = Some(aWarning); 54 55 nsAutoString localizedString; 56 if (StaticPrefs::layers_offmainthreadcomposition_log_animations() && 57 mPerformanceWarning->ToLocalizedString(localizedString)) { 58 nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString); 59 AnimationUtils::LogAsyncAnimationFailure(logMessage, aElement); 60 } 61 } 62 63 bool PropertyValuePair::operator==(const PropertyValuePair& aOther) const { 64 if (mProperty != aOther.mProperty) { 65 return false; 66 } 67 if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) { 68 return true; 69 } 70 if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) { 71 return false; 72 } 73 return Servo_DeclarationBlock_Equals(mServoDeclarationBlock, 74 aOther.mServoDeclarationBlock); 75 } 76 77 namespace dom { 78 79 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect, AnimationEffect, 80 mTarget.mElement) 81 82 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect, AnimationEffect) 83 NS_IMPL_CYCLE_COLLECTION_TRACE_END 84 85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect) 86 NS_INTERFACE_MAP_END_INHERITING(AnimationEffect) 87 88 NS_IMPL_ADDREF_INHERITED(KeyframeEffect, AnimationEffect) 89 NS_IMPL_RELEASE_INHERITED(KeyframeEffect, AnimationEffect) 90 91 KeyframeEffect::KeyframeEffect(Document* aDocument, 92 OwningAnimationTarget&& aTarget, 93 TimingParams&& aTiming, 94 const KeyframeEffectParams& aOptions) 95 : AnimationEffect(aDocument, std::move(aTiming)), 96 mTarget(std::move(aTarget)), 97 mEffectOptions(aOptions) {} 98 99 KeyframeEffect::KeyframeEffect(Document* aDocument, 100 OwningAnimationTarget&& aTarget, 101 const KeyframeEffect& aOther) 102 : AnimationEffect(aDocument, TimingParams{aOther.SpecifiedTiming()}), 103 mTarget(std::move(aTarget)), 104 mEffectOptions{aOther.IterationComposite(), aOther.Composite(), 105 mTarget.mPseudoRequest}, 106 mKeyframes(aOther.mKeyframes.Clone()), 107 mProperties(aOther.mProperties.Clone()), 108 mBaseValues(aOther.mBaseValues.Clone()) {} 109 110 JSObject* KeyframeEffect::WrapObject(JSContext* aCx, 111 JS::Handle<JSObject*> aGivenProto) { 112 return KeyframeEffect_Binding::Wrap(aCx, this, aGivenProto); 113 } 114 115 IterationCompositeOperation KeyframeEffect::IterationComposite() const { 116 return mEffectOptions.mIterationComposite; 117 } 118 119 void KeyframeEffect::SetIterationComposite( 120 const IterationCompositeOperation& aIterationComposite) { 121 if (mEffectOptions.mIterationComposite == aIterationComposite) { 122 return; 123 } 124 125 if (mAnimation && mAnimation->IsRelevant()) { 126 MutationObservers::NotifyAnimationChanged(mAnimation); 127 } 128 129 mEffectOptions.mIterationComposite = aIterationComposite; 130 RequestRestyle(EffectCompositor::RestyleType::Layer); 131 } 132 133 CompositeOperation KeyframeEffect::Composite() const { 134 return mEffectOptions.mComposite; 135 } 136 137 void KeyframeEffect::SetComposite(const CompositeOperation& aComposite) { 138 if (mEffectOptions.mComposite == aComposite) { 139 return; 140 } 141 142 mEffectOptions.mComposite = aComposite; 143 144 if (mAnimation && mAnimation->IsRelevant()) { 145 MutationObservers::NotifyAnimationChanged(mAnimation); 146 } 147 148 if (mTarget) { 149 RefPtr<const ComputedStyle> computedStyle = 150 GetTargetComputedStyle(Flush::None); 151 if (computedStyle) { 152 UpdateProperties(computedStyle); 153 } 154 } 155 } 156 157 void KeyframeEffect::NotifySpecifiedTimingUpdated() { 158 // Use the same document for a pseudo element and its parent element. 159 // Use nullptr if we don't have mTarget, so disable the mutation batch. 160 nsAutoAnimationMutationBatch mb(mTarget ? mTarget.mElement->OwnerDoc() 161 : nullptr); 162 163 if (mAnimation) { 164 mAnimation->NotifyEffectTimingUpdated(); 165 166 if (mAnimation->IsRelevant()) { 167 MutationObservers::NotifyAnimationChanged(mAnimation); 168 } 169 170 RequestRestyle(EffectCompositor::RestyleType::Layer); 171 } 172 } 173 174 void KeyframeEffect::NotifyAnimationTimingUpdated( 175 PostRestyleMode aPostRestyle) { 176 UpdateTargetRegistration(); 177 178 // If the effect is not relevant it will be removed from the target 179 // element's effect set. However, effects not in the effect set 180 // will not be included in the set of candidate effects for running on 181 // the compositor and hence they won't have their compositor status 182 // updated. As a result, we need to make sure we clear their compositor 183 // status here. 184 bool isRelevant = mAnimation && mAnimation->IsRelevant(); 185 if (!isRelevant) { 186 ResetIsRunningOnCompositor(); 187 } 188 189 // Request restyle if necessary. 190 if (aPostRestyle == PostRestyleMode::IfNeeded && mAnimation && 191 !mProperties.IsEmpty() && HasComputedTimingChanged()) { 192 EffectCompositor::RestyleType restyleType = 193 CanThrottle() ? EffectCompositor::RestyleType::Throttled 194 : EffectCompositor::RestyleType::Standard; 195 RequestRestyle(restyleType); 196 } 197 198 // Detect changes to "in effect" status since we need to recalculate the 199 // animation cascade for this element whenever that changes. 200 // Note that updating mInEffectOnLastAnimationTimingUpdate has to be done 201 // after above CanThrottle() call since the function uses the flag inside it. 202 bool inEffect = IsInEffect(); 203 if (inEffect != mInEffectOnLastAnimationTimingUpdate) { 204 MarkCascadeNeedsUpdate(); 205 mInEffectOnLastAnimationTimingUpdate = inEffect; 206 } 207 208 // If we're no longer "in effect", our ComposeStyle method will never be 209 // called and we will never have a chance to update mProgressOnLastCompose 210 // and mCurrentIterationOnLastCompose. 211 // We clear them here to ensure that if we later become "in effect" we will 212 // request a restyle (above). 213 if (!inEffect) { 214 mProgressOnLastCompose.SetNull(); 215 mCurrentIterationOnLastCompose = 0; 216 } 217 } 218 219 static bool KeyframesEqualIgnoringComputedOffsets( 220 const nsTArray<Keyframe>& aLhs, const nsTArray<Keyframe>& aRhs) { 221 if (aLhs.Length() != aRhs.Length()) { 222 return false; 223 } 224 225 for (size_t i = 0, len = aLhs.Length(); i < len; ++i) { 226 const Keyframe& a = aLhs[i]; 227 const Keyframe& b = aRhs[i]; 228 if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction || 229 a.mPropertyValues != b.mPropertyValues) { 230 return false; 231 } 232 } 233 return true; 234 } 235 236 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes 237 void KeyframeEffect::SetKeyframes(JSContext* aContext, 238 JS::Handle<JSObject*> aKeyframes, 239 ErrorResult& aRv) { 240 nsTArray<Keyframe> keyframes = KeyframeUtils::GetKeyframesFromObject( 241 aContext, mDocument, aKeyframes, "KeyframeEffect.setKeyframes", aRv); 242 if (aRv.Failed()) { 243 return; 244 } 245 246 RefPtr<const ComputedStyle> style = GetTargetComputedStyle(Flush::None); 247 SetKeyframes(std::move(keyframes), style, nullptr /* AnimationTimeline */); 248 } 249 250 void KeyframeEffect::SetKeyframes(nsTArray<Keyframe>&& aKeyframes, 251 const ComputedStyle* aStyle, 252 const AnimationTimeline* aTimeline) { 253 if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) { 254 return; 255 } 256 257 mKeyframes = std::move(aKeyframes); 258 KeyframeUtils::DistributeKeyframes(mKeyframes); 259 260 if (mAnimation && mAnimation->IsRelevant()) { 261 MutationObservers::NotifyAnimationChanged(mAnimation); 262 } 263 264 // We need to call UpdateProperties() unless the target element doesn't have 265 // style (e.g. the target element is not associated with any document). 266 if (aStyle) { 267 UpdateProperties(aStyle, aTimeline); 268 } 269 } 270 271 void KeyframeEffect::ReplaceTransitionStartValue(AnimationValue&& aStartValue) { 272 if (!aStartValue.mServo) { 273 return; 274 } 275 276 // A typical transition should have a single property and a single segment. 277 // 278 // (And for atypical transitions, that is, those updated by script, we don't 279 // apply the replacing behavior.) 280 if (mProperties.Length() != 1 || mProperties[0].mSegments.Length() != 1) { 281 return; 282 } 283 284 // Likewise, check that the keyframes are of the expected shape. 285 if (mKeyframes.Length() != 2 || mKeyframes[0].mPropertyValues.Length() != 1) { 286 return; 287 } 288 289 // Check that the value we are about to substitute in is actually for the 290 // same property. 291 CSSPropertyId property(eCSSProperty_UNKNOWN); 292 Servo_AnimationValue_GetPropertyId(aStartValue.mServo, &property); 293 if (property != mProperties[0].mProperty) { 294 return; 295 } 296 297 mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock = 298 Servo_AnimationValue_Uncompute(aStartValue.mServo).Consume(); 299 mProperties[0].mSegments[0].mFromValue = std::move(aStartValue); 300 } 301 302 static bool IsEffectiveProperty(const EffectSet& aEffects, 303 const CSSPropertyId& aProperty) { 304 return !aEffects.PropertiesWithImportantRules().HasProperty(aProperty) || 305 !aEffects.PropertiesForAnimationsLevel().HasProperty(aProperty); 306 } 307 308 const AnimationProperty* KeyframeEffect::GetEffectiveAnimationOfProperty( 309 const CSSPropertyId& aProperty, const EffectSet& aEffects) const { 310 MOZ_ASSERT(mTarget && &aEffects == EffectSet::Get(mTarget)); 311 312 for (const AnimationProperty& property : mProperties) { 313 if (aProperty != property.mProperty) { 314 continue; 315 } 316 317 // Only include the property if it is not overridden by !important rules in 318 // the transitions level. 319 return IsEffectiveProperty(aEffects, property.mProperty) ? &property 320 : nullptr; 321 } 322 return nullptr; 323 } 324 325 bool KeyframeEffect::HasEffectiveAnimationOfPropertySet( 326 const nsCSSPropertyIDSet& aPropertySet, const EffectSet& aEffectSet) const { 327 for (const AnimationProperty& property : mProperties) { 328 if (aPropertySet.HasProperty(property.mProperty) && 329 IsEffectiveProperty(aEffectSet, property.mProperty)) { 330 return true; 331 } 332 } 333 return false; 334 } 335 336 nsCSSPropertyIDSet KeyframeEffect::GetPropertiesForCompositor( 337 EffectSet& aEffects, const nsIFrame* aFrame) const { 338 MOZ_ASSERT(&aEffects == EffectSet::Get(mTarget)); 339 340 nsCSSPropertyIDSet properties; 341 342 if (!mAnimation || !mAnimation->IsRelevant()) { 343 return properties; 344 } 345 346 static constexpr nsCSSPropertyIDSet compositorAnimatables = 347 nsCSSPropertyIDSet::CompositorAnimatables(); 348 static constexpr nsCSSPropertyIDSet transformLikeProperties = 349 nsCSSPropertyIDSet::TransformLikeProperties(); 350 351 nsCSSPropertyIDSet transformSet; 352 AnimationPerformanceWarning::Type dummyWarning; 353 354 for (const AnimationProperty& property : mProperties) { 355 if (!compositorAnimatables.HasProperty(property.mProperty)) { 356 continue; 357 } 358 359 // Transform-like properties are combined together on the compositor so we 360 // need to evaluate them as a group. We build up a separate set here then 361 // evaluate it as a separate step below. 362 if (transformLikeProperties.HasProperty(property.mProperty)) { 363 transformSet.AddProperty(property.mProperty.mId); 364 continue; 365 } 366 367 KeyframeEffect::MatchForCompositor matchResult = 368 IsMatchForCompositor(nsCSSPropertyIDSet{property.mProperty.mId}, aFrame, 369 aEffects, dummyWarning); 370 if (matchResult == 371 KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty || 372 matchResult == KeyframeEffect::MatchForCompositor::No) { 373 continue; 374 } 375 properties.AddProperty(property.mProperty.mId); 376 } 377 378 if (!transformSet.IsEmpty()) { 379 KeyframeEffect::MatchForCompositor matchResult = 380 IsMatchForCompositor(transformSet, aFrame, aEffects, dummyWarning); 381 if (matchResult == KeyframeEffect::MatchForCompositor::Yes || 382 matchResult == KeyframeEffect::MatchForCompositor::IfNeeded) { 383 properties |= transformSet; 384 } 385 } 386 387 return properties; 388 } 389 390 AnimatedPropertyIDSet KeyframeEffect::GetPropertySet() const { 391 AnimatedPropertyIDSet result; 392 393 for (const AnimationProperty& property : mProperties) { 394 result.AddProperty(property.mProperty); 395 } 396 397 return result; 398 } 399 400 #ifdef DEBUG 401 bool SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA, 402 const nsTArray<Keyframe>& aB) { 403 if (aA.Length() != aB.Length()) { 404 return false; 405 } 406 407 for (size_t i = 0; i < aA.Length(); i++) { 408 const Keyframe& a = aA[i]; 409 const Keyframe& b = aB[i]; 410 if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction || 411 a.mPropertyValues != b.mPropertyValues) { 412 return false; 413 } 414 } 415 416 return true; 417 } 418 #endif 419 420 static bool HasCurrentColor( 421 const nsTArray<AnimationPropertySegment>& aSegments) { 422 for (const AnimationPropertySegment& segment : aSegments) { 423 if ((!segment.mFromValue.IsNull() && segment.mFromValue.IsCurrentColor()) || 424 (!segment.mToValue.IsNull() && segment.mToValue.IsCurrentColor())) { 425 return true; 426 } 427 } 428 return false; 429 } 430 void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle, 431 const AnimationTimeline* aTimeline) { 432 MOZ_ASSERT(aStyle); 433 434 nsTArray<AnimationProperty> properties = BuildProperties(aStyle); 435 436 bool propertiesChanged = mProperties != properties; 437 438 // We need to update base styles even if any properties are not changed at all 439 // since base styles might have been changed due to parent style changes, etc. 440 bool baseStylesChanged = false; 441 EnsureBaseStyles(aStyle, properties, aTimeline, 442 !propertiesChanged ? &baseStylesChanged : nullptr); 443 444 if (!propertiesChanged) { 445 if (baseStylesChanged) { 446 RequestRestyle(EffectCompositor::RestyleType::Layer); 447 } 448 return; 449 } 450 451 // Preserve the state of the mIsRunningOnCompositor flag. 452 nsCSSPropertyIDSet runningOnCompositorProperties; 453 454 for (const AnimationProperty& property : mProperties) { 455 if (property.mIsRunningOnCompositor) { 456 runningOnCompositorProperties.AddProperty(property.mProperty.mId); 457 } 458 } 459 460 mProperties = std::move(properties); 461 UpdateEffectSet(); 462 463 mCumulativeChanges = {}; 464 for (AnimationProperty& property : mProperties) { 465 property.mIsRunningOnCompositor = 466 runningOnCompositorProperties.HasProperty(property.mProperty); 467 CalculateCumulativeChangesForProperty(property); 468 } 469 470 MarkCascadeNeedsUpdate(); 471 472 if (mAnimation) { 473 mAnimation->NotifyEffectPropertiesUpdated(); 474 } 475 476 RequestRestyle(EffectCompositor::RestyleType::Layer); 477 } 478 479 void KeyframeEffect::UpdateBaseStyle(const ComputedStyle* aStyle) { 480 EnsureBaseStyles(aStyle, BuildProperties(aStyle), nullptr, nullptr); 481 } 482 483 void KeyframeEffect::EnsureBaseStyles( 484 const ComputedStyle* aComputedValues, 485 const nsTArray<AnimationProperty>& aProperties, 486 const AnimationTimeline* aTimeline, bool* aBaseStylesChanged) { 487 if (aBaseStylesChanged != nullptr) { 488 *aBaseStylesChanged = false; 489 } 490 491 if (!mTarget) { 492 return; 493 } 494 495 BaseValuesHashmap previousBaseStyles; 496 if (aBaseStylesChanged != nullptr) { 497 previousBaseStyles = std::move(mBaseValues); 498 } 499 500 mBaseValues.Clear(); 501 502 nsPresContext* presContext = 503 nsContentUtils::GetContextForContent(mTarget.mElement); 504 // If |aProperties| is empty we're not going to dereference |presContext| so 505 // we don't care if it is nullptr. 506 // 507 // We could just return early when |aProperties| is empty and save looking up 508 // the pres context, but that won't save any effort normally since we don't 509 // call this function if we have no keyframes to begin with. Furthermore, the 510 // case where |presContext| is nullptr is so rare (we've only ever seen in 511 // fuzzing, and even then we've never been able to reproduce it reliably) 512 // it's not worth the runtime cost of an extra branch. 513 MOZ_ASSERT(presContext || aProperties.IsEmpty(), 514 "Typically presContext should not be nullptr but if it is" 515 " we should have also failed to calculate the computed values" 516 " passed-in as aProperties"); 517 518 if (!aTimeline) { 519 // If we pass a valid timeline, we use it (note: this happens when we create 520 // a new animation or replace the old one, for CSS Animations and CSS 521 // Transitions). Otherwise, we check the timeline from |mAnimation|. 522 aTimeline = mAnimation ? mAnimation->GetTimeline() : nullptr; 523 } 524 525 RefPtr<const ComputedStyle> baseComputedStyle; 526 for (const AnimationProperty& property : aProperties) { 527 EnsureBaseStyle(property, presContext, aComputedValues, aTimeline, 528 baseComputedStyle); 529 } 530 531 if (aBaseStylesChanged != nullptr && 532 std::any_of( 533 mBaseValues.cbegin(), mBaseValues.cend(), [&](const auto& entry) { 534 return AnimationValue(entry.GetData()) != 535 AnimationValue(previousBaseStyles.Get(entry.GetKey())); 536 })) { 537 *aBaseStylesChanged = true; 538 } 539 } 540 541 void KeyframeEffect::EnsureBaseStyle( 542 const AnimationProperty& aProperty, nsPresContext* aPresContext, 543 const ComputedStyle* aComputedStyle, const AnimationTimeline* aTimeline, 544 RefPtr<const ComputedStyle>& aBaseComputedStyle) { 545 auto needBaseStyleForScrollTimeline = 546 [this](const AnimationProperty& aProperty, 547 const AnimationTimeline* aTimeline) { 548 static constexpr TimeDuration zeroDuration; 549 const TimingParams& timing = NormalizedTiming(); 550 // For scroll-timeline with a positive delay, it's possible to scroll 551 // back and forth between delay phase and active phase, so we need to 552 // keep its base style and maybe use it to override the animations in 553 // delay on the compositor. 554 return aTimeline && aTimeline->IsScrollTimeline() && 555 nsCSSPropertyIDSet::CompositorAnimatables().HasProperty( 556 aProperty.mProperty) && 557 (timing.Delay() > zeroDuration || 558 timing.EndDelay() > zeroDuration); 559 }; 560 auto hasAdditiveValues = [](const AnimationProperty& aProperty) { 561 for (const AnimationPropertySegment& segment : aProperty.mSegments) { 562 if (!segment.HasReplaceableValues()) { 563 return true; 564 } 565 } 566 return false; 567 }; 568 569 // Note: Check base style for compositor (i.e. for scroll-driven animations) 570 // first because it is much cleaper. 571 const bool needBaseStyle = 572 needBaseStyleForScrollTimeline(aProperty, aTimeline) || 573 hasAdditiveValues(aProperty); 574 if (!needBaseStyle) { 575 return; 576 } 577 578 if (!aBaseComputedStyle) { 579 MOZ_ASSERT(mTarget, "Should have a valid target"); 580 581 Element* animatingElement = 582 mTarget.mElement->GetPseudoElement(mTarget.mPseudoRequest); 583 if (!animatingElement) { 584 return; 585 } 586 aBaseComputedStyle = aPresContext->StyleSet()->GetBaseContextForElement( 587 animatingElement, aComputedStyle); 588 } 589 RefPtr<StyleAnimationValue> baseValue = 590 Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle, 591 &aProperty.mProperty) 592 .Consume(); 593 mBaseValues.InsertOrUpdate(aProperty.mProperty, std::move(baseValue)); 594 } 595 596 void KeyframeEffect::WillComposeStyle() { 597 ComputedTiming computedTiming = GetComputedTiming(); 598 mProgressOnLastCompose = computedTiming.mProgress; 599 mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration; 600 } 601 602 void KeyframeEffect::ComposeStyleRule(StyleAnimationValueMap& aAnimationValues, 603 const AnimationProperty& aProperty, 604 const AnimationPropertySegment& aSegment, 605 const ComputedTiming& aComputedTiming) { 606 auto* opaqueTable = 607 reinterpret_cast<RawServoAnimationValueTable*>(&mBaseValues); 608 Servo_AnimationCompose(&aAnimationValues, opaqueTable, &aProperty.mProperty, 609 &aSegment, &aProperty.mSegments.LastElement(), 610 &aComputedTiming, mEffectOptions.mIterationComposite); 611 } 612 613 void KeyframeEffect::ComposeStyle( 614 StyleAnimationValueMap& aComposeResult, 615 const InvertibleAnimatedPropertyIDSet& aPropertiesToSkip, 616 EndpointBehavior aEndpointBehavior) { 617 ComputedTiming computedTiming = GetComputedTiming(nullptr, aEndpointBehavior); 618 619 // If the progress is null, we don't have fill data for the current 620 // time so we shouldn't animate. 621 if (computedTiming.mProgress.IsNull()) { 622 return; 623 } 624 625 for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd; 626 ++propIdx) { 627 const AnimationProperty& prop = mProperties[propIdx]; 628 629 MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key"); 630 MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0, 631 "incorrect last to key"); 632 633 if (aPropertiesToSkip.HasProperty(prop.mProperty)) { 634 continue; 635 } 636 637 MOZ_ASSERT(prop.mSegments.Length() > 0, 638 "property should not be in animations if it has no segments"); 639 640 // FIXME: Maybe cache the current segment? 641 const AnimationPropertySegment *segment = prop.mSegments.Elements(), 642 *segmentEnd = 643 segment + prop.mSegments.Length(); 644 while (segment->mToKey <= computedTiming.mProgress.Value()) { 645 MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys"); 646 if ((segment + 1) == segmentEnd) { 647 break; 648 } 649 ++segment; 650 MOZ_ASSERT(segment->mFromKey == (segment - 1)->mToKey, "incorrect keys"); 651 } 652 MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys"); 653 MOZ_ASSERT(segment >= prop.mSegments.Elements() && 654 size_t(segment - prop.mSegments.Elements()) < 655 prop.mSegments.Length(), 656 "out of array bounds"); 657 658 ComposeStyleRule(aComposeResult, prop, *segment, computedTiming); 659 } 660 661 // If the animation produces a change hint that affects the overflow region, 662 // we need to record the current time to unthrottle the animation 663 // periodically when the animation is being throttled because it's scrolled 664 // out of view. 665 if (HasPropertiesThatMightAffectOverflow()) { 666 nsPresContext* presContext = 667 nsContentUtils::GetContextForContent(mTarget.mElement); 668 EffectSet* effectSet = EffectSet::Get(mTarget); 669 if (presContext && effectSet) { 670 TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); 671 effectSet->UpdateLastOverflowAnimationSyncTime(now); 672 } 673 } 674 } 675 676 bool KeyframeEffect::IsRunningOnCompositor() const { 677 // We consider animation is running on compositor if there is at least 678 // one property running on compositor. 679 // Animation.IsRunningOnCompotitor will return more fine grained 680 // information in bug 1196114. 681 for (const AnimationProperty& property : mProperties) { 682 if (property.mIsRunningOnCompositor) { 683 return true; 684 } 685 } 686 return false; 687 } 688 689 void KeyframeEffect::SetIsRunningOnCompositor(NonCustomCSSPropertyId aProperty, 690 bool aIsRunning) { 691 MOZ_ASSERT(aProperty != eCSSPropertyExtra_variable, 692 "Can't animate variables on compositor"); 693 MOZ_ASSERT( 694 nsCSSProps::PropHasFlags(aProperty, CSSPropFlags::CanAnimateOnCompositor), 695 "Property being animated on compositor is not a recognized " 696 "compositor-animatable property"); 697 698 for (AnimationProperty& property : mProperties) { 699 if (property.mProperty.mId == aProperty) { 700 property.mIsRunningOnCompositor = aIsRunning; 701 // We currently only set a performance warning message when animations 702 // cannot be run on the compositor, so if this animation is running 703 // on the compositor we don't need a message. 704 if (aIsRunning) { 705 property.mPerformanceWarning.reset(); 706 } else if (mAnimation && mAnimation->IsPartialPrerendered()) { 707 ResetPartialPrerendered(); 708 } 709 return; 710 } 711 } 712 } 713 714 void KeyframeEffect::SetIsRunningOnCompositor( 715 const nsCSSPropertyIDSet& aPropertySet, bool aIsRunning) { 716 for (AnimationProperty& property : mProperties) { 717 if (aPropertySet.HasProperty(property.mProperty)) { 718 MOZ_ASSERT(nsCSSProps::PropHasFlags(property.mProperty.mId, 719 CSSPropFlags::CanAnimateOnCompositor), 720 "Property being animated on compositor is a recognized " 721 "compositor-animatable property"); 722 property.mIsRunningOnCompositor = aIsRunning; 723 // We currently only set a performance warning message when animations 724 // cannot be run on the compositor, so if this animation is running 725 // on the compositor we don't need a message. 726 if (aIsRunning) { 727 property.mPerformanceWarning.reset(); 728 } 729 } 730 } 731 732 if (!aIsRunning && mAnimation && mAnimation->IsPartialPrerendered()) { 733 ResetPartialPrerendered(); 734 } 735 } 736 737 void KeyframeEffect::ResetIsRunningOnCompositor() { 738 for (AnimationProperty& property : mProperties) { 739 property.mIsRunningOnCompositor = false; 740 } 741 742 if (mAnimation && mAnimation->IsPartialPrerendered()) { 743 ResetPartialPrerendered(); 744 } 745 } 746 747 void KeyframeEffect::ResetPartialPrerendered() { 748 MOZ_ASSERT(mAnimation && mAnimation->IsPartialPrerendered()); 749 750 nsIFrame* frame = GetPrimaryFrame(); 751 if (!frame) { 752 return; 753 } 754 755 nsIWidget* widget = frame->GetNearestWidget(); 756 if (!widget) { 757 return; 758 } 759 760 if (WindowRenderer* windowRenderer = widget->GetWindowRenderer()) { 761 windowRenderer->RemovePartialPrerenderedAnimation( 762 mAnimation->IdOnCompositor(), mAnimation); 763 } 764 } 765 766 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion( 767 const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) { 768 MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); 769 return aOptions.GetAsKeyframeEffectOptions(); 770 } 771 772 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion( 773 const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) { 774 MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); 775 return aOptions.GetAsKeyframeAnimationOptions(); 776 } 777 778 template <class OptionsType> 779 static KeyframeEffectParams KeyframeEffectParamsFromUnion( 780 const OptionsType& aOptions, CallerType aCallerType, ErrorResult& aRv) { 781 KeyframeEffectParams result; 782 if (aOptions.IsUnrestrictedDouble()) { 783 return result; 784 } 785 786 const KeyframeEffectOptions& options = 787 KeyframeEffectOptionsFromUnion(aOptions); 788 789 // |result.mPseudoRequest| uses the default value, i.e. NotPseudo. 790 result.mIterationComposite = options.mIterationComposite; 791 result.mComposite = options.mComposite; 792 793 if (DOMStringIsNull(options.mPseudoElement)) { 794 return result; 795 } 796 797 Maybe<PseudoStyleRequest> pseudoRequest = 798 nsCSSPseudoElements::ParsePseudoElement(options.mPseudoElement); 799 if (!pseudoRequest) { 800 // Per the spec, we throw SyntaxError for syntactically invalid pseudos. 801 aRv.ThrowSyntaxError( 802 nsPrintfCString("'%s' is a syntactically invalid pseudo-element.", 803 NS_ConvertUTF16toUTF8(options.mPseudoElement).get())); 804 return result; 805 } 806 807 result.mPseudoRequest = std::move(*pseudoRequest); 808 if (!AnimationUtils::IsSupportedPseudoForAnimations(result.mPseudoRequest)) { 809 // Per the spec, we throw SyntaxError for unsupported pseudos. 810 aRv.ThrowSyntaxError( 811 nsPrintfCString("'%s' is an unsupported pseudo-element.", 812 NS_ConvertUTF16toUTF8(options.mPseudoElement).get())); 813 } 814 815 return result; 816 } 817 818 template <class OptionsType> 819 /* static */ 820 already_AddRefed<KeyframeEffect> KeyframeEffect::ConstructKeyframeEffect( 821 const GlobalObject& aGlobal, Element* aTarget, 822 JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions, 823 ErrorResult& aRv) { 824 // We should get the document from `aGlobal` instead of the current Realm 825 // to make this works in Xray case. 826 // 827 // In all non-Xray cases, `aGlobal` matches the current Realm, so this 828 // matches the spec behavior. 829 // 830 // In Xray case, the new objects should be created using the document of 831 // the target global, but the KeyframeEffect constructors are called in the 832 // caller's compartment to access `aKeyframes` object. 833 Document* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get()); 834 if (!doc) { 835 aRv.Throw(NS_ERROR_FAILURE); 836 return nullptr; 837 } 838 839 KeyframeEffectParams effectOptions = 840 KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType(), aRv); 841 // An invalid Pseudo-element aborts all further steps. 842 if (aRv.Failed()) { 843 return nullptr; 844 } 845 846 TimingParams timingParams = TimingParams::FromOptionsUnion(aOptions, aRv); 847 if (aRv.Failed()) { 848 return nullptr; 849 } 850 851 RefPtr<KeyframeEffect> effect = new KeyframeEffect( 852 doc, OwningAnimationTarget(aTarget, effectOptions.mPseudoRequest), 853 std::move(timingParams), effectOptions); 854 855 effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv); 856 if (aRv.Failed()) { 857 return nullptr; 858 } 859 860 return effect.forget(); 861 } 862 863 nsTArray<AnimationProperty> KeyframeEffect::BuildProperties( 864 const ComputedStyle* aStyle) { 865 MOZ_ASSERT(aStyle); 866 867 nsTArray<AnimationProperty> result; 868 // If mTarget is false (i.e. mTarget.mElement is null), return an empty 869 // property array. 870 if (!mTarget) { 871 return result; 872 } 873 874 // When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes 875 // calculate computed values from |mKeyframes|, they could possibly 876 // trigger a subsequent restyle in which we rebuild animations. If that 877 // happens we could find that |mKeyframes| is overwritten while it is 878 // being iterated over. Normally that shouldn't happen but just in case we 879 // make a copy of |mKeyframes| first and iterate over that instead. 880 auto keyframesCopy(mKeyframes.Clone()); 881 882 result = KeyframeUtils::GetAnimationPropertiesFromKeyframes( 883 keyframesCopy, mTarget.mElement, mTarget.mPseudoRequest, aStyle, 884 mEffectOptions.mComposite); 885 886 #ifdef DEBUG 887 MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy), 888 "Apart from the computed offset members, the keyframes array" 889 " should not be modified"); 890 #endif 891 892 mKeyframes = std::move(keyframesCopy); 893 return result; 894 } 895 896 template <typename FrameEnumFunc> 897 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame, 898 FrameEnumFunc&& aFunc) { 899 while (aFrame) { 900 aFunc(aFrame); 901 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); 902 } 903 } 904 905 void KeyframeEffect::UpdateTarget(Element* aElement, 906 const PseudoStyleRequest& aPseudoRequest) { 907 OwningAnimationTarget newTarget(aElement, aPseudoRequest); 908 909 if (mTarget == newTarget) { 910 // Assign the same target, skip it. 911 return; 912 } 913 914 if (mTarget) { 915 // Call ResetIsRunningOnCompositor() prior to UnregisterTarget() since 916 // ResetIsRunningOnCompositor() might try to get the EffectSet associated 917 // with this keyframe effect to remove partial pre-render animation from 918 // the layer manager. 919 ResetIsRunningOnCompositor(); 920 UnregisterTarget(); 921 922 RequestRestyle(EffectCompositor::RestyleType::Layer); 923 924 nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc()); 925 if (mAnimation) { 926 MutationObservers::NotifyAnimationRemoved(mAnimation); 927 } 928 } 929 930 mTarget = newTarget; 931 932 if (mTarget) { 933 UpdateTargetRegistration(); 934 RefPtr<const ComputedStyle> computedStyle = 935 GetTargetComputedStyle(Flush::None); 936 if (computedStyle) { 937 UpdateProperties(computedStyle); 938 } 939 940 RequestRestyle(EffectCompositor::RestyleType::Layer); 941 942 nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc()); 943 if (mAnimation) { 944 MutationObservers::NotifyAnimationAdded(mAnimation); 945 } 946 } 947 948 if (mAnimation) { 949 mAnimation->NotifyEffectTargetUpdated(); 950 } 951 } 952 953 void KeyframeEffect::UpdateTargetRegistration() { 954 if (!mTarget) { 955 return; 956 } 957 958 bool isRelevant = mAnimation && mAnimation->IsRelevant(); 959 960 // Animation::IsRelevant() returns a cached value. It only updates when 961 // something calls Animation::UpdateRelevance. Whenever our timing changes, 962 // we should be notifying our Animation before calling this, so 963 // Animation::IsRelevant() should be up-to-date by the time we get here. 964 MOZ_ASSERT(isRelevant == 965 ((IsCurrent() || IsInEffect()) && mAnimation && 966 mAnimation->ReplaceState() != AnimationReplaceState::Removed), 967 "Out of date Animation::IsRelevant value"); 968 969 if (isRelevant && !mInEffectSet) { 970 EffectSet* effectSet = EffectSet::GetOrCreate(mTarget); 971 effectSet->AddEffect(*this); 972 mInEffectSet = true; 973 UpdateEffectSet(effectSet); 974 nsIFrame* frame = GetPrimaryFrame(); 975 EnumerateContinuationsOrIBSplitSiblings( 976 frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); }); 977 } else if (!isRelevant && mInEffectSet) { 978 UnregisterTarget(); 979 } 980 } 981 982 void KeyframeEffect::UnregisterTarget() { 983 if (!mInEffectSet) { 984 return; 985 } 986 987 EffectSet* effectSet = EffectSet::Get(mTarget); 988 MOZ_ASSERT(effectSet, 989 "If mInEffectSet is true, there must be an EffectSet" 990 " on the target element"); 991 mInEffectSet = false; 992 if (effectSet) { 993 effectSet->RemoveEffect(*this); 994 995 if (effectSet->IsEmpty()) { 996 EffectSet::DestroyEffectSet(mTarget); 997 } 998 } 999 nsIFrame* frame = GetPrimaryFrame(); 1000 EnumerateContinuationsOrIBSplitSiblings( 1001 frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); }); 1002 } 1003 1004 void KeyframeEffect::RequestRestyle( 1005 EffectCompositor::RestyleType aRestyleType) { 1006 if (!mTarget) { 1007 return; 1008 } 1009 nsPresContext* presContext = 1010 nsContentUtils::GetContextForContent(mTarget.mElement); 1011 if (presContext && mAnimation) { 1012 presContext->EffectCompositor()->RequestRestyle( 1013 mTarget.mElement, mTarget.mPseudoRequest, aRestyleType, 1014 mAnimation->CascadeLevel()); 1015 } 1016 } 1017 1018 already_AddRefed<const ComputedStyle> KeyframeEffect::GetTargetComputedStyle( 1019 Flush aFlushType) const { 1020 if (!GetRenderedDocument()) { 1021 return nullptr; 1022 } 1023 1024 MOZ_ASSERT(mTarget, 1025 "Should only have a document when we have a target element"); 1026 1027 OwningAnimationTarget kungfuDeathGrip(mTarget.mElement, 1028 mTarget.mPseudoRequest); 1029 1030 return aFlushType == Flush::Style 1031 ? nsComputedDOMStyle::GetComputedStyle(mTarget.mElement, 1032 mTarget.mPseudoRequest) 1033 : nsComputedDOMStyle::GetComputedStyleNoFlush( 1034 mTarget.mElement, mTarget.mPseudoRequest); 1035 } 1036 1037 #ifdef DEBUG 1038 void DumpAnimationProperties( 1039 const StylePerDocumentStyleData* aRawData, 1040 nsTArray<AnimationProperty>& aAnimationProperties) { 1041 for (auto& p : aAnimationProperties) { 1042 printf("%s\n", 1043 nsCString(nsCSSProps::GetStringValue(p.mProperty.mId)).get()); 1044 for (auto& s : p.mSegments) { 1045 nsAutoCString fromValue, toValue; 1046 s.mFromValue.SerializeSpecifiedValue(p.mProperty, aRawData, fromValue); 1047 s.mToValue.SerializeSpecifiedValue(p.mProperty, aRawData, toValue); 1048 printf(" %f..%f: %s..%s\n", s.mFromKey, s.mToKey, fromValue.get(), 1049 toValue.get()); 1050 } 1051 } 1052 } 1053 #endif 1054 1055 /* static */ 1056 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor( 1057 const GlobalObject& aGlobal, Element* aTarget, 1058 JS::Handle<JSObject*> aKeyframes, 1059 const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, 1060 ErrorResult& aRv) { 1061 return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv); 1062 } 1063 1064 /* static */ 1065 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor( 1066 const GlobalObject& aGlobal, Element* aTarget, 1067 JS::Handle<JSObject*> aKeyframes, 1068 const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, 1069 ErrorResult& aRv) { 1070 return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv); 1071 } 1072 1073 /* static */ 1074 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor( 1075 const GlobalObject& aGlobal, KeyframeEffect& aSource, ErrorResult& aRv) { 1076 Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context()); 1077 if (!doc) { 1078 aRv.Throw(NS_ERROR_FAILURE); 1079 return nullptr; 1080 } 1081 1082 // Create a new KeyframeEffect object with aSource's target, 1083 // iteration composite operation, composite operation, and spacing mode. 1084 // The constructor creates a new AnimationEffect object by 1085 // aSource's TimingParams. 1086 // Note: we don't need to re-throw exceptions since the value specified on 1087 // aSource's timing object can be assumed valid. 1088 RefPtr<KeyframeEffect> effect = 1089 new KeyframeEffect(doc, OwningAnimationTarget{aSource.mTarget}, aSource); 1090 // Copy cumulative changes. mCumulativeChangeHint should be the same as the 1091 // source one because both of targets are the same. 1092 effect->mCumulativeChanges = aSource.mCumulativeChanges; 1093 return effect.forget(); 1094 } 1095 1096 void KeyframeEffect::SetPseudoElement(const nsAString& aPseudoElement, 1097 ErrorResult& aRv) { 1098 if (DOMStringIsNull(aPseudoElement)) { 1099 UpdateTarget(mTarget.mElement, PseudoStyleRequest::NotPseudo()); 1100 return; 1101 } 1102 1103 // Note: ParsePseudoELement() returns Some(NotPseudo) for the null string, 1104 // so we handle null case before this. 1105 Maybe<PseudoStyleRequest> pseudoRequest = 1106 nsCSSPseudoElements::ParsePseudoElement(aPseudoElement); 1107 if (!pseudoRequest || pseudoRequest->IsNotPseudo()) { 1108 // Per the spec, we throw SyntaxError for syntactically invalid pseudos. 1109 aRv.ThrowSyntaxError( 1110 nsPrintfCString("'%s' is a syntactically invalid pseudo-element.", 1111 NS_ConvertUTF16toUTF8(aPseudoElement).get())); 1112 return; 1113 } 1114 1115 if (!AnimationUtils::IsSupportedPseudoForAnimations(*pseudoRequest)) { 1116 // Per the spec, we throw SyntaxError for unsupported pseudos. 1117 aRv.ThrowSyntaxError( 1118 nsPrintfCString("'%s' is an unsupported pseudo-element.", 1119 NS_ConvertUTF16toUTF8(aPseudoElement).get())); 1120 return; 1121 } 1122 1123 UpdateTarget(mTarget.mElement, *pseudoRequest); 1124 } 1125 1126 static void CreatePropertyValue( 1127 const CSSPropertyId& aProperty, float aOffset, 1128 const Maybe<StyleComputedTimingFunction>& aTimingFunction, 1129 const AnimationValue& aValue, dom::CompositeOperation aComposite, 1130 const StylePerDocumentStyleData* aRawData, 1131 AnimationPropertyValueDetails& aResult) { 1132 aResult.mOffset = aOffset; 1133 1134 if (!aValue.IsNull()) { 1135 nsAutoCString stringValue; 1136 aValue.SerializeSpecifiedValue(aProperty, aRawData, stringValue); 1137 aResult.mValue.Construct(stringValue); 1138 } 1139 1140 if (aTimingFunction) { 1141 aResult.mEasing.Construct(); 1142 aTimingFunction->AppendToString(aResult.mEasing.Value()); 1143 } else { 1144 aResult.mEasing.Construct("linear"_ns); 1145 } 1146 1147 aResult.mComposite = aComposite; 1148 } 1149 1150 void KeyframeEffect::GetProperties( 1151 nsTArray<AnimationPropertyDetails>& aProperties, ErrorResult& aRv) const { 1152 const StylePerDocumentStyleData* rawData = 1153 mDocument->EnsureStyleSet().RawData(); 1154 1155 for (const AnimationProperty& property : mProperties) { 1156 AnimationPropertyDetails propertyDetails; 1157 property.mProperty.ToString(propertyDetails.mProperty); 1158 propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor; 1159 1160 nsAutoString localizedString; 1161 if (property.mPerformanceWarning && 1162 property.mPerformanceWarning->ToLocalizedString(localizedString)) { 1163 propertyDetails.mWarning.Construct(localizedString); 1164 } 1165 1166 if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(), 1167 mozilla::fallible)) { 1168 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1169 return; 1170 } 1171 1172 for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length(); 1173 segmentIdx < segmentLen; segmentIdx++) { 1174 const AnimationPropertySegment& segment = property.mSegments[segmentIdx]; 1175 1176 binding_detail::FastAnimationPropertyValueDetails fromValue; 1177 CreatePropertyValue(property.mProperty, segment.mFromKey, 1178 segment.mTimingFunction, segment.mFromValue, 1179 segment.mFromComposite, rawData, fromValue); 1180 // We don't apply timing functions for zero-length segments, so 1181 // don't return one here. 1182 if (segment.mFromKey == segment.mToKey) { 1183 fromValue.mEasing.Reset(); 1184 } 1185 // Even though we called SetCapacity before, this could fail, since we 1186 // might add multiple elements to propertyDetails.mValues for an element 1187 // of property.mSegments in the cases mentioned below. 1188 if (!propertyDetails.mValues.AppendElement(fromValue, 1189 mozilla::fallible)) { 1190 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1191 return; 1192 } 1193 1194 // Normally we can ignore the to-value for this segment since it is 1195 // identical to the from-value from the next segment. However, we need 1196 // to add it if either: 1197 // a) this is the last segment, or 1198 // b) the next segment's from-value differs. 1199 if (segmentIdx == segmentLen - 1 || 1200 property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) { 1201 binding_detail::FastAnimationPropertyValueDetails toValue; 1202 CreatePropertyValue(property.mProperty, segment.mToKey, Nothing(), 1203 segment.mToValue, segment.mToComposite, rawData, 1204 toValue); 1205 // It doesn't really make sense to have a timing function on the 1206 // last property value or before a sudden jump so we just drop the 1207 // easing property altogether. 1208 toValue.mEasing.Reset(); 1209 if (!propertyDetails.mValues.AppendElement(toValue, 1210 mozilla::fallible)) { 1211 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1212 return; 1213 } 1214 } 1215 } 1216 1217 aProperties.AppendElement(propertyDetails); 1218 } 1219 } 1220 1221 void KeyframeEffect::GetKeyframes(JSContext* aCx, nsTArray<JSObject*>& aResult, 1222 ErrorResult& aRv) const { 1223 MOZ_ASSERT(aResult.IsEmpty()); 1224 MOZ_ASSERT(!aRv.Failed()); 1225 1226 if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) { 1227 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1228 return; 1229 } 1230 1231 bool isCSSAnimation = mAnimation && mAnimation->AsCSSAnimation(); 1232 1233 // For Servo, when we have CSS Animation @keyframes with variables, we convert 1234 // shorthands to longhands if needed, and store a reference to the unparsed 1235 // value. When it comes time to serialize, however, what do you serialize for 1236 // a longhand that comes from a variable reference in a shorthand? Servo says, 1237 // "an empty string" which is not particularly helpful. 1238 // 1239 // We should just store shorthands as-is (bug 1391537) and then return the 1240 // variable references, but for now, since we don't do that, and in order to 1241 // be consistent with Gecko, we just expand the variables (assuming we have 1242 // enough context to do so). For that we need to grab the ComputedStyle so we 1243 // know what custom property values to provide. 1244 RefPtr<const ComputedStyle> computedStyle; 1245 if (isCSSAnimation) { 1246 // The following will flush style but that's ok since if you update a 1247 // variable's computed value, you expect to see that updated value in the 1248 // result of getKeyframes(). 1249 // 1250 // If we don't have a target, the following will return null. In that case 1251 // we might end up returning variables as-is or empty string. That should be 1252 // acceptable however, since such a case is rare and this is only 1253 // short-term (and unshipped) behavior until bug 1391537 is fixed. 1254 computedStyle = GetTargetComputedStyle(Flush::Style); 1255 } 1256 1257 const StylePerDocumentStyleData* rawData = 1258 mDocument->EnsureStyleSet().RawData(); 1259 1260 for (const Keyframe& keyframe : mKeyframes) { 1261 // Set up a dictionary object for the explicit members 1262 BaseComputedKeyframe keyframeDict; 1263 if (keyframe.mOffset) { 1264 keyframeDict.mOffset.SetValue(keyframe.mOffset.value()); 1265 } 1266 MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet, 1267 "Invalid computed offset"); 1268 keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset); 1269 if (keyframe.mTimingFunction) { 1270 keyframeDict.mEasing.Truncate(); 1271 keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing); 1272 } // else if null, leave easing as its default "linear". 1273 1274 keyframeDict.mComposite = keyframe.mComposite; 1275 1276 JS::Rooted<JS::Value> keyframeJSValue(aCx); 1277 if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) { 1278 aRv.Throw(NS_ERROR_FAILURE); 1279 return; 1280 } 1281 1282 JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject()); 1283 for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) { 1284 nsAutoCString stringValue; 1285 if (propertyValue.mServoDeclarationBlock) { 1286 Servo_DeclarationBlock_SerializeOneValue( 1287 propertyValue.mServoDeclarationBlock, &propertyValue.mProperty, 1288 &stringValue, computedStyle, rawData); 1289 } else if (auto* value = mBaseValues.GetWeak(propertyValue.mProperty)) { 1290 Servo_AnimationValue_Serialize(value, &propertyValue.mProperty, rawData, 1291 &stringValue); 1292 } 1293 1294 // Basically, we need to do the mapping: 1295 // * eCSSProperty_offset => "cssOffset" 1296 // * eCSSProperty_float => "cssFloat" 1297 // This means if property refers to the CSS "offset"/"float" property, 1298 // return the string "cssOffset"/"cssFloat". (So avoid overlapping 1299 // "offset" property in BaseKeyframe.) 1300 // https://drafts.csswg.org/web-animations/#property-name-conversion 1301 const char* name = nullptr; 1302 nsAutoCString customName; 1303 switch (propertyValue.mProperty.mId) { 1304 case NonCustomCSSPropertyId::eCSSPropertyExtra_variable: 1305 customName.Append("--"); 1306 customName.Append(nsAtomCString(propertyValue.mProperty.mCustomName)); 1307 name = customName.get(); 1308 break; 1309 case NonCustomCSSPropertyId::eCSSProperty_offset: 1310 name = "cssOffset"; 1311 break; 1312 case NonCustomCSSPropertyId::eCSSProperty_float: 1313 // FIXME: Bug 1582314: Should handle cssFloat manually if we remove it 1314 // from nsCSSProps::PropertyIDLName(). 1315 default: 1316 name = nsCSSProps::PropertyIDLName(propertyValue.mProperty.mId); 1317 } 1318 1319 JS::Rooted<JS::Value> value(aCx); 1320 if (!NonVoidUTF8StringToJsval(aCx, stringValue, &value) || 1321 !JS_DefineProperty(aCx, keyframeObject, name, value, 1322 JSPROP_ENUMERATE)) { 1323 aRv.Throw(NS_ERROR_FAILURE); 1324 return; 1325 } 1326 } 1327 1328 aResult.AppendElement(keyframeObject); 1329 } 1330 } 1331 1332 /* static */ const TimeDuration 1333 KeyframeEffect::OverflowRegionRefreshInterval() { 1334 // The amount of time we can wait between updating throttled animations 1335 // on the main thread that influence the overflow region. 1336 static const TimeDuration kOverflowRegionRefreshInterval = 1337 TimeDuration::FromMilliseconds(200); 1338 1339 return kOverflowRegionRefreshInterval; 1340 } 1341 1342 static bool CanOptimizeAwayDueToOpacity(const KeyframeEffect& aEffect, 1343 const nsIFrame& aFrame) { 1344 if (!aFrame.Style()->IsInOpacityZeroSubtree()) { 1345 return false; 1346 } 1347 1348 // Find the root of the opacity: 0 subtree. 1349 const nsIFrame* root = &aFrame; 1350 while (true) { 1351 auto* parent = root->GetInFlowParent(); 1352 if (!parent || !parent->Style()->IsInOpacityZeroSubtree()) { 1353 break; 1354 } 1355 root = parent; 1356 } 1357 1358 MOZ_ASSERT(root && root->Style()->IsInOpacityZeroSubtree()); 1359 1360 // Even if we're in an opacity: zero subtree, if the root of the subtree may 1361 // have an opacity animation, we can't optimize us away, as we may become 1362 // visible ourselves. 1363 return (root != &aFrame || !aEffect.HasOpacityChange()) && 1364 !root->HasAnimationOfOpacity(); 1365 } 1366 1367 bool KeyframeEffect::CanThrottleIfNotVisible(nsIFrame& aFrame) const { 1368 // Unless we are newly in-effect, we can throttle the animation if the 1369 // animation is paint only and the target frame is out of view or the document 1370 // is in background tabs. 1371 if (!mInEffectOnLastAnimationTimingUpdate || !CanIgnoreIfNotVisible()) { 1372 return false; 1373 } 1374 1375 PresShell* presShell = GetPresShell(); 1376 if (presShell && !presShell->IsActive()) { 1377 return true; 1378 } 1379 1380 // The frame may be indirectly rendered as a mask or clipPath or via a use 1381 // element. 1382 if (SVGObserverUtils::SelfOrAncestorHasRenderingObservers(&aFrame)) { 1383 return false; 1384 } 1385 1386 const bool isVisibilityHidden = 1387 !aFrame.IsVisibleOrMayHaveVisibleDescendants(); 1388 const bool canOptimizeAwayVisibility = 1389 isVisibilityHidden && !HasVisibilityChange(); 1390 1391 const bool invisible = canOptimizeAwayVisibility || 1392 CanOptimizeAwayDueToOpacity(*this, aFrame) || 1393 aFrame.IsScrolledOutOfView(); 1394 if (!invisible) { 1395 return false; 1396 } 1397 1398 // If there are no overflow change hints, we don't need to worry about 1399 // unthrottling the animation periodically to update scrollbar positions for 1400 // the overflow region. 1401 if (!HasPropertiesThatMightAffectOverflow()) { 1402 return true; 1403 } 1404 1405 // Don't throttle finite animations since the animation might suddenly 1406 // come into view and if it was throttled it will be out-of-sync. 1407 if (HasFiniteActiveDuration()) { 1408 return false; 1409 } 1410 1411 return isVisibilityHidden ? CanThrottleOverflowChangesInScrollable(aFrame) 1412 : CanThrottleOverflowChanges(aFrame); 1413 } 1414 1415 bool KeyframeEffect::CanThrottle() const { 1416 // Unthrottle if we are not in effect or current. This will be the case when 1417 // our owning animation has finished, is idle, or when we are in the delay 1418 // phase (but without a backwards fill). In each case the computed progress 1419 // value produced on each tick will be the same so we will skip requesting 1420 // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get 1421 // here will be because of a change in state (e.g. we are newly finished or 1422 // newly no longer in effect) in which case we shouldn't throttle the sample. 1423 if (!IsInEffect() || !IsCurrent()) { 1424 return false; 1425 } 1426 1427 nsIFrame* const frame = GetStyleFrame(); 1428 if (!frame) { 1429 // There are two possible cases here. 1430 // a) No target element 1431 // b) The target element has no frame, e.g. because it is in a display:none 1432 // subtree. 1433 // In either case we can throttle the animation because there is no 1434 // need to update on the main thread. 1435 return true; 1436 } 1437 1438 // Do not throttle any animations during print preview. 1439 if (frame->PresContext()->IsPrintingOrPrintPreview()) { 1440 return false; 1441 } 1442 1443 if (CanThrottleIfNotVisible(*frame)) { 1444 return true; 1445 } 1446 1447 EffectSet* effectSet = nullptr; 1448 for (const AnimationProperty& property : mProperties) { 1449 if (!property.mIsRunningOnCompositor) { 1450 return false; 1451 } 1452 1453 MOZ_ASSERT(nsCSSPropertyIDSet::CompositorAnimatables().HasProperty( 1454 property.mProperty), 1455 "The property should be able to run on the compositor"); 1456 if (!effectSet) { 1457 effectSet = EffectSet::Get(mTarget); 1458 MOZ_ASSERT(effectSet, 1459 "CanThrottle should be called on an effect " 1460 "associated with a target element"); 1461 } 1462 1463 DisplayItemType displayItemType = 1464 LayerAnimationInfo::GetDisplayItemTypeForProperty( 1465 property.mProperty.mId); 1466 1467 // Note that AnimationInfo::GetGenarationFromFrame() is supposed to work 1468 // with the primary frame instead of the style frame. 1469 Maybe<uint64_t> generation = layers::AnimationInfo::GetGenerationFromFrame( 1470 GetPrimaryFrame(), displayItemType); 1471 // Unthrottle if the animation needs to be brought up to date 1472 if (!generation || effectSet->GetAnimationGeneration() != *generation) { 1473 return false; 1474 } 1475 1476 MOZ_ASSERT(HasEffectiveAnimationOfProperty(property.mProperty, *effectSet), 1477 "There should be an effective animation of the property while " 1478 "it is marked as being run on the compositor"); 1479 1480 // If this is a transform animation that affects the overflow region, 1481 // we should unthrottle the animation periodically. 1482 if (HasPropertiesThatMightAffectOverflow() && 1483 !CanThrottleOverflowChangesInScrollable(*frame)) { 1484 return false; 1485 } 1486 } 1487 1488 return true; 1489 } 1490 1491 bool KeyframeEffect::CanThrottleOverflowChanges(const nsIFrame& aFrame) const { 1492 TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh(); 1493 1494 EffectSet* effectSet = EffectSet::Get(mTarget); 1495 MOZ_ASSERT(effectSet, 1496 "CanOverflowTransformChanges is expected to be called" 1497 " on an effect in an effect set"); 1498 MOZ_ASSERT(mAnimation, 1499 "CanOverflowTransformChanges is expected to be called" 1500 " on an effect with a parent animation"); 1501 TimeStamp lastSyncTime = effectSet->LastOverflowAnimationSyncTime(); 1502 // If this animation can cause overflow, we can throttle some of the ticks. 1503 return (!lastSyncTime.IsNull() && 1504 (now - lastSyncTime) < OverflowRegionRefreshInterval()); 1505 } 1506 1507 bool KeyframeEffect::CanThrottleOverflowChangesInScrollable( 1508 nsIFrame& aFrame) const { 1509 // If the target element is not associated with any documents, we don't care 1510 // it. 1511 Document* doc = GetRenderedDocument(); 1512 if (!doc) { 1513 return true; 1514 } 1515 1516 // If we know that the animation cannot cause overflow, 1517 // we can just disable flushes for this animation. 1518 1519 // If we have no intersection observers, we don't care about overflow. 1520 if (!doc->HasIntersectionObservers()) { 1521 return true; 1522 } 1523 1524 if (CanThrottleOverflowChanges(aFrame)) { 1525 return true; 1526 } 1527 1528 // If the nearest scroll container ancestor has overflow:hidden, 1529 // we don't care about overflow. 1530 ScrollContainerFrame* scrollContainerFrame = 1531 nsLayoutUtils::GetNearestScrollContainerFrame(&aFrame); 1532 if (!scrollContainerFrame) { 1533 return true; 1534 } 1535 1536 ScrollStyles ss = scrollContainerFrame->GetScrollStyles(); 1537 if (ss.mVertical == StyleOverflow::Hidden && 1538 ss.mHorizontal == StyleOverflow::Hidden && 1539 scrollContainerFrame->GetLogicalScrollPosition() == nsPoint(0, 0)) { 1540 return true; 1541 } 1542 1543 return false; 1544 } 1545 1546 nsIFrame* KeyframeEffect::GetStyleFrame() const { 1547 nsIFrame* frame = GetPrimaryFrame(); 1548 if (!frame) { 1549 return nullptr; 1550 } 1551 1552 return nsLayoutUtils::GetStyleFrame(frame); 1553 } 1554 1555 nsIFrame* KeyframeEffect::GetPrimaryFrame() const { 1556 nsIFrame* frame = nullptr; 1557 if (!mTarget) { 1558 return frame; 1559 } 1560 1561 switch (mTarget.mPseudoRequest.mType) { 1562 case PseudoStyleType::before: 1563 frame = nsLayoutUtils::GetBeforeFrame(mTarget.mElement); 1564 break; 1565 case PseudoStyleType::after: 1566 frame = nsLayoutUtils::GetAfterFrame(mTarget.mElement); 1567 break; 1568 case PseudoStyleType::marker: 1569 frame = nsLayoutUtils::GetMarkerFrame(mTarget.mElement); 1570 break; 1571 case PseudoStyleType::backdrop: 1572 frame = nsLayoutUtils::GetBackdropFrame(mTarget.mElement); 1573 break; 1574 case PseudoStyleType::viewTransition: 1575 case PseudoStyleType::viewTransitionGroup: 1576 case PseudoStyleType::viewTransitionImagePair: 1577 case PseudoStyleType::viewTransitionOld: 1578 case PseudoStyleType::viewTransitionNew: 1579 if (Element* pseudoElement = 1580 mTarget.mElement->GetPseudoElement(mTarget.mPseudoRequest)) { 1581 frame = pseudoElement->GetPrimaryFrame(); 1582 } 1583 break; 1584 default: 1585 frame = mTarget.mElement->GetPrimaryFrame(); 1586 MOZ_ASSERT(mTarget.mPseudoRequest.IsNotPseudo(), 1587 "unknown mTarget.mPseudoRequest"); 1588 } 1589 1590 return frame; 1591 } 1592 1593 Document* KeyframeEffect::GetRenderedDocument() const { 1594 if (!mTarget) { 1595 return nullptr; 1596 } 1597 return mTarget.mElement->GetComposedDoc(); 1598 } 1599 1600 PresShell* KeyframeEffect::GetPresShell() const { 1601 Document* doc = GetRenderedDocument(); 1602 if (!doc) { 1603 return nullptr; 1604 } 1605 return doc->GetPresShell(); 1606 } 1607 1608 /* static */ 1609 bool KeyframeEffect::IsGeometricProperty( 1610 const NonCustomCSSPropertyId aProperty) { 1611 MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), 1612 "Property should be a longhand property"); 1613 1614 switch (aProperty) { 1615 case eCSSProperty_bottom: 1616 case eCSSProperty_height: 1617 case eCSSProperty_left: 1618 case eCSSProperty_margin_bottom: 1619 case eCSSProperty_margin_left: 1620 case eCSSProperty_margin_right: 1621 case eCSSProperty_margin_top: 1622 case eCSSProperty_padding_bottom: 1623 case eCSSProperty_padding_left: 1624 case eCSSProperty_padding_right: 1625 case eCSSProperty_padding_top: 1626 case eCSSProperty_right: 1627 case eCSSProperty_top: 1628 case eCSSProperty_width: 1629 return true; 1630 default: 1631 return false; 1632 } 1633 } 1634 1635 /* static */ 1636 bool KeyframeEffect::CanAnimateTransformOnCompositor( 1637 const nsIFrame* aFrame, 1638 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) { 1639 // In some cases, such as when we are simply collecting all the compositor 1640 // animations regardless of the frame on which they run in order to calculate 1641 // change hints, |aFrame| will be the style frame. However, even in that case 1642 // we should look at the primary frame since that is where the transform will 1643 // be applied. 1644 const nsIFrame* primaryFrame = 1645 nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame); 1646 1647 // Async 'transform' animations of aFrames with SVG transforms is not 1648 // supported. See bug 779599. 1649 if (primaryFrame->GetParentSVGTransforms()) { 1650 aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG; 1651 return false; 1652 } 1653 1654 // If there's any content that might have non-scaling stroke then we can't 1655 // run in the compositor. 1656 if (primaryFrame->IsSVGFrame() && 1657 primaryFrame->HasAnyStateBits( 1658 NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE)) { 1659 aPerformanceWarning = AnimationPerformanceWarning::Type::NonScalingStroke; 1660 return false; 1661 } 1662 1663 return true; 1664 } 1665 1666 bool KeyframeEffect::ShouldBlockAsyncTransformAnimations( 1667 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet, 1668 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const { 1669 // If we depend on the SVG url (no matter whether there are any offset-path 1670 // animations), we cannot run any transform-like animations in the compositor 1671 // because we cannot resolve the url in the compositor if its style uses url. 1672 if (aFrame->StyleDisplay()->mOffsetPath.IsUrl()) { 1673 return true; 1674 } 1675 1676 EffectSet* effectSet = EffectSet::Get(mTarget); 1677 // The various transform properties ('transform', 'scale' etc.) get combined 1678 // on the compositor. 1679 // 1680 // As a result, if we have an animation of 'scale' and 'translate', but the 1681 // 'translate' property is covered by an !important rule, we will not be 1682 // able to combine the result on the compositor since we won't have the 1683 // !important rule to incorporate. In that case we should run all the 1684 // transform-related animations on the main thread (where we have the 1685 // !important rule). 1686 nsCSSPropertyIDSet blockedProperties = 1687 effectSet->PropertiesForAnimationsLevel().Intersect( 1688 effectSet->PropertiesWithImportantRules()); 1689 if (blockedProperties.Intersects(aPropertySet)) { 1690 aPerformanceWarning = 1691 AnimationPerformanceWarning::Type::TransformIsBlockedByImportantRules; 1692 return true; 1693 } 1694 1695 MOZ_ASSERT(mAnimation); 1696 for (const AnimationProperty& property : mProperties) { 1697 // If there is a property for animations level that is overridden by 1698 // !important rules, it should not block other animations from running 1699 // on the compositor. 1700 // NOTE: We don't currently check for !important rules for properties that 1701 // don't run on the compositor. As result such properties (e.g. margin-left) 1702 // can still block async animations even if they are overridden by 1703 // !important rules. 1704 if (effectSet && 1705 effectSet->PropertiesWithImportantRules().HasProperty( 1706 property.mProperty) && 1707 effectSet->PropertiesForAnimationsLevel().HasProperty( 1708 property.mProperty)) { 1709 continue; 1710 } 1711 1712 // Check for unsupported transform animations 1713 if (LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM) 1714 .HasProperty(property.mProperty)) { 1715 if (!CanAnimateTransformOnCompositor(aFrame, aPerformanceWarning)) { 1716 return true; 1717 } 1718 } 1719 1720 // If there are any offset-path animations whose animation values are url(), 1721 // we have to sync with the main thread when resolving it. 1722 if (property.mProperty.mId == eCSSProperty_offset_path) { 1723 for (const auto& seg : property.mSegments) { 1724 if (seg.mFromValue.IsOffsetPathUrl() || 1725 seg.mToValue.IsOffsetPathUrl()) { 1726 return true; 1727 } 1728 } 1729 } 1730 } 1731 1732 return false; 1733 } 1734 1735 bool KeyframeEffect::HasGeometricProperties() const { 1736 for (const AnimationProperty& property : mProperties) { 1737 if (IsGeometricProperty(property.mProperty.mId)) { 1738 return true; 1739 } 1740 } 1741 1742 return false; 1743 } 1744 1745 void KeyframeEffect::SetPerformanceWarning( 1746 const nsCSSPropertyIDSet& aPropertySet, 1747 const AnimationPerformanceWarning& aWarning) { 1748 nsCSSPropertyIDSet curr = aPropertySet; 1749 for (AnimationProperty& property : mProperties) { 1750 if (!curr.HasProperty(property.mProperty)) { 1751 continue; 1752 } 1753 property.SetPerformanceWarning(aWarning, mTarget.mElement); 1754 curr.RemoveProperty(property.mProperty.mId); 1755 if (curr.IsEmpty()) { 1756 return; 1757 } 1758 } 1759 } 1760 1761 void KeyframeEffect::CalculateCumulativeChangesForProperty( 1762 const AnimationProperty& aProperty) { 1763 if (aProperty.mProperty.IsCustom()) { 1764 // Custom properties don't affect rendering on their own. 1765 return; 1766 } 1767 1768 constexpr auto kInterestingFlags = 1769 CSSPropFlags::AffectsLayout | CSSPropFlags::AffectsOverflow; 1770 if (aProperty.mProperty.mId == eCSSProperty_opacity) { 1771 mCumulativeChanges.mOpacity = true; 1772 return; // We know opacity is visual-only. 1773 } 1774 1775 if (aProperty.mProperty.mId == eCSSProperty_visibility) { 1776 mCumulativeChanges.mVisibility = true; 1777 return; // We know visibility is visual-only. 1778 } 1779 1780 if (aProperty.mProperty.mId == eCSSProperty_background_color) { 1781 if (!mCumulativeChanges.mHasBackgroundColorCurrentColor) { 1782 mCumulativeChanges.mHasBackgroundColorCurrentColor = 1783 HasCurrentColor(aProperty.mSegments); 1784 } 1785 return; // We know background-color is visual-only. 1786 } 1787 1788 auto flags = nsCSSProps::PropFlags(aProperty.mProperty.mId); 1789 if (!(flags & kInterestingFlags)) { 1790 return; // Property is visual-only. 1791 } 1792 1793 bool anyChange = false; 1794 for (const AnimationPropertySegment& segment : aProperty.mSegments) { 1795 if (!segment.HasReplaceableValues() || 1796 segment.mFromValue != segment.mToValue) { 1797 // We can't know non-replaceable values until we compose the animation, so 1798 // assume a change there. 1799 anyChange = true; 1800 break; 1801 } 1802 } 1803 1804 if (!anyChange) { 1805 return; 1806 } 1807 1808 mCumulativeChanges.mOverflow |= bool(flags & CSSPropFlags::AffectsOverflow); 1809 mCumulativeChanges.mLayout |= bool(flags & CSSPropFlags::AffectsLayout); 1810 } 1811 1812 void KeyframeEffect::SetAnimation(Animation* aAnimation) { 1813 if (mAnimation == aAnimation) { 1814 return; 1815 } 1816 1817 // Restyle for the old animation. 1818 RequestRestyle(EffectCompositor::RestyleType::Layer); 1819 1820 mAnimation = aAnimation; 1821 1822 UpdateNormalizedTiming(); 1823 1824 // The order of these function calls is important: 1825 // NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check 1826 // if it should create the effectSet or not, and MarkCascadeNeedsUpdate() 1827 // needs a valid effectSet, so we should call them in this order. 1828 if (mAnimation) { 1829 mAnimation->UpdateRelevance(); 1830 } 1831 NotifyAnimationTimingUpdated(PostRestyleMode::IfNeeded); 1832 if (mAnimation) { 1833 MarkCascadeNeedsUpdate(); 1834 } 1835 } 1836 1837 bool KeyframeEffect::CanIgnoreIfNotVisible() const { 1838 if (!StaticPrefs::dom_animations_offscreen_throttling()) { 1839 return false; 1840 } 1841 return !mCumulativeChanges.mLayout; 1842 } 1843 1844 void KeyframeEffect::MarkCascadeNeedsUpdate() { 1845 if (!mTarget) { 1846 return; 1847 } 1848 1849 EffectSet* effectSet = EffectSet::Get(mTarget); 1850 if (!effectSet) { 1851 return; 1852 } 1853 effectSet->MarkCascadeNeedsUpdate(); 1854 } 1855 1856 /* static */ 1857 bool KeyframeEffect::HasComputedTimingChanged( 1858 const ComputedTiming& aComputedTiming, 1859 IterationCompositeOperation aIterationComposite, 1860 const Nullable<double>& aProgressOnLastCompose, 1861 uint64_t aCurrentIterationOnLastCompose) { 1862 // Typically we don't need to request a restyle if the progress hasn't 1863 // changed since the last call to ComposeStyle. The one exception is if the 1864 // iteration composite mode is 'accumulate' and the current iteration has 1865 // changed, since that will often produce a different result. 1866 return aComputedTiming.mProgress != aProgressOnLastCompose || 1867 (aIterationComposite == IterationCompositeOperation::Accumulate && 1868 aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose); 1869 } 1870 1871 bool KeyframeEffect::HasComputedTimingChanged() const { 1872 ComputedTiming computedTiming = GetComputedTiming(); 1873 return HasComputedTimingChanged( 1874 computedTiming, mEffectOptions.mIterationComposite, 1875 mProgressOnLastCompose, mCurrentIterationOnLastCompose); 1876 } 1877 1878 bool KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const { 1879 // For display:table content, transform animations run on the table wrapper 1880 // frame. If we are being passed a frame that doesn't support transforms 1881 // (i.e. the inner table frame) we could just return false, but it possibly 1882 // means we looked up the wrong EffectSet so for now we just assert instead. 1883 MOZ_ASSERT(aFrame && aFrame->SupportsCSSTransforms(), 1884 "We should be passed a frame that supports transforms"); 1885 1886 if (!IsCurrent()) { 1887 return false; 1888 } 1889 1890 if (!mAnimation || 1891 mAnimation->ReplaceState() == AnimationReplaceState::Removed) { 1892 return false; 1893 } 1894 1895 for (const AnimationProperty& prop : mProperties) { 1896 if (prop.mProperty.mId != eCSSProperty_transform && 1897 prop.mProperty.mId != eCSSProperty_scale && 1898 prop.mProperty.mId != eCSSProperty_rotate) { 1899 continue; 1900 } 1901 1902 AnimationValue baseStyle = BaseStyle(prop.mProperty); 1903 if (!baseStyle.IsNull()) { 1904 gfx::MatrixScales size = baseStyle.GetScaleValue(aFrame); 1905 if (size != gfx::MatrixScales()) { 1906 return true; 1907 } 1908 } 1909 1910 // This is actually overestimate because there are some cases that combining 1911 // the base value and from/to value produces 1:1 scale. But it doesn't 1912 // really matter. 1913 for (const AnimationPropertySegment& segment : prop.mSegments) { 1914 if (!segment.mFromValue.IsNull()) { 1915 gfx::MatrixScales from = segment.mFromValue.GetScaleValue(aFrame); 1916 if (from != gfx::MatrixScales()) { 1917 return true; 1918 } 1919 } 1920 if (!segment.mToValue.IsNull()) { 1921 gfx::MatrixScales to = segment.mToValue.GetScaleValue(aFrame); 1922 if (to != gfx::MatrixScales()) { 1923 return true; 1924 } 1925 } 1926 } 1927 } 1928 1929 return false; 1930 } 1931 1932 void KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const { 1933 if (!mInEffectSet) { 1934 return; 1935 } 1936 1937 EffectSet* effectSet = aEffectSet ? aEffectSet : EffectSet::Get(mTarget); 1938 if (!effectSet) { 1939 return; 1940 } 1941 1942 nsIFrame* styleFrame = GetStyleFrame(); 1943 if (HasAnimationOfPropertySet(nsCSSPropertyIDSet::OpacityProperties())) { 1944 effectSet->SetMayHaveOpacityAnimation(); 1945 EnumerateContinuationsOrIBSplitSiblings(styleFrame, [](nsIFrame* aFrame) { 1946 aFrame->SetMayHaveOpacityAnimation(); 1947 }); 1948 } 1949 1950 nsIFrame* primaryFrame = GetPrimaryFrame(); 1951 if (HasAnimationOfPropertySet( 1952 nsCSSPropertyIDSet::TransformLikeProperties())) { 1953 effectSet->SetMayHaveTransformAnimation(); 1954 // For table frames, it's not clear if we should iterate over the 1955 // continuations of the table wrapper or the inner table frame. 1956 // 1957 // Fortunately, this is not currently an issue because we only split tables 1958 // when printing (page breaks) where we don't run animations. 1959 EnumerateContinuationsOrIBSplitSiblings(primaryFrame, [](nsIFrame* aFrame) { 1960 aFrame->SetMayHaveTransformAnimation(); 1961 }); 1962 } 1963 } 1964 1965 KeyframeEffect::MatchForCompositor KeyframeEffect::IsMatchForCompositor( 1966 const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame, 1967 const EffectSet& aEffects, 1968 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const { 1969 MOZ_ASSERT(mAnimation); 1970 1971 if (!mAnimation->IsRelevant()) { 1972 return KeyframeEffect::MatchForCompositor::No; 1973 } 1974 1975 if (mAnimation->ShouldBeSynchronizedWithMainThread(aPropertySet, aFrame, 1976 aPerformanceWarning)) { 1977 // For a given |aFrame|, we don't want some animations of |aProperty| to 1978 // run on the compositor and others to run on the main thread, so if any 1979 // need to be synchronized with the main thread, run them all there. 1980 return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty; 1981 } 1982 1983 if (mAnimation->UsingScrollTimeline()) { 1984 const ScrollTimeline* scrollTimeline = 1985 mAnimation->GetTimeline()->AsScrollTimeline(); 1986 // We don't send this animation to the compositor if 1987 // 1. the APZ is disabled entirely or for the source, or 1988 // 2. the associated scroll-timeline is inactive, or 1989 // 3. the scrolling direction is not available (i.e. no scroll range). 1990 // 4. the scroll style of the scroller is overflow:hidden. 1991 if (!scrollTimeline->APZIsActiveForSource() || 1992 !scrollTimeline->IsActive() || 1993 !scrollTimeline->ScrollingDirectionIsAvailable() || 1994 scrollTimeline->SourceScrollStyle() == StyleOverflow::Hidden) { 1995 return KeyframeEffect::MatchForCompositor::No; 1996 } 1997 1998 // FIXME: Bug 1818346. Support OMTA for view-timeline. We disable it for now 1999 // because we need to make view-timeline-inset animations run on the OMTA as 2000 // well before enable this. 2001 if (scrollTimeline->IsViewTimeline()) { 2002 return KeyframeEffect::MatchForCompositor::No; 2003 } 2004 } 2005 2006 if (!HasEffectiveAnimationOfPropertySet(aPropertySet, aEffects)) { 2007 return KeyframeEffect::MatchForCompositor::No; 2008 } 2009 2010 // If we know that the animation is not visible, we don't need to send the 2011 // animation to the compositor. 2012 if (!aFrame->IsVisibleOrMayHaveVisibleDescendants() || 2013 CanOptimizeAwayDueToOpacity(*this, *aFrame) || 2014 aFrame->IsScrolledOutOfView()) { 2015 return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty; 2016 } 2017 2018 if (aPropertySet.HasProperty(eCSSProperty_background_color)) { 2019 if (!StaticPrefs::gfx_omta_background_color()) { 2020 return KeyframeEffect::MatchForCompositor::No; 2021 } 2022 2023 // We don't yet support off-main-thread background-color animations on 2024 // canvas frame or on <html> or <body> which generate 2025 // nsDisplayCanvasBackgroundColor or nsDisplaySolidColor display item. 2026 if (aFrame->IsCanvasFrame() || 2027 (aFrame->GetContent() && 2028 (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::body) || 2029 aFrame->GetContent()->IsHTMLElement(nsGkAtoms::html)))) { 2030 return KeyframeEffect::MatchForCompositor::No; 2031 } 2032 } 2033 2034 // We can't run this background color animation on the compositor if there 2035 // is any `current-color` keyframe. 2036 if (mCumulativeChanges.mHasBackgroundColorCurrentColor) { 2037 aPerformanceWarning = AnimationPerformanceWarning::Type::HasCurrentColor; 2038 return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty; 2039 } 2040 2041 return mAnimation->IsPlaying() ? KeyframeEffect::MatchForCompositor::Yes 2042 : KeyframeEffect::MatchForCompositor::IfNeeded; 2043 } 2044 2045 } // namespace dom 2046 } // namespace mozilla