Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / painting / ActiveLayerTracker.cpp
blob053445e48695e6aafddb683f49ec96b03b73972d
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/. */
7 #include "ActiveLayerTracker.h"
9 #include "mozilla/AnimationUtils.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/gfx/gfxVars.h"
12 #include "mozilla/gfx/Matrix.h"
13 #include "mozilla/EffectSet.h"
14 #include "mozilla/MotionPathUtils.h"
15 #include "mozilla/PodOperations.h"
16 #include "mozilla/StaticPtr.h"
17 #include "gfx2DGlue.h"
18 #include "nsExpirationTracker.h"
19 #include "nsContainerFrame.h"
20 #include "nsIContent.h"
21 #include "nsRefreshDriver.h"
22 #include "nsPIDOMWindow.h"
23 #include "mozilla/dom/Document.h"
24 #include "nsAnimationManager.h"
25 #include "nsStyleTransformMatrix.h"
26 #include "nsTransitionManager.h"
27 #include "nsDisplayList.h"
28 #include "nsDOMCSSDeclaration.h"
29 #include "nsLayoutUtils.h"
31 namespace mozilla {
33 using namespace gfx;
35 /**
36 * This tracks the state of a frame that may need active layers due to
37 * ongoing content changes or style changes that indicate animation.
39 * When no changes of *any* kind are detected after 75-100ms we remove this
40 * object. Because we only track all kinds of activity with a single
41 * nsExpirationTracker, it's possible a frame might remain active somewhat
42 * spuriously if different kinds of changes kept happening, but that almost
43 * certainly doesn't matter.
45 class LayerActivity {
46 public:
47 enum ActivityIndex {
48 ACTIVITY_OPACITY,
49 ACTIVITY_TRANSFORM,
51 ACTIVITY_SCALE,
52 ACTIVITY_TRIGGERED_REPAINT,
54 // keep as last item
55 ACTIVITY_COUNT
58 explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) {
59 PodArrayZero(mRestyleCounts);
61 ~LayerActivity();
62 nsExpirationState* GetExpirationState() { return &mState; }
63 uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) {
64 return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
67 static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID 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;
87 static ActivityIndex GetActivityIndexForPropertySet(
88 const nsCSSPropertyIDSet& aPropertySet) {
89 if (aPropertySet.IsSubsetOf(
90 nsCSSPropertyIDSet::TransformLikeProperties())) {
91 return ACTIVITY_TRANSFORM;
93 MOZ_ASSERT(
94 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()));
95 return ACTIVITY_OPACITY;
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;
105 nsExpirationState mState;
107 // Previous scale due to the CSS transform property.
108 Maybe<MatrixScales> mPreviousTransformScale;
110 // Number of restyle operations detected
111 uint8_t mRestyleCounts[ACTIVITY_COUNT];
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 };
120 explicit LayerActivityTracker(nsIEventTarget* aEventTarget)
121 : nsExpirationTracker<LayerActivity, 4>(
122 GENERATION_MS, "LayerActivityTracker", aEventTarget) {}
123 ~LayerActivityTracker() override { AgeAllGenerations(); }
125 void NotifyExpired(LayerActivity* aObject) override;
128 static StaticAutoPtr<LayerActivityTracker> gLayerActivityTracker;
130 LayerActivity::~LayerActivity() {
131 if (mFrame || mContent) {
132 NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
133 gLayerActivityTracker->RemoveObject(this);
137 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
138 NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
140 void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) {
141 RemoveObject(aObject);
143 nsIFrame* f = aObject->mFrame;
144 nsIContent* c = aObject->mContent;
145 aObject->mFrame = nullptr;
146 aObject->mContent = nullptr;
148 MOZ_ASSERT((f == nullptr) != (c == nullptr),
149 "A LayerActivity object should always have a reference to either "
150 "its frame or its content");
152 if (f) {
153 f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
154 f->RemoveProperty(LayerActivityProperty());
155 } else {
156 c->RemoveProperty(nsGkAtoms::LayerActivity);
160 static LayerActivity* GetLayerActivity(nsIFrame* aFrame) {
161 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
162 return nullptr;
164 return aFrame->GetProperty(LayerActivityProperty());
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());
176 layerActivity = new LayerActivity(aFrame);
177 gLayerActivityTracker->AddObject(layerActivity);
178 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
179 aFrame->SetProperty(LayerActivityProperty(), layerActivity);
181 return layerActivity;
184 static void IncrementMutationCount(uint8_t* aCount) {
185 *aCount = uint8_t(std::min(0xFF, *aCount + 1));
188 /* static */
189 void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame,
190 nsIContent* aContent) {
191 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
192 return;
194 LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty());
195 aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
196 if (!layerActivity) {
197 return;
199 layerActivity->mFrame = nullptr;
200 layerActivity->mContent = aContent;
201 aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
202 nsINode::DeleteProperty<LayerActivity>, true);
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;
213 layerActivity->mContent = nullptr;
214 layerActivity->mFrame = aFrame;
215 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
216 aFrame->SetProperty(LayerActivityProperty(), layerActivity);
219 static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame,
220 LayerActivity* aActivity) {
221 const nsStyleDisplay* display = aFrame->StyleDisplay();
222 if (!display->HasTransformProperty() && !display->HasIndividualTransform() &&
223 display->mOffsetPath.IsNone()) {
224 // The transform was removed.
225 aActivity->mPreviousTransformScale = Nothing();
226 IncrementMutationCount(
227 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
228 return;
231 // Compute the new scale due to the CSS transform property.
232 // Note: Motion path doesn't contribute to scale factor. (It only has 2d
233 // translate and 2d rotate, so we use Nothing() for it.)
234 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
235 Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
236 display->mTranslate, display->mRotate, display->mScale, nullptr,
237 display->mTransform, refBox, AppUnitsPerCSSPixel());
238 Matrix transform2D;
239 if (!transform.Is2D(&transform2D)) {
240 // We don't attempt to handle 3D transforms; just assume the scale changed.
241 aActivity->mPreviousTransformScale = Nothing();
242 IncrementMutationCount(
243 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
244 return;
247 MatrixScales scale = transform2D.ScaleFactors();
248 if (aActivity->mPreviousTransformScale == Some(scale)) {
249 return; // Nothing changed.
252 aActivity->mPreviousTransformScale = Some(scale);
253 IncrementMutationCount(
254 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
257 /* static */
258 void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame,
259 nsCSSPropertyID aProperty) {
260 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
261 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
262 IncrementMutationCount(&mutationCount);
264 if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) {
265 IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
269 static bool IsPresContextInScriptAnimationCallback(
270 nsPresContext* aPresContext) {
271 if (aPresContext->RefreshDriver()->IsInRefresh()) {
272 return true;
274 // Treat timeouts/setintervals as scripted animation callbacks for our
275 // purposes.
276 nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
277 return win && win->IsRunningTimeout();
280 /* static */
281 void ActiveLayerTracker::NotifyInlineStyleRuleModified(
282 nsIFrame* aFrame, nsCSSPropertyID aProperty) {
283 if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
284 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
285 // We know this is animated, so just hack the mutation count.
286 layerActivity->RestyleCountForProperty(aProperty) = 0xff;
290 /* static */
291 void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) {
292 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
293 if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
294 // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic.
295 // Just max out the restyle count if we're in an animation callback.
296 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] =
297 0xFF;
298 } else {
299 IncrementMutationCount(
300 &layerActivity
301 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]);
305 static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder,
306 nsIFrame* aFrame) {
307 return ActiveLayerTracker::IsStyleAnimated(
308 aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) ||
309 (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
310 ActiveLayerTracker::IsStyleAnimated(
311 aBuilder, aFrame,
312 nsCSSPropertyIDSet{
313 eCSSProperty_offset_distance, eCSSProperty_offset_rotate,
314 eCSSProperty_offset_anchor, eCSSProperty_offset_position}));
317 /* static */
318 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder,
319 nsIFrame* aFrame) {
320 return IsStyleAnimated(aBuilder, aFrame,
321 nsCSSPropertyIDSet::CSSTransformProperties()) ||
322 IsMotionPathAnimated(aBuilder, aFrame);
325 /* static */
326 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) {
327 return IsStyleAnimated(nullptr, aFrame,
328 nsCSSPropertyIDSet::CSSTransformProperties()) ||
329 IsMotionPathAnimated(nullptr, aFrame);
332 /* static */
333 bool ActiveLayerTracker::IsStyleAnimated(
334 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
335 const nsCSSPropertyIDSet& aPropertySet) {
336 MOZ_ASSERT(
337 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) ||
338 aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()),
339 "Only subset of opacity or transform-like properties set calls this");
341 // For display:table content, transforms are applied to the table wrapper
342 // (primary frame) but their will-change style will be specified on the style
343 // frame and, unlike other transform properties, not inherited.
344 // As a result, for transform properties only we need to be careful to look up
345 // the will-change style on the _style_ frame.
346 const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame);
347 const nsCSSPropertyIDSet transformSet =
348 nsCSSPropertyIDSet::TransformLikeProperties();
349 if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits &
350 StyleWillChangeBits::TRANSFORM)) &&
351 aPropertySet.Intersects(transformSet) &&
352 (!aBuilder ||
353 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
354 return true;
356 if ((aFrame->StyleDisplay()->mWillChange.bits &
357 StyleWillChangeBits::OPACITY) &&
358 aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
359 (!aBuilder ||
360 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
361 return !StaticPrefs::gfx_will_change_ignore_opacity();
364 LayerActivity* layerActivity = GetLayerActivity(aFrame);
365 if (layerActivity) {
366 LayerActivity::ActivityIndex activityIndex =
367 LayerActivity::GetActivityIndexForPropertySet(aPropertySet);
368 if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
369 // If the frame needs to be repainted frequently, we probably don't get
370 // much from treating the property as animated, *unless* this frame's
371 // 'scale' (which includes the bounds changes of a rotation) is changing.
372 // Marking a scaling transform as animating allows us to avoid resizing
373 // the texture, even if we have to repaint the contents of that texture.
374 if (layerActivity
375 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] <
376 2 ||
377 (aPropertySet.Intersects(transformSet) &&
378 IsScaleSubjectToAnimation(aFrame))) {
379 return true;
384 if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) {
385 return true;
388 if (!aPropertySet.Intersects(transformSet) ||
389 !aFrame->Combines3DTransformWithAncestors()) {
390 return false;
393 // For preserve-3d, we check if there is any transform animation on its parent
394 // frames in the 3d rendering context. If there is one, this function will
395 // return true.
396 return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet);
399 /* static */
400 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) {
401 // Check whether JavaScript is animating this frame's scale.
402 LayerActivity* layerActivity = GetLayerActivity(aFrame);
403 if (layerActivity &&
404 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
405 return true;
408 return AnimationUtils::FrameHasAnimatedScale(aFrame);
411 /* static */
412 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; }
414 } // namespace mozilla