|
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 #include "RestyleTracker.h" |
|
12 #include "nsStyleChangeList.h" |
|
13 #include "RestyleManager.h" |
|
14 #include "GeckoProfiler.h" |
|
15 |
|
16 namespace mozilla { |
|
17 |
|
18 inline nsIDocument* |
|
19 RestyleTracker::Document() const { |
|
20 return mRestyleManager->PresContext()->Document(); |
|
21 } |
|
22 |
|
23 #define RESTYLE_ARRAY_STACKSIZE 128 |
|
24 |
|
25 struct LaterSiblingCollector { |
|
26 RestyleTracker* tracker; |
|
27 nsTArray< nsRefPtr<dom::Element> >* elements; |
|
28 }; |
|
29 |
|
30 static PLDHashOperator |
|
31 CollectLaterSiblings(nsISupports* aElement, |
|
32 RestyleTracker::RestyleData& aData, |
|
33 void* aSiblingCollector) |
|
34 { |
|
35 dom::Element* element = |
|
36 static_cast<dom::Element*>(aElement); |
|
37 LaterSiblingCollector* collector = |
|
38 static_cast<LaterSiblingCollector*>(aSiblingCollector); |
|
39 // Only collect the entries that actually need restyling by us (and |
|
40 // haven't, for example, already been restyled). |
|
41 // It's important to not mess with the flags on entries not in our |
|
42 // document. |
|
43 if (element->GetCurrentDoc() == collector->tracker->Document() && |
|
44 element->HasFlag(collector->tracker->RestyleBit()) && |
|
45 (aData.mRestyleHint & eRestyle_LaterSiblings)) { |
|
46 collector->elements->AppendElement(element); |
|
47 } |
|
48 |
|
49 return PL_DHASH_NEXT; |
|
50 } |
|
51 |
|
52 struct RestyleCollector { |
|
53 RestyleTracker* tracker; |
|
54 RestyleTracker::RestyleEnumerateData** restyleArrayPtr; |
|
55 }; |
|
56 |
|
57 static PLDHashOperator |
|
58 CollectRestyles(nsISupports* aElement, |
|
59 RestyleTracker::RestyleData& aData, |
|
60 void* aRestyleCollector) |
|
61 { |
|
62 dom::Element* element = |
|
63 static_cast<dom::Element*>(aElement); |
|
64 RestyleCollector* collector = |
|
65 static_cast<RestyleCollector*>(aRestyleCollector); |
|
66 // Only collect the entries that actually need restyling by us (and |
|
67 // haven't, for example, already been restyled). |
|
68 // It's important to not mess with the flags on entries not in our |
|
69 // document. |
|
70 if (element->GetCurrentDoc() != collector->tracker->Document() || |
|
71 !element->HasFlag(collector->tracker->RestyleBit())) { |
|
72 return PL_DHASH_NEXT; |
|
73 } |
|
74 |
|
75 NS_ASSERTION(!element->HasFlag(collector->tracker->RootBit()) || |
|
76 // Maybe we're just not reachable via the frame tree? |
|
77 (element->GetFlattenedTreeParent() && |
|
78 (!element->GetFlattenedTreeParent()->GetPrimaryFrame()|| |
|
79 element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf())) || |
|
80 // Or not reachable due to an async reinsert we have |
|
81 // pending? If so, we'll have a reframe hint around. |
|
82 // That incidentally makes it safe that we still have |
|
83 // the bit, since any descendants that didn't get added |
|
84 // to the roots list because we had the bits will be |
|
85 // completely restyled in a moment. |
|
86 (aData.mChangeHint & nsChangeHint_ReconstructFrame), |
|
87 "Why did this not get handled while processing mRestyleRoots?"); |
|
88 |
|
89 // Unset the restyle bits now, so if they get readded later as we |
|
90 // process we won't clobber that adding of the bit. |
|
91 element->UnsetFlags(collector->tracker->RestyleBit() | |
|
92 collector->tracker->RootBit()); |
|
93 |
|
94 RestyleTracker::RestyleEnumerateData** restyleArrayPtr = |
|
95 collector->restyleArrayPtr; |
|
96 RestyleTracker::RestyleEnumerateData* currentRestyle = |
|
97 *restyleArrayPtr; |
|
98 currentRestyle->mElement = element; |
|
99 currentRestyle->mRestyleHint = aData.mRestyleHint; |
|
100 currentRestyle->mChangeHint = aData.mChangeHint; |
|
101 |
|
102 // Increment to the next slot in the array |
|
103 *restyleArrayPtr = currentRestyle + 1; |
|
104 |
|
105 return PL_DHASH_NEXT; |
|
106 } |
|
107 |
|
108 inline void |
|
109 RestyleTracker::ProcessOneRestyle(Element* aElement, |
|
110 nsRestyleHint aRestyleHint, |
|
111 nsChangeHint aChangeHint) |
|
112 { |
|
113 NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0, |
|
114 "Someone should have handled this before calling us"); |
|
115 NS_PRECONDITION(Document(), "Must have a document"); |
|
116 NS_PRECONDITION(aElement->GetCurrentDoc() == Document(), |
|
117 "Element has unexpected document"); |
|
118 |
|
119 nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); |
|
120 if (aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) { |
|
121 mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint, |
|
122 *this, |
|
123 (aRestyleHint & eRestyle_Subtree) != 0); |
|
124 } else if (aChangeHint && |
|
125 (primaryFrame || |
|
126 (aChangeHint & nsChangeHint_ReconstructFrame))) { |
|
127 // Don't need to recompute style; just apply the hint |
|
128 nsStyleChangeList changeList; |
|
129 changeList.AppendChange(primaryFrame, aElement, aChangeHint); |
|
130 mRestyleManager->ProcessRestyledFrames(changeList); |
|
131 } |
|
132 } |
|
133 |
|
134 void |
|
135 RestyleTracker::DoProcessRestyles() |
|
136 { |
|
137 PROFILER_LABEL("CSS", "ProcessRestyles"); |
|
138 |
|
139 mRestyleManager->BeginProcessingRestyles(); |
|
140 |
|
141 // loop so that we process any restyle events generated by processing |
|
142 while (mPendingRestyles.Count()) { |
|
143 if (mHaveLaterSiblingRestyles) { |
|
144 // Convert them to individual restyles on all the later siblings |
|
145 nsAutoTArray<nsRefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr; |
|
146 LaterSiblingCollector siblingCollector = { this, &laterSiblingArr }; |
|
147 mPendingRestyles.Enumerate(CollectLaterSiblings, &siblingCollector); |
|
148 for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) { |
|
149 Element* element = laterSiblingArr[i]; |
|
150 for (nsIContent* sibling = element->GetNextSibling(); |
|
151 sibling; |
|
152 sibling = sibling->GetNextSibling()) { |
|
153 if (sibling->IsElement() && |
|
154 AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree, |
|
155 NS_STYLE_HINT_NONE)) { |
|
156 // Nothing else to do here; we'll handle the following |
|
157 // siblings when we get to |sibling| in laterSiblingArr. |
|
158 break; |
|
159 } |
|
160 } |
|
161 } |
|
162 |
|
163 // Now remove all those eRestyle_LaterSiblings bits |
|
164 for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) { |
|
165 Element* element = laterSiblingArr[i]; |
|
166 NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?"); |
|
167 RestyleData data; |
|
168 #ifdef DEBUG |
|
169 bool found = |
|
170 #endif |
|
171 mPendingRestyles.Get(element, &data); |
|
172 NS_ASSERTION(found, "Where did our entry go?"); |
|
173 data.mRestyleHint = |
|
174 nsRestyleHint(data.mRestyleHint & ~eRestyle_LaterSiblings); |
|
175 |
|
176 mPendingRestyles.Put(element, data); |
|
177 } |
|
178 |
|
179 mHaveLaterSiblingRestyles = false; |
|
180 } |
|
181 |
|
182 uint32_t rootCount; |
|
183 while ((rootCount = mRestyleRoots.Length())) { |
|
184 // Make sure to pop the element off our restyle root array, so |
|
185 // that we can freely append to the array as we process this |
|
186 // element. |
|
187 nsRefPtr<Element> element; |
|
188 element.swap(mRestyleRoots[rootCount - 1]); |
|
189 mRestyleRoots.RemoveElementAt(rootCount - 1); |
|
190 |
|
191 // Do the document check before calling GetRestyleData, since we |
|
192 // don't want to do the sibling-processing GetRestyleData does if |
|
193 // the node is no longer relevant. |
|
194 if (element->GetCurrentDoc() != Document()) { |
|
195 // Content node has been removed from our document; nothing else |
|
196 // to do here |
|
197 continue; |
|
198 } |
|
199 |
|
200 RestyleData data; |
|
201 if (!GetRestyleData(element, &data)) { |
|
202 continue; |
|
203 } |
|
204 |
|
205 ProcessOneRestyle(element, data.mRestyleHint, data.mChangeHint); |
|
206 } |
|
207 |
|
208 if (mHaveLaterSiblingRestyles) { |
|
209 // Keep processing restyles for now |
|
210 continue; |
|
211 } |
|
212 |
|
213 // Now we only have entries with change hints left. To be safe in |
|
214 // case of reentry from the handing of the change hint, use a |
|
215 // scratch array instead of calling out to ProcessOneRestyle while |
|
216 // enumerating the hashtable. Use the stack if we can, otherwise |
|
217 // fall back on heap-allocation. |
|
218 nsAutoTArray<RestyleEnumerateData, RESTYLE_ARRAY_STACKSIZE> restyleArr; |
|
219 RestyleEnumerateData* restylesToProcess = |
|
220 restyleArr.AppendElements(mPendingRestyles.Count()); |
|
221 if (restylesToProcess) { |
|
222 RestyleEnumerateData* lastRestyle = restylesToProcess; |
|
223 RestyleCollector collector = { this, &lastRestyle }; |
|
224 mPendingRestyles.Enumerate(CollectRestyles, &collector); |
|
225 |
|
226 // Clear the hashtable now that we don't need it anymore |
|
227 mPendingRestyles.Clear(); |
|
228 |
|
229 for (RestyleEnumerateData* currentRestyle = restylesToProcess; |
|
230 currentRestyle != lastRestyle; |
|
231 ++currentRestyle) { |
|
232 ProcessOneRestyle(currentRestyle->mElement, |
|
233 currentRestyle->mRestyleHint, |
|
234 currentRestyle->mChangeHint); |
|
235 } |
|
236 } |
|
237 } |
|
238 |
|
239 mRestyleManager->EndProcessingRestyles(); |
|
240 } |
|
241 |
|
242 bool |
|
243 RestyleTracker::GetRestyleData(Element* aElement, RestyleData* aData) |
|
244 { |
|
245 NS_PRECONDITION(aElement->GetCurrentDoc() == Document(), |
|
246 "Unexpected document; this will lead to incorrect behavior!"); |
|
247 |
|
248 if (!aElement->HasFlag(RestyleBit())) { |
|
249 NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?"); |
|
250 return false; |
|
251 } |
|
252 |
|
253 #ifdef DEBUG |
|
254 bool gotData = |
|
255 #endif |
|
256 mPendingRestyles.Get(aElement, aData); |
|
257 NS_ASSERTION(gotData, "Must have data if restyle bit is set"); |
|
258 |
|
259 if (aData->mRestyleHint & eRestyle_LaterSiblings) { |
|
260 // Someone readded the eRestyle_LaterSiblings hint for this |
|
261 // element. Leave it around for now, but remove the other restyle |
|
262 // hints and the change hint for it. Also unset its root bit, |
|
263 // since it's no longer a root with the new restyle data. |
|
264 RestyleData newData; |
|
265 newData.mChangeHint = nsChangeHint(0); |
|
266 newData.mRestyleHint = eRestyle_LaterSiblings; |
|
267 mPendingRestyles.Put(aElement, newData); |
|
268 aElement->UnsetFlags(RootBit()); |
|
269 aData->mRestyleHint = |
|
270 nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings); |
|
271 } else { |
|
272 mPendingRestyles.Remove(aElement); |
|
273 aElement->UnsetFlags(mRestyleBits); |
|
274 } |
|
275 |
|
276 return true; |
|
277 } |
|
278 |
|
279 } // namespace mozilla |
|
280 |