|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /** |
|
7 * A class which manages pending restyles. This handles keeping track |
|
8 * of what nodes restyles need to happen on and so forth. |
|
9 */ |
|
10 |
|
11 #ifndef mozilla_RestyleTracker_h |
|
12 #define mozilla_RestyleTracker_h |
|
13 |
|
14 #include "mozilla/dom/Element.h" |
|
15 #include "nsDataHashtable.h" |
|
16 #include "nsIFrame.h" |
|
17 #include "mozilla/SplayTree.h" |
|
18 |
|
19 namespace mozilla { |
|
20 |
|
21 class RestyleManager; |
|
22 |
|
23 /** |
|
24 * Helper class that collects a list of frames that need |
|
25 * UpdateOverflow() called on them, and coalesces them |
|
26 * to avoid walking up the same ancestor tree multiple times. |
|
27 */ |
|
28 class OverflowChangedTracker |
|
29 { |
|
30 public: |
|
31 enum ChangeKind { |
|
32 /** |
|
33 * The frame was explicitly added as a result of |
|
34 * nsChangeHint_UpdatePostTransformOverflow and hence may have had a style |
|
35 * change that changes its geometry relative to parent, without reflowing. |
|
36 */ |
|
37 TRANSFORM_CHANGED, |
|
38 /** |
|
39 * The overflow areas of children have changed |
|
40 * and we need to call UpdateOverflow on the frame. |
|
41 */ |
|
42 CHILDREN_CHANGED, |
|
43 /** |
|
44 * The overflow areas of children have changed |
|
45 * and we need to call UpdateOverflow on the frame. |
|
46 * Also call UpdateOverflow on the parent even if the |
|
47 * overflow areas of the frame does not change. |
|
48 */ |
|
49 CHILDREN_AND_PARENT_CHANGED |
|
50 }; |
|
51 |
|
52 OverflowChangedTracker() : |
|
53 mSubtreeRoot(nullptr) |
|
54 {} |
|
55 |
|
56 ~OverflowChangedTracker() |
|
57 { |
|
58 NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!"); |
|
59 } |
|
60 |
|
61 /** |
|
62 * Add a frame that has had a style change, and needs its |
|
63 * overflow updated. |
|
64 * |
|
65 * If there are pre-transform overflow areas stored for this |
|
66 * frame, then we will call FinishAndStoreOverflow with those |
|
67 * areas instead of UpdateOverflow(). |
|
68 * |
|
69 * If the overflow area changes, then UpdateOverflow will also |
|
70 * be called on the parent. |
|
71 */ |
|
72 void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) { |
|
73 uint32_t depth = aFrame->GetDepthInFrameTree(); |
|
74 Entry *entry = nullptr; |
|
75 if (!mEntryList.empty()) { |
|
76 entry = mEntryList.find(Entry(aFrame, depth)); |
|
77 } |
|
78 if (entry == nullptr) { |
|
79 // Add new entry. |
|
80 mEntryList.insert(new Entry(aFrame, depth, aChangeKind)); |
|
81 } else { |
|
82 // Update the existing entry if the new value is stronger. |
|
83 entry->mChangeKind = std::max(entry->mChangeKind, aChangeKind); |
|
84 } |
|
85 } |
|
86 |
|
87 /** |
|
88 * Remove a frame. |
|
89 */ |
|
90 void RemoveFrame(nsIFrame* aFrame) { |
|
91 if (mEntryList.empty()) { |
|
92 return; |
|
93 } |
|
94 |
|
95 uint32_t depth = aFrame->GetDepthInFrameTree(); |
|
96 if (mEntryList.find(Entry(aFrame, depth))) { |
|
97 delete mEntryList.remove(Entry(aFrame, depth)); |
|
98 } |
|
99 } |
|
100 |
|
101 /** |
|
102 * Set the subtree root to limit overflow updates. This must be set if and |
|
103 * only if currently reflowing aSubtreeRoot, to ensure overflow changes will |
|
104 * still propagate correctly. |
|
105 */ |
|
106 void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) { |
|
107 mSubtreeRoot = aSubtreeRoot; |
|
108 } |
|
109 |
|
110 /** |
|
111 * Update the overflow of all added frames, and clear the entry list. |
|
112 * |
|
113 * Start from those deepest in the frame tree and works upwards. This stops |
|
114 * us from processing the same frame twice. |
|
115 */ |
|
116 void Flush() { |
|
117 while (!mEntryList.empty()) { |
|
118 Entry *entry = mEntryList.removeMin(); |
|
119 nsIFrame *frame = entry->mFrame; |
|
120 |
|
121 bool overflowChanged = false; |
|
122 if (entry->mChangeKind == CHILDREN_AND_PARENT_CHANGED) { |
|
123 // Need to union the overflow areas of the children. |
|
124 // Always update the parent, even if the overflow does not change. |
|
125 frame->UpdateOverflow(); |
|
126 overflowChanged = true; |
|
127 } else if (entry->mChangeKind == CHILDREN_CHANGED) { |
|
128 // Need to union the overflow areas of the children. |
|
129 // Only update the parent if the overflow changes. |
|
130 overflowChanged = frame->UpdateOverflow(); |
|
131 } else { |
|
132 // Take a faster path that doesn't require unioning the overflow areas |
|
133 // of our children. |
|
134 |
|
135 #ifdef DEBUG |
|
136 bool hasInitialOverflowPropertyApplied = false; |
|
137 frame->Properties().Get(nsIFrame::DebugInitialOverflowPropertyApplied(), |
|
138 &hasInitialOverflowPropertyApplied); |
|
139 NS_ASSERTION(hasInitialOverflowPropertyApplied, |
|
140 "InitialOverflowProperty must be set first."); |
|
141 #endif |
|
142 |
|
143 nsOverflowAreas* overflow = |
|
144 static_cast<nsOverflowAreas*>(frame->Properties().Get(nsIFrame::InitialOverflowProperty())); |
|
145 if (overflow) { |
|
146 // FinishAndStoreOverflow will change the overflow areas passed in, |
|
147 // so make a copy. |
|
148 nsOverflowAreas overflowCopy = *overflow; |
|
149 frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize()); |
|
150 } else { |
|
151 nsRect bounds(nsPoint(0, 0), frame->GetSize()); |
|
152 nsOverflowAreas boundsOverflow; |
|
153 boundsOverflow.SetAllTo(bounds); |
|
154 frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size()); |
|
155 } |
|
156 |
|
157 // We can't tell if the overflow changed, so be conservative |
|
158 overflowChanged = true; |
|
159 } |
|
160 |
|
161 // If the frame style changed (e.g. positioning offsets) |
|
162 // then we need to update the parent with the overflow areas of its |
|
163 // children. |
|
164 if (overflowChanged) { |
|
165 nsIFrame *parent = frame->GetParent(); |
|
166 if (parent && parent != mSubtreeRoot) { |
|
167 Entry* parentEntry = mEntryList.find(Entry(parent, entry->mDepth - 1)); |
|
168 if (parentEntry) { |
|
169 parentEntry->mChangeKind = std::max(parentEntry->mChangeKind, CHILDREN_CHANGED); |
|
170 } else { |
|
171 mEntryList.insert(new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED)); |
|
172 } |
|
173 } |
|
174 } |
|
175 delete entry; |
|
176 } |
|
177 } |
|
178 |
|
179 private: |
|
180 struct Entry : SplayTreeNode<Entry> |
|
181 { |
|
182 Entry(nsIFrame* aFrame, uint32_t aDepth, ChangeKind aChangeKind = CHILDREN_CHANGED) |
|
183 : mFrame(aFrame) |
|
184 , mDepth(aDepth) |
|
185 , mChangeKind(aChangeKind) |
|
186 {} |
|
187 |
|
188 bool operator==(const Entry& aOther) const |
|
189 { |
|
190 return mFrame == aOther.mFrame; |
|
191 } |
|
192 |
|
193 /** |
|
194 * Sort by *reverse* depth in the tree, and break ties with |
|
195 * the frame pointer. |
|
196 */ |
|
197 bool operator<(const Entry& aOther) const |
|
198 { |
|
199 if (mDepth == aOther.mDepth) { |
|
200 return mFrame < aOther.mFrame; |
|
201 } |
|
202 return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */ |
|
203 } |
|
204 |
|
205 static int compare(const Entry& aOne, const Entry& aTwo) |
|
206 { |
|
207 if (aOne == aTwo) { |
|
208 return 0; |
|
209 } else if (aOne < aTwo) { |
|
210 return -1; |
|
211 } else { |
|
212 return 1; |
|
213 } |
|
214 } |
|
215 |
|
216 nsIFrame* mFrame; |
|
217 /* Depth in the frame tree */ |
|
218 uint32_t mDepth; |
|
219 ChangeKind mChangeKind; |
|
220 }; |
|
221 |
|
222 /* A list of frames to process, sorted by their depth in the frame tree */ |
|
223 SplayTree<Entry, Entry> mEntryList; |
|
224 |
|
225 /* Don't update overflow of this frame or its ancestors. */ |
|
226 const nsIFrame* mSubtreeRoot; |
|
227 }; |
|
228 |
|
229 class RestyleTracker { |
|
230 public: |
|
231 typedef mozilla::dom::Element Element; |
|
232 |
|
233 RestyleTracker(uint32_t aRestyleBits) : |
|
234 mRestyleBits(aRestyleBits), |
|
235 mHaveLaterSiblingRestyles(false) |
|
236 { |
|
237 NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0, |
|
238 "Why do we have these bits set?"); |
|
239 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0, |
|
240 "Must have a restyle flag"); |
|
241 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != |
|
242 ELEMENT_PENDING_RESTYLE_FLAGS, |
|
243 "Shouldn't have both restyle flags set"); |
|
244 NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) != 0, |
|
245 "Must have root flag"); |
|
246 NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) != |
|
247 (ELEMENT_ALL_RESTYLE_FLAGS & ~ELEMENT_PENDING_RESTYLE_FLAGS), |
|
248 "Shouldn't have both root flags"); |
|
249 } |
|
250 |
|
251 void Init(RestyleManager* aRestyleManager) { |
|
252 mRestyleManager = aRestyleManager; |
|
253 } |
|
254 |
|
255 uint32_t Count() const { |
|
256 return mPendingRestyles.Count(); |
|
257 } |
|
258 |
|
259 /** |
|
260 * Add a restyle for the given element to the tracker. Returns true |
|
261 * if the element already had eRestyle_LaterSiblings set on it. |
|
262 */ |
|
263 bool AddPendingRestyle(Element* aElement, nsRestyleHint aRestyleHint, |
|
264 nsChangeHint aMinChangeHint); |
|
265 |
|
266 /** |
|
267 * Process the restyles we've been tracking. |
|
268 */ |
|
269 void ProcessRestyles() { |
|
270 // Fast-path the common case (esp. for the animation restyle |
|
271 // tracker) of not having anything to do. |
|
272 if (mPendingRestyles.Count()) { |
|
273 DoProcessRestyles(); |
|
274 } |
|
275 } |
|
276 |
|
277 // Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit |
|
278 uint32_t RestyleBit() const { |
|
279 return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS; |
|
280 } |
|
281 |
|
282 // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit |
|
283 uint32_t RootBit() const { |
|
284 return mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS; |
|
285 } |
|
286 |
|
287 struct RestyleData { |
|
288 nsRestyleHint mRestyleHint; // What we want to restyle |
|
289 nsChangeHint mChangeHint; // The minimal change hint for "self" |
|
290 }; |
|
291 |
|
292 /** |
|
293 * If the given Element has a restyle pending for it, return the |
|
294 * relevant restyle data. This function will clear everything other |
|
295 * than a possible eRestyle_LaterSiblings hint for aElement out of |
|
296 * our hashtable. The returned aData will never have an |
|
297 * eRestyle_LaterSiblings hint in it. |
|
298 * |
|
299 * The return value indicates whether any restyle data was found for |
|
300 * the element. If false is returned, then the state of *aData is |
|
301 * undefined. |
|
302 */ |
|
303 bool GetRestyleData(Element* aElement, RestyleData* aData); |
|
304 |
|
305 /** |
|
306 * The document we're associated with. |
|
307 */ |
|
308 inline nsIDocument* Document() const; |
|
309 |
|
310 struct RestyleEnumerateData : public RestyleData { |
|
311 nsRefPtr<Element> mElement; |
|
312 }; |
|
313 |
|
314 private: |
|
315 /** |
|
316 * Handle a single mPendingRestyles entry. aRestyleHint must not |
|
317 * include eRestyle_LaterSiblings; that needs to be dealt with |
|
318 * before calling this function. |
|
319 */ |
|
320 inline void ProcessOneRestyle(Element* aElement, |
|
321 nsRestyleHint aRestyleHint, |
|
322 nsChangeHint aChangeHint); |
|
323 |
|
324 /** |
|
325 * The guts of our restyle processing. |
|
326 */ |
|
327 void DoProcessRestyles(); |
|
328 |
|
329 typedef nsDataHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable; |
|
330 typedef nsAutoTArray< nsRefPtr<Element>, 32> RestyleRootArray; |
|
331 // Our restyle bits. These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and |
|
332 // will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS and one flag |
|
333 // that's not in ELEMENT_PENDING_RESTYLE_FLAGS. |
|
334 uint32_t mRestyleBits; |
|
335 RestyleManager* mRestyleManager; // Owns us |
|
336 // A hashtable that maps elements to RestyleData structs. The |
|
337 // values only make sense if the element's current document is our |
|
338 // document and it has our RestyleBit() flag set. In particular, |
|
339 // said bit might not be set if the element had a restyle posted and |
|
340 // then was moved around in the DOM. |
|
341 PendingRestyleTable mPendingRestyles; |
|
342 // An array that keeps track of our possible restyle roots. This |
|
343 // maintains the invariant that if A and B are both restyle roots |
|
344 // and A is an ancestor of B then A will come after B in the array. |
|
345 // We maintain this invariant by checking whether an element has an |
|
346 // ancestor with the restyle root bit set before appending it to the |
|
347 // array. |
|
348 RestyleRootArray mRestyleRoots; |
|
349 // True if we have some entries with the eRestyle_LaterSiblings |
|
350 // flag. We need this to avoid enumerating the hashtable looking |
|
351 // for such entries when we can't possibly have any. |
|
352 bool mHaveLaterSiblingRestyles; |
|
353 }; |
|
354 |
|
355 inline bool RestyleTracker::AddPendingRestyle(Element* aElement, |
|
356 nsRestyleHint aRestyleHint, |
|
357 nsChangeHint aMinChangeHint) |
|
358 { |
|
359 RestyleData existingData; |
|
360 existingData.mRestyleHint = nsRestyleHint(0); |
|
361 existingData.mChangeHint = NS_STYLE_HINT_NONE; |
|
362 |
|
363 // Check the RestyleBit() flag before doing the hashtable Get, since |
|
364 // it's possible that the data in the hashtable isn't actually |
|
365 // relevant anymore (if the flag is not set). |
|
366 if (aElement->HasFlag(RestyleBit())) { |
|
367 mPendingRestyles.Get(aElement, &existingData); |
|
368 } else { |
|
369 aElement->SetFlags(RestyleBit()); |
|
370 } |
|
371 |
|
372 bool hadRestyleLaterSiblings = |
|
373 (existingData.mRestyleHint & eRestyle_LaterSiblings) != 0; |
|
374 existingData.mRestyleHint = |
|
375 nsRestyleHint(existingData.mRestyleHint | aRestyleHint); |
|
376 NS_UpdateHint(existingData.mChangeHint, aMinChangeHint); |
|
377 |
|
378 mPendingRestyles.Put(aElement, existingData); |
|
379 |
|
380 // We can only treat this element as a restyle root if we would |
|
381 // actually restyle its descendants (so either call |
|
382 // ReResolveStyleContext on it or just reframe it). |
|
383 if ((aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) || |
|
384 (aMinChangeHint & nsChangeHint_ReconstructFrame)) { |
|
385 for (const Element* cur = aElement; !cur->HasFlag(RootBit()); ) { |
|
386 nsIContent* parent = cur->GetFlattenedTreeParent(); |
|
387 // Stop if we have no parent or the parent is not an element or |
|
388 // we're part of the viewport scrollbars (because those are not |
|
389 // frametree descendants of the primary frame of the root |
|
390 // element). |
|
391 // XXXbz maybe the primary frame of the root should be the root scrollframe? |
|
392 if (!parent || !parent->IsElement() || |
|
393 // If we've hit the root via a native anonymous kid and that |
|
394 // this native anonymous kid is not obviously a descendant |
|
395 // of the root's primary frame, assume we're under the root |
|
396 // scrollbars. Since those don't get reresolved when |
|
397 // reresolving the root, we need to make sure to add the |
|
398 // element to mRestyleRoots. |
|
399 (cur->IsInNativeAnonymousSubtree() && !parent->GetParent() && |
|
400 cur->GetPrimaryFrame() && |
|
401 cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) { |
|
402 mRestyleRoots.AppendElement(aElement); |
|
403 break; |
|
404 } |
|
405 cur = parent->AsElement(); |
|
406 } |
|
407 // At this point some ancestor of aElement (possibly aElement |
|
408 // itself) is in mRestyleRoots. Set the root bit on aElement, to |
|
409 // speed up searching for an existing root on its descendants. |
|
410 aElement->SetFlags(RootBit()); |
|
411 } |
|
412 |
|
413 mHaveLaterSiblingRestyles = |
|
414 mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0; |
|
415 return hadRestyleLaterSiblings; |
|
416 } |
|
417 |
|
418 } // namespace mozilla |
|
419 |
|
420 #endif /* mozilla_RestyleTracker_h */ |