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 /**
8 * A class which manages pending restyles. This handles keeping track
9 * of what nodes restyles need to happen on and so forth.
10 */
11
12 #ifndef mozilla_RestyleTracker_h
13 #define mozilla_RestyleTracker_h
14
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/OverflowChangedTracker.h"
17 #include "nsAutoPtr.h"
18 #include "nsClassHashtable.h"
19 #include "nsContainerFrame.h"
20 #include "nsIContentInlines.h"
21 #include "mozilla/SplayTree.h"
22 #include "mozilla/RestyleLogging.h"
23 #include "GeckoProfiler.h"
24 #include "mozilla/Maybe.h"
25
26 namespace mozilla {
27
28 class ElementRestyler;
29 class GeckoRestyleManager;
30
31 class RestyleTracker {
32 public:
33 typedef mozilla::dom::Element Element;
34
35 friend class ElementRestyler; // for AddPendingRestyleToTable
36
RestyleTracker(Element::FlagsType aRestyleBits)37 explicit RestyleTracker(Element::FlagsType aRestyleBits)
38 : mRestyleBits(aRestyleBits),
39 mHaveLaterSiblingRestyles(false),
40 mHaveSelectors(false) {
41 NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
42 "Why do we have these bits set?");
43 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
44 "Must have a restyle flag");
45 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
46 ELEMENT_PENDING_RESTYLE_FLAGS,
47 "Shouldn't have both restyle flags set");
48 NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) != 0,
49 "Must have root flag");
50 NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) !=
51 ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS,
52 "Shouldn't have both root flags");
53 }
54
Init(GeckoRestyleManager * aRestyleManager)55 void Init(GeckoRestyleManager* aRestyleManager) {
56 mRestyleManager = aRestyleManager;
57 }
58
Count()59 uint32_t Count() const { return mPendingRestyles.Count(); }
60
61 /**
62 * Add a restyle for the given element to the tracker. Returns true
63 * if the element already had eRestyle_LaterSiblings set on it.
64 *
65 * aRestyleRoot is the closest restyle root for aElement. If the caller
66 * does not know what the closest restyle root is, Nothing should be
67 * passed. A Some(nullptr) restyle root can be passed if there is no
68 * ancestor element that is a restyle root.
69 */
70 bool AddPendingRestyle(
71 Element* aElement, nsRestyleHint aRestyleHint,
72 nsChangeHint aMinChangeHint,
73 const RestyleHintData* aRestyleHintData = nullptr,
74 const mozilla::Maybe<Element*>& aRestyleRoot = mozilla::Nothing());
75
76 Element* FindClosestRestyleRoot(Element* aElement);
77
78 /**
79 * Process the restyles we've been tracking.
80 */
81 void DoProcessRestyles();
82
83 // Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit
RestyleBit()84 uint32_t RestyleBit() const {
85 return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
86 }
87
88 // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
RootBit()89 Element::FlagsType RootBit() const {
90 return mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS;
91 }
92
93 // Return our ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR bit if present,
94 // or 0 if it is not.
ConditionalDescendantsBit()95 Element::FlagsType ConditionalDescendantsBit() const {
96 return mRestyleBits & ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR;
97 }
98
99 struct Hints {
100 nsRestyleHint mRestyleHint; // What we want to restyle
101 nsChangeHint mChangeHint; // The minimal change hint for "self"
102 RestyleHintData mRestyleHintData; // Data associated with mRestyleHint
103 };
104
105 struct RestyleData : Hints {
RestyleDataRestyleData106 RestyleData() {
107 mRestyleHint = nsRestyleHint(0);
108 mChangeHint = nsChangeHint(0);
109 }
110
RestyleDataRestyleData111 RestyleData(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint,
112 const RestyleHintData* aRestyleHintData) {
113 mRestyleHint = aRestyleHint;
114 mChangeHint = aChangeHint;
115 if (aRestyleHintData) {
116 mRestyleHintData = *aRestyleHintData;
117 }
118 }
119
120 // Descendant elements we must check that we ended up restyling, ordered
121 // with the same invariant as mRestyleRoots. The elements here are those
122 // that we called AddPendingRestyle for and found the element this is
123 // the RestyleData for as its nearest restyle root.
124 nsTArray<RefPtr<Element>> mDescendants;
125 #if defined(MOZ_GECKO_PROFILER)
126 UniqueProfilerBacktrace mBacktrace;
127 #endif
128 };
129
130 /**
131 * If the given Element has a restyle pending for it, return the
132 * relevant restyle data. This function will clear everything other
133 * than a possible eRestyle_LaterSiblings hint for aElement out of
134 * our hashtable. The returned aData will never have an
135 * eRestyle_LaterSiblings hint in it.
136 *
137 * The return value indicates whether any restyle data was found for
138 * the element. aData is set to nullptr iff false is returned.
139 */
140 bool GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData);
141
142 /**
143 * Returns whether there is a RestyleData entry in mPendingRestyles
144 * for the given element.
145 */
HasRestyleData(Element * aElement)146 bool HasRestyleData(Element* aElement) {
147 return mPendingRestyles.Contains(aElement);
148 }
149
150 /**
151 * For each element in aElements, appends it to mRestyleRoots if it
152 * has its restyle bit set. This is used to ensure we restyle elements
153 * that we did not add as restyle roots initially (due to there being
154 * an ancestor with the restyle root bit set), but which we might
155 * not have got around to restyling due to the restyle process
156 * terminating early with RestyleResul::eStop (see ElementRestyler::Restyle).
157 *
158 * This function must be called with elements in order such that
159 * appending them to mRestyleRoots maintains its ordering invariant that
160 * ancestors appear after descendants.
161 */
162 void AddRestyleRootsIfAwaitingRestyle(
163 const nsTArray<RefPtr<Element>>& aElements);
164
165 /**
166 * Converts any eRestyle_SomeDescendants restyle hints in the pending restyle
167 * table into eRestyle_Subtree hints and clears out the associated arrays of
168 * nsCSSSelector pointers. This is called in response to a style sheet change
169 * that might have cause an nsCSSSelector to be destroyed.
170 */
171 void ClearSelectors();
172
173 /**
174 * The document we're associated with.
175 */
176 inline nsIDocument* Document() const;
177
178 #ifdef RESTYLE_LOGGING
179 // Defined in RestyleTrackerInlines.h.
180 inline bool ShouldLogRestyle();
181 inline int32_t& LoggingDepth();
182 #endif
183
184 private:
185 bool AddPendingRestyleToTable(
186 Element* aElement, nsRestyleHint aRestyleHint,
187 nsChangeHint aMinChangeHint,
188 const RestyleHintData* aRestyleHintData = nullptr);
189
190 /**
191 * Handle a single mPendingRestyles entry. aRestyleHint must not
192 * include eRestyle_LaterSiblings; that needs to be dealt with
193 * before calling this function.
194 */
195 inline void ProcessOneRestyle(Element* aElement, nsRestyleHint aRestyleHint,
196 nsChangeHint aChangeHint,
197 const RestyleHintData& aRestyleHintData);
198
199 typedef nsClassHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable;
200 typedef AutoTArray<RefPtr<Element>, 32> RestyleRootArray;
201 // Our restyle bits. These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and
202 // will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS, one flag
203 // from ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS, and might also include
204 // ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR.
205 Element::FlagsType mRestyleBits;
206 GeckoRestyleManager* mRestyleManager; // Owns us
207 // A hashtable that maps elements to pointers to RestyleData structs. The
208 // values only make sense if the element's current document is our
209 // document and it has our RestyleBit() flag set. In particular,
210 // said bit might not be set if the element had a restyle posted and
211 // then was moved around in the DOM.
212 PendingRestyleTable mPendingRestyles;
213 // An array that keeps track of our possible restyle roots. This
214 // maintains the invariant that if A and B are both restyle roots
215 // and A is an ancestor of B then A will come after B in the array.
216 // We maintain this invariant by checking whether an element has an
217 // ancestor with the restyle root bit set before appending it to the
218 // array.
219 RestyleRootArray mRestyleRoots;
220 // True if we have some entries with the eRestyle_LaterSiblings
221 // flag. We need this to avoid enumerating the hashtable looking
222 // for such entries when we can't possibly have any.
223 bool mHaveLaterSiblingRestyles;
224 // True if we have some entries with selectors in the restyle hint data.
225 // We use this to skip iterating over mPendingRestyles in ClearSelectors.
226 bool mHaveSelectors;
227 };
228
AddPendingRestyleToTable(Element * aElement,nsRestyleHint aRestyleHint,nsChangeHint aMinChangeHint,const RestyleHintData * aRestyleHintData)229 inline bool RestyleTracker::AddPendingRestyleToTable(
230 Element* aElement, nsRestyleHint aRestyleHint, nsChangeHint aMinChangeHint,
231 const RestyleHintData* aRestyleHintData) {
232 RestyleData* existingData;
233
234 if (aRestyleHintData &&
235 !aRestyleHintData->mSelectorsForDescendants.IsEmpty()) {
236 mHaveSelectors = true;
237 }
238
239 // Check the RestyleBit() flag before doing the hashtable Get, since
240 // it's possible that the data in the hashtable isn't actually
241 // relevant anymore (if the flag is not set).
242 if (aElement->HasFlag(RestyleBit())) {
243 mPendingRestyles.Get(aElement, &existingData);
244 } else {
245 aElement->SetFlags(RestyleBit());
246 existingData = nullptr;
247 }
248
249 if (aRestyleHint & eRestyle_SomeDescendants) {
250 NS_ASSERTION(ConditionalDescendantsBit(),
251 "why are we getting eRestyle_SomeDescendants in an "
252 "animation-only restyle?");
253 aElement->SetFlags(ConditionalDescendantsBit());
254 }
255
256 if (!existingData) {
257 RestyleData* rd =
258 new RestyleData(aRestyleHint, aMinChangeHint, aRestyleHintData);
259 #if defined(MOZ_GECKO_PROFILER)
260 if (profiler_feature_active(ProfilerFeature::Restyle)) {
261 rd->mBacktrace = profiler_get_backtrace();
262 }
263 #endif
264 mPendingRestyles.Put(aElement, rd);
265 return false;
266 }
267
268 bool hadRestyleLaterSiblings =
269 (existingData->mRestyleHint & eRestyle_LaterSiblings) != 0;
270 existingData->mRestyleHint =
271 nsRestyleHint(existingData->mRestyleHint | aRestyleHint);
272 existingData->mChangeHint |= aMinChangeHint;
273 if (aRestyleHintData) {
274 existingData->mRestyleHintData.mSelectorsForDescendants.AppendElements(
275 aRestyleHintData->mSelectorsForDescendants);
276 }
277
278 return hadRestyleLaterSiblings;
279 }
280
FindClosestRestyleRoot(Element * aElement)281 inline mozilla::dom::Element* RestyleTracker::FindClosestRestyleRoot(
282 Element* aElement) {
283 Element* cur = aElement;
284 while (!cur->HasFlag(RootBit())) {
285 nsIContent* parent = cur->GetFlattenedTreeParent();
286 // Stop if we have no parent or the parent is not an element or
287 // we're part of the viewport scrollbars (because those are not
288 // frametree descendants of the primary frame of the root
289 // element).
290 // XXXbz maybe the primary frame of the root should be the root scrollframe?
291 if (!parent || !parent->IsElement() ||
292 // If we've hit the root via a native anonymous kid and that
293 // this native anonymous kid is not obviously a descendant
294 // of the root's primary frame, assume we're under the root
295 // scrollbars. Since those don't get reresolved when
296 // reresolving the root, we need to make sure to add the
297 // element to mRestyleRoots.
298 (cur->IsInNativeAnonymousSubtree() && !parent->GetParent() &&
299 cur->GetPrimaryFrame() &&
300 cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) {
301 return nullptr;
302 }
303 cur = parent->AsElement();
304 }
305 return cur;
306 }
307
AddPendingRestyle(Element * aElement,nsRestyleHint aRestyleHint,nsChangeHint aMinChangeHint,const RestyleHintData * aRestyleHintData,const mozilla::Maybe<Element * > & aRestyleRoot)308 inline bool RestyleTracker::AddPendingRestyle(
309 Element* aElement, nsRestyleHint aRestyleHint, nsChangeHint aMinChangeHint,
310 const RestyleHintData* aRestyleHintData,
311 const mozilla::Maybe<Element*>& aRestyleRoot) {
312 bool hadRestyleLaterSiblings = AddPendingRestyleToTable(
313 aElement, aRestyleHint, aMinChangeHint, aRestyleHintData);
314
315 // We can only treat this element as a restyle root if we would
316 // actually restyle its descendants (so either call
317 // ElementRestyler::Restyle on it or just reframe it).
318 if ((aRestyleHint & ~eRestyle_LaterSiblings) ||
319 (aMinChangeHint & nsChangeHint_ReconstructFrame)) {
320 Element* cur =
321 aRestyleRoot ? *aRestyleRoot : FindClosestRestyleRoot(aElement);
322 if (!cur) {
323 mRestyleRoots.AppendElement(aElement);
324 cur = aElement;
325 }
326 // At this point some ancestor of aElement (possibly aElement
327 // itself) is in mRestyleRoots. Set the root bit on aElement, to
328 // speed up searching for an existing root on its descendants.
329 aElement->SetFlags(RootBit());
330 if (cur != aElement) {
331 // We are already going to restyle cur, one of aElement's ancestors,
332 // but we might not end up restyling all the way down to aElement.
333 // Record it in the RestyleData so we can ensure it does get restyled
334 // after we deal with cur.
335 //
336 // As with the mRestyleRoots array, mDescendants maintains the
337 // invariant that if two elements appear in the array and one
338 // is an ancestor of the other, that the ancestor appears after
339 // the descendant.
340 RestyleData* curData;
341 mPendingRestyles.Get(cur, &curData);
342
343 // Even if cur has a ForceDescendants restyle hint, we're not guaranteed
344 // to reach aElement in the case the PresShell posts a restyle event from
345 // PostRecreateFramesFor, so we need to track it here.
346 MOZ_ASSERT(curData, "expected to find a RestyleData for cur");
347 if (curData) {
348 curData->mDescendants.AppendElement(aElement);
349 }
350 }
351 }
352
353 // If we need to restyle later siblings, we will need a flag on parent to note
354 // that some children need restyle for nsComputedDOMStyle.
355 if (aRestyleHint & eRestyle_LaterSiblings) {
356 nsIContent* parent = aElement->GetFlattenedTreeParent();
357 if (parent && parent->IsElement()) {
358 parent->SetFlags(ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT);
359 }
360 }
361
362 mHaveLaterSiblingRestyles =
363 mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
364 return hadRestyleLaterSiblings;
365 }
366
367 } // namespace mozilla
368
369 #endif /* mozilla_RestyleTracker_h */
370