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