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"
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.
52 ACTIVITY_TRIGGERED_REPAINT
,
58 explicit LayerActivity(nsIFrame
* aFrame
) : mFrame(aFrame
), mContent(nullptr) {
59 PodArrayZero(mRestyleCounts
);
62 nsExpirationState
* GetExpirationState() { return &mState
; }
63 uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty
) {
64 return mRestyleCounts
[GetActivityIndexForProperty(aProperty
)];
67 static ActivityIndex
GetActivityIndexForProperty(nsCSSPropertyID 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
;
83 return ACTIVITY_OPACITY
;
87 static ActivityIndex
GetActivityIndexForPropertySet(
88 const nsCSSPropertyIDSet
& aPropertySet
) {
89 if (aPropertySet
.IsSubsetOf(
90 nsCSSPropertyIDSet::TransformLikeProperties())) {
91 return ACTIVITY_TRANSFORM
;
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.
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> {
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");
153 f
->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY
);
154 f
->RemoveProperty(LayerActivityProperty());
156 c
->RemoveProperty(nsGkAtoms::LayerActivity
);
160 static LayerActivity
* GetLayerActivity(nsIFrame
* aFrame
) {
161 if (!aFrame
->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY
)) {
164 return aFrame
->GetProperty(LayerActivityProperty());
167 static LayerActivity
* GetLayerActivityForUpdate(nsIFrame
* aFrame
) {
168 LayerActivity
* layerActivity
= GetLayerActivity(aFrame
);
170 gLayerActivityTracker
->MarkUsed(layerActivity
);
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));
189 void ActiveLayerTracker::TransferActivityToContent(nsIFrame
* aFrame
,
190 nsIContent
* aContent
) {
191 if (!aFrame
->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY
)) {
194 LayerActivity
* layerActivity
= aFrame
->TakeProperty(LayerActivityProperty());
195 aFrame
->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY
);
196 if (!layerActivity
) {
199 layerActivity
->mFrame
= nullptr;
200 layerActivity
->mContent
= aContent
;
201 aContent
->SetProperty(nsGkAtoms::LayerActivity
, layerActivity
,
202 nsINode::DeleteProperty
<LayerActivity
>, true);
206 void ActiveLayerTracker::TransferActivityToFrame(nsIContent
* aContent
,
208 auto* layerActivity
= static_cast<LayerActivity
*>(
209 aContent
->TakeProperty(nsGkAtoms::LayerActivity
));
210 if (!layerActivity
) {
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
]);
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());
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
]);
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
]);
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()) {
274 // Treat timeouts/setintervals as scripted animation callbacks for our
276 nsPIDOMWindowInner
* win
= aPresContext
->Document()->GetInnerWindow();
277 return win
&& win
->IsRunningTimeout();
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;
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
] =
299 IncrementMutationCount(
301 ->mRestyleCounts
[LayerActivity::ACTIVITY_TRIGGERED_REPAINT
]);
305 static bool IsMotionPathAnimated(nsDisplayListBuilder
* aBuilder
,
307 return ActiveLayerTracker::IsStyleAnimated(
308 aBuilder
, aFrame
, nsCSSPropertyIDSet
{eCSSProperty_offset_path
}) ||
309 (!aFrame
->StyleDisplay()->mOffsetPath
.IsNone() &&
310 ActiveLayerTracker::IsStyleAnimated(
313 eCSSProperty_offset_distance
, eCSSProperty_offset_rotate
,
314 eCSSProperty_offset_anchor
, eCSSProperty_offset_position
}));
318 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder
* aBuilder
,
320 return IsStyleAnimated(aBuilder
, aFrame
,
321 nsCSSPropertyIDSet::CSSTransformProperties()) ||
322 IsMotionPathAnimated(aBuilder
, aFrame
);
326 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame
* aFrame
) {
327 return IsStyleAnimated(nullptr, aFrame
,
328 nsCSSPropertyIDSet::CSSTransformProperties()) ||
329 IsMotionPathAnimated(nullptr, aFrame
);
333 bool ActiveLayerTracker::IsStyleAnimated(
334 nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
335 const nsCSSPropertyIDSet
& aPropertySet
) {
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
) &&
353 aBuilder
->IsInWillChangeBudget(aFrame
, aFrame
->GetSize()))) {
356 if ((aFrame
->StyleDisplay()->mWillChange
.bits
&
357 StyleWillChangeBits::OPACITY
) &&
358 aPropertySet
.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
360 aBuilder
->IsInWillChangeBudget(aFrame
, aFrame
->GetSize()))) {
361 return !StaticPrefs::gfx_will_change_ignore_opacity();
364 LayerActivity
* layerActivity
= GetLayerActivity(aFrame
);
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.
375 ->mRestyleCounts
[LayerActivity::ACTIVITY_TRIGGERED_REPAINT
] <
377 (aPropertySet
.Intersects(transformSet
) &&
378 IsScaleSubjectToAnimation(aFrame
))) {
384 if (nsLayoutUtils::HasEffectiveAnimation(aFrame
, aPropertySet
)) {
388 if (!aPropertySet
.Intersects(transformSet
) ||
389 !aFrame
->Combines3DTransformWithAncestors()) {
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
396 return IsStyleAnimated(aBuilder
, aFrame
->GetParent(), aPropertySet
);
400 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame
* aFrame
) {
401 // Check whether JavaScript is animating this frame's scale.
402 LayerActivity
* layerActivity
= GetLayerActivity(aFrame
);
404 layerActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_SCALE
] >= 2) {
408 return AnimationUtils::FrameHasAnimatedScale(aFrame
);
412 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker
= nullptr; }
414 } // namespace mozilla