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 "mozilla/AnimationUtils.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/gfx/Matrix.h"
12 #include "mozilla/EffectSet.h"
13 #include "mozilla/PodOperations.h"
14 #include "mozilla/RuleNodeCacheConditions.h"
15 #include "gfx2DGlue.h"
16 #include "nsExpirationTracker.h"
17 #include "nsContainerFrame.h"
18 #include "nsIContent.h"
19 #include "nsRefreshDriver.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsIDocument.h"
22 #include "nsAnimationManager.h"
23 #include "nsStyleTransformMatrix.h"
24 #include "nsTransitionManager.h"
25 #include "nsDisplayList.h"
26 #include "nsDOMCSSDeclaration.h"
27
28 namespace mozilla {
29
30 using namespace gfx;
31
32 /**
33 * This tracks the state of a frame that may need active layers due to
34 * ongoing content changes or style changes that indicate animation.
35 *
36 * When no changes of *any* kind are detected after 75-100ms we remove this
37 * object. Because we only track all kinds of activity with a single
38 * nsExpirationTracker, it's possible a frame might remain active somewhat
39 * spuriously if different kinds of changes kept happening, but that almost
40 * certainly doesn't matter.
41 */
42 class LayerActivity {
43 public:
44 enum ActivityIndex {
45 ACTIVITY_OPACITY,
46 ACTIVITY_TRANSFORM,
47 ACTIVITY_LEFT,
48 ACTIVITY_TOP,
49 ACTIVITY_RIGHT,
50 ACTIVITY_BOTTOM,
51 ACTIVITY_BACKGROUND_POSITION,
52
53 ACTIVITY_SCALE,
54
55 // keep as last item
56 ACTIVITY_COUNT
57 };
58
LayerActivity(nsIFrame * aFrame)59 explicit LayerActivity(nsIFrame* aFrame)
60 : mFrame(aFrame), mContent(nullptr), mContentActive(false) {
61 PodArrayZero(mRestyleCounts);
62 }
63 ~LayerActivity();
GetExpirationState()64 nsExpirationState* GetExpirationState() { return &mState; }
RestyleCountForProperty(nsCSSPropertyID aProperty)65 uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty) {
66 return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
67 }
68
GetActivityIndexForProperty(nsCSSPropertyID aProperty)69 static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty) {
70 switch (aProperty) {
71 case eCSSProperty_opacity:
72 return ACTIVITY_OPACITY;
73 case eCSSProperty_transform:
74 return ACTIVITY_TRANSFORM;
75 case eCSSProperty_left:
76 return ACTIVITY_LEFT;
77 case eCSSProperty_top:
78 return ACTIVITY_TOP;
79 case eCSSProperty_right:
80 return ACTIVITY_RIGHT;
81 case eCSSProperty_bottom:
82 return ACTIVITY_BOTTOM;
83 case eCSSProperty_background_position:
84 return ACTIVITY_BACKGROUND_POSITION;
85 case eCSSProperty_background_position_x:
86 return ACTIVITY_BACKGROUND_POSITION;
87 case eCSSProperty_background_position_y:
88 return ACTIVITY_BACKGROUND_POSITION;
89 default:
90 MOZ_ASSERT(false);
91 return ACTIVITY_OPACITY;
92 }
93 }
94
95 // While tracked, exactly one of mFrame or mContent is non-null, depending
96 // on whether this property is stored on a frame or on a content node.
97 // When this property is expired by the layer activity tracker, both mFrame
98 // and mContent are nulled-out and the property is deleted.
99 nsIFrame* mFrame;
100 nsIContent* mContent;
101
102 nsExpirationState mState;
103
104 // Previous scale due to the CSS transform property.
105 Maybe<Size> mPreviousTransformScale;
106
107 // The scroll frame during for which we most recently received a call to
108 // NotifyAnimatedFromScrollHandler.
109 WeakFrame mAnimatingScrollHandlerFrame;
110 // The set of activities that were triggered during
111 // mAnimatingScrollHandlerFrame's scroll event handler.
112 EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
113
114 // Number of restyle operations detected
115 uint8_t mRestyleCounts[ACTIVITY_COUNT];
116 bool mContentActive;
117 };
118
119 class LayerActivityTracker final
120 : public nsExpirationTracker<LayerActivity, 4> {
121 public:
122 // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
123 enum { GENERATION_MS = 100 };
LayerActivityTracker(nsIEventTarget * aEventTarget)124 explicit LayerActivityTracker(nsIEventTarget* aEventTarget)
125 : nsExpirationTracker<LayerActivity, 4>(
126 GENERATION_MS, "LayerActivityTracker", aEventTarget),
127 mDestroying(false) {}
~LayerActivityTracker()128 ~LayerActivityTracker() {
129 mDestroying = true;
130 AgeAllGenerations();
131 }
132
133 virtual void NotifyExpired(LayerActivity* aObject) override;
134
135 public:
136 WeakFrame mCurrentScrollHandlerFrame;
137
138 private:
139 bool mDestroying;
140 };
141
142 static LayerActivityTracker* gLayerActivityTracker = nullptr;
143
~LayerActivity()144 LayerActivity::~LayerActivity() {
145 if (mFrame || mContent) {
146 NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
147 gLayerActivityTracker->RemoveObject(this);
148 }
149 }
150
151 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty,LayerActivity)152 NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
153
154 void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) {
155 if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
156 // Reset the restyle counts, but let the layer activity survive.
157 PodArrayZero(aObject->mRestyleCounts);
158 MarkUsed(aObject);
159 return;
160 }
161
162 RemoveObject(aObject);
163
164 nsIFrame* f = aObject->mFrame;
165 nsIContent* c = aObject->mContent;
166 aObject->mFrame = nullptr;
167 aObject->mContent = nullptr;
168
169 MOZ_ASSERT((f == nullptr) != (c == nullptr),
170 "A LayerActivity object should always have a reference to either "
171 "its frame or its content");
172
173 if (f) {
174 // The pres context might have been detached during the delay -
175 // that's fine, just skip the paint.
176 if (f->PresContext()->GetContainerWeak()) {
177 f->SchedulePaint();
178 }
179 f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
180 f->DeleteProperty(LayerActivityProperty());
181 } else {
182 c->DeleteProperty(nsGkAtoms::LayerActivity);
183 }
184 }
185
GetLayerActivity(nsIFrame * aFrame)186 static LayerActivity* GetLayerActivity(nsIFrame* aFrame) {
187 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
188 return nullptr;
189 }
190 return aFrame->GetProperty(LayerActivityProperty());
191 }
192
GetLayerActivityForUpdate(nsIFrame * aFrame)193 static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) {
194 LayerActivity* layerActivity = GetLayerActivity(aFrame);
195 if (layerActivity) {
196 gLayerActivityTracker->MarkUsed(layerActivity);
197 } else {
198 if (!gLayerActivityTracker) {
199 gLayerActivityTracker = new LayerActivityTracker(
200 SystemGroup::EventTargetFor(TaskCategory::Other));
201 }
202 layerActivity = new LayerActivity(aFrame);
203 gLayerActivityTracker->AddObject(layerActivity);
204 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
205 aFrame->SetProperty(LayerActivityProperty(), layerActivity);
206 }
207 return layerActivity;
208 }
209
IncrementMutationCount(uint8_t * aCount)210 static void IncrementMutationCount(uint8_t* aCount) {
211 *aCount = uint8_t(std::min(0xFF, *aCount + 1));
212 }
213
TransferActivityToContent(nsIFrame * aFrame,nsIContent * aContent)214 /* static */ void ActiveLayerTracker::TransferActivityToContent(
215 nsIFrame* aFrame, nsIContent* aContent) {
216 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
217 return;
218 }
219 LayerActivity* layerActivity =
220 aFrame->RemoveProperty(LayerActivityProperty());
221 aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
222 if (!layerActivity) {
223 return;
224 }
225 layerActivity->mFrame = nullptr;
226 layerActivity->mContent = aContent;
227 aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
228 nsINode::DeleteProperty<LayerActivity>, true);
229 }
230
TransferActivityToFrame(nsIContent * aContent,nsIFrame * aFrame)231 /* static */ void ActiveLayerTracker::TransferActivityToFrame(
232 nsIContent* aContent, nsIFrame* aFrame) {
233 LayerActivity* layerActivity = static_cast<LayerActivity*>(
234 aContent->UnsetProperty(nsGkAtoms::LayerActivity));
235 if (!layerActivity) {
236 return;
237 }
238 layerActivity->mContent = nullptr;
239 layerActivity->mFrame = aFrame;
240 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
241 aFrame->SetProperty(LayerActivityProperty(), layerActivity);
242 }
243
IncrementScaleRestyleCountIfNeeded(nsIFrame * aFrame,LayerActivity * aActivity)244 static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame,
245 LayerActivity* aActivity) {
246 const nsStyleDisplay* display = aFrame->StyleDisplay();
247 RefPtr<nsCSSValueSharedList> transformList = display->GetCombinedTransform();
248 if (!transformList) {
249 // The transform was removed.
250 aActivity->mPreviousTransformScale = Nothing();
251 IncrementMutationCount(
252 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
253 return;
254 }
255
256 // Compute the new scale due to the CSS transform property.
257 nsPresContext* presContext = aFrame->PresContext();
258 RuleNodeCacheConditions dummy;
259 bool dummyBool;
260 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
261 Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
262 transformList->mHead, aFrame->StyleContext(), presContext, dummy, refBox,
263 presContext->AppUnitsPerCSSPixel(), &dummyBool);
264 Matrix transform2D;
265 if (!transform.Is2D(&transform2D)) {
266 // We don't attempt to handle 3D transforms; just assume the scale changed.
267 aActivity->mPreviousTransformScale = Nothing();
268 IncrementMutationCount(
269 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
270 return;
271 }
272
273 Size scale = transform2D.ScaleFactors(true);
274 if (aActivity->mPreviousTransformScale == Some(scale)) {
275 return; // Nothing changed.
276 }
277
278 aActivity->mPreviousTransformScale = Some(scale);
279 IncrementMutationCount(
280 &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
281 }
282
NotifyRestyle(nsIFrame * aFrame,nsCSSPropertyID aProperty)283 /* static */ void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame,
284 nsCSSPropertyID aProperty) {
285 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
286 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
287 IncrementMutationCount(&mutationCount);
288
289 if (aProperty == eCSSProperty_transform) {
290 IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
291 }
292 }
293
NotifyOffsetRestyle(nsIFrame * aFrame)294 /* static */ void ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame) {
295 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
296 IncrementMutationCount(
297 &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]);
298 IncrementMutationCount(
299 &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]);
300 IncrementMutationCount(
301 &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]);
302 IncrementMutationCount(
303 &layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]);
304 }
305
NotifyAnimated(nsIFrame * aFrame,nsCSSPropertyID aProperty,const nsAString & aNewValue,nsDOMCSSDeclaration * aDOMCSSDecl)306 /* static */ void ActiveLayerTracker::NotifyAnimated(
307 nsIFrame* aFrame, nsCSSPropertyID aProperty, const nsAString& aNewValue,
308 nsDOMCSSDeclaration* aDOMCSSDecl) {
309 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
310 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
311 if (mutationCount != 0xFF) {
312 nsAutoString oldValue;
313 aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
314 if (aNewValue != oldValue) {
315 // We know this is animated, so just hack the mutation count.
316 mutationCount = 0xFF;
317 }
318 }
319 }
320
NotifyAnimatedFromScrollHandler(nsIFrame * aFrame,nsCSSPropertyID aProperty,nsIFrame * aScrollFrame)321 /* static */ void ActiveLayerTracker::NotifyAnimatedFromScrollHandler(
322 nsIFrame* aFrame, nsCSSPropertyID aProperty, nsIFrame* aScrollFrame) {
323 if (aFrame->PresContext() != aScrollFrame->PresContext()) {
324 // Don't allow cross-document dependencies.
325 return;
326 }
327 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
328 LayerActivity::ActivityIndex activityIndex =
329 LayerActivity::GetActivityIndexForProperty(aProperty);
330
331 if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
332 // Discard any activity of a different scroll frame. We only track the
333 // most recent scroll handler induced activity.
334 layerActivity->mScrollHandlerInducedActivity.clear();
335 layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
336 }
337
338 layerActivity->mScrollHandlerInducedActivity += activityIndex;
339 }
340
IsPresContextInScriptAnimationCallback(nsPresContext * aPresContext)341 static bool IsPresContextInScriptAnimationCallback(
342 nsPresContext* aPresContext) {
343 if (aPresContext->RefreshDriver()->IsInRefresh()) {
344 return true;
345 }
346 // Treat timeouts/setintervals as scripted animation callbacks for our
347 // purposes.
348 nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
349 return win && win->IsRunningTimeout();
350 }
351
NotifyInlineStyleRuleModified(nsIFrame * aFrame,nsCSSPropertyID aProperty,const nsAString & aNewValue,nsDOMCSSDeclaration * aDOMCSSDecl)352 /* static */ void ActiveLayerTracker::NotifyInlineStyleRuleModified(
353 nsIFrame* aFrame, nsCSSPropertyID aProperty, const nsAString& aNewValue,
354 nsDOMCSSDeclaration* aDOMCSSDecl) {
355 if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
356 NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
357 }
358 if (gLayerActivityTracker &&
359 gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
360 NotifyAnimatedFromScrollHandler(
361 aFrame, aProperty,
362 gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
363 }
364 }
365
IsStyleMaybeAnimated(nsIFrame * aFrame,nsCSSPropertyID aProperty)366 /* static */ bool ActiveLayerTracker::IsStyleMaybeAnimated(
367 nsIFrame* aFrame, nsCSSPropertyID aProperty) {
368 return IsStyleAnimated(nullptr, aFrame, aProperty);
369 }
370
IsBackgroundPositionAnimated(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)371 /* static */ bool ActiveLayerTracker::IsBackgroundPositionAnimated(
372 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
373 return IsStyleAnimated(aBuilder, aFrame,
374 eCSSProperty_background_position_x) ||
375 IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_y);
376 }
377
CheckScrollInducedActivity(LayerActivity * aLayerActivity,LayerActivity::ActivityIndex aActivityIndex,nsDisplayListBuilder * aBuilder)378 static bool CheckScrollInducedActivity(
379 LayerActivity* aLayerActivity, LayerActivity::ActivityIndex aActivityIndex,
380 nsDisplayListBuilder* aBuilder) {
381 if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
382 !aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
383 return false;
384 }
385
386 nsIScrollableFrame* scrollFrame =
387 do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
388 if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
389 return true;
390 }
391
392 // The scroll frame has been destroyed or has become inactive. Clear it from
393 // the layer activity so that it can expire.
394 aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
395 aLayerActivity->mScrollHandlerInducedActivity.clear();
396 return false;
397 }
398
IsStyleAnimated(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,nsCSSPropertyID aProperty)399 /* static */ bool ActiveLayerTracker::IsStyleAnimated(
400 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
401 nsCSSPropertyID aProperty) {
402 // TODO: Add some abuse restrictions
403 if ((aFrame->StyleDisplay()->mWillChangeBitField &
404 NS_STYLE_WILL_CHANGE_TRANSFORM) &&
405 aProperty == eCSSProperty_transform &&
406 (!aBuilder ||
407 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
408 return true;
409 }
410 if ((aFrame->StyleDisplay()->mWillChangeBitField &
411 NS_STYLE_WILL_CHANGE_OPACITY) &&
412 aProperty == eCSSProperty_opacity &&
413 (!aBuilder ||
414 aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
415 return true;
416 }
417
418 LayerActivity* layerActivity = GetLayerActivity(aFrame);
419 if (layerActivity) {
420 LayerActivity::ActivityIndex activityIndex =
421 LayerActivity::GetActivityIndexForProperty(aProperty);
422 if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
423 return true;
424 }
425 if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
426 return true;
427 }
428 }
429 if (aProperty == eCSSProperty_transform &&
430 aFrame->Combines3DTransformWithAncestors()) {
431 return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
432 }
433 return nsLayoutUtils::HasEffectiveAnimation(aFrame, aProperty);
434 }
435
IsOffsetStyleAnimated(nsIFrame * aFrame)436 /* static */ bool ActiveLayerTracker::IsOffsetStyleAnimated(nsIFrame* aFrame) {
437 LayerActivity* layerActivity = GetLayerActivity(aFrame);
438 if (layerActivity) {
439 if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 ||
440 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 ||
441 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 ||
442 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2) {
443 return true;
444 }
445 }
446 // We should also check for running CSS animations of these properties once
447 // bug 1009693 is fixed. Until that happens, layerization isn't useful for
448 // animations of these properties because we'll invalidate the layer contents
449 // on every change anyway.
450 // See bug 1151346 for a patch that adds a check for CSS animations.
451 return false;
452 }
453
IsScaleSubjectToAnimation(nsIFrame * aFrame)454 /* static */ bool ActiveLayerTracker::IsScaleSubjectToAnimation(
455 nsIFrame* aFrame) {
456 // Check whether JavaScript is animating this frame's scale.
457 LayerActivity* layerActivity = GetLayerActivity(aFrame);
458 if (layerActivity &&
459 layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
460 return true;
461 }
462
463 // Check if any animations, transitions, etc. associated with this frame may
464 // animate its scale.
465 EffectSet* effects = EffectSet::GetEffectSet(aFrame);
466 if (effects &&
467 AnimationUtils::EffectSetContainsAnimatedScale(*effects, aFrame)) {
468 return true;
469 }
470
471 return false;
472 }
473
NotifyContentChange(nsIFrame * aFrame)474 /* static */ void ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame) {
475 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
476 layerActivity->mContentActive = true;
477 }
478
IsContentActive(nsIFrame * aFrame)479 /* static */ bool ActiveLayerTracker::IsContentActive(nsIFrame* aFrame) {
480 LayerActivity* layerActivity = GetLayerActivity(aFrame);
481 return layerActivity && layerActivity->mContentActive;
482 }
483
SetCurrentScrollHandlerFrame(nsIFrame * aFrame)484 /* static */ void ActiveLayerTracker::SetCurrentScrollHandlerFrame(
485 nsIFrame* aFrame) {
486 if (!gLayerActivityTracker) {
487 gLayerActivityTracker = new LayerActivityTracker(
488 SystemGroup::EventTargetFor(TaskCategory::Other));
489 }
490 gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
491 }
492
Shutdown()493 /* static */ void ActiveLayerTracker::Shutdown() {
494 delete gLayerActivityTracker;
495 gLayerActivityTracker = nullptr;
496 }
497
498 } // namespace mozilla
499