no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / painting / ActiveLayerTracker.cpp
blobb30ecdf5c23c929f020d07b583564646f26fb721
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 // 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]);
240 return;
243 Matrix4x4 transform;
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);
262 Matrix transform2D;
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]);
268 return;
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]);
281 /* static */
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()) {
296 return true;
298 // Treat timeouts/setintervals as scripted animation callbacks for our
299 // purposes.
300 nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
301 return win && win->IsRunningTimeout();
304 /* static */
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;
314 /* static */
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] =
321 0xFF;
322 } else {
323 IncrementMutationCount(
324 &layerActivity
325 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]);
329 static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder,
330 nsIFrame* aFrame) {
331 return ActiveLayerTracker::IsStyleAnimated(
332 aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) ||
333 (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
334 ActiveLayerTracker::IsStyleAnimated(
335 aBuilder, aFrame,
336 nsCSSPropertyIDSet{
337 eCSSProperty_offset_distance, eCSSProperty_offset_rotate,
338 eCSSProperty_offset_anchor, eCSSProperty_offset_position}));
341 /* static */
342 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder,
343 nsIFrame* aFrame) {
344 return IsStyleAnimated(aBuilder, aFrame,
345 nsCSSPropertyIDSet::CSSTransformProperties()) ||
346 IsMotionPathAnimated(aBuilder, aFrame);
349 /* static */
350 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) {
351 return IsStyleAnimated(nullptr, aFrame,
352 nsCSSPropertyIDSet::CSSTransformProperties()) ||
353 IsMotionPathAnimated(nullptr, aFrame);
356 /* static */
357 bool ActiveLayerTracker::IsStyleAnimated(
358 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
359 const nsCSSPropertyIDSet& aPropertySet) {
360 MOZ_ASSERT(
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) &&
376 (!aBuilder ||
377 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
378 return true;
380 if ((aFrame->StyleDisplay()->mWillChange.bits &
381 StyleWillChangeBits::OPACITY) &&
382 aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
383 (!aBuilder ||
384 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
385 return !StaticPrefs::gfx_will_change_ignore_opacity();
388 LayerActivity* layerActivity = GetLayerActivity(aFrame);
389 if (layerActivity) {
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.
398 if (layerActivity
399 ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] <
400 2 ||
401 (aPropertySet.Intersects(transformSet) &&
402 IsScaleSubjectToAnimation(aFrame))) {
403 return true;
408 if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) {
409 return true;
412 if (!aPropertySet.Intersects(transformSet) ||
413 !aFrame->Combines3DTransformWithAncestors()) {
414 return false;
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
419 // return true.
420 return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet);
423 /* static */
424 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) {
425 // Check whether JavaScript is animating this frame's scale.
426 LayerActivity* layerActivity = GetLayerActivity(aFrame);
427 if (layerActivity &&
428 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
429 return true;
432 return AnimationUtils::FrameHasAnimatedScale(aFrame);
435 /* static */
436 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; }
438 } // namespace mozilla