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