layout/base/RestyleTracker.h

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:a898c359d202
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 */

mercurial