ActiveLayerTracker.cpp (15798B)
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 "ActiveLayerTracker.h" 8 9 #include "gfx2DGlue.h" 10 #include "mozilla/AnimationUtils.h" 11 #include "mozilla/EffectSet.h" 12 #include "mozilla/MotionPathUtils.h" 13 #include "mozilla/PodOperations.h" 14 #include "mozilla/StaticPtr.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/gfx/Matrix.h" 17 #include "mozilla/gfx/gfxVars.h" 18 #include "nsAnimationManager.h" 19 #include "nsContainerFrame.h" 20 #include "nsDOMCSSDeclaration.h" 21 #include "nsDisplayList.h" 22 #include "nsExpirationTracker.h" 23 #include "nsIContent.h" 24 #include "nsLayoutUtils.h" 25 #include "nsPIDOMWindow.h" 26 #include "nsRefreshDriver.h" 27 #include "nsStyleTransformMatrix.h" 28 #include "nsTransitionManager.h" 29 30 namespace mozilla { 31 32 using namespace gfx; 33 34 /** 35 * This tracks the state of a frame that may need active layers due to 36 * ongoing content changes or style changes that indicate animation. 37 * 38 * When no changes of *any* kind are detected after 75-100ms we remove this 39 * object. Because we only track all kinds of activity with a single 40 * nsExpirationTracker, it's possible a frame might remain active somewhat 41 * spuriously if different kinds of changes kept happening, but that almost 42 * certainly doesn't matter. 43 */ 44 class LayerActivity { 45 public: 46 enum ActivityIndex { 47 ACTIVITY_OPACITY, 48 ACTIVITY_TRANSFORM, 49 50 ACTIVITY_SCALE, 51 ACTIVITY_TRIGGERED_REPAINT, 52 53 // keep as last item 54 ACTIVITY_COUNT 55 }; 56 57 explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) { 58 PodArrayZero(mRestyleCounts); 59 } 60 ~LayerActivity(); 61 nsExpirationState* GetExpirationState() { return &mState; } 62 uint8_t& RestyleCountForProperty(NonCustomCSSPropertyId aProperty) { 63 return mRestyleCounts[GetActivityIndexForProperty(aProperty)]; 64 } 65 66 static ActivityIndex GetActivityIndexForProperty( 67 NonCustomCSSPropertyId aProperty) { 68 switch (aProperty) { 69 case eCSSProperty_opacity: 70 return ACTIVITY_OPACITY; 71 case eCSSProperty_transform: 72 case eCSSProperty_translate: 73 case eCSSProperty_rotate: 74 case eCSSProperty_scale: 75 case eCSSProperty_offset_path: 76 case eCSSProperty_offset_distance: 77 case eCSSProperty_offset_rotate: 78 case eCSSProperty_offset_anchor: 79 case eCSSProperty_offset_position: 80 return ACTIVITY_TRANSFORM; 81 default: 82 MOZ_ASSERT(false); 83 return ACTIVITY_OPACITY; 84 } 85 } 86 87 static ActivityIndex GetActivityIndexForPropertySet( 88 const nsCSSPropertyIDSet& aPropertySet) { 89 if (aPropertySet.IsSubsetOf( 90 nsCSSPropertyIDSet::TransformLikeProperties())) { 91 return ACTIVITY_TRANSFORM; 92 } 93 MOZ_ASSERT( 94 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties())); 95 return ACTIVITY_OPACITY; 96 } 97 98 // While tracked, exactly one of mFrame or mContent is non-null, depending 99 // on whether this property is stored on a frame or on a content node. 100 // When this property is expired by the layer activity tracker, both mFrame 101 // and mContent are nulled-out and the property is deleted. 102 nsIFrame* mFrame; 103 nsIContent* mContent; 104 105 nsExpirationState mState; 106 107 // Previous scale due to the CSS transform property. 108 Maybe<MatrixScales> mPreviousTransformScale; 109 110 // Number of restyle operations detected 111 uint8_t mRestyleCounts[ACTIVITY_COUNT]; 112 }; 113 114 class LayerActivityTracker final 115 : public nsExpirationTracker<LayerActivity, 4> { 116 public: 117 // 75-100ms is a good timeout period. We use 4 generations of 25ms each. 118 enum { GENERATION_MS = 100 }; 119 120 explicit LayerActivityTracker(nsIEventTarget* aEventTarget) 121 : nsExpirationTracker<LayerActivity, 4>( 122 GENERATION_MS, "LayerActivityTracker"_ns, aEventTarget) {} 123 ~LayerActivityTracker() override { AgeAllGenerations(); } 124 125 void NotifyExpired(LayerActivity* aObject) override; 126 }; 127 128 static StaticAutoPtr<LayerActivityTracker> gLayerActivityTracker; 129 130 LayerActivity::~LayerActivity() { 131 if (mFrame || mContent) { 132 NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker"); 133 gLayerActivityTracker->RemoveObject(this); 134 } 135 } 136 137 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set 138 NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity) 139 140 void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) { 141 RemoveObject(aObject); 142 143 nsIFrame* f = aObject->mFrame; 144 nsIContent* c = aObject->mContent; 145 aObject->mFrame = nullptr; 146 aObject->mContent = nullptr; 147 148 MOZ_ASSERT((f == nullptr) != (c == nullptr), 149 "A LayerActivity object should always have a reference to either " 150 "its frame or its content"); 151 152 if (f) { 153 f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); 154 f->RemoveProperty(LayerActivityProperty()); 155 } else { 156 c->RemoveProperty(nsGkAtoms::LayerActivity); 157 } 158 } 159 160 static LayerActivity* GetLayerActivity(nsIFrame* aFrame) { 161 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { 162 return nullptr; 163 } 164 return aFrame->GetProperty(LayerActivityProperty()); 165 } 166 167 static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) { 168 LayerActivity* layerActivity = GetLayerActivity(aFrame); 169 if (layerActivity) { 170 gLayerActivityTracker->MarkUsed(layerActivity); 171 } else { 172 if (!gLayerActivityTracker) { 173 gLayerActivityTracker = 174 new LayerActivityTracker(GetMainThreadSerialEventTarget()); 175 } 176 layerActivity = new LayerActivity(aFrame); 177 gLayerActivityTracker->AddObject(layerActivity); 178 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); 179 aFrame->SetProperty(LayerActivityProperty(), layerActivity); 180 } 181 return layerActivity; 182 } 183 184 static void IncrementMutationCount(uint8_t* aCount) { 185 *aCount = uint8_t(std::min(0xFF, *aCount + 1)); 186 } 187 188 /* static */ 189 void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, 190 nsIContent* aContent) { 191 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { 192 return; 193 } 194 LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty()); 195 aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); 196 if (!layerActivity) { 197 return; 198 } 199 layerActivity->mFrame = nullptr; 200 layerActivity->mContent = aContent; 201 aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity, 202 nsINode::DeleteProperty<LayerActivity>, true); 203 } 204 205 /* static */ 206 void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, 207 nsIFrame* aFrame) { 208 auto* layerActivity = static_cast<LayerActivity*>( 209 aContent->TakeProperty(nsGkAtoms::LayerActivity)); 210 if (!layerActivity) { 211 return; 212 } 213 layerActivity->mContent = nullptr; 214 layerActivity->mFrame = aFrame; 215 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); 216 aFrame->SetProperty(LayerActivityProperty(), layerActivity); 217 } 218 219 static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, 220 LayerActivity* aActivity) { 221 // This function is basically a simplified copy of 222 // nsDisplayTransform::GetResultingTransformMatrixInternal. 223 224 Matrix parentsChildrenOnlyTransform; 225 const bool parentHasChildrenOnlyTransform = 226 aFrame->GetParentSVGTransforms(&parentsChildrenOnlyTransform); 227 228 const nsStyleDisplay* display = aFrame->StyleDisplay(); 229 if (!aFrame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) || 230 (!display->HasTransformProperty() && !display->HasIndividualTransform() && 231 display->mOffsetPath.IsNone() && !parentHasChildrenOnlyTransform)) { 232 if (aActivity->mPreviousTransformScale.isSome()) { 233 // The transform was removed. 234 aActivity->mPreviousTransformScale = Nothing(); 235 IncrementMutationCount( 236 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); 237 } 238 239 return; 240 } 241 242 Matrix4x4 transform; 243 if (aFrame->IsCSSTransformed()) { 244 // Compute the new scale due to the CSS transform property. 245 // Note: Motion path doesn't contribute to scale factor. (It only has 2d 246 // translate and 2d rotate, so we use Nothing() for it.) 247 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); 248 transform = nsStyleTransformMatrix::ReadTransforms( 249 display->mTranslate, display->mRotate, display->mScale, nullptr, 250 display->mTransform, refBox, AppUnitsPerCSSPixel()); 251 } 252 253 if (parentHasChildrenOnlyTransform) { 254 transform *= Matrix4x4::From2D(parentsChildrenOnlyTransform); 255 } 256 257 Matrix transform2D; 258 if (!transform.Is2D(&transform2D)) { 259 // We don't attempt to handle 3D transforms; just assume the scale changed. 260 aActivity->mPreviousTransformScale = Nothing(); 261 IncrementMutationCount( 262 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); 263 return; 264 } 265 266 MatrixScales scale = transform2D.ScaleFactors(); 267 if (aActivity->mPreviousTransformScale == Some(scale)) { 268 return; // Nothing changed. 269 } 270 271 aActivity->mPreviousTransformScale = Some(scale); 272 IncrementMutationCount( 273 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]); 274 } 275 276 /* static */ 277 void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, 278 NonCustomCSSPropertyId aProperty) { 279 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); 280 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); 281 IncrementMutationCount(&mutationCount); 282 283 if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) { 284 IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity); 285 } 286 } 287 288 static bool IsPresContextInScriptAnimationCallback( 289 nsPresContext* aPresContext) { 290 if (aPresContext->RefreshDriver()->IsInRefresh()) { 291 return true; 292 } 293 // Treat timeouts/setintervals as scripted animation callbacks for our 294 // purposes. 295 nsGlobalWindowInner* win = 296 nsGlobalWindowInner::Cast(aPresContext->Document()->GetInnerWindow()); 297 return win && win->IsRunningTimeout(); 298 } 299 300 /* static */ 301 void ActiveLayerTracker::NotifyInlineStyleRuleModified( 302 nsIFrame* aFrame, NonCustomCSSPropertyId aProperty) { 303 if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { 304 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); 305 // We know this is animated, so just hack the mutation count. 306 layerActivity->RestyleCountForProperty(aProperty) = 0xff; 307 } 308 } 309 310 /* static */ 311 void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) { 312 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); 313 if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { 314 // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic. 315 // Just max out the restyle count if we're in an animation callback. 316 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] = 317 0xFF; 318 } else { 319 IncrementMutationCount( 320 &layerActivity 321 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]); 322 } 323 } 324 325 static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder, 326 nsIFrame* aFrame) { 327 return ActiveLayerTracker::IsStyleAnimated( 328 aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) || 329 (!aFrame->StyleDisplay()->mOffsetPath.IsNone() && 330 ActiveLayerTracker::IsStyleAnimated( 331 aBuilder, aFrame, 332 nsCSSPropertyIDSet{ 333 eCSSProperty_offset_distance, eCSSProperty_offset_rotate, 334 eCSSProperty_offset_anchor, eCSSProperty_offset_position})); 335 } 336 337 /* static */ 338 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder, 339 nsIFrame* aFrame) { 340 return IsStyleAnimated(aBuilder, aFrame, 341 nsCSSPropertyIDSet::CSSTransformProperties()) || 342 IsMotionPathAnimated(aBuilder, aFrame); 343 } 344 345 /* static */ 346 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) { 347 return IsStyleAnimated(nullptr, aFrame, 348 nsCSSPropertyIDSet::CSSTransformProperties()) || 349 IsMotionPathAnimated(nullptr, aFrame); 350 } 351 352 /* static */ 353 bool ActiveLayerTracker::IsStyleAnimated( 354 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, 355 const nsCSSPropertyIDSet& aPropertySet) { 356 MOZ_ASSERT( 357 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) || 358 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()), 359 "Only subset of opacity or transform-like properties set calls this"); 360 361 // For display:table content, transforms are applied to the table wrapper 362 // (primary frame) but their will-change style will be specified on the style 363 // frame and, unlike other transform properties, not inherited. 364 // As a result, for transform properties only we need to be careful to look up 365 // the will-change style on the _style_ frame. 366 const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame); 367 const nsCSSPropertyIDSet transformSet = 368 nsCSSPropertyIDSet::TransformLikeProperties(); 369 if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits & 370 StyleWillChangeBits::TRANSFORM)) && 371 aPropertySet.Intersects(transformSet) && 372 (!aBuilder || 373 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { 374 return true; 375 } 376 if ((aFrame->StyleDisplay()->mWillChange.bits & 377 StyleWillChangeBits::OPACITY) && 378 aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) && 379 (!aBuilder || 380 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) { 381 return !StaticPrefs::gfx_will_change_ignore_opacity(); 382 } 383 384 LayerActivity* layerActivity = GetLayerActivity(aFrame); 385 if (layerActivity) { 386 LayerActivity::ActivityIndex activityIndex = 387 LayerActivity::GetActivityIndexForPropertySet(aPropertySet); 388 if (layerActivity->mRestyleCounts[activityIndex] >= 2) { 389 // If the frame needs to be repainted frequently, we probably don't get 390 // much from treating the property as animated, *unless* this frame's 391 // 'scale' (which includes the bounds changes of a rotation) is changing. 392 // Marking a scaling transform as animating allows us to avoid resizing 393 // the texture, even if we have to repaint the contents of that texture. 394 if (layerActivity 395 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] < 396 2 || 397 (aPropertySet.Intersects(transformSet) && 398 IsScaleSubjectToAnimation(aFrame))) { 399 return true; 400 } 401 } 402 } 403 404 if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) { 405 return true; 406 } 407 408 if (!aPropertySet.Intersects(transformSet) || 409 !aFrame->Combines3DTransformWithAncestors()) { 410 return false; 411 } 412 413 // For preserve-3d, we check if there is any transform animation on its parent 414 // frames in the 3d rendering context. If there is one, this function will 415 // return true. 416 return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet); 417 } 418 419 /* static */ 420 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) { 421 // Check whether JavaScript is animating this frame's scale. 422 LayerActivity* layerActivity = GetLayerActivity(aFrame); 423 if (layerActivity && 424 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) { 425 return true; 426 } 427 428 return AnimationUtils::FrameHasAnimatedScale(aFrame); 429 } 430 431 /* static */ 432 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; } 433 434 } // namespace mozilla