|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "ActiveLayerTracker.h" |
|
6 |
|
7 #include "nsExpirationTracker.h" |
|
8 #include "nsIFrame.h" |
|
9 #include "nsIContent.h" |
|
10 #include "nsRefreshDriver.h" |
|
11 #include "nsPIDOMWindow.h" |
|
12 #include "nsIDocument.h" |
|
13 #include "nsAnimationManager.h" |
|
14 #include "nsTransitionManager.h" |
|
15 |
|
16 namespace mozilla { |
|
17 |
|
18 /** |
|
19 * This tracks the state of a frame that may need active layers due to |
|
20 * ongoing content changes or style changes that indicate animation. |
|
21 * |
|
22 * When no changes of *any* kind are detected after 75-100ms we remove this |
|
23 * object. Because we only track all kinds of activity with a single |
|
24 * nsExpirationTracker, it's possible a frame might remain active somewhat |
|
25 * spuriously if different kinds of changes kept happening, but that almost |
|
26 * certainly doesn't matter. |
|
27 */ |
|
28 class LayerActivity { |
|
29 public: |
|
30 LayerActivity(nsIFrame* aFrame) |
|
31 : mFrame(aFrame) |
|
32 , mOpacityRestyleCount(0) |
|
33 , mTransformRestyleCount(0) |
|
34 , mLeftRestyleCount(0) |
|
35 , mTopRestyleCount(0) |
|
36 , mRightRestyleCount(0) |
|
37 , mBottomRestyleCount(0) |
|
38 , mMarginLeftRestyleCount(0) |
|
39 , mMarginTopRestyleCount(0) |
|
40 , mMarginRightRestyleCount(0) |
|
41 , mMarginBottomRestyleCount(0) |
|
42 , mContentActive(false) |
|
43 {} |
|
44 ~LayerActivity(); |
|
45 nsExpirationState* GetExpirationState() { return &mState; } |
|
46 uint8_t& RestyleCountForProperty(nsCSSProperty aProperty) |
|
47 { |
|
48 switch (aProperty) { |
|
49 case eCSSProperty_opacity: return mOpacityRestyleCount; |
|
50 case eCSSProperty_transform: return mTransformRestyleCount; |
|
51 case eCSSProperty_left: return mLeftRestyleCount; |
|
52 case eCSSProperty_top: return mTopRestyleCount; |
|
53 case eCSSProperty_right: return mRightRestyleCount; |
|
54 case eCSSProperty_bottom: return mBottomRestyleCount; |
|
55 case eCSSProperty_margin_left: return mMarginLeftRestyleCount; |
|
56 case eCSSProperty_margin_top: return mMarginTopRestyleCount; |
|
57 case eCSSProperty_margin_right: return mMarginRightRestyleCount; |
|
58 case eCSSProperty_margin_bottom: return mMarginBottomRestyleCount; |
|
59 default: MOZ_ASSERT(false); return mOpacityRestyleCount; |
|
60 } |
|
61 } |
|
62 |
|
63 nsIFrame* mFrame; |
|
64 nsExpirationState mState; |
|
65 // Number of restyle operations detected |
|
66 uint8_t mOpacityRestyleCount; |
|
67 uint8_t mTransformRestyleCount; |
|
68 uint8_t mLeftRestyleCount; |
|
69 uint8_t mTopRestyleCount; |
|
70 uint8_t mRightRestyleCount; |
|
71 uint8_t mBottomRestyleCount; |
|
72 uint8_t mMarginLeftRestyleCount; |
|
73 uint8_t mMarginTopRestyleCount; |
|
74 uint8_t mMarginRightRestyleCount; |
|
75 uint8_t mMarginBottomRestyleCount; |
|
76 bool mContentActive; |
|
77 }; |
|
78 |
|
79 class LayerActivityTracker MOZ_FINAL : public nsExpirationTracker<LayerActivity,4> { |
|
80 public: |
|
81 // 75-100ms is a good timeout period. We use 4 generations of 25ms each. |
|
82 enum { GENERATION_MS = 100 }; |
|
83 LayerActivityTracker() |
|
84 : nsExpirationTracker<LayerActivity,4>(GENERATION_MS) {} |
|
85 ~LayerActivityTracker() { |
|
86 AgeAllGenerations(); |
|
87 } |
|
88 |
|
89 virtual void NotifyExpired(LayerActivity* aObject); |
|
90 }; |
|
91 |
|
92 static LayerActivityTracker* gLayerActivityTracker = nullptr; |
|
93 |
|
94 LayerActivity::~LayerActivity() |
|
95 { |
|
96 if (mFrame) { |
|
97 NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker"); |
|
98 gLayerActivityTracker->RemoveObject(this); |
|
99 } |
|
100 } |
|
101 |
|
102 static void DestroyLayerActivity(void* aPropertyValue) |
|
103 { |
|
104 delete static_cast<LayerActivity*>(aPropertyValue); |
|
105 } |
|
106 |
|
107 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set |
|
108 NS_DECLARE_FRAME_PROPERTY(LayerActivityProperty, DestroyLayerActivity) |
|
109 |
|
110 void |
|
111 LayerActivityTracker::NotifyExpired(LayerActivity* aObject) |
|
112 { |
|
113 RemoveObject(aObject); |
|
114 |
|
115 nsIFrame* f = aObject->mFrame; |
|
116 aObject->mFrame = nullptr; |
|
117 |
|
118 // The pres context might have been detached during the delay - |
|
119 // that's fine, just skip the paint. |
|
120 if (f->PresContext()->GetContainerWeak()) { |
|
121 f->SchedulePaint(); |
|
122 } |
|
123 f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); |
|
124 f->Properties().Delete(LayerActivityProperty()); |
|
125 } |
|
126 |
|
127 static LayerActivity* |
|
128 GetLayerActivity(nsIFrame* aFrame) |
|
129 { |
|
130 if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) { |
|
131 return nullptr; |
|
132 } |
|
133 FrameProperties properties = aFrame->Properties(); |
|
134 return static_cast<LayerActivity*>(properties.Get(LayerActivityProperty())); |
|
135 } |
|
136 |
|
137 static LayerActivity* |
|
138 GetLayerActivityForUpdate(nsIFrame* aFrame) |
|
139 { |
|
140 FrameProperties properties = aFrame->Properties(); |
|
141 LayerActivity* layerActivity = |
|
142 static_cast<LayerActivity*>(properties.Get(LayerActivityProperty())); |
|
143 if (layerActivity) { |
|
144 gLayerActivityTracker->MarkUsed(layerActivity); |
|
145 } else { |
|
146 if (!gLayerActivityTracker) { |
|
147 gLayerActivityTracker = new LayerActivityTracker(); |
|
148 } |
|
149 layerActivity = new LayerActivity(aFrame); |
|
150 gLayerActivityTracker->AddObject(layerActivity); |
|
151 aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY); |
|
152 properties.Set(LayerActivityProperty(), layerActivity); |
|
153 } |
|
154 return layerActivity; |
|
155 } |
|
156 |
|
157 static void |
|
158 IncrementMutationCount(uint8_t* aCount) |
|
159 { |
|
160 *aCount = uint8_t(std::min(0xFF, *aCount + 1)); |
|
161 } |
|
162 |
|
163 /* static */ void |
|
164 ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSProperty aProperty) |
|
165 { |
|
166 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); |
|
167 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); |
|
168 IncrementMutationCount(&mutationCount); |
|
169 } |
|
170 |
|
171 /* static */ void |
|
172 ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame) |
|
173 { |
|
174 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); |
|
175 IncrementMutationCount(&layerActivity->mLeftRestyleCount); |
|
176 IncrementMutationCount(&layerActivity->mTopRestyleCount); |
|
177 IncrementMutationCount(&layerActivity->mRightRestyleCount); |
|
178 IncrementMutationCount(&layerActivity->mBottomRestyleCount); |
|
179 } |
|
180 |
|
181 /* static */ void |
|
182 ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty) |
|
183 { |
|
184 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); |
|
185 uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty); |
|
186 // We know this is animated, so just hack the mutation count. |
|
187 mutationCount = 0xFF; |
|
188 } |
|
189 |
|
190 static bool |
|
191 IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext) |
|
192 { |
|
193 if (aPresContext->RefreshDriver()->IsInRefresh()) { |
|
194 return true; |
|
195 } |
|
196 // Treat timeouts/setintervals as scripted animation callbacks for our |
|
197 // purposes. |
|
198 nsPIDOMWindow* win = aPresContext->Document()->GetInnerWindow(); |
|
199 return win && win->IsRunningTimeout(); |
|
200 } |
|
201 |
|
202 /* static */ void |
|
203 ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame, |
|
204 nsCSSProperty aProperty) |
|
205 { |
|
206 if (!IsPresContextInScriptAnimationCallback(aFrame->PresContext())) { |
|
207 return; |
|
208 } |
|
209 NotifyAnimated(aFrame, aProperty); |
|
210 } |
|
211 |
|
212 /* static */ bool |
|
213 ActiveLayerTracker::IsStyleAnimated(nsIFrame* aFrame, nsCSSProperty aProperty) |
|
214 { |
|
215 // TODO: Add some abuse restrictions |
|
216 if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) && |
|
217 aProperty == eCSSProperty_transform) { |
|
218 return true; |
|
219 } |
|
220 if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) && |
|
221 aProperty == eCSSProperty_opacity) { |
|
222 return true; |
|
223 } |
|
224 |
|
225 LayerActivity* layerActivity = GetLayerActivity(aFrame); |
|
226 if (layerActivity) { |
|
227 if (layerActivity->RestyleCountForProperty(aProperty) >= 2) { |
|
228 return true; |
|
229 } |
|
230 } |
|
231 if (aProperty == eCSSProperty_transform && aFrame->Preserves3D()) { |
|
232 return IsStyleAnimated(aFrame->GetParent(), aProperty); |
|
233 } |
|
234 nsIContent* content = aFrame->GetContent(); |
|
235 if (content) { |
|
236 if (mozilla::HasAnimationOrTransition<ElementAnimations>( |
|
237 content, nsGkAtoms::animationsProperty, aProperty)) { |
|
238 return true; |
|
239 } |
|
240 if (mozilla::HasAnimationOrTransition<ElementTransitions>( |
|
241 content, nsGkAtoms::transitionsProperty, aProperty)) { |
|
242 return true; |
|
243 } |
|
244 } |
|
245 |
|
246 return false; |
|
247 } |
|
248 |
|
249 /* static */ bool |
|
250 ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame) |
|
251 { |
|
252 LayerActivity* layerActivity = GetLayerActivity(aFrame); |
|
253 if (layerActivity) { |
|
254 if (layerActivity->mLeftRestyleCount >= 2 || |
|
255 layerActivity->mTopRestyleCount >= 2 || |
|
256 layerActivity->mRightRestyleCount >= 2 || |
|
257 layerActivity->mBottomRestyleCount >= 2 || |
|
258 layerActivity->mMarginLeftRestyleCount >= 2 || |
|
259 layerActivity->mMarginTopRestyleCount >= 2 || |
|
260 layerActivity->mMarginRightRestyleCount >= 2 || |
|
261 layerActivity->mMarginBottomRestyleCount >= 2) { |
|
262 return true; |
|
263 } |
|
264 } |
|
265 return false; |
|
266 } |
|
267 |
|
268 /* static */ void |
|
269 ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame) |
|
270 { |
|
271 LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame); |
|
272 layerActivity->mContentActive = true; |
|
273 } |
|
274 |
|
275 /* static */ bool |
|
276 ActiveLayerTracker::IsContentActive(nsIFrame* aFrame) |
|
277 { |
|
278 LayerActivity* layerActivity = GetLayerActivity(aFrame); |
|
279 return layerActivity && layerActivity->mContentActive; |
|
280 } |
|
281 |
|
282 /* static */ void |
|
283 ActiveLayerTracker::Shutdown() |
|
284 { |
|
285 delete gLayerActivityTracker; |
|
286 gLayerActivityTracker = nullptr; |
|
287 } |
|
288 |
|
289 } |