Line data Source code
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 :
13 : #include "GeckoProfiler.h"
14 : #include "nsFrameManager.h"
15 : #include "nsIDocument.h"
16 : #include "nsStyleChangeList.h"
17 : #include "mozilla/GeckoRestyleManager.h"
18 : #include "RestyleTrackerInlines.h"
19 : #include "nsTransitionManager.h"
20 : #include "mozilla/AutoRestyleTimelineMarker.h"
21 :
22 : namespace mozilla {
23 :
24 : #ifdef RESTYLE_LOGGING
25 : static nsCString
26 0 : GetDocumentURI(nsIDocument* aDocument)
27 : {
28 0 : nsCString result;
29 0 : nsAutoString url;
30 0 : nsresult rv = aDocument->GetDocumentURI(url);
31 0 : if (NS_SUCCEEDED(rv)) {
32 0 : result.Append(NS_ConvertUTF16toUTF8(url).get());
33 : }
34 :
35 0 : return result;
36 : }
37 :
38 : static nsCString
39 0 : FrameTagToString(dom::Element* aElement)
40 : {
41 0 : nsCString result;
42 0 : nsIFrame* frame = aElement->GetPrimaryFrame();
43 0 : if (frame) {
44 0 : nsFrame::ListTag(result, frame);
45 : } else {
46 0 : nsAutoString buf;
47 0 : aElement->NodeInfo()->NameAtom()->ToString(buf);
48 0 : result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
49 : }
50 0 : return result;
51 : }
52 : #endif
53 :
54 : inline nsIDocument*
55 2129 : RestyleTracker::Document() const {
56 2129 : return mRestyleManager->PresContext()->Document();
57 : }
58 :
59 : #define RESTYLE_ARRAY_STACKSIZE 128
60 :
61 38 : struct RestyleEnumerateData : RestyleTracker::Hints {
62 : RefPtr<dom::Element> mElement;
63 : UniqueProfilerBacktrace mBacktrace;
64 : };
65 :
66 : inline void
67 120 : RestyleTracker::ProcessOneRestyle(Element* aElement,
68 : nsRestyleHint aRestyleHint,
69 : nsChangeHint aChangeHint,
70 : const RestyleHintData& aRestyleHintData)
71 : {
72 120 : NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0,
73 : "Someone should have handled this before calling us");
74 120 : NS_PRECONDITION(Document(), "Must have a document");
75 120 : NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
76 : "Element has unexpected document");
77 :
78 120 : LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
79 : GeckoRestyleManager::RestyleHintToString(aRestyleHint).get(),
80 : GeckoRestyleManager::ChangeHintToString(aChangeHint).get());
81 :
82 120 : nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
83 :
84 120 : if (aRestyleHint & ~eRestyle_LaterSiblings) {
85 : #ifdef RESTYLE_LOGGING
86 109 : if (ShouldLogRestyle() && primaryFrame &&
87 0 : GeckoRestyleManager::StructsToLog() != 0) {
88 0 : LOG_RESTYLE("style context tree before restyle:");
89 0 : LOG_RESTYLE_INDENT();
90 0 : primaryFrame->StyleContext()->AsGecko()->LogStyleContextTree(
91 0 : LoggingDepth(), GeckoRestyleManager::StructsToLog());
92 : }
93 : #endif
94 109 : mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
95 109 : *this, aRestyleHint, aRestyleHintData);
96 22 : } else if (aChangeHint &&
97 1 : (primaryFrame ||
98 1 : (aChangeHint & nsChangeHint_ReconstructFrame))) {
99 : // Don't need to recompute style; just apply the hint
100 22 : nsStyleChangeList changeList(StyleBackendType::Gecko);
101 11 : changeList.AppendChange(primaryFrame, aElement, aChangeHint);
102 11 : mRestyleManager->ProcessRestyledFrames(changeList);
103 : }
104 120 : }
105 :
106 : void
107 25 : RestyleTracker::DoProcessRestyles()
108 : {
109 50 : nsAutoCString docURL("N/A");
110 25 : if (profiler_is_active()) {
111 0 : nsIURI *uri = Document()->GetDocumentURI();
112 0 : if (uri) {
113 0 : docURL = uri->GetSpecOrDefault();
114 : }
115 : }
116 50 : AUTO_PROFILER_LABEL_DYNAMIC("RestyleTracker::DoProcessRestyles", CSS,
117 : docURL.get());
118 :
119 : // Create a AnimationsWithDestroyedFrame during restyling process to
120 : // stop animations and transitions on elements that have no frame at the end
121 : // of the restyling process.
122 : RestyleManager::AnimationsWithDestroyedFrame
123 50 : animationsWithDestroyedFrame(mRestyleManager);
124 :
125 : // Create a ReframingStyleContexts struct on the stack and put it in our
126 : // mReframingStyleContexts for almost all of the remaining scope of
127 : // this function.
128 : //
129 : // It needs to be *in* scope during BeginProcessingRestyles, which
130 : // might (if mDoRebuildAllStyleData is true) do substantial amounts of
131 : // restyle processing.
132 : //
133 : // However, it needs to be *out* of scope during
134 : // EndProcessingRestyles, since we should release the style contexts
135 : // it holds prior to any EndReconstruct call that
136 : // EndProcessingRestyles makes. This is because in EndReconstruct we
137 : // try to destroy the old rule tree using the GC mechanism, which
138 : // means it only gets destroyed if it's unreferenced (and if it's
139 : // referenced, we assert). So we want the ReframingStyleContexts
140 : // (which holds old style contexts) to be destroyed before the
141 : // EndReconstruct so those style contexts go away before
142 : // EndReconstruct.
143 : {
144 : GeckoRestyleManager::ReframingStyleContexts
145 50 : reframingStyleContexts(mRestyleManager);
146 :
147 25 : mRestyleManager->BeginProcessingRestyles(*this);
148 :
149 25 : LOG_RESTYLE("Processing %d pending %srestyles with %d restyle roots for %s",
150 : mPendingRestyles.Count(),
151 : mRestyleManager->PresContext()->TransitionManager()->
152 : InAnimationOnlyStyleUpdate()
153 : ? (const char*) "animation " : (const char*) "",
154 : static_cast<int>(mRestyleRoots.Length()),
155 : GetDocumentURI(Document()).get());
156 50 : LOG_RESTYLE_INDENT();
157 :
158 : // loop so that we process any restyle events generated by processing
159 73 : while (mPendingRestyles.Count()) {
160 24 : if (mHaveLaterSiblingRestyles) {
161 : // Convert them to individual restyles on all the later siblings
162 6 : AutoTArray<RefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
163 36 : for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
164 33 : auto element = static_cast<dom::Element*>(iter.Key());
165 33 : MOZ_ASSERT(!element->IsStyledByServo(),
166 : "Should not have Servo-styled elements here");
167 : // Only collect the entries that actually need restyling by us (and
168 : // haven't, for example, already been restyled).
169 : // It's important to not mess with the flags on entries not in our
170 : // document.
171 99 : if (element->GetComposedDoc() == Document() &&
172 66 : element->HasFlag(RestyleBit()) &&
173 33 : (iter.Data()->mRestyleHint & eRestyle_LaterSiblings)) {
174 6 : laterSiblingArr.AppendElement(element);
175 : }
176 : }
177 9 : for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
178 6 : Element* element = laterSiblingArr[i];
179 6 : MOZ_ASSERT(!element->IsStyledByServo());
180 21 : for (nsIContent* sibling = element->GetNextSibling();
181 21 : sibling;
182 15 : sibling = sibling->GetNextSibling()) {
183 15 : if (sibling->IsElement()) {
184 15 : LOG_RESTYLE("adding pending restyle for %s due to "
185 : "eRestyle_LaterSiblings hint on %s",
186 : FrameTagToString(sibling->AsElement()).get(),
187 : FrameTagToString(element->AsElement()).get());
188 15 : if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
189 : nsChangeHint(0))) {
190 : // Nothing else to do here; we'll handle the following
191 : // siblings when we get to |sibling| in laterSiblingArr.
192 0 : break;
193 : }
194 : }
195 : }
196 : }
197 :
198 : // Now remove all those eRestyle_LaterSiblings bits
199 9 : for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
200 6 : Element* element = laterSiblingArr[i];
201 6 : NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?");
202 : RestyleData* data;
203 : #ifdef DEBUG
204 : bool found =
205 : #endif
206 6 : mPendingRestyles.Get(element, &data);
207 6 : NS_ASSERTION(found, "Where did our entry go?");
208 12 : data->mRestyleHint =
209 6 : nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
210 : }
211 :
212 3 : LOG_RESTYLE("%d pending restyles after expanding out "
213 : "eRestyle_LaterSiblings", mPendingRestyles.Count());
214 :
215 3 : mHaveLaterSiblingRestyles = false;
216 : }
217 :
218 : uint32_t rootCount;
219 240 : while ((rootCount = mRestyleRoots.Length())) {
220 : // Make sure to pop the element off our restyle root array, so
221 : // that we can freely append to the array as we process this
222 : // element.
223 214 : RefPtr<Element> element;
224 108 : element.swap(mRestyleRoots[rootCount - 1]);
225 108 : mRestyleRoots.RemoveElementAt(rootCount - 1);
226 :
227 108 : LOG_RESTYLE("processing style root %s at index %d",
228 : FrameTagToString(element).get(), rootCount - 1);
229 214 : LOG_RESTYLE_INDENT();
230 :
231 : // Do the document check before calling GetRestyleData, since we
232 : // don't want to do the sibling-processing GetRestyleData does if
233 : // the node is no longer relevant.
234 108 : if (element->GetComposedDoc() != Document()) {
235 : // Content node has been removed from our document; nothing else
236 : // to do here
237 0 : LOG_RESTYLE("skipping, no longer in the document");
238 0 : continue;
239 : }
240 :
241 214 : nsAutoPtr<RestyleData> data;
242 108 : if (!GetRestyleData(element, data)) {
243 2 : LOG_RESTYLE("skipping, already restyled");
244 2 : continue;
245 : }
246 :
247 : {
248 : AutoRestyleTimelineMarker marker(
249 106 : mRestyleManager->PresContext()->GetDocShell(),
250 318 : data->mRestyleHint & eRestyle_AllHintsWithAnimations);
251 212 : Maybe<AutoProfilerTracing> tracing;
252 106 : if (profiler_feature_active(ProfilerFeature::Restyle)) {
253 0 : tracing.emplace("Paint", "Styles", Move(data->mBacktrace));
254 : }
255 106 : ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
256 212 : data->mRestyleHintData);
257 106 : AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
258 : }
259 : }
260 :
261 24 : if (mHaveLaterSiblingRestyles) {
262 : // Keep processing restyles for now
263 0 : continue;
264 : }
265 :
266 : // Now we only have entries with change hints left. To be safe in
267 : // case of reentry from the handing of the change hint, use a
268 : // scratch array instead of calling out to ProcessOneRestyle while
269 : // enumerating the hashtable. Use the stack if we can, otherwise
270 : // fall back on heap-allocation.
271 48 : AutoTArray<RestyleEnumerateData, RESTYLE_ARRAY_STACKSIZE> restyleArr;
272 : RestyleEnumerateData* restylesToProcess =
273 24 : restyleArr.AppendElements(mPendingRestyles.Count());
274 24 : if (restylesToProcess) {
275 24 : RestyleEnumerateData* restyle = restylesToProcess;
276 : #ifdef RESTYLE_LOGGING
277 24 : uint32_t count = 0;
278 : #endif
279 43 : for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
280 19 : auto element = static_cast<dom::Element*>(iter.Key());
281 19 : RestyleTracker::RestyleData* data = iter.Data();
282 :
283 : // Only collect the entries that actually need restyling by us (and
284 : // haven't, for example, already been restyled).
285 : // It's important to not mess with the flags on entries not in our
286 : // document.
287 38 : if (element->GetComposedDoc() != Document() ||
288 19 : !element->HasFlag(RestyleBit())) {
289 5 : LOG_RESTYLE("skipping pending restyle %s, already restyled or no "
290 : "longer in the document",
291 : FrameTagToString(element).get());
292 5 : continue;
293 : }
294 :
295 14 : NS_ASSERTION(
296 : !element->HasFlag(RootBit()) ||
297 : // Maybe we're just not reachable via the frame tree?
298 : (element->GetFlattenedTreeParent() &&
299 : (!element->GetFlattenedTreeParent()->GetPrimaryFrame() ||
300 : element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf() ||
301 : element->GetComposedDoc()->GetShell()->FrameManager()
302 : ->GetDisplayContentsStyleFor(element))) ||
303 : // Or not reachable due to an async reinsert we have
304 : // pending? If so, we'll have a reframe hint around.
305 : // That incidentally makes it safe that we still have
306 : // the bit, since any descendants that didn't get added
307 : // to the roots list because we had the bits will be
308 : // completely restyled in a moment.
309 : (data->mChangeHint & nsChangeHint_ReconstructFrame),
310 : "Why did this not get handled while processing mRestyleRoots?");
311 :
312 : // Unset the restyle bits now, so if they get readded later as we
313 : // process we won't clobber that adding of the bit.
314 42 : element->UnsetFlags(RestyleBit() |
315 14 : RootBit() |
316 28 : ConditionalDescendantsBit());
317 :
318 14 : restyle->mElement = element;
319 14 : restyle->mRestyleHint = data->mRestyleHint;
320 14 : restyle->mChangeHint = data->mChangeHint;
321 : // We can move data since we'll be clearing mPendingRestyles after
322 : // we finish enumerating it.
323 14 : restyle->mRestyleHintData = Move(data->mRestyleHintData);
324 14 : restyle->mBacktrace = Move(data->mBacktrace);
325 :
326 : #ifdef RESTYLE_LOGGING
327 14 : count++;
328 : #endif
329 :
330 : // Increment to the next slot in the array
331 14 : restyle++;
332 : }
333 :
334 24 : RestyleEnumerateData* lastRestyle = restyle;
335 :
336 : // Clear the hashtable now that we don't need it anymore
337 24 : mPendingRestyles.Clear();
338 :
339 : #ifdef RESTYLE_LOGGING
340 24 : uint32_t index = 0;
341 : #endif
342 38 : for (RestyleEnumerateData* currentRestyle = restylesToProcess;
343 38 : currentRestyle != lastRestyle;
344 : ++currentRestyle) {
345 14 : LOG_RESTYLE("processing pending restyle %s at index %d/%d",
346 : FrameTagToString(currentRestyle->mElement).get(),
347 : index++, count);
348 28 : LOG_RESTYLE_INDENT();
349 :
350 28 : Maybe<AutoProfilerTracing> tracing;
351 14 : if (profiler_feature_active(ProfilerFeature::Restyle)) {
352 0 : tracing.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
353 : }
354 :
355 : {
356 : AutoRestyleTimelineMarker marker(
357 14 : mRestyleManager->PresContext()->GetDocShell(),
358 42 : currentRestyle->mRestyleHint & eRestyle_AllHintsWithAnimations);
359 14 : ProcessOneRestyle(currentRestyle->mElement,
360 : currentRestyle->mRestyleHint,
361 : currentRestyle->mChangeHint,
362 14 : currentRestyle->mRestyleHintData);
363 : }
364 : }
365 : }
366 : }
367 : }
368 :
369 : // mPendingRestyles is now empty.
370 25 : mHaveSelectors = false;
371 :
372 25 : mRestyleManager->EndProcessingRestyles();
373 25 : }
374 :
375 : bool
376 1729 : RestyleTracker::GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData)
377 : {
378 1729 : NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
379 : "Unexpected document; this will lead to incorrect behavior!");
380 :
381 1729 : if (!aElement->HasFlag(RestyleBit())) {
382 1587 : NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?");
383 1587 : return false;
384 : }
385 :
386 142 : mPendingRestyles.Remove(aElement, &aData);
387 142 : NS_ASSERTION(aData.get(), "Must have data if restyle bit is set");
388 :
389 142 : if (aData->mRestyleHint & eRestyle_LaterSiblings) {
390 : // Someone readded the eRestyle_LaterSiblings hint for this
391 : // element. Leave it around for now, but remove the other restyle
392 : // hints and the change hint for it. Also unset its root bit,
393 : // since it's no longer a root with the new restyle data.
394 :
395 : // During a normal restyle, we should have already processed the
396 : // mDescendants array the last time we processed the restyle
397 : // for this element. But in RebuildAllStyleData, we don't initially
398 : // expand out eRestyle_LaterSiblings, so we can get in here the
399 : // first time we need to process a restyle for this element. In that
400 : // case, it's fine for us to have a non-empty mDescendants, since
401 : // we know that RebuildAllStyleData adds eRestyle_ForceDescendants
402 : // and we're guaranteed we'll restyle the entire tree.
403 0 : NS_ASSERTION(mRestyleManager->InRebuildAllStyleData() ||
404 : aData->mDescendants.IsEmpty(),
405 : "expected descendants to be handled by now");
406 :
407 0 : RestyleData* newData = new RestyleData;
408 0 : newData->mChangeHint = nsChangeHint(0);
409 0 : newData->mRestyleHint = eRestyle_LaterSiblings;
410 0 : mPendingRestyles.Put(aElement, newData);
411 0 : aElement->UnsetFlags(RootBit());
412 0 : aData->mRestyleHint =
413 0 : nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings);
414 : } else {
415 142 : aElement->UnsetFlags(mRestyleBits);
416 : }
417 :
418 142 : return true;
419 : }
420 :
421 : void
422 1308 : RestyleTracker::AddRestyleRootsIfAwaitingRestyle(
423 : const nsTArray<RefPtr<Element>>& aElements)
424 : {
425 : // The RestyleData for a given element has stored in mDescendants
426 : // the list of descendants we need to end up restyling. Since we
427 : // won't necessarily end up restyling them, due to the restyle
428 : // process finishing early (see how RestyleResult::eStop is handled
429 : // in ElementRestyler::Restyle), we add them to the list of restyle
430 : // roots to handle the next time around the
431 : // RestyleTracker::DoProcessRestyles loop.
432 : //
433 : // Note that aElements must maintain the same invariant
434 : // that mRestyleRoots does, i.e. that ancestors appear after descendants.
435 : // Since we call AddRestyleRootsIfAwaitingRestyle only after we have
436 : // removed the restyle root we are currently processing from the end of
437 : // mRestyleRoots, and the only elements we get here in aElements are
438 : // descendants of that restyle root, we are safe to simply append to the
439 : // end of mRestyleRoots to maintain its invariant.
440 1344 : for (size_t i = 0; i < aElements.Length(); i++) {
441 36 : Element* element = aElements[i];
442 36 : if (element->HasFlag(RestyleBit())) {
443 24 : mRestyleRoots.AppendElement(element);
444 : }
445 : }
446 1308 : }
447 :
448 : void
449 48 : RestyleTracker::ClearSelectors()
450 : {
451 48 : if (!mHaveSelectors) {
452 48 : return;
453 : }
454 0 : for (auto it = mPendingRestyles.Iter(); !it.Done(); it.Next()) {
455 0 : RestyleData* data = it.Data();
456 0 : if (data->mRestyleHint & eRestyle_SomeDescendants) {
457 0 : data->mRestyleHint =
458 0 : (data->mRestyleHint & ~eRestyle_SomeDescendants) | eRestyle_Subtree;
459 0 : data->mRestyleHintData.mSelectorsForDescendants.Clear();
460 : } else {
461 0 : MOZ_ASSERT(data->mRestyleHintData.mSelectorsForDescendants.IsEmpty());
462 : }
463 : }
464 0 : mHaveSelectors = false;
465 : }
466 :
467 : } // namespace mozilla
|