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 // This function is basically a simplified copy of
222 // nsDisplayTransform::GetResultingTransformMatrixInternal.
224 Matrix svgTransform
, parentsChildrenOnlyTransform
;
225 const bool hasSVGTransforms
=
226 aFrame
->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED
) &&
227 aFrame
->IsSVGTransformed(&svgTransform
, &parentsChildrenOnlyTransform
);
229 const nsStyleDisplay
* display
= aFrame
->StyleDisplay();
230 if (!aFrame
->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED
) ||
231 (!display
->HasTransformProperty() && !display
->HasIndividualTransform() &&
232 display
->mOffsetPath
.IsNone() && !hasSVGTransforms
)) {
233 if (aActivity
->mPreviousTransformScale
.isSome()) {
234 // The transform was removed.
235 aActivity
->mPreviousTransformScale
= Nothing();
236 IncrementMutationCount(
237 &aActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_SCALE
]);
244 if (aFrame
->IsCSSTransformed()) {
245 // Compute the new scale due to the CSS transform property.
246 // Note: Motion path doesn't contribute to scale factor. (It only has 2d
247 // translate and 2d rotate, so we use Nothing() for it.)
248 nsStyleTransformMatrix::TransformReferenceBox
refBox(aFrame
);
249 transform
= nsStyleTransformMatrix::ReadTransforms(
250 display
->mTranslate
, display
->mRotate
, display
->mScale
, nullptr,
251 display
->mTransform
, refBox
, AppUnitsPerCSSPixel());
252 } else if (hasSVGTransforms
) {
253 transform
= Matrix4x4::From2D(svgTransform
);
256 const bool parentHasChildrenOnlyTransform
=
257 hasSVGTransforms
&& !parentsChildrenOnlyTransform
.IsIdentity();
258 if (parentHasChildrenOnlyTransform
) {
259 transform
*= Matrix4x4::From2D(parentsChildrenOnlyTransform
);
263 if (!transform
.Is2D(&transform2D
)) {
264 // We don't attempt to handle 3D transforms; just assume the scale changed.
265 aActivity
->mPreviousTransformScale
= Nothing();
266 IncrementMutationCount(
267 &aActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_SCALE
]);
271 MatrixScales scale
= transform2D
.ScaleFactors();
272 if (aActivity
->mPreviousTransformScale
== Some(scale
)) {
273 return; // Nothing changed.
276 aActivity
->mPreviousTransformScale
= Some(scale
);
277 IncrementMutationCount(
278 &aActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_SCALE
]);
282 void ActiveLayerTracker::NotifyRestyle(nsIFrame
* aFrame
,
283 nsCSSPropertyID aProperty
) {
284 LayerActivity
* layerActivity
= GetLayerActivityForUpdate(aFrame
);
285 uint8_t& mutationCount
= layerActivity
->RestyleCountForProperty(aProperty
);
286 IncrementMutationCount(&mutationCount
);
288 if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty
)) {
289 IncrementScaleRestyleCountIfNeeded(aFrame
, layerActivity
);
293 static bool IsPresContextInScriptAnimationCallback(
294 nsPresContext
* aPresContext
) {
295 if (aPresContext
->RefreshDriver()->IsInRefresh()) {
298 // Treat timeouts/setintervals as scripted animation callbacks for our
300 nsPIDOMWindowInner
* win
= aPresContext
->Document()->GetInnerWindow();
301 return win
&& win
->IsRunningTimeout();
305 void ActiveLayerTracker::NotifyInlineStyleRuleModified(
306 nsIFrame
* aFrame
, nsCSSPropertyID aProperty
) {
307 if (IsPresContextInScriptAnimationCallback(aFrame
->PresContext())) {
308 LayerActivity
* layerActivity
= GetLayerActivityForUpdate(aFrame
);
309 // We know this is animated, so just hack the mutation count.
310 layerActivity
->RestyleCountForProperty(aProperty
) = 0xff;
315 void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame
* aFrame
) {
316 LayerActivity
* layerActivity
= GetLayerActivityForUpdate(aFrame
);
317 if (IsPresContextInScriptAnimationCallback(aFrame
->PresContext())) {
318 // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic.
319 // Just max out the restyle count if we're in an animation callback.
320 layerActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_TRIGGERED_REPAINT
] =
323 IncrementMutationCount(
325 ->mRestyleCounts
[LayerActivity::ACTIVITY_TRIGGERED_REPAINT
]);
329 static bool IsMotionPathAnimated(nsDisplayListBuilder
* aBuilder
,
331 return ActiveLayerTracker::IsStyleAnimated(
332 aBuilder
, aFrame
, nsCSSPropertyIDSet
{eCSSProperty_offset_path
}) ||
333 (!aFrame
->StyleDisplay()->mOffsetPath
.IsNone() &&
334 ActiveLayerTracker::IsStyleAnimated(
337 eCSSProperty_offset_distance
, eCSSProperty_offset_rotate
,
338 eCSSProperty_offset_anchor
, eCSSProperty_offset_position
}));
342 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder
* aBuilder
,
344 return IsStyleAnimated(aBuilder
, aFrame
,
345 nsCSSPropertyIDSet::CSSTransformProperties()) ||
346 IsMotionPathAnimated(aBuilder
, aFrame
);
350 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame
* aFrame
) {
351 return IsStyleAnimated(nullptr, aFrame
,
352 nsCSSPropertyIDSet::CSSTransformProperties()) ||
353 IsMotionPathAnimated(nullptr, aFrame
);
357 bool ActiveLayerTracker::IsStyleAnimated(
358 nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
359 const nsCSSPropertyIDSet
& aPropertySet
) {
361 aPropertySet
.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) ||
362 aPropertySet
.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()),
363 "Only subset of opacity or transform-like properties set calls this");
365 // For display:table content, transforms are applied to the table wrapper
366 // (primary frame) but their will-change style will be specified on the style
367 // frame and, unlike other transform properties, not inherited.
368 // As a result, for transform properties only we need to be careful to look up
369 // the will-change style on the _style_ frame.
370 const nsIFrame
* styleFrame
= nsLayoutUtils::GetStyleFrame(aFrame
);
371 const nsCSSPropertyIDSet transformSet
=
372 nsCSSPropertyIDSet::TransformLikeProperties();
373 if ((styleFrame
&& (styleFrame
->StyleDisplay()->mWillChange
.bits
&
374 StyleWillChangeBits::TRANSFORM
)) &&
375 aPropertySet
.Intersects(transformSet
) &&
377 aBuilder
->IsInWillChangeBudget(aFrame
, aFrame
->GetSize()))) {
380 if ((aFrame
->StyleDisplay()->mWillChange
.bits
&
381 StyleWillChangeBits::OPACITY
) &&
382 aPropertySet
.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
384 aBuilder
->IsInWillChangeBudget(aFrame
, aFrame
->GetSize()))) {
385 return !StaticPrefs::gfx_will_change_ignore_opacity();
388 LayerActivity
* layerActivity
= GetLayerActivity(aFrame
);
390 LayerActivity::ActivityIndex activityIndex
=
391 LayerActivity::GetActivityIndexForPropertySet(aPropertySet
);
392 if (layerActivity
->mRestyleCounts
[activityIndex
] >= 2) {
393 // If the frame needs to be repainted frequently, we probably don't get
394 // much from treating the property as animated, *unless* this frame's
395 // 'scale' (which includes the bounds changes of a rotation) is changing.
396 // Marking a scaling transform as animating allows us to avoid resizing
397 // the texture, even if we have to repaint the contents of that texture.
399 ->mRestyleCounts
[LayerActivity::ACTIVITY_TRIGGERED_REPAINT
] <
401 (aPropertySet
.Intersects(transformSet
) &&
402 IsScaleSubjectToAnimation(aFrame
))) {
408 if (nsLayoutUtils::HasEffectiveAnimation(aFrame
, aPropertySet
)) {
412 if (!aPropertySet
.Intersects(transformSet
) ||
413 !aFrame
->Combines3DTransformWithAncestors()) {
417 // For preserve-3d, we check if there is any transform animation on its parent
418 // frames in the 3d rendering context. If there is one, this function will
420 return IsStyleAnimated(aBuilder
, aFrame
->GetParent(), aPropertySet
);
424 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame
* aFrame
) {
425 // Check whether JavaScript is animating this frame's scale.
426 LayerActivity
* layerActivity
= GetLayerActivity(aFrame
);
428 layerActivity
->mRestyleCounts
[LayerActivity::ACTIVITY_SCALE
] >= 2) {
432 return AnimationUtils::FrameHasAnimatedScale(aFrame
);
436 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker
= nullptr; }
438 } // namespace mozilla