/src/mozilla-central/layout/base/RestyleManager.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | #include "mozilla/RestyleManager.h" |
8 | | |
9 | | #include "mozilla/AutoRestyleTimelineMarker.h" |
10 | | #include "mozilla/AutoTimelineMarker.h" |
11 | | #include "mozilla/ComputedStyle.h" |
12 | | #include "mozilla/ComputedStyleInlines.h" |
13 | | #include "mozilla/DocumentStyleRootIterator.h" |
14 | | #include "mozilla/LayerAnimationInfo.h" |
15 | | #include "mozilla/ServoBindings.h" |
16 | | #include "mozilla/ServoStyleSetInlines.h" |
17 | | #include "mozilla/Unused.h" |
18 | | #include "mozilla/ViewportFrame.h" |
19 | | #include "mozilla/dom/ChildIterator.h" |
20 | | #include "mozilla/dom/ElementInlines.h" |
21 | | #include "mozilla/dom/HTMLBodyElement.h" |
22 | | |
23 | | #include "Layers.h" |
24 | | #include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords |
25 | | #include "nsAnimationManager.h" |
26 | | #include "nsBlockFrame.h" |
27 | | #include "nsBulletFrame.h" |
28 | | #include "nsContentUtils.h" |
29 | | #include "nsCSSFrameConstructor.h" |
30 | | #include "nsCSSRendering.h" |
31 | | #include "nsIDocumentInlines.h" |
32 | | #include "nsIFrame.h" |
33 | | #include "nsIFrameInlines.h" |
34 | | #include "nsImageFrame.h" |
35 | | #include "nsIPresShellInlines.h" |
36 | | #include "nsPlaceholderFrame.h" |
37 | | #include "nsPrintfCString.h" |
38 | | #include "nsRefreshDriver.h" |
39 | | #include "nsStyleChangeList.h" |
40 | | #include "nsStyleUtil.h" |
41 | | #include "nsTransitionManager.h" |
42 | | #include "StickyScrollContainer.h" |
43 | | #include "mozilla/EffectSet.h" |
44 | | #include "mozilla/IntegerRange.h" |
45 | | #include "mozilla/ViewportFrame.h" |
46 | | #include "SVGObserverUtils.h" |
47 | | #include "SVGTextFrame.h" |
48 | | #include "ActiveLayerTracker.h" |
49 | | #include "nsSVGIntegrationUtils.h" |
50 | | |
51 | | #ifdef ACCESSIBILITY |
52 | | #include "nsAccessibilityService.h" |
53 | | #endif |
54 | | |
55 | | using namespace mozilla::dom; |
56 | | |
57 | | namespace mozilla { |
58 | | |
59 | | RestyleManager::RestyleManager(nsPresContext* aPresContext) |
60 | | : mPresContext(aPresContext) |
61 | | , mRestyleGeneration(1) |
62 | | , mUndisplayedRestyleGeneration(1) |
63 | | , mInStyleRefresh(false) |
64 | | , mAnimationGeneration(0) |
65 | 0 | { |
66 | 0 | MOZ_ASSERT(mPresContext); |
67 | 0 | } |
68 | | |
69 | | void |
70 | | RestyleManager::ContentInserted(nsIContent* aChild) |
71 | 0 | { |
72 | 0 | MOZ_ASSERT(aChild->GetParentNode()); |
73 | 0 | RestyleForInsertOrChange(aChild); |
74 | 0 | } |
75 | | |
76 | | void |
77 | | RestyleManager::ContentAppended(nsIContent* aFirstNewContent) |
78 | 0 | { |
79 | 0 | MOZ_ASSERT(aFirstNewContent->GetParent()); |
80 | 0 |
|
81 | 0 | // The container cannot be a document, but might be a ShadowRoot. |
82 | 0 | if (!aFirstNewContent->GetParentNode()->IsElement()) { |
83 | 0 | return; |
84 | 0 | } |
85 | 0 | Element* container = aFirstNewContent->GetParentNode()->AsElement(); |
86 | 0 |
|
87 | | #ifdef DEBUG |
88 | | { |
89 | | for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { |
90 | | NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(), |
91 | | "anonymous nodes should not be in child lists"); |
92 | | } |
93 | | } |
94 | | #endif |
95 | | uint32_t selectorFlags = |
96 | 0 | container->GetFlags() & (NODE_ALL_SELECTOR_FLAGS & |
97 | 0 | ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); |
98 | 0 | if (selectorFlags == 0) |
99 | 0 | return; |
100 | 0 | |
101 | 0 | if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { |
102 | 0 | // see whether we need to restyle the container |
103 | 0 | bool wasEmpty = true; // :empty or :-moz-only-whitespace |
104 | 0 | for (nsIContent* cur = container->GetFirstChild(); |
105 | 0 | cur != aFirstNewContent; |
106 | 0 | cur = cur->GetNextSibling()) { |
107 | 0 | // We don't know whether we're testing :empty or :-moz-only-whitespace, |
108 | 0 | // so be conservative and assume :-moz-only-whitespace (i.e., make |
109 | 0 | // IsSignificantChild less likely to be true, and thus make us more |
110 | 0 | // likely to restyle). |
111 | 0 | if (nsStyleUtil::IsSignificantChild(cur, false)) { |
112 | 0 | wasEmpty = false; |
113 | 0 | break; |
114 | 0 | } |
115 | 0 | } |
116 | 0 | if (wasEmpty) { |
117 | 0 | RestyleForEmptyChange(container); |
118 | 0 | return; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | |
122 | 0 | if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { |
123 | 0 | PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0)); |
124 | 0 | // Restyling the container is the most we can do here, so we're done. |
125 | 0 | return; |
126 | 0 | } |
127 | 0 | |
128 | 0 | if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { |
129 | 0 | // restyle the last element child before this node |
130 | 0 | for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); |
131 | 0 | cur; |
132 | 0 | cur = cur->GetPreviousSibling()) { |
133 | 0 | if (cur->IsElement()) { |
134 | 0 | PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0)); |
135 | 0 | break; |
136 | 0 | } |
137 | 0 | } |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | void |
142 | | RestyleManager::RestyleForEmptyChange(Element* aContainer) |
143 | 0 | { |
144 | 0 | // In some cases (:empty + E, :empty ~ E), a change in the content of |
145 | 0 | // an element requires restyling its parent's siblings. |
146 | 0 | nsRestyleHint hint = eRestyle_Subtree; |
147 | 0 | nsIContent* grandparent = aContainer->GetParent(); |
148 | 0 | if (grandparent && |
149 | 0 | (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { |
150 | 0 | hint = nsRestyleHint(hint | eRestyle_LaterSiblings); |
151 | 0 | } |
152 | 0 | PostRestyleEvent(aContainer, hint, nsChangeHint(0)); |
153 | 0 | } |
154 | | |
155 | | void |
156 | | RestyleManager::MaybeRestyleForEdgeChildChange(Element* aContainer, |
157 | | nsIContent* aChangedChild) |
158 | 0 | { |
159 | 0 | MOZ_ASSERT(aContainer->GetFlags() & NODE_HAS_EDGE_CHILD_SELECTOR); |
160 | 0 | MOZ_ASSERT(aChangedChild->GetParent() == aContainer); |
161 | 0 | // restyle the previously-first element child if it is after this node |
162 | 0 | bool passedChild = false; |
163 | 0 | for (nsIContent* content = aContainer->GetFirstChild(); |
164 | 0 | content; |
165 | 0 | content = content->GetNextSibling()) { |
166 | 0 | if (content == aChangedChild) { |
167 | 0 | passedChild = true; |
168 | 0 | continue; |
169 | 0 | } |
170 | 0 | if (content->IsElement()) { |
171 | 0 | if (passedChild) { |
172 | 0 | PostRestyleEvent(content->AsElement(), eRestyle_Subtree, |
173 | 0 | nsChangeHint(0)); |
174 | 0 | } |
175 | 0 | break; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | // restyle the previously-last element child if it is before this node |
179 | 0 | passedChild = false; |
180 | 0 | for (nsIContent* content = aContainer->GetLastChild(); |
181 | 0 | content; |
182 | 0 | content = content->GetPreviousSibling()) { |
183 | 0 | if (content == aChangedChild) { |
184 | 0 | passedChild = true; |
185 | 0 | continue; |
186 | 0 | } |
187 | 0 | if (content->IsElement()) { |
188 | 0 | if (passedChild) { |
189 | 0 | PostRestyleEvent(content->AsElement(), eRestyle_Subtree, |
190 | 0 | nsChangeHint(0)); |
191 | 0 | } |
192 | 0 | break; |
193 | 0 | } |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | // Needed since we can't use PostRestyleEvent on non-elements (with |
198 | | // eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree | |
199 | | // eRestyle_LaterSiblings) as appropriate). |
200 | | static void |
201 | | RestyleSiblingsStartingWith(RestyleManager* aRestyleManager, |
202 | | nsIContent* aStartingSibling /* may be null */) |
203 | 0 | { |
204 | 0 | for (nsIContent* sibling = aStartingSibling; sibling; |
205 | 0 | sibling = sibling->GetNextSibling()) { |
206 | 0 | if (sibling->IsElement()) { |
207 | 0 | aRestyleManager-> |
208 | 0 | PostRestyleEvent(sibling->AsElement(), |
209 | 0 | nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings), |
210 | 0 | nsChangeHint(0)); |
211 | 0 | break; |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | | |
216 | | template<typename CharT> |
217 | | bool |
218 | | WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) |
219 | 0 | { |
220 | 0 | for (auto index : IntegerRange(aUpTo)) { |
221 | 0 | if (!dom::IsSpaceCharacter(aBuffer[index])) { |
222 | 0 | return false; |
223 | 0 | } |
224 | 0 | } |
225 | 0 | return true; |
226 | 0 | } Unexecuted instantiation: bool mozilla::WhitespaceOnly<char16_t>(char16_t const*, unsigned long) Unexecuted instantiation: bool mozilla::WhitespaceOnly<char>(char const*, unsigned long) |
227 | | |
228 | | template<typename CharT> |
229 | | bool |
230 | | WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, |
231 | | size_t aOldLength, |
232 | | size_t aNewLength) |
233 | 0 | { |
234 | 0 | MOZ_ASSERT(aOldLength <= aNewLength); |
235 | 0 | if (!WhitespaceOnly(aBuffer, aOldLength)) { |
236 | 0 | // The old text was already not whitespace-only. |
237 | 0 | return false; |
238 | 0 | } |
239 | 0 | |
240 | 0 | return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength); |
241 | 0 | } Unexecuted instantiation: bool mozilla::WhitespaceOnlyChangedOnAppend<char16_t>(char16_t const*, unsigned long, unsigned long) Unexecuted instantiation: bool mozilla::WhitespaceOnlyChangedOnAppend<char>(char const*, unsigned long, unsigned long) |
242 | | |
243 | | static bool |
244 | | HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) |
245 | 0 | { |
246 | 0 | MOZ_ASSERT(aChild->GetParent() == aContainer); |
247 | 0 | for (nsIContent* child = aContainer->GetFirstChild(); |
248 | 0 | child; |
249 | 0 | child = child->GetNextSibling()) { |
250 | 0 | if (child == aChild) { |
251 | 0 | continue; |
252 | 0 | } |
253 | 0 | // We don't know whether we're testing :empty or :-moz-only-whitespace, |
254 | 0 | // so be conservative and assume :-moz-only-whitespace (i.e., make |
255 | 0 | // IsSignificantChild less likely to be true, and thus make us more |
256 | 0 | // likely to restyle). |
257 | 0 | if (nsStyleUtil::IsSignificantChild(child, false)) { |
258 | 0 | return true; |
259 | 0 | } |
260 | 0 | } |
261 | 0 |
|
262 | 0 | return false; |
263 | 0 | } |
264 | | |
265 | | void |
266 | | RestyleManager::CharacterDataChanged(nsIContent* aContent, |
267 | | const CharacterDataChangeInfo& aInfo) |
268 | 0 | { |
269 | 0 | nsINode* parent = aContent->GetParentNode(); |
270 | 0 | MOZ_ASSERT(parent, "How were we notified of a stray node?"); |
271 | 0 |
|
272 | 0 | uint32_t slowSelectorFlags = parent->GetFlags() & NODE_ALL_SELECTOR_FLAGS; |
273 | 0 | if (!(slowSelectorFlags & (NODE_HAS_EMPTY_SELECTOR | |
274 | 0 | NODE_HAS_EDGE_CHILD_SELECTOR))) { |
275 | 0 | // Nothing to do, no other slow selector can change as a result of this. |
276 | 0 | return; |
277 | 0 | } |
278 | 0 | |
279 | 0 | if (!aContent->IsText()) { |
280 | 0 | // Doesn't matter to styling (could be a processing instruction or a |
281 | 0 | // comment), it can't change whether any selectors match or don't. |
282 | 0 | return; |
283 | 0 | } |
284 | 0 | |
285 | 0 | |
286 | 0 | if (MOZ_UNLIKELY(!parent->IsElement())) { |
287 | 0 | MOZ_ASSERT(parent->IsShadowRoot()); |
288 | 0 | return; |
289 | 0 | } |
290 | 0 |
|
291 | 0 | if (MOZ_UNLIKELY(aContent->IsRootOfAnonymousSubtree())) { |
292 | 0 | // This is an anonymous node and thus isn't in child lists, so isn't taken |
293 | 0 | // into account for selector matching the relevant selectors here. |
294 | 0 | return; |
295 | 0 | } |
296 | 0 | |
297 | 0 | // Handle appends specially since they're common and we can know both the old |
298 | 0 | // and the new text exactly. |
299 | 0 | // |
300 | 0 | // TODO(emilio): This could be made much more general if :-moz-only-whitespace |
301 | 0 | // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only |
302 | 0 | // need to know whether we went from empty to non-empty, and that's trivial to |
303 | 0 | // know, with CharacterDataChangeInfo... |
304 | 0 | if (!aInfo.mAppend) { |
305 | 0 | // FIXME(emilio): This restyles unnecessarily if the text node is the only |
306 | 0 | // child of the parent element. Fortunately, it's uncommon to have such |
307 | 0 | // nodes and this not being an append. |
308 | 0 | // |
309 | 0 | // See the testcase in bug 1427625 for a test-case that triggers this. |
310 | 0 | RestyleForInsertOrChange(aContent); |
311 | 0 | return; |
312 | 0 | } |
313 | 0 | |
314 | 0 | const nsTextFragment* text = aContent->GetText(); |
315 | 0 |
|
316 | 0 | const size_t oldLength = aInfo.mChangeStart; |
317 | 0 | const size_t newLength = text->GetLength(); |
318 | 0 |
|
319 | 0 | const bool emptyChanged = !oldLength && newLength; |
320 | 0 |
|
321 | 0 | const bool whitespaceOnlyChanged = text->Is2b() |
322 | 0 | ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength) |
323 | 0 | : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength); |
324 | 0 |
|
325 | 0 | if (!emptyChanged && !whitespaceOnlyChanged) { |
326 | 0 | return; |
327 | 0 | } |
328 | 0 | |
329 | 0 | if (slowSelectorFlags & NODE_HAS_EMPTY_SELECTOR) { |
330 | 0 | if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { |
331 | 0 | // We used to be empty, restyle the parent. |
332 | 0 | RestyleForEmptyChange(parent->AsElement()); |
333 | 0 | return; |
334 | 0 | } |
335 | 0 | } |
336 | 0 | |
337 | 0 | if (slowSelectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { |
338 | 0 | MaybeRestyleForEdgeChildChange(parent->AsElement(), aContent); |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | | // Restyling for a ContentInserted or CharacterDataChanged notification. |
343 | | // This could be used for ContentRemoved as well if we got the |
344 | | // notification before the removal happened (and sometimes |
345 | | // CharacterDataChanged is more like a removal than an addition). |
346 | | // The comments are written and variables are named in terms of it being |
347 | | // a ContentInserted notification. |
348 | | void |
349 | | RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) |
350 | 0 | { |
351 | 0 | nsINode* parentNode = aChild->GetParentNode(); |
352 | 0 |
|
353 | 0 | MOZ_ASSERT(parentNode); |
354 | 0 | // The container might be a document or a ShadowRoot. |
355 | 0 | if (!parentNode->IsElement()) { |
356 | 0 | return; |
357 | 0 | } |
358 | 0 | Element* container = parentNode->AsElement(); |
359 | 0 |
|
360 | 0 | NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(), |
361 | 0 | "anonymous nodes should not be in child lists"); |
362 | 0 | uint32_t selectorFlags = container->GetFlags() & NODE_ALL_SELECTOR_FLAGS; |
363 | 0 | if (selectorFlags == 0) |
364 | 0 | return; |
365 | 0 | |
366 | 0 | if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { |
367 | 0 | // See whether we need to restyle the container due to :empty / |
368 | 0 | // :-moz-only-whitespace. |
369 | 0 | const bool wasEmpty = !HasAnySignificantSibling(container, aChild); |
370 | 0 | if (wasEmpty) { |
371 | 0 | // FIXME(emilio): When coming from CharacterDataChanged this can restyle |
372 | 0 | // unnecessarily. Also can restyle unnecessarily if aChild is not |
373 | 0 | // significant anyway, though that's more unlikely. |
374 | 0 | RestyleForEmptyChange(container); |
375 | 0 | return; |
376 | 0 | } |
377 | 0 | } |
378 | 0 | |
379 | 0 | if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { |
380 | 0 | PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0)); |
381 | 0 | // Restyling the container is the most we can do here, so we're done. |
382 | 0 | return; |
383 | 0 | } |
384 | 0 | |
385 | 0 | if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { |
386 | 0 | // Restyle all later siblings. |
387 | 0 | RestyleSiblingsStartingWith(this, aChild->GetNextSibling()); |
388 | 0 | } |
389 | 0 |
|
390 | 0 | if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { |
391 | 0 | MaybeRestyleForEdgeChildChange(container, aChild); |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | | void |
396 | | RestyleManager::ContentRemoved(nsIContent* aOldChild, |
397 | | nsIContent* aFollowingSibling) |
398 | 0 | { |
399 | 0 | MOZ_ASSERT(aOldChild->GetParentNode()); |
400 | 0 |
|
401 | 0 | // Computed style data isn't useful for detached nodes, and we'll need to |
402 | 0 | // recompute it anyway if we ever insert the nodes back into a document. |
403 | 0 | if (aOldChild->IsElement()) { |
404 | 0 | RestyleManager::ClearServoDataFromSubtree(aOldChild->AsElement()); |
405 | 0 | } |
406 | 0 |
|
407 | 0 | // The container might be a document or a ShadowRoot. |
408 | 0 | if (!aOldChild->GetParentNode()->IsElement()) { |
409 | 0 | return; |
410 | 0 | } |
411 | 0 | Element* container = aOldChild->GetParentNode()->AsElement(); |
412 | 0 |
|
413 | 0 | if (aOldChild->IsRootOfAnonymousSubtree()) { |
414 | 0 | // This should be an assert, but this is called incorrectly in |
415 | 0 | // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging |
416 | 0 | // up the logs. Make it an assert again when that's fixed. |
417 | 0 | MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode), |
418 | 0 | "anonymous nodes should not be in child lists (bug 439258)"); |
419 | 0 | } |
420 | 0 | uint32_t selectorFlags = container->GetFlags() & NODE_ALL_SELECTOR_FLAGS; |
421 | 0 | if (selectorFlags == 0) |
422 | 0 | return; |
423 | 0 | |
424 | 0 | if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { |
425 | 0 | // see whether we need to restyle the container |
426 | 0 | bool isEmpty = true; // :empty or :-moz-only-whitespace |
427 | 0 | for (nsIContent* child = container->GetFirstChild(); |
428 | 0 | child; |
429 | 0 | child = child->GetNextSibling()) { |
430 | 0 | // We don't know whether we're testing :empty or :-moz-only-whitespace, |
431 | 0 | // so be conservative and assume :-moz-only-whitespace (i.e., make |
432 | 0 | // IsSignificantChild less likely to be true, and thus make us more |
433 | 0 | // likely to restyle). |
434 | 0 | if (nsStyleUtil::IsSignificantChild(child, false)) { |
435 | 0 | isEmpty = false; |
436 | 0 | break; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | if (isEmpty) { |
440 | 0 | RestyleForEmptyChange(container); |
441 | 0 | return; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | |
445 | 0 | if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { |
446 | 0 | PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0)); |
447 | 0 | // Restyling the container is the most we can do here, so we're done. |
448 | 0 | return; |
449 | 0 | } |
450 | 0 | |
451 | 0 | if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { |
452 | 0 | // Restyle all later siblings. |
453 | 0 | RestyleSiblingsStartingWith(this, aFollowingSibling); |
454 | 0 | } |
455 | 0 |
|
456 | 0 | if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { |
457 | 0 | // restyle the now-first element child if it was after aOldChild |
458 | 0 | bool reachedFollowingSibling = false; |
459 | 0 | for (nsIContent* content = container->GetFirstChild(); |
460 | 0 | content; |
461 | 0 | content = content->GetNextSibling()) { |
462 | 0 | if (content == aFollowingSibling) { |
463 | 0 | reachedFollowingSibling = true; |
464 | 0 | // do NOT continue here; we might want to restyle this node |
465 | 0 | } |
466 | 0 | if (content->IsElement()) { |
467 | 0 | if (reachedFollowingSibling) { |
468 | 0 | PostRestyleEvent(content->AsElement(), eRestyle_Subtree, |
469 | 0 | nsChangeHint(0)); |
470 | 0 | } |
471 | 0 | break; |
472 | 0 | } |
473 | 0 | } |
474 | 0 | // restyle the now-last element child if it was before aOldChild |
475 | 0 | reachedFollowingSibling = (aFollowingSibling == nullptr); |
476 | 0 | for (nsIContent* content = container->GetLastChild(); |
477 | 0 | content; |
478 | 0 | content = content->GetPreviousSibling()) { |
479 | 0 | if (content->IsElement()) { |
480 | 0 | if (reachedFollowingSibling) { |
481 | 0 | PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0)); |
482 | 0 | } |
483 | 0 | break; |
484 | 0 | } |
485 | 0 | if (content == aFollowingSibling) { |
486 | 0 | reachedFollowingSibling = true; |
487 | 0 | } |
488 | 0 | } |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | | /** |
493 | | * Calculates the change hint and the restyle hint for a given content state |
494 | | * change. |
495 | | * |
496 | | * This is called from both Restyle managers. |
497 | | */ |
498 | | void |
499 | | RestyleManager::ContentStateChangedInternal(const Element& aElement, |
500 | | EventStates aStateMask, |
501 | | nsChangeHint* aOutChangeHint) |
502 | 0 | { |
503 | 0 | MOZ_ASSERT(!mInStyleRefresh); |
504 | 0 | MOZ_ASSERT(aOutChangeHint); |
505 | 0 |
|
506 | 0 | *aOutChangeHint = nsChangeHint(0); |
507 | 0 | // Any change to a content state that affects which frames we construct |
508 | 0 | // must lead to a frame reconstruct here if we already have a frame. |
509 | 0 | // Note that we never decide through non-CSS means to not create frames |
510 | 0 | // based on content states, so if we already don't have a frame we don't |
511 | 0 | // need to force a reframe -- if it's needed, the HasStateDependentStyle |
512 | 0 | // call will handle things. |
513 | 0 | nsIFrame* primaryFrame = aElement.GetPrimaryFrame(); |
514 | 0 | if (primaryFrame) { |
515 | 0 | // If it's generated content, ignore LOADING/etc state changes on it. |
516 | 0 | if (!primaryFrame->IsGeneratedContentFrame() && |
517 | 0 | aStateMask.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | |
518 | 0 | NS_EVENT_STATE_USERDISABLED | |
519 | 0 | NS_EVENT_STATE_SUPPRESSED | |
520 | 0 | NS_EVENT_STATE_LOADING)) { |
521 | 0 | *aOutChangeHint = nsChangeHint_ReconstructFrame; |
522 | 0 | } else { |
523 | 0 | auto* disp = primaryFrame->StyleDisplay(); |
524 | 0 | if (disp->HasAppearance()) { |
525 | 0 | nsITheme* theme = PresContext()->GetTheme(); |
526 | 0 | if (theme && |
527 | 0 | theme->ThemeSupportsWidget(PresContext(), primaryFrame, |
528 | 0 | disp->mAppearance)) { |
529 | 0 | bool repaint = false; |
530 | 0 | theme->WidgetStateChanged(primaryFrame, disp->mAppearance, nullptr, |
531 | 0 | &repaint, nullptr); |
532 | 0 | if (repaint) { |
533 | 0 | *aOutChangeHint |= nsChangeHint_RepaintFrame; |
534 | 0 | } |
535 | 0 | } |
536 | 0 | } |
537 | 0 | } |
538 | 0 |
|
539 | 0 | primaryFrame->ContentStatesChanged(aStateMask); |
540 | 0 | } |
541 | 0 |
|
542 | 0 | if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) { |
543 | 0 | // Exposing information to the page about whether the link is |
544 | 0 | // visited or not isn't really something we can worry about here. |
545 | 0 | // FIXME: We could probably do this a bit better. |
546 | 0 | *aOutChangeHint |= nsChangeHint_RepaintFrame; |
547 | 0 | } |
548 | 0 | } |
549 | | |
550 | | /* static */ nsCString |
551 | | RestyleManager::RestyleHintToString(nsRestyleHint aHint) |
552 | 0 | { |
553 | 0 | nsCString result; |
554 | 0 | bool any = false; |
555 | 0 | const char* names[] = { |
556 | 0 | "Self", "SomeDescendants", "Subtree", "LaterSiblings", "CSSTransitions", |
557 | 0 | "CSSAnimations", "StyleAttribute", "StyleAttribute_Animations", |
558 | 0 | "Force", "ForceDescendants" |
559 | 0 | }; |
560 | 0 | uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1); |
561 | 0 | uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1); |
562 | 0 | for (uint32_t i = 0; i < ArrayLength(names); i++) { |
563 | 0 | if (hint & (1 << i)) { |
564 | 0 | if (any) { |
565 | 0 | result.AppendLiteral(" | "); |
566 | 0 | } |
567 | 0 | result.AppendPrintf("eRestyle_%s", names[i]); |
568 | 0 | any = true; |
569 | 0 | } |
570 | 0 | } |
571 | 0 | if (rest) { |
572 | 0 | if (any) { |
573 | 0 | result.AppendLiteral(" | "); |
574 | 0 | } |
575 | 0 | result.AppendPrintf("0x%0x", rest); |
576 | 0 | } else { |
577 | 0 | if (!any) { |
578 | 0 | result.AppendLiteral("0"); |
579 | 0 | } |
580 | 0 | } |
581 | 0 | return result; |
582 | 0 | } |
583 | | |
584 | | #ifdef DEBUG |
585 | | /* static */ nsCString |
586 | | RestyleManager::ChangeHintToString(nsChangeHint aHint) |
587 | | { |
588 | | nsCString result; |
589 | | bool any = false; |
590 | | const char* names[] = { |
591 | | "RepaintFrame", "NeedReflow", "ClearAncestorIntrinsics", |
592 | | "ClearDescendantIntrinsics", "NeedDirtyReflow", "SyncFrameView", |
593 | | "UpdateCursor", "UpdateEffects", "UpdateOpacityLayer", |
594 | | "UpdateTransformLayer", "ReconstructFrame", "UpdateOverflow", |
595 | | "UpdateSubtreeOverflow", "UpdatePostTransformOverflow", |
596 | | "UpdateParentOverflow", |
597 | | "ChildrenOnlyTransform", "RecomputePosition", "UpdateContainingBlock", |
598 | | "BorderStyleNoneChange", "UpdateTextPath", "SchedulePaint", |
599 | | "NeutralChange", "InvalidateRenderingObservers", |
600 | | "ReflowChangesSizeOrPosition", "UpdateComputedBSize", |
601 | | "UpdateUsesOpacity", "UpdateBackgroundPosition", |
602 | | "AddOrRemoveTransform", "ScrollbarChange", |
603 | | "UpdateWidgetProperties", "UpdateTableCellSpans", |
604 | | "VisibilityChange" |
605 | | }; |
606 | | static_assert(nsChangeHint_AllHints == |
607 | | static_cast<uint32_t>((1ull << ArrayLength(names)) - 1), |
608 | | "Name list doesn't match change hints."); |
609 | | uint32_t hint = aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1); |
610 | | uint32_t rest = aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1); |
611 | | if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) { |
612 | | result.AppendLiteral("NS_STYLE_HINT_REFLOW"); |
613 | | hint = hint & ~NS_STYLE_HINT_REFLOW; |
614 | | any = true; |
615 | | } else if ((hint & nsChangeHint_AllReflowHints) == nsChangeHint_AllReflowHints) { |
616 | | result.AppendLiteral("nsChangeHint_AllReflowHints"); |
617 | | hint = hint & ~nsChangeHint_AllReflowHints; |
618 | | any = true; |
619 | | } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) { |
620 | | result.AppendLiteral("NS_STYLE_HINT_VISUAL"); |
621 | | hint = hint & ~NS_STYLE_HINT_VISUAL; |
622 | | any = true; |
623 | | } |
624 | | for (uint32_t i = 0; i < ArrayLength(names); i++) { |
625 | | if (hint & (1u << i)) { |
626 | | if (any) { |
627 | | result.AppendLiteral(" | "); |
628 | | } |
629 | | result.AppendPrintf("nsChangeHint_%s", names[i]); |
630 | | any = true; |
631 | | } |
632 | | } |
633 | | if (rest) { |
634 | | if (any) { |
635 | | result.AppendLiteral(" | "); |
636 | | } |
637 | | result.AppendPrintf("0x%0x", rest); |
638 | | } else { |
639 | | if (!any) { |
640 | | result.AppendLiteral("nsChangeHint(0)"); |
641 | | } |
642 | | } |
643 | | return result; |
644 | | } |
645 | | #endif |
646 | | |
647 | | /** |
648 | | * Frame construction helpers follow. |
649 | | */ |
650 | | #ifdef DEBUG |
651 | | static bool gInApplyRenderingChangeToTree = false; |
652 | | #endif |
653 | | |
654 | | /** |
655 | | * Sync views on aFrame and all of aFrame's descendants (following placeholders), |
656 | | * if aChange has nsChangeHint_SyncFrameView. |
657 | | * Calls DoApplyRenderingChangeToTree on all aFrame's out-of-flow descendants |
658 | | * (following placeholders), if aChange has nsChangeHint_RepaintFrame. |
659 | | * aFrame should be some combination of nsChangeHint_SyncFrameView, |
660 | | * nsChangeHint_RepaintFrame, nsChangeHint_UpdateOpacityLayer and |
661 | | * nsChangeHint_SchedulePaint, nothing else. |
662 | | */ |
663 | | static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, |
664 | | nsChangeHint aChange); |
665 | | |
666 | | static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint); |
667 | | |
668 | | /** |
669 | | * To handle nsChangeHint_ChildrenOnlyTransform we must iterate over the child |
670 | | * frames of the SVG frame concerned. This helper function is used to find that |
671 | | * SVG frame when we encounter nsChangeHint_ChildrenOnlyTransform to ensure |
672 | | * that we iterate over the intended children, since sometimes we end up |
673 | | * handling that hint while processing hints for one of the SVG frame's |
674 | | * ancestor frames. |
675 | | * |
676 | | * The reason that we sometimes end up trying to process the hint for an |
677 | | * ancestor of the SVG frame that the hint is intended for is due to the way we |
678 | | * process restyle events. ApplyRenderingChangeToTree adjusts the frame from |
679 | | * the restyled element's principle frame to one of its ancestor frames based |
680 | | * on what nsCSSRendering::FindBackground returns, since the background style |
681 | | * may have been propagated up to an ancestor frame. Processing hints using an |
682 | | * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is |
683 | | * a special case since it is intended to update the children of a specific |
684 | | * frame. |
685 | | */ |
686 | | static nsIFrame* |
687 | | GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) |
688 | 0 | { |
689 | 0 | if (aFrame->IsViewportFrame()) { |
690 | 0 | // This happens if the root-<svg> is fixed positioned, in which case we |
691 | 0 | // can't use aFrame->GetContent() to find the primary frame, since |
692 | 0 | // GetContent() returns nullptr for ViewportFrame. |
693 | 0 | aFrame = aFrame->PrincipalChildList().FirstChild(); |
694 | 0 | } |
695 | 0 | // For an nsHTMLScrollFrame, this will get the SVG frame that has the |
696 | 0 | // children-only transforms: |
697 | 0 | aFrame = aFrame->GetContent()->GetPrimaryFrame(); |
698 | 0 | if (aFrame->IsSVGOuterSVGFrame()) { |
699 | 0 | aFrame = aFrame->PrincipalChildList().FirstChild(); |
700 | 0 | MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(), |
701 | 0 | "Where is the nsSVGOuterSVGFrame's anon child??"); |
702 | 0 | } |
703 | 0 | MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer), |
704 | 0 | "Children-only transforms only expected on SVG frames"); |
705 | 0 | return aFrame; |
706 | 0 | } |
707 | | |
708 | | // Returns true if this function managed to successfully move a frame, and |
709 | | // false if it could not process the position change, and a reflow should |
710 | | // be performed instead. |
711 | | static bool |
712 | | RecomputePosition(nsIFrame* aFrame) |
713 | 0 | { |
714 | 0 | // Don't process position changes on table frames, since we already handle |
715 | 0 | // the dynamic position change on the table wrapper frame, and the |
716 | 0 | // reflow-based fallback code path also ignores positions on inner table |
717 | 0 | // frames. |
718 | 0 | if (aFrame->IsTableFrame()) { |
719 | 0 | return true; |
720 | 0 | } |
721 | 0 | |
722 | 0 | const nsStyleDisplay* display = aFrame->StyleDisplay(); |
723 | 0 | // Changes to the offsets of a non-positioned element can safely be ignored. |
724 | 0 | if (display->mPosition == NS_STYLE_POSITION_STATIC) { |
725 | 0 | return true; |
726 | 0 | } |
727 | 0 | |
728 | 0 | // Don't process position changes on frames which have views or the ones which |
729 | 0 | // have a view somewhere in their descendants, because the corresponding view |
730 | 0 | // needs to be repositioned properly as well. |
731 | 0 | if (aFrame->HasView() || |
732 | 0 | (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) { |
733 | 0 | StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); |
734 | 0 | return false; |
735 | 0 | } |
736 | 0 | |
737 | 0 | // Flexbox and Grid layout supports CSS Align and the optimizations below |
738 | 0 | // don't support that yet. |
739 | 0 | if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { |
740 | 0 | nsIFrame* ph = aFrame->GetPlaceholderFrame(); |
741 | 0 | if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) { |
742 | 0 | StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); |
743 | 0 | return false; |
744 | 0 | } |
745 | 0 | } |
746 | 0 | |
747 | 0 | aFrame->SchedulePaint(); |
748 | 0 |
|
749 | 0 | // For relative positioning, we can simply update the frame rect |
750 | 0 | if (display->IsRelativelyPositionedStyle()) { |
751 | 0 | // Move the frame |
752 | 0 | if (display->mPosition == NS_STYLE_POSITION_STICKY) { |
753 | 0 | // Update sticky positioning for an entire element at once, starting with |
754 | 0 | // the first continuation or ib-split sibling. |
755 | 0 | // It's rare that the frame we already have isn't already the first |
756 | 0 | // continuation or ib-split sibling, but it can happen when styles differ |
757 | 0 | // across continuations such as ::first-line or ::first-letter, and in |
758 | 0 | // those cases we will generally (but maybe not always) do the work twice. |
759 | 0 | nsIFrame* firstContinuation = |
760 | 0 | nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
761 | 0 |
|
762 | 0 | StickyScrollContainer::ComputeStickyOffsets(firstContinuation); |
763 | 0 | StickyScrollContainer* ssc = |
764 | 0 | StickyScrollContainer::GetStickyScrollContainerForFrame( |
765 | 0 | firstContinuation); |
766 | 0 | if (ssc) { |
767 | 0 | ssc->PositionContinuations(firstContinuation); |
768 | 0 | } |
769 | 0 | } else { |
770 | 0 | MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition, |
771 | 0 | "Unexpected type of positioning"); |
772 | 0 | for (nsIFrame* cont = aFrame; cont; |
773 | 0 | cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
774 | 0 | nsIFrame* cb = cont->GetContainingBlock(); |
775 | 0 | nsMargin newOffsets; |
776 | 0 | WritingMode wm = cb->GetWritingMode(); |
777 | 0 | const LogicalSize size(wm, cb->GetContentRectRelativeToSelf().Size()); |
778 | 0 |
|
779 | 0 | ReflowInput::ComputeRelativeOffsets(wm, cont, size, newOffsets); |
780 | 0 | NS_ASSERTION(newOffsets.left == -newOffsets.right && |
781 | 0 | newOffsets.top == -newOffsets.bottom, |
782 | 0 | "ComputeRelativeOffsets should return valid results"); |
783 | 0 |
|
784 | 0 | // ReflowInput::ApplyRelativePositioning would work here, but |
785 | 0 | // since we've already checked mPosition and aren't changing the frame's |
786 | 0 | // normal position, go ahead and add the offsets directly. |
787 | 0 | // First, we need to ensure that the normal position is stored though. |
788 | 0 | bool hasProperty; |
789 | 0 | nsPoint normalPosition = cont->GetNormalPosition(&hasProperty); |
790 | 0 | if (!hasProperty) { |
791 | 0 | cont->AddProperty(nsIFrame::NormalPositionProperty(), |
792 | 0 | new nsPoint(normalPosition)); |
793 | 0 | } |
794 | 0 | cont->SetPosition(normalPosition + |
795 | 0 | nsPoint(newOffsets.left, newOffsets.top)); |
796 | 0 | } |
797 | 0 | } |
798 | 0 |
|
799 | 0 | return true; |
800 | 0 | } |
801 | 0 |
|
802 | 0 | // For the absolute positioning case, set up a fake HTML reflow state for |
803 | 0 | // the frame, and then get the offsets and size from it. If the frame's size |
804 | 0 | // doesn't need to change, we can simply update the frame position. Otherwise |
805 | 0 | // we fall back to a reflow. |
806 | 0 | RefPtr<gfxContext> rc = |
807 | 0 | aFrame->PresShell()->CreateReferenceRenderingContext(); |
808 | 0 |
|
809 | 0 | // Construct a bogus parent reflow state so that there's a usable |
810 | 0 | // containing block reflow state. |
811 | 0 | nsIFrame* parentFrame = aFrame->GetParent(); |
812 | 0 | WritingMode parentWM = parentFrame->GetWritingMode(); |
813 | 0 | WritingMode frameWM = aFrame->GetWritingMode(); |
814 | 0 | LogicalSize parentSize = parentFrame->GetLogicalSize(); |
815 | 0 |
|
816 | 0 | nsFrameState savedState = parentFrame->GetStateBits(); |
817 | 0 | ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc, |
818 | 0 | parentSize); |
819 | 0 | parentFrame->RemoveStateBits(~nsFrameState(0)); |
820 | 0 | parentFrame->AddStateBits(savedState); |
821 | 0 |
|
822 | 0 | // The bogus parent state here was created with no parent state of its own, |
823 | 0 | // and therefore it won't have an mCBReflowInput set up. |
824 | 0 | // But we may need one (for InitCBReflowInput in a child state), so let's |
825 | 0 | // try to create one here for the cases where it will be needed. |
826 | 0 | Maybe<ReflowInput> cbReflowInput; |
827 | 0 | nsIFrame* cbFrame = parentFrame->GetContainingBlock(); |
828 | 0 | if (cbFrame && (aFrame->GetContainingBlock() != parentFrame || |
829 | 0 | parentFrame->IsTableFrame())) { |
830 | 0 | LogicalSize cbSize = cbFrame->GetLogicalSize(); |
831 | 0 | cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc, cbSize); |
832 | 0 | cbReflowInput->ComputedPhysicalMargin() = cbFrame->GetUsedMargin(); |
833 | 0 | cbReflowInput->ComputedPhysicalPadding() = cbFrame->GetUsedPadding(); |
834 | 0 | cbReflowInput->ComputedPhysicalBorderPadding() = |
835 | 0 | cbFrame->GetUsedBorderAndPadding(); |
836 | 0 | parentReflowInput.mCBReflowInput = cbReflowInput.ptr(); |
837 | 0 | } |
838 | 0 |
|
839 | 0 | NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_INTRINSICSIZE && |
840 | 0 | parentSize.BSize(parentWM) != NS_INTRINSICSIZE, |
841 | 0 | "parentSize should be valid"); |
842 | 0 | parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0)); |
843 | 0 | parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0)); |
844 | 0 | parentReflowInput.ComputedPhysicalMargin().SizeTo(0, 0, 0, 0); |
845 | 0 |
|
846 | 0 | parentReflowInput.ComputedPhysicalPadding() = parentFrame->GetUsedPadding(); |
847 | 0 | parentReflowInput.ComputedPhysicalBorderPadding() = |
848 | 0 | parentFrame->GetUsedBorderAndPadding(); |
849 | 0 | LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM); |
850 | 0 | availSize.BSize(frameWM) = NS_INTRINSICSIZE; |
851 | 0 |
|
852 | 0 | ViewportFrame* viewport = do_QueryFrame(parentFrame); |
853 | 0 | nsSize cbSize = viewport ? |
854 | 0 | viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput).Size() |
855 | 0 | : aFrame->GetContainingBlock()->GetSize(); |
856 | 0 | const nsMargin& parentBorder = |
857 | 0 | parentReflowInput.mStyleBorder->GetComputedBorder(); |
858 | 0 | cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom()); |
859 | 0 | LogicalSize lcbSize(frameWM, cbSize); |
860 | 0 | ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame, |
861 | 0 | availSize, &lcbSize); |
862 | 0 | nsSize computedSize(reflowInput.ComputedWidth(), |
863 | 0 | reflowInput.ComputedHeight()); |
864 | 0 | computedSize.width += reflowInput.ComputedPhysicalBorderPadding().LeftRight(); |
865 | 0 | if (computedSize.height != NS_INTRINSICSIZE) { |
866 | 0 | computedSize.height += |
867 | 0 | reflowInput.ComputedPhysicalBorderPadding().TopBottom(); |
868 | 0 | } |
869 | 0 | nsSize size = aFrame->GetSize(); |
870 | 0 | // The RecomputePosition hint is not used if any offset changed between auto |
871 | 0 | // and non-auto. If computedSize.height == NS_INTRINSICSIZE then the new |
872 | 0 | // element height will be its intrinsic height, and since 'top' and 'bottom''s |
873 | 0 | // auto-ness hasn't changed, the old height must also be its intrinsic |
874 | 0 | // height, which we can assume hasn't changed (or reflow would have |
875 | 0 | // been triggered). |
876 | 0 | if (computedSize.width == size.width && |
877 | 0 | (computedSize.height == NS_INTRINSICSIZE || computedSize.height == size.height)) { |
878 | 0 | // If we're solving for 'left' or 'top', then compute it here, in order to |
879 | 0 | // match the reflow code path. |
880 | 0 | if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().left) { |
881 | 0 | reflowInput.ComputedPhysicalOffsets().left = cbSize.width - |
882 | 0 | reflowInput.ComputedPhysicalOffsets().right - |
883 | 0 | reflowInput.ComputedPhysicalMargin().right - |
884 | 0 | size.width - |
885 | 0 | reflowInput.ComputedPhysicalMargin().left; |
886 | 0 | } |
887 | 0 |
|
888 | 0 | if (NS_AUTOOFFSET == reflowInput.ComputedPhysicalOffsets().top) { |
889 | 0 | reflowInput.ComputedPhysicalOffsets().top = cbSize.height - |
890 | 0 | reflowInput.ComputedPhysicalOffsets().bottom - |
891 | 0 | reflowInput.ComputedPhysicalMargin().bottom - |
892 | 0 | size.height - |
893 | 0 | reflowInput.ComputedPhysicalMargin().top; |
894 | 0 | } |
895 | 0 |
|
896 | 0 | // Move the frame |
897 | 0 | nsPoint pos(parentBorder.left + reflowInput.ComputedPhysicalOffsets().left + |
898 | 0 | reflowInput.ComputedPhysicalMargin().left, |
899 | 0 | parentBorder.top + reflowInput.ComputedPhysicalOffsets().top + |
900 | 0 | reflowInput.ComputedPhysicalMargin().top); |
901 | 0 | aFrame->SetPosition(pos); |
902 | 0 |
|
903 | 0 | return true; |
904 | 0 | } |
905 | 0 |
|
906 | 0 | // Fall back to a reflow |
907 | 0 | StyleChangeReflow(aFrame, nsChangeHint_NeedReflow); |
908 | 0 | return false; |
909 | 0 | } |
910 | | |
911 | | static bool |
912 | | HasBoxAncestor(nsIFrame* aFrame) |
913 | 0 | { |
914 | 0 | for (nsIFrame* f = aFrame; f; f = f->GetParent()) { |
915 | 0 | if (f->IsXULBoxFrame()) { |
916 | 0 | return true; |
917 | 0 | } |
918 | 0 | } |
919 | 0 | return false; |
920 | 0 | } |
921 | | |
922 | | /** |
923 | | * Return true if aFrame's subtree has placeholders for out-of-flow content |
924 | | * whose 'position' style's bit in aPositionMask is set. |
925 | | */ |
926 | | static bool |
927 | | FrameHasPositionedPlaceholderDescendants(nsIFrame* aFrame, |
928 | | uint32_t aPositionMask) |
929 | 0 | { |
930 | 0 | MOZ_ASSERT(aPositionMask & (1 << NS_STYLE_POSITION_FIXED)); |
931 | 0 |
|
932 | 0 | for (nsIFrame::ChildListIterator lists(aFrame); !lists.IsDone(); lists.Next()) { |
933 | 0 | for (nsIFrame* f : lists.CurrentList()) { |
934 | 0 | if (f->IsPlaceholderFrame()) { |
935 | 0 | nsIFrame* outOfFlow = |
936 | 0 | nsPlaceholderFrame::GetRealFrameForPlaceholder(f); |
937 | 0 | // If SVG text frames could appear here, they could confuse us since |
938 | 0 | // they ignore their position style ... but they can't. |
939 | 0 | NS_ASSERTION(!nsSVGUtils::IsInSVGTextSubtree(outOfFlow), |
940 | 0 | "SVG text frames can't be out of flow"); |
941 | 0 | if (aPositionMask & (1 << outOfFlow->StyleDisplay()->mPosition)) { |
942 | 0 | return true; |
943 | 0 | } |
944 | 0 | } |
945 | 0 | uint32_t positionMask = aPositionMask; |
946 | 0 | // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or |
947 | 0 | // f->IsFixedPosContainingBlock() here. However, that would only |
948 | 0 | // be testing the *new* style of the frame, which might exclude |
949 | 0 | // descendants that currently have this frame as an abs-pos |
950 | 0 | // containing block. Taking the codepath where we don't reframe |
951 | 0 | // could lead to an unsafe call to |
952 | 0 | // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed |
953 | 0 | // the descendant and taken it off the absolute list. |
954 | 0 | if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) { |
955 | 0 | return true; |
956 | 0 | } |
957 | 0 | } |
958 | 0 | } |
959 | 0 | return false; |
960 | 0 | } |
961 | | |
962 | | static bool |
963 | | NeedToReframeForAddingOrRemovingTransform(nsIFrame* aFrame) |
964 | 0 | { |
965 | 0 | static_assert(0 <= NS_STYLE_POSITION_ABSOLUTE && |
966 | 0 | NS_STYLE_POSITION_ABSOLUTE < 32, "Style constant out of range"); |
967 | 0 | static_assert(0 <= NS_STYLE_POSITION_FIXED && |
968 | 0 | NS_STYLE_POSITION_FIXED < 32, "Style constant out of range"); |
969 | 0 |
|
970 | 0 | uint32_t positionMask; |
971 | 0 | // Don't call aFrame->IsPositioned here, since that returns true if |
972 | 0 | // the frame already has a transform, and we want to ignore that here |
973 | 0 | if (aFrame->IsAbsolutelyPositioned() || aFrame->IsRelativelyPositioned()) { |
974 | 0 | // This frame is a container for abs-pos descendants whether or not it |
975 | 0 | // has a transform. |
976 | 0 | // So abs-pos descendants are no problem; we only need to reframe if |
977 | 0 | // we have fixed-pos descendants. |
978 | 0 | positionMask = 1 << NS_STYLE_POSITION_FIXED; |
979 | 0 | } else { |
980 | 0 | // This frame may not be a container for abs-pos descendants already. |
981 | 0 | // So reframe if we have abs-pos or fixed-pos descendants. |
982 | 0 | positionMask = |
983 | 0 | (1 << NS_STYLE_POSITION_FIXED) | (1 << NS_STYLE_POSITION_ABSOLUTE); |
984 | 0 | } |
985 | 0 | for (nsIFrame* f = aFrame; f; |
986 | 0 | f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { |
987 | 0 | if (FrameHasPositionedPlaceholderDescendants(f, positionMask)) { |
988 | 0 | return true; |
989 | 0 | } |
990 | 0 | } |
991 | 0 | return false; |
992 | 0 | } |
993 | | |
994 | | static void |
995 | | DoApplyRenderingChangeToTree(nsIFrame* aFrame, |
996 | | nsChangeHint aChange) |
997 | 0 | { |
998 | 0 | MOZ_ASSERT(gInApplyRenderingChangeToTree, |
999 | 0 | "should only be called within ApplyRenderingChangeToTree"); |
1000 | 0 |
|
1001 | 0 | for ( ; aFrame; aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { |
1002 | 0 | // Invalidate and sync views on all descendant frames, following placeholders. |
1003 | 0 | // We don't need to update transforms in SyncViewsAndInvalidateDescendants, because |
1004 | 0 | // there can't be any out-of-flows or popups that need to be transformed; |
1005 | 0 | // all out-of-flow descendants of the transformed element must also be |
1006 | 0 | // descendants of the transformed frame. |
1007 | 0 | SyncViewsAndInvalidateDescendants(aFrame, |
1008 | 0 | nsChangeHint(aChange & (nsChangeHint_RepaintFrame | |
1009 | 0 | nsChangeHint_SyncFrameView | |
1010 | 0 | nsChangeHint_UpdateOpacityLayer | |
1011 | 0 | nsChangeHint_SchedulePaint))); |
1012 | 0 | // This must be set to true if the rendering change needs to |
1013 | 0 | // invalidate content. If it's false, a composite-only paint |
1014 | 0 | // (empty transaction) will be scheduled. |
1015 | 0 | bool needInvalidatingPaint = false; |
1016 | 0 |
|
1017 | 0 | // if frame has view, will already be invalidated |
1018 | 0 | if (aChange & nsChangeHint_RepaintFrame) { |
1019 | 0 | // Note that this whole block will be skipped when painting is suppressed |
1020 | 0 | // (due to our caller ApplyRendingChangeToTree() discarding the |
1021 | 0 | // nsChangeHint_RepaintFrame hint). If you add handling for any other |
1022 | 0 | // hints within this block, be sure that they too should be ignored when |
1023 | 0 | // painting is suppressed. |
1024 | 0 | needInvalidatingPaint = true; |
1025 | 0 | aFrame->InvalidateFrameSubtree(); |
1026 | 0 | if ((aChange & nsChangeHint_UpdateEffects) && |
1027 | 0 | aFrame->IsFrameOfType(nsIFrame::eSVG) && |
1028 | 0 | !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { |
1029 | 0 | // Need to update our overflow rects: |
1030 | 0 | nsSVGUtils::ScheduleReflowSVG(aFrame); |
1031 | 0 | } |
1032 | 0 |
|
1033 | 0 | ActiveLayerTracker::NotifyNeedsRepaint(aFrame); |
1034 | 0 | } |
1035 | 0 | if (aChange & nsChangeHint_UpdateTextPath) { |
1036 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
1037 | 0 | // Invalidate and reflow the entire SVGTextFrame: |
1038 | 0 | NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath), |
1039 | 0 | "expected frame for a <textPath> element"); |
1040 | 0 | nsIFrame* text = nsLayoutUtils::GetClosestFrameOfType( |
1041 | 0 | aFrame, LayoutFrameType::SVGText); |
1042 | 0 | NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame"); |
1043 | 0 | static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange(); |
1044 | 0 | } else { |
1045 | 0 | MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath"); |
1046 | 0 | } |
1047 | 0 | } |
1048 | 0 | if (aChange & nsChangeHint_UpdateOpacityLayer) { |
1049 | 0 | // FIXME/bug 796697: we can get away with empty transactions for |
1050 | 0 | // opacity updates in many cases. |
1051 | 0 | needInvalidatingPaint = true; |
1052 | 0 |
|
1053 | 0 | ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); |
1054 | 0 | if (nsSVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { |
1055 | 0 | // SVG effects paints the opacity without using |
1056 | 0 | // nsDisplayOpacity. We need to invalidate manually. |
1057 | 0 | aFrame->InvalidateFrameSubtree(); |
1058 | 0 | } |
1059 | 0 | } |
1060 | 0 | if ((aChange & nsChangeHint_UpdateTransformLayer) && |
1061 | 0 | aFrame->IsTransformed()) { |
1062 | 0 | ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform); |
1063 | 0 | // If we're not already going to do an invalidating paint, see |
1064 | 0 | // if we can get away with only updating the transform on a |
1065 | 0 | // layer for this frame, and not scheduling an invalidating |
1066 | 0 | // paint. |
1067 | 0 | if (!needInvalidatingPaint) { |
1068 | 0 | nsDisplayItem::Layer* layer; |
1069 | 0 | needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly(&layer); |
1070 | 0 |
|
1071 | 0 | if (!needInvalidatingPaint) { |
1072 | 0 | // Since we're not going to paint, we need to resend animation |
1073 | 0 | // data to the layer. |
1074 | 0 | MOZ_ASSERT(layer, "this can't happen if there's no layer"); |
1075 | 0 | nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( |
1076 | 0 | layer, nullptr, nullptr, aFrame, eCSSProperty_transform); |
1077 | 0 | } |
1078 | 0 | } |
1079 | 0 | } |
1080 | 0 | if (aChange & nsChangeHint_ChildrenOnlyTransform) { |
1081 | 0 | needInvalidatingPaint = true; |
1082 | 0 | nsIFrame* childFrame = |
1083 | 0 | GetFrameForChildrenOnlyTransformHint(aFrame)->PrincipalChildList().FirstChild(); |
1084 | 0 | for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { |
1085 | 0 | ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform); |
1086 | 0 | } |
1087 | 0 | } |
1088 | 0 | if (aChange & nsChangeHint_SchedulePaint) { |
1089 | 0 | needInvalidatingPaint = true; |
1090 | 0 | } |
1091 | 0 | aFrame->SchedulePaint(needInvalidatingPaint |
1092 | 0 | ? nsIFrame::PAINT_DEFAULT |
1093 | 0 | : nsIFrame::PAINT_COMPOSITE_ONLY); |
1094 | 0 | } |
1095 | 0 | } |
1096 | | |
1097 | | static void |
1098 | | SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, nsChangeHint aChange) |
1099 | 0 | { |
1100 | 0 | MOZ_ASSERT(gInApplyRenderingChangeToTree, |
1101 | 0 | "should only be called within ApplyRenderingChangeToTree"); |
1102 | 0 |
|
1103 | 0 | NS_ASSERTION(nsChangeHint_size_t(aChange) == |
1104 | 0 | (aChange & (nsChangeHint_RepaintFrame | |
1105 | 0 | nsChangeHint_SyncFrameView | |
1106 | 0 | nsChangeHint_UpdateOpacityLayer | |
1107 | 0 | nsChangeHint_SchedulePaint)), |
1108 | 0 | "Invalid change flag"); |
1109 | 0 |
|
1110 | 0 | if (aChange & nsChangeHint_SyncFrameView) { |
1111 | 0 | aFrame->SyncFrameViewProperties(); |
1112 | 0 | } |
1113 | 0 |
|
1114 | 0 | nsIFrame::ChildListIterator lists(aFrame); |
1115 | 0 | for (; !lists.IsDone(); lists.Next()) { |
1116 | 0 | for (nsIFrame* child : lists.CurrentList()) { |
1117 | 0 | if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { |
1118 | 0 | // only do frames that don't have placeholders |
1119 | 0 | if (child->IsPlaceholderFrame()) { |
1120 | 0 | // do the out-of-flow frame and its continuations |
1121 | 0 | nsIFrame* outOfFlowFrame = |
1122 | 0 | nsPlaceholderFrame::GetRealFrameForPlaceholder(child); |
1123 | 0 | DoApplyRenderingChangeToTree(outOfFlowFrame, aChange); |
1124 | 0 | } else if (lists.CurrentID() == nsIFrame::kPopupList) { |
1125 | 0 | DoApplyRenderingChangeToTree(child, aChange); |
1126 | 0 | } else { // regular frame |
1127 | 0 | SyncViewsAndInvalidateDescendants(child, aChange); |
1128 | 0 | } |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | } |
1133 | | |
1134 | | static bool |
1135 | | IsPrimaryFrameOfRootOrBodyElement(nsIFrame* aFrame) |
1136 | 0 | { |
1137 | 0 | nsIContent* content = aFrame->GetContent(); |
1138 | 0 | if (!content) { |
1139 | 0 | return false; |
1140 | 0 | } |
1141 | 0 | |
1142 | 0 | nsIDocument* document = content->OwnerDoc(); |
1143 | 0 | Element* root = document->GetRootElement(); |
1144 | 0 | if (!root) { |
1145 | 0 | return false; |
1146 | 0 | } |
1147 | 0 | nsIFrame* rootFrame = root->GetPrimaryFrame(); |
1148 | 0 | if (!rootFrame) { |
1149 | 0 | return false; |
1150 | 0 | } |
1151 | 0 | if (aFrame == rootFrame) { |
1152 | 0 | return true; |
1153 | 0 | } |
1154 | 0 | |
1155 | 0 | Element* body = document->GetBodyElement(); |
1156 | 0 | if (!body) { |
1157 | 0 | return false; |
1158 | 0 | } |
1159 | 0 | nsIFrame* bodyFrame = body->GetPrimaryFrame(); |
1160 | 0 | if (!bodyFrame) { |
1161 | 0 | return false; |
1162 | 0 | } |
1163 | 0 | if (aFrame == bodyFrame) { |
1164 | 0 | return true; |
1165 | 0 | } |
1166 | 0 | |
1167 | 0 | return false; |
1168 | 0 | } |
1169 | | |
1170 | | static void |
1171 | | ApplyRenderingChangeToTree(nsIPresShell* aPresShell, |
1172 | | nsIFrame* aFrame, |
1173 | | nsChangeHint aChange) |
1174 | 0 | { |
1175 | 0 | // We check StyleDisplay()->HasTransformStyle() in addition to checking |
1176 | 0 | // IsTransformed() since we can get here for some frames that don't support |
1177 | 0 | // CSS transforms. |
1178 | 0 | NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) || |
1179 | 0 | aFrame->IsTransformed() || |
1180 | 0 | aFrame->StyleDisplay()->HasTransformStyle(), |
1181 | 0 | "Unexpected UpdateTransformLayer hint"); |
1182 | 0 |
|
1183 | 0 | if (aPresShell->IsPaintingSuppressed()) { |
1184 | 0 | // Don't allow synchronous rendering changes when painting is turned off. |
1185 | 0 | aChange &= ~nsChangeHint_RepaintFrame; |
1186 | 0 | if (!aChange) { |
1187 | 0 | return; |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | |
1191 | 0 | // Trigger rendering updates by damaging this frame and any |
1192 | 0 | // continuations of this frame. |
1193 | | #ifdef DEBUG |
1194 | | gInApplyRenderingChangeToTree = true; |
1195 | | #endif |
1196 | 0 | if (aChange & nsChangeHint_RepaintFrame) { |
1197 | 0 | // If the frame is the primary frame of either the body element or |
1198 | 0 | // the html element, we propagate the repaint change hint to the |
1199 | 0 | // viewport. This is necessary for background and scrollbar colors |
1200 | 0 | // propagation. |
1201 | 0 | if (IsPrimaryFrameOfRootOrBodyElement(aFrame)) { |
1202 | 0 | nsIFrame* rootFrame = aFrame-> |
1203 | 0 | PresShell()->FrameConstructor()->GetRootFrame(); |
1204 | 0 | MOZ_ASSERT(rootFrame, "No root frame?"); |
1205 | 0 | DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame); |
1206 | 0 | aChange &= ~nsChangeHint_RepaintFrame; |
1207 | 0 | if (!aChange) { |
1208 | 0 | return; |
1209 | 0 | } |
1210 | 0 | } |
1211 | 0 | } |
1212 | 0 | DoApplyRenderingChangeToTree(aFrame, aChange); |
1213 | | #ifdef DEBUG |
1214 | | gInApplyRenderingChangeToTree = false; |
1215 | | #endif |
1216 | | } |
1217 | | |
1218 | | static void |
1219 | | AddSubtreeToOverflowTracker(nsIFrame* aFrame, |
1220 | | OverflowChangedTracker& aOverflowChangedTracker) |
1221 | 0 | { |
1222 | 0 | if (aFrame->FrameMaintainsOverflow()) { |
1223 | 0 | aOverflowChangedTracker.AddFrame(aFrame, |
1224 | 0 | OverflowChangedTracker::CHILDREN_CHANGED); |
1225 | 0 | } |
1226 | 0 | nsIFrame::ChildListIterator lists(aFrame); |
1227 | 0 | for (; !lists.IsDone(); lists.Next()) { |
1228 | 0 | for (nsIFrame* child : lists.CurrentList()) { |
1229 | 0 | AddSubtreeToOverflowTracker(child, aOverflowChangedTracker); |
1230 | 0 | } |
1231 | 0 | } |
1232 | 0 | } |
1233 | | |
1234 | | static void |
1235 | | StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) |
1236 | 0 | { |
1237 | 0 | nsIPresShell::IntrinsicDirty dirtyType; |
1238 | 0 | if (aHint & nsChangeHint_ClearDescendantIntrinsics) { |
1239 | 0 | NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics, |
1240 | 0 | "Please read the comments in nsChangeHint.h"); |
1241 | 0 | NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow, |
1242 | 0 | "ClearDescendantIntrinsics requires NeedDirtyReflow"); |
1243 | 0 | dirtyType = nsIPresShell::eStyleChange; |
1244 | 0 | } else if ((aHint & nsChangeHint_UpdateComputedBSize) && |
1245 | 0 | aFrame->HasAnyStateBits( |
1246 | 0 | NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { |
1247 | 0 | dirtyType = nsIPresShell::eStyleChange; |
1248 | 0 | } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) { |
1249 | 0 | dirtyType = nsIPresShell::eTreeChange; |
1250 | 0 | } else if ((aHint & nsChangeHint_UpdateComputedBSize) && |
1251 | 0 | HasBoxAncestor(aFrame)) { |
1252 | 0 | // The frame's computed BSize is changing, and we have a box ancestor |
1253 | 0 | // whose cached intrinsic height may need to be updated. |
1254 | 0 | dirtyType = nsIPresShell::eTreeChange; |
1255 | 0 | } else { |
1256 | 0 | dirtyType = nsIPresShell::eResize; |
1257 | 0 | } |
1258 | 0 |
|
1259 | 0 | nsFrameState dirtyBits; |
1260 | 0 | if (aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) { |
1261 | 0 | dirtyBits = nsFrameState(0); |
1262 | 0 | } else if ((aHint & nsChangeHint_NeedDirtyReflow) || |
1263 | 0 | dirtyType == nsIPresShell::eStyleChange) { |
1264 | 0 | dirtyBits = NS_FRAME_IS_DIRTY; |
1265 | 0 | } else { |
1266 | 0 | dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN; |
1267 | 0 | } |
1268 | 0 |
|
1269 | 0 | // If we're not going to clear any intrinsic sizes on the frames, and |
1270 | 0 | // there are no dirty bits to set, then there's nothing to do. |
1271 | 0 | if (dirtyType == nsIPresShell::eResize && !dirtyBits) |
1272 | 0 | return; |
1273 | 0 | |
1274 | 0 | nsIPresShell::ReflowRootHandling rootHandling; |
1275 | 0 | if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) { |
1276 | 0 | rootHandling = nsIPresShell::ePositionOrSizeChange; |
1277 | 0 | } else { |
1278 | 0 | rootHandling = nsIPresShell::eNoPositionOrSizeChange; |
1279 | 0 | } |
1280 | 0 |
|
1281 | 0 | do { |
1282 | 0 | aFrame->PresShell()->FrameNeedsReflow( |
1283 | 0 | aFrame, dirtyType, dirtyBits, rootHandling); |
1284 | 0 | aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); |
1285 | 0 | } while (aFrame); |
1286 | 0 | } |
1287 | | |
1288 | | // Get the next sibling which might have a frame. This only considers siblings |
1289 | | // that stylo post-traversal looks at, so only elements and text. In |
1290 | | // particular, it ignores comments. |
1291 | | static nsIContent* |
1292 | | NextSiblingWhichMayHaveFrame(nsIContent* aContent) |
1293 | 0 | { |
1294 | 0 | for (nsIContent* next = aContent->GetNextSibling(); |
1295 | 0 | next; |
1296 | 0 | next = next->GetNextSibling()) { |
1297 | 0 | if (next->IsElement() || next->IsText()) { |
1298 | 0 | return next; |
1299 | 0 | } |
1300 | 0 | } |
1301 | 0 |
|
1302 | 0 | return nullptr; |
1303 | 0 | } |
1304 | | |
1305 | | void |
1306 | | RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) |
1307 | 0 | { |
1308 | 0 | NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), |
1309 | 0 | "Someone forgot a script blocker"); |
1310 | 0 |
|
1311 | 0 | // See bug 1378219 comment 9: |
1312 | 0 | // Recursive calls here are a bit worrying, but apparently do happen in the |
1313 | 0 | // wild (although not currently in any of our automated tests). Try to get a |
1314 | 0 | // stack from Nightly/Dev channel to figure out what's going on and whether |
1315 | 0 | // it's OK. |
1316 | 0 | MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion"); |
1317 | 0 |
|
1318 | 0 | if (aChangeList.IsEmpty()) { |
1319 | 0 | return; |
1320 | 0 | } |
1321 | 0 | |
1322 | 0 | // If mDestroyedFrames is null, we want to create a new hashtable here |
1323 | 0 | // and destroy it on exit; but if it is already non-null (because we're in |
1324 | 0 | // a recursive call), we will continue to use the existing table to |
1325 | 0 | // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit. |
1326 | 0 | // We use a MaybeClearDestroyedFrames helper to conditionally reset the |
1327 | 0 | // mDestroyedFrames pointer when this method returns. |
1328 | 0 | typedef decltype(mDestroyedFrames) DestroyedFramesT; |
1329 | 0 | class MOZ_RAII MaybeClearDestroyedFrames |
1330 | 0 | { |
1331 | 0 | private: |
1332 | 0 | DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames |
1333 | 0 | const bool mResetOnDestruction; |
1334 | 0 | public: |
1335 | 0 | explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget) |
1336 | 0 | : mDestroyedFramesRef(aTarget) |
1337 | 0 | , mResetOnDestruction(!aTarget) // reset only if target starts out null |
1338 | 0 | { |
1339 | 0 | } |
1340 | 0 | ~MaybeClearDestroyedFrames() |
1341 | 0 | { |
1342 | 0 | if (mResetOnDestruction) { |
1343 | 0 | mDestroyedFramesRef.reset(nullptr); |
1344 | 0 | } |
1345 | 0 | } |
1346 | 0 | }; |
1347 | 0 |
|
1348 | 0 | MaybeClearDestroyedFrames maybeClear(mDestroyedFrames); |
1349 | 0 | if (!mDestroyedFrames) { |
1350 | 0 | mDestroyedFrames = MakeUnique<nsTHashtable<nsPtrHashKey<const nsIFrame>>>(); |
1351 | 0 | } |
1352 | 0 |
|
1353 | 0 | AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT); |
1354 | 0 |
|
1355 | 0 | nsPresContext* presContext = PresContext(); |
1356 | 0 | nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); |
1357 | 0 |
|
1358 | 0 | // Handle nsChangeHint_ScrollbarChange, by either updating the |
1359 | 0 | // scrollbars on the viewport, or upgrading the change hint to frame-reconstruct. |
1360 | 0 | for (nsStyleChangeData& data : aChangeList) { |
1361 | 0 | if (data.mHint & nsChangeHint_ScrollbarChange) { |
1362 | 0 | data.mHint &= ~nsChangeHint_ScrollbarChange; |
1363 | 0 | bool doReconstruct = true; // assume the worst |
1364 | 0 |
|
1365 | 0 | // Only bother with this if we're html/body, since: |
1366 | 0 | // (a) It'd be *expensive* to reframe these particular nodes. They're |
1367 | 0 | // at the root, so reframing would mean rebuilding the world. |
1368 | 0 | // (b) It's often *unnecessary* to reframe for "overflow" changes on |
1369 | 0 | // these particular nodes. In general, the only reason we reframe |
1370 | 0 | // for "overflow" changes is so we can construct (or destroy) a |
1371 | 0 | // scrollframe & scrollbars -- and the html/body nodes often don't |
1372 | 0 | // need their own scrollframe/scrollbars because they coopt the ones |
1373 | 0 | // on the viewport (which always exist). So depending on whether |
1374 | 0 | // that's happening, we can skip the reframe for these nodes. |
1375 | 0 | if (data.mContent->IsAnyOfHTMLElements(nsGkAtoms::body, |
1376 | 0 | nsGkAtoms::html)) { |
1377 | 0 | // If the restyled element provided/provides the scrollbar styles for |
1378 | 0 | // the viewport before and/or after this restyle, AND it's not coopting |
1379 | 0 | // that responsibility from some other element (which would need |
1380 | 0 | // reconstruction to make its own scrollframe now), THEN: we don't need |
1381 | 0 | // to reconstruct - we can just reflow, because no scrollframe is being |
1382 | 0 | // added/removed. |
1383 | 0 | nsIContent* prevOverrideNode = |
1384 | 0 | presContext->GetViewportScrollStylesOverrideElement(); |
1385 | 0 | nsIContent* newOverrideNode = |
1386 | 0 | presContext->UpdateViewportScrollStylesOverride(); |
1387 | 0 |
|
1388 | 0 | if (data.mContent == prevOverrideNode || |
1389 | 0 | data.mContent == newOverrideNode) { |
1390 | 0 | // If we get here, the restyled element provided the scrollbar styles |
1391 | 0 | // for viewport before this restyle, OR it will provide them after. |
1392 | 0 | if (!prevOverrideNode || !newOverrideNode || |
1393 | 0 | prevOverrideNode == newOverrideNode) { |
1394 | 0 | // If we get here, the restyled element is NOT replacing (or being |
1395 | 0 | // replaced by) some other element as the viewport's |
1396 | 0 | // scrollbar-styles provider. (If it were, we'd potentially need to |
1397 | 0 | // reframe to create a dedicated scrollframe for whichever element |
1398 | 0 | // is being booted from providing viewport scrollbar styles.) |
1399 | 0 | // |
1400 | 0 | // Under these conditions, we're OK to assume that this "overflow" |
1401 | 0 | // change only impacts the root viewport's scrollframe, which |
1402 | 0 | // already exists, so we can simply reflow instead of reframing. |
1403 | 0 | // When requesting this reflow, we send the exact same change hints |
1404 | 0 | // that "width" and "height" would send (since conceptually, |
1405 | 0 | // adding/removing scrollbars is like changing the available |
1406 | 0 | // space). |
1407 | 0 | data.mHint |= (nsChangeHint_ReflowHintsForISizeChange | |
1408 | 0 | nsChangeHint_ReflowHintsForBSizeChange); |
1409 | 0 | doReconstruct = false; |
1410 | 0 | } |
1411 | 0 | } |
1412 | 0 | } |
1413 | 0 | if (doReconstruct) { |
1414 | 0 | data.mHint |= nsChangeHint_ReconstructFrame; |
1415 | 0 | } |
1416 | 0 | } |
1417 | 0 | } |
1418 | 0 |
|
1419 | 0 | bool didUpdateCursor = false; |
1420 | 0 |
|
1421 | 0 | for (size_t i = 0; i < aChangeList.Length(); ++i) { |
1422 | 0 |
|
1423 | 0 | // Collect and coalesce adjacent siblings for lazy frame construction. |
1424 | 0 | // Eventually it would be even better to make RecreateFramesForContent |
1425 | 0 | // accept a range and coalesce all adjacent reconstructs (bug 1344139). |
1426 | 0 | size_t lazyRangeStart = i; |
1427 | 0 | while (i < aChangeList.Length() && |
1428 | 0 | aChangeList[i].mContent && |
1429 | 0 | aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) && |
1430 | 0 | (i == lazyRangeStart || |
1431 | 0 | NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) == |
1432 | 0 | aChangeList[i].mContent)) |
1433 | 0 | { |
1434 | 0 | MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame); |
1435 | 0 | MOZ_ASSERT(!aChangeList[i].mFrame); |
1436 | 0 | ++i; |
1437 | 0 | } |
1438 | 0 | if (i != lazyRangeStart) { |
1439 | 0 | nsIContent* start = aChangeList[lazyRangeStart].mContent; |
1440 | 0 | nsIContent* end = NextSiblingWhichMayHaveFrame(aChangeList[i-1].mContent); |
1441 | 0 | if (!end) { |
1442 | 0 | frameConstructor->ContentAppended( |
1443 | 0 | start, |
1444 | 0 | nsCSSFrameConstructor::InsertionKind::Sync); |
1445 | 0 | } else { |
1446 | 0 | frameConstructor->ContentRangeInserted( |
1447 | 0 | start, |
1448 | 0 | end, |
1449 | 0 | nullptr, |
1450 | 0 | nsCSSFrameConstructor::InsertionKind::Sync); |
1451 | 0 | } |
1452 | 0 | } |
1453 | 0 | for (size_t j = lazyRangeStart; j < i; ++j) { |
1454 | 0 | MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() || |
1455 | 0 | !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME)); |
1456 | 0 | } |
1457 | 0 | if (i == aChangeList.Length()) { |
1458 | 0 | break; |
1459 | 0 | } |
1460 | 0 | |
1461 | 0 | const nsStyleChangeData& data = aChangeList[i]; |
1462 | 0 | nsIFrame* frame = data.mFrame; |
1463 | 0 | nsIContent* content = data.mContent; |
1464 | 0 | nsChangeHint hint = data.mHint; |
1465 | 0 | bool didReflowThisFrame = false; |
1466 | 0 |
|
1467 | 0 | NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) || |
1468 | 0 | (hint & nsChangeHint_NeedReflow), |
1469 | 0 | "Reflow hint bits set without actually asking for a reflow"); |
1470 | 0 |
|
1471 | 0 | // skip any frame that has been destroyed due to a ripple effect |
1472 | 0 | if (frame && mDestroyedFrames->Contains(frame)) { |
1473 | 0 | continue; |
1474 | 0 | } |
1475 | 0 | |
1476 | 0 | if (frame && frame->GetContent() != content) { |
1477 | 0 | // XXXbz this is due to image maps messing with the primary frame of |
1478 | 0 | // <area>s. See bug 135040. Remove this block once that's fixed. |
1479 | 0 | frame = nullptr; |
1480 | 0 | if (!(hint & nsChangeHint_ReconstructFrame)) { |
1481 | 0 | continue; |
1482 | 0 | } |
1483 | 0 | } |
1484 | 0 | |
1485 | 0 | if ((hint & nsChangeHint_UpdateContainingBlock) && frame && |
1486 | 0 | !(hint & nsChangeHint_ReconstructFrame)) { |
1487 | 0 | if (NeedToReframeForAddingOrRemovingTransform(frame) || |
1488 | 0 | frame->IsFieldSetFrame() || |
1489 | 0 | frame->GetContentInsertionFrame() != frame) { |
1490 | 0 | // The frame has positioned children that need to be reparented, or |
1491 | 0 | // it can't easily be converted to/from being an abs-pos container |
1492 | 0 | // correctly. |
1493 | 0 | hint |= nsChangeHint_ReconstructFrame; |
1494 | 0 | } else { |
1495 | 0 | for (nsIFrame* cont = frame; cont; |
1496 | 0 | cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1497 | 0 | // Normally frame construction would set state bits as needed, |
1498 | 0 | // but we're not going to reconstruct the frame so we need to set them. |
1499 | 0 | // It's because we need to set this state on each affected frame |
1500 | 0 | // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up |
1501 | 0 | // to ancestors (i.e. it can't be an change hint that is handled for |
1502 | 0 | // descendants). |
1503 | 0 | if (cont->IsAbsPosContainingBlock()) { |
1504 | 0 | if (!cont->IsAbsoluteContainer() && |
1505 | 0 | (cont->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { |
1506 | 0 | cont->MarkAsAbsoluteContainingBlock(); |
1507 | 0 | } |
1508 | 0 | } else { |
1509 | 0 | if (cont->IsAbsoluteContainer()) { |
1510 | 0 | if (cont->HasAbsolutelyPositionedChildren()) { |
1511 | 0 | // If |cont| still has absolutely positioned children, |
1512 | 0 | // we can't call MarkAsNotAbsoluteContainingBlock. This |
1513 | 0 | // will remove a frame list that still has children in |
1514 | 0 | // it that we need to keep track of. |
1515 | 0 | // The optimization of removing it isn't particularly |
1516 | 0 | // important, although it does mean we skip some tests. |
1517 | 0 | NS_WARNING("skipping removal of absolute containing block"); |
1518 | 0 | } else { |
1519 | 0 | cont->MarkAsNotAbsoluteContainingBlock(); |
1520 | 0 | } |
1521 | 0 | } |
1522 | 0 | } |
1523 | 0 | } |
1524 | 0 | } |
1525 | 0 | } |
1526 | 0 |
|
1527 | 0 | if ((hint & nsChangeHint_AddOrRemoveTransform) && frame && |
1528 | 0 | !(hint & nsChangeHint_ReconstructFrame)) { |
1529 | 0 | for (nsIFrame* cont = frame; cont; |
1530 | 0 | cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1531 | 0 | if (cont->StyleDisplay()->HasTransform(cont)) { |
1532 | 0 | cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); |
1533 | 0 | } |
1534 | 0 | // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be |
1535 | 0 | // transformed by other means. It's OK to have the bit even if it's |
1536 | 0 | // not needed. |
1537 | 0 | } |
1538 | 0 | } |
1539 | 0 |
|
1540 | 0 | if (hint & nsChangeHint_ReconstructFrame) { |
1541 | 0 | // If we ever start passing true here, be careful of restyles |
1542 | 0 | // that involve a reframe and animations. In particular, if the |
1543 | 0 | // restyle we're processing here is an animation restyle, but |
1544 | 0 | // the style resolution we will do for the frame construction |
1545 | 0 | // happens async when we're not in an animation restyle already, |
1546 | 0 | // problems could arise. |
1547 | 0 | // We could also have problems with triggering of CSS transitions |
1548 | 0 | // on elements whose frames are reconstructed, since we depend on |
1549 | 0 | // the reconstruction happening synchronously. |
1550 | 0 | frameConstructor->RecreateFramesForContent( |
1551 | 0 | content, nsCSSFrameConstructor::InsertionKind::Sync); |
1552 | 0 | } else { |
1553 | 0 | NS_ASSERTION(frame, "This shouldn't happen"); |
1554 | 0 |
|
1555 | 0 | if (!frame->FrameMaintainsOverflow()) { |
1556 | 0 | // frame does not maintain overflow rects, so avoid calling |
1557 | 0 | // FinishAndStoreOverflow on it: |
1558 | 0 | hint &= ~(nsChangeHint_UpdateOverflow | |
1559 | 0 | nsChangeHint_ChildrenOnlyTransform | |
1560 | 0 | nsChangeHint_UpdatePostTransformOverflow | |
1561 | 0 | nsChangeHint_UpdateParentOverflow); |
1562 | 0 | } |
1563 | 0 |
|
1564 | 0 | if (!(frame->GetStateBits() & NS_FRAME_MAY_BE_TRANSFORMED)) { |
1565 | 0 | // Frame can not be transformed, and thus a change in transform will |
1566 | 0 | // have no effect and we should not use the |
1567 | 0 | // nsChangeHint_UpdatePostTransformOverflow hint. |
1568 | 0 | hint &= ~nsChangeHint_UpdatePostTransformOverflow; |
1569 | 0 | } |
1570 | 0 |
|
1571 | 0 | if (hint & nsChangeHint_AddOrRemoveTransform) { |
1572 | 0 | // When dropping a running transform animation we will first add an |
1573 | 0 | // nsChangeHint_UpdateTransformLayer hint as part of the animation-only |
1574 | 0 | // restyle. During the subsequent regular restyle, if the animation was |
1575 | 0 | // the only reason the element had any transform applied, we will add |
1576 | 0 | // nsChangeHint_AddOrRemoveTransform as part of the regular restyle. |
1577 | 0 | // |
1578 | 0 | // With the Gecko backend, these two change hints are processed |
1579 | 0 | // after each restyle but when using the Servo backend they accumulate |
1580 | 0 | // and are processed together after we have already removed the |
1581 | 0 | // transform as part of the regular restyle. Since we don't actually |
1582 | 0 | // need the nsChangeHint_UpdateTransformLayer hint if we already have |
1583 | 0 | // a nsChangeHint_AddOrRemoveTransform hint, and since we |
1584 | 0 | // will fail an assertion in ApplyRenderingChangeToTree if we try |
1585 | 0 | // specify nsChangeHint_UpdateTransformLayer but don't have any |
1586 | 0 | // transform style, we just drop the unneeded hint here. |
1587 | 0 | hint &= ~nsChangeHint_UpdateTransformLayer; |
1588 | 0 | } |
1589 | 0 |
|
1590 | 0 | if (hint & nsChangeHint_UpdateEffects) { |
1591 | 0 | for (nsIFrame* cont = frame; cont; |
1592 | 0 | cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1593 | 0 | SVGObserverUtils::UpdateEffects(cont); |
1594 | 0 | } |
1595 | 0 | } |
1596 | 0 | if ((hint & nsChangeHint_InvalidateRenderingObservers) || |
1597 | 0 | ((hint & nsChangeHint_UpdateOpacityLayer) && |
1598 | 0 | frame->IsFrameOfType(nsIFrame::eSVG) && |
1599 | 0 | !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) { |
1600 | 0 | SVGObserverUtils::InvalidateRenderingObservers(frame); |
1601 | 0 | frame->SchedulePaint(); |
1602 | 0 | } |
1603 | 0 | if (hint & nsChangeHint_NeedReflow) { |
1604 | 0 | StyleChangeReflow(frame, hint); |
1605 | 0 | didReflowThisFrame = true; |
1606 | 0 | } |
1607 | 0 |
|
1608 | 0 | // Here we need to propagate repaint frame change hint instead of update |
1609 | 0 | // opacity layer change hint when we do opacity optimization for SVG. |
1610 | 0 | // We can't do it in nsStyleEffects::CalcDifference() just like we do |
1611 | 0 | // for the optimization for 0.99 over opacity values since we have no way |
1612 | 0 | // to call nsSVGUtils::CanOptimizeOpacity() there. |
1613 | 0 | if ((hint & nsChangeHint_UpdateOpacityLayer) && |
1614 | 0 | nsSVGUtils::CanOptimizeOpacity(frame) && |
1615 | 0 | frame->IsFrameOfType(nsIFrame::eSVGGeometry)) { |
1616 | 0 | hint &= ~nsChangeHint_UpdateOpacityLayer; |
1617 | 0 | hint |= nsChangeHint_RepaintFrame; |
1618 | 0 | } |
1619 | 0 |
|
1620 | 0 | if ((hint & nsChangeHint_UpdateUsesOpacity) && |
1621 | 0 | frame->IsFrameOfType(nsIFrame::eTablePart)) { |
1622 | 0 | NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer, |
1623 | 0 | "should only return UpdateUsesOpacity hint " |
1624 | 0 | "when also returning UpdateOpacityLayer hint"); |
1625 | 0 | // When an internal table part (including cells) changes between |
1626 | 0 | // having opacity 1 and non-1, it changes whether its |
1627 | 0 | // backgrounds (and those of table parts inside of it) are |
1628 | 0 | // painted as part of the table's nsDisplayTableBorderBackground |
1629 | 0 | // display item, or part of its own display item. That requires |
1630 | 0 | // invalidation, so change UpdateOpacityLayer to RepaintFrame. |
1631 | 0 | hint &= ~nsChangeHint_UpdateOpacityLayer; |
1632 | 0 | hint |= nsChangeHint_RepaintFrame; |
1633 | 0 | } |
1634 | 0 |
|
1635 | 0 | // Opacity disables preserve-3d, so if we toggle it, then we also need |
1636 | 0 | // to update the overflow areas of all potentially affected frames. |
1637 | 0 | if ((hint & nsChangeHint_UpdateUsesOpacity) && |
1638 | 0 | frame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) { |
1639 | 0 | hint |= nsChangeHint_UpdateSubtreeOverflow; |
1640 | 0 | } |
1641 | 0 |
|
1642 | 0 | if (hint & nsChangeHint_UpdateBackgroundPosition) { |
1643 | 0 | // For most frame types, DLBI can detect background position changes, |
1644 | 0 | // so we only need to schedule a paint. |
1645 | 0 | hint |= nsChangeHint_SchedulePaint; |
1646 | 0 | if (frame->IsFrameOfType(nsIFrame::eTablePart) || |
1647 | 0 | frame->IsFrameOfType(nsIFrame::eMathML)) { |
1648 | 0 | // Table parts and MathML frames don't build display items for their |
1649 | 0 | // backgrounds, so DLBI can't detect background-position changes for |
1650 | 0 | // these frames. Repaint the whole frame. |
1651 | 0 | hint |= nsChangeHint_RepaintFrame; |
1652 | 0 | } |
1653 | 0 | } |
1654 | 0 |
|
1655 | 0 | if (hint & (nsChangeHint_RepaintFrame | nsChangeHint_SyncFrameView | |
1656 | 0 | nsChangeHint_UpdateOpacityLayer | nsChangeHint_UpdateTransformLayer | |
1657 | 0 | nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) { |
1658 | 0 | ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint); |
1659 | 0 | } |
1660 | 0 | if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) { |
1661 | 0 | ActiveLayerTracker::NotifyOffsetRestyle(frame); |
1662 | 0 | // It is possible for this to fall back to a reflow |
1663 | 0 | if (!RecomputePosition(frame)) { |
1664 | 0 | didReflowThisFrame = true; |
1665 | 0 | } |
1666 | 0 | } |
1667 | 0 | NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) || |
1668 | 0 | (hint & nsChangeHint_UpdateOverflow), |
1669 | 0 | "nsChangeHint_UpdateOverflow should be passed too"); |
1670 | 0 | if (!didReflowThisFrame && |
1671 | 0 | (hint & (nsChangeHint_UpdateOverflow | |
1672 | 0 | nsChangeHint_UpdatePostTransformOverflow | |
1673 | 0 | nsChangeHint_UpdateParentOverflow | |
1674 | 0 | nsChangeHint_UpdateSubtreeOverflow))) { |
1675 | 0 | if (hint & nsChangeHint_UpdateSubtreeOverflow) { |
1676 | 0 | for (nsIFrame* cont = frame; cont; cont = |
1677 | 0 | nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1678 | 0 | AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker); |
1679 | 0 | } |
1680 | 0 | // The work we just did in AddSubtreeToOverflowTracker |
1681 | 0 | // subsumes some of the other hints: |
1682 | 0 | hint &= ~(nsChangeHint_UpdateOverflow | |
1683 | 0 | nsChangeHint_UpdatePostTransformOverflow); |
1684 | 0 | } |
1685 | 0 | if (hint & nsChangeHint_ChildrenOnlyTransform) { |
1686 | 0 | // The overflow areas of the child frames need to be updated: |
1687 | 0 | nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame); |
1688 | 0 | nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild(); |
1689 | 0 | NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame), |
1690 | 0 | "SVG frames should not have continuations " |
1691 | 0 | "or ib-split siblings"); |
1692 | 0 | NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame), |
1693 | 0 | "SVG frames should not have continuations " |
1694 | 0 | "or ib-split siblings"); |
1695 | 0 | for ( ; childFrame; childFrame = childFrame->GetNextSibling()) { |
1696 | 0 | MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG), |
1697 | 0 | "Not expecting non-SVG children"); |
1698 | 0 | // If |childFrame| is dirty or has dirty children, we don't bother |
1699 | 0 | // updating overflows since that will happen when it's reflowed. |
1700 | 0 | if (!(childFrame->GetStateBits() & |
1701 | 0 | (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { |
1702 | 0 | mOverflowChangedTracker.AddFrame(childFrame, |
1703 | 0 | OverflowChangedTracker::CHILDREN_CHANGED); |
1704 | 0 | } |
1705 | 0 | NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame), |
1706 | 0 | "SVG frames should not have continuations " |
1707 | 0 | "or ib-split siblings"); |
1708 | 0 | NS_ASSERTION(childFrame->GetParent() == hintFrame, |
1709 | 0 | "SVG child frame not expected to have different parent"); |
1710 | 0 | } |
1711 | 0 | } |
1712 | 0 | // If |frame| is dirty or has dirty children, we don't bother updating |
1713 | 0 | // overflows since that will happen when it's reflowed. |
1714 | 0 | if (!(frame->GetStateBits() & |
1715 | 0 | (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN))) { |
1716 | 0 | if (hint & (nsChangeHint_UpdateOverflow | |
1717 | 0 | nsChangeHint_UpdatePostTransformOverflow)) { |
1718 | 0 | OverflowChangedTracker::ChangeKind changeKind; |
1719 | 0 | // If we have both nsChangeHint_UpdateOverflow and |
1720 | 0 | // nsChangeHint_UpdatePostTransformOverflow, |
1721 | 0 | // CHILDREN_CHANGED is selected as it is |
1722 | 0 | // strictly stronger. |
1723 | 0 | if (hint & nsChangeHint_UpdateOverflow) { |
1724 | 0 | changeKind = OverflowChangedTracker::CHILDREN_CHANGED; |
1725 | 0 | } else { |
1726 | 0 | changeKind = OverflowChangedTracker::TRANSFORM_CHANGED; |
1727 | 0 | } |
1728 | 0 | for (nsIFrame* cont = frame; cont; cont = |
1729 | 0 | nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1730 | 0 | mOverflowChangedTracker.AddFrame(cont, changeKind); |
1731 | 0 | } |
1732 | 0 | } |
1733 | 0 | // UpdateParentOverflow hints need to be processed in addition |
1734 | 0 | // to the above, since if the processing of the above hints |
1735 | 0 | // yields no change, the update will not propagate to the |
1736 | 0 | // parent. |
1737 | 0 | if (hint & nsChangeHint_UpdateParentOverflow) { |
1738 | 0 | MOZ_ASSERT(frame->GetParent(), |
1739 | 0 | "shouldn't get style hints for the root frame"); |
1740 | 0 | for (nsIFrame* cont = frame; cont; cont = |
1741 | 0 | nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { |
1742 | 0 | mOverflowChangedTracker.AddFrame(cont->GetParent(), |
1743 | 0 | OverflowChangedTracker::CHILDREN_CHANGED); |
1744 | 0 | } |
1745 | 0 | } |
1746 | 0 | } |
1747 | 0 | } |
1748 | 0 | if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) { |
1749 | 0 | presContext->PresShell()->SynthesizeMouseMove(false); |
1750 | 0 | didUpdateCursor = true; |
1751 | 0 | } |
1752 | 0 | if (hint & nsChangeHint_UpdateWidgetProperties) { |
1753 | 0 | frame->UpdateWidgetProperties(); |
1754 | 0 | } |
1755 | 0 | if (hint & nsChangeHint_UpdateTableCellSpans) { |
1756 | 0 | frameConstructor->UpdateTableCellSpans(content); |
1757 | 0 | } |
1758 | 0 | if (hint & nsChangeHint_VisibilityChange) { |
1759 | 0 | frame->UpdateVisibleDescendantsState(); |
1760 | 0 | } |
1761 | 0 | } |
1762 | 0 | } |
1763 | 0 |
|
1764 | 0 | aChangeList.Clear(); |
1765 | 0 | } |
1766 | | |
1767 | | /* static */ uint64_t |
1768 | | RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aFrame) |
1769 | 0 | { |
1770 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(aFrame); |
1771 | 0 | return effectSet ? effectSet->GetAnimationGeneration() : 0; |
1772 | 0 | } |
1773 | | |
1774 | | void |
1775 | | RestyleManager::IncrementAnimationGeneration() |
1776 | 0 | { |
1777 | 0 | // We update the animation generation at start of each call to |
1778 | 0 | // ProcessPendingRestyles so we should ignore any subsequent (redundant) |
1779 | 0 | // calls that occur while we are still processing restyles. |
1780 | 0 | if (!mInStyleRefresh) { |
1781 | 0 | ++mAnimationGeneration; |
1782 | 0 | } |
1783 | 0 | } |
1784 | | |
1785 | | /* static */ void |
1786 | | RestyleManager::AddLayerChangesForAnimation(nsIFrame* aFrame, |
1787 | | nsIContent* aContent, |
1788 | | nsChangeHint aHintForThisFrame, |
1789 | | nsStyleChangeList& |
1790 | | aChangeListToProcess) |
1791 | 0 | { |
1792 | 0 | if (!aFrame || !aContent) { |
1793 | 0 | return; |
1794 | 0 | } |
1795 | 0 | |
1796 | 0 | uint64_t frameGeneration = |
1797 | 0 | RestyleManager::GetAnimationGenerationForFrame(aFrame); |
1798 | 0 |
|
1799 | 0 | nsChangeHint hint = nsChangeHint(0); |
1800 | 0 | for (const LayerAnimationInfo::Record& layerInfo : |
1801 | 0 | LayerAnimationInfo::sRecords) { |
1802 | 0 | Maybe<uint64_t> generation = |
1803 | 0 | layers::AnimationInfo::GetGenerationFromFrame(aFrame, |
1804 | 0 | layerInfo.mLayerType); |
1805 | 0 | if (generation && frameGeneration != *generation) { |
1806 | 0 | // If we have a transform layer bug don't have any transform style, we |
1807 | 0 | // probably just removed the transform but haven't destroyed the layer |
1808 | 0 | // yet. In this case we will typically add the appropriate change hint |
1809 | 0 | // (nsChangeHint_UpdateContainingBlock) when we compare styles so in |
1810 | 0 | // theory we could skip adding any change hint here. |
1811 | 0 | // |
1812 | 0 | // However, sometimes when we compare styles we'll get no change. For |
1813 | 0 | // example, if the transform style was 'none' when we sent the transform |
1814 | 0 | // animation to the compositor and the current transform style is now |
1815 | 0 | // 'none' we'll think nothing changed but actually we still need to |
1816 | 0 | // trigger an update to clear whatever style the transform animation set |
1817 | 0 | // on the compositor. To handle this case we simply set all the change |
1818 | 0 | // hints relevant to removing transform style (since we don't know exactly |
1819 | 0 | // what changes happened while the animation was running on the |
1820 | 0 | // compositor). |
1821 | 0 | // |
1822 | 0 | // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we |
1823 | 0 | // did, ApplyRenderingChangeToTree would complain that we're updating a |
1824 | 0 | // transform layer without a transform. |
1825 | 0 | if (layerInfo.mLayerType == DisplayItemType::TYPE_TRANSFORM && |
1826 | 0 | !aFrame->StyleDisplay()->HasTransformStyle()) { |
1827 | 0 | // Add all the hints for a removing a transform if they are not already |
1828 | 0 | // set for this frame. |
1829 | 0 | if (!(NS_IsHintSubset( |
1830 | 0 | nsChangeHint_ComprehensiveAddOrRemoveTransform, |
1831 | 0 | aHintForThisFrame))) { |
1832 | 0 | hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform; |
1833 | 0 | } |
1834 | 0 | continue; |
1835 | 0 | } |
1836 | 0 | hint |= layerInfo.mChangeHint; |
1837 | 0 | } |
1838 | 0 |
|
1839 | 0 | // We consider it's the first paint for the frame if we have an animation |
1840 | 0 | // for the property but have no layer, for the case of WebRender, no |
1841 | 0 | // corresponding animation info. |
1842 | 0 | // Note that in case of animations which has properties preventing running |
1843 | 0 | // on the compositor, e.g., width or height, corresponding layer is not |
1844 | 0 | // created at all, but even in such cases, we normally set valid change |
1845 | 0 | // hint for such animations in each tick, i.e. restyles in each tick. As |
1846 | 0 | // a result, we usually do restyles for such animations in every tick on |
1847 | 0 | // the main-thread. The only animations which will be affected by this |
1848 | 0 | // explicit change hint are animations that have opacity/transform but did |
1849 | 0 | // not have those properies just before. e.g, setting transform by |
1850 | 0 | // setKeyframes or changing target element from other target which prevents |
1851 | 0 | // running on the compositor, etc. |
1852 | 0 | if (!generation && |
1853 | 0 | nsLayoutUtils::HasEffectiveAnimation(aFrame, layerInfo.mProperty)) { |
1854 | 0 | hint |= layerInfo.mChangeHint; |
1855 | 0 | } |
1856 | 0 | } |
1857 | 0 |
|
1858 | 0 | if (hint) { |
1859 | 0 | aChangeListToProcess.AppendChange(aFrame, aContent, hint); |
1860 | 0 | } |
1861 | 0 | } |
1862 | | |
1863 | | RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame( |
1864 | | RestyleManager* aRestyleManager) |
1865 | | : mRestyleManager(aRestyleManager) |
1866 | | , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) |
1867 | 0 | { |
1868 | 0 | MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame, |
1869 | 0 | "shouldn't construct recursively"); |
1870 | 0 | mRestyleManager->mAnimationsWithDestroyedFrame = this; |
1871 | 0 | } |
1872 | | |
1873 | | void |
1874 | | RestyleManager::AnimationsWithDestroyedFrame |
1875 | | ::StopAnimationsForElementsWithoutFrames() |
1876 | 0 | { |
1877 | 0 | StopAnimationsWithoutFrame(mContents, CSSPseudoElementType::NotPseudo); |
1878 | 0 | StopAnimationsWithoutFrame(mBeforeContents, CSSPseudoElementType::before); |
1879 | 0 | StopAnimationsWithoutFrame(mAfterContents, CSSPseudoElementType::after); |
1880 | 0 | } |
1881 | | |
1882 | | void |
1883 | | RestyleManager::AnimationsWithDestroyedFrame |
1884 | | ::StopAnimationsWithoutFrame( |
1885 | | nsTArray<RefPtr<nsIContent>>& aArray, |
1886 | | CSSPseudoElementType aPseudoType) |
1887 | 0 | { |
1888 | 0 | nsAnimationManager* animationManager = |
1889 | 0 | mRestyleManager->PresContext()->AnimationManager(); |
1890 | 0 | nsTransitionManager* transitionManager = |
1891 | 0 | mRestyleManager->PresContext()->TransitionManager(); |
1892 | 0 | for (nsIContent* content : aArray) { |
1893 | 0 | if (aPseudoType == CSSPseudoElementType::NotPseudo) { |
1894 | 0 | if (content->GetPrimaryFrame()) { |
1895 | 0 | continue; |
1896 | 0 | } |
1897 | 0 | } else if (aPseudoType == CSSPseudoElementType::before) { |
1898 | 0 | if (nsLayoutUtils::GetBeforeFrame(content)) { |
1899 | 0 | continue; |
1900 | 0 | } |
1901 | 0 | } else if (aPseudoType == CSSPseudoElementType::after) { |
1902 | 0 | if (nsLayoutUtils::GetAfterFrame(content)) { |
1903 | 0 | continue; |
1904 | 0 | } |
1905 | 0 | } |
1906 | 0 | dom::Element* element = content->AsElement(); |
1907 | 0 |
|
1908 | 0 | animationManager->StopAnimationsForElement(element, aPseudoType); |
1909 | 0 | transitionManager->StopAnimationsForElement(element, aPseudoType); |
1910 | 0 |
|
1911 | 0 | // All other animations should keep running but not running on the |
1912 | 0 | // *compositor* at this point. |
1913 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType); |
1914 | 0 | if (effectSet) { |
1915 | 0 | for (KeyframeEffect* effect : *effectSet) { |
1916 | 0 | effect->ResetIsRunningOnCompositor(); |
1917 | 0 | } |
1918 | 0 | } |
1919 | 0 | } |
1920 | 0 | } |
1921 | | |
1922 | | #ifdef DEBUG |
1923 | | static bool |
1924 | | IsAnonBox(const nsIFrame* aFrame) |
1925 | | { |
1926 | | return aFrame->Style()->IsAnonBox(); |
1927 | | } |
1928 | | |
1929 | | static const nsIFrame* |
1930 | | FirstContinuationOrPartOfIBSplit(const nsIFrame* aFrame) |
1931 | | { |
1932 | | if (!aFrame) { |
1933 | | return nullptr; |
1934 | | } |
1935 | | |
1936 | | return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
1937 | | } |
1938 | | |
1939 | | static const nsIFrame* |
1940 | | ExpectedOwnerForChild(const nsIFrame* aFrame) |
1941 | | { |
1942 | | const nsIFrame* parent = aFrame->GetParent(); |
1943 | | if (aFrame->IsTableFrame()) { |
1944 | | MOZ_ASSERT(parent->IsTableWrapperFrame()); |
1945 | | parent = parent->GetParent(); |
1946 | | } |
1947 | | |
1948 | | if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) { |
1949 | | if (parent->IsLineFrame()) { |
1950 | | parent = parent->GetParent(); |
1951 | | } |
1952 | | return parent->IsViewportFrame() ? |
1953 | | nullptr : FirstContinuationOrPartOfIBSplit(parent); |
1954 | | } |
1955 | | |
1956 | | if (aFrame->IsBulletFrame()) { |
1957 | | return FirstContinuationOrPartOfIBSplit(parent); |
1958 | | } |
1959 | | |
1960 | | if (aFrame->IsLineFrame()) { |
1961 | | // A ::first-line always ends up here via its block, which is therefore the |
1962 | | // right expected owner. That block can be an |
1963 | | // anonymous box. For example, we could have a ::first-line on a columnated |
1964 | | // block; the blockframe is the column-content anonymous box in that case. |
1965 | | // So we don't want to end up in the code below, which steps out of anon |
1966 | | // boxes. Just return the parent of the line frame, which is the block. |
1967 | | return parent; |
1968 | | } |
1969 | | |
1970 | | if (aFrame->IsLetterFrame()) { |
1971 | | // Ditto for ::first-letter. A first-letter always arrives here via its |
1972 | | // direct parent, except when it's parented to a ::first-line. |
1973 | | if (parent->IsLineFrame()) { |
1974 | | parent = parent->GetParent(); |
1975 | | } |
1976 | | return FirstContinuationOrPartOfIBSplit(parent); |
1977 | | } |
1978 | | |
1979 | | if (parent->IsLetterFrame()) { |
1980 | | // Things never have ::first-letter as their expected parent. Go |
1981 | | // on up to the ::first-letter's parent. |
1982 | | parent = parent->GetParent(); |
1983 | | } |
1984 | | |
1985 | | parent = FirstContinuationOrPartOfIBSplit(parent); |
1986 | | |
1987 | | // We've handled already anon boxes and bullet frames, so now we're looking at |
1988 | | // a frame of a DOM element or pseudo. Hop through anon and line-boxes |
1989 | | // generated by our DOM parent, and go find the owner frame for it. |
1990 | | while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) { |
1991 | | auto* pseudo = parent->Style()->GetPseudo(); |
1992 | | if (pseudo == nsCSSAnonBoxes::tableWrapper()) { |
1993 | | const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild(); |
1994 | | MOZ_ASSERT(tableFrame->IsTableFrame()); |
1995 | | // Handle :-moz-table and :-moz-inline-table. |
1996 | | parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame; |
1997 | | } else { |
1998 | | // We get the in-flow parent here so that we can handle the OOF anonymous |
1999 | | // boxed to get the correct parent. |
2000 | | parent = parent->GetInFlowParent(); |
2001 | | } |
2002 | | parent = FirstContinuationOrPartOfIBSplit(parent); |
2003 | | } |
2004 | | |
2005 | | return parent; |
2006 | | } |
2007 | | |
2008 | | void |
2009 | | ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const |
2010 | | { |
2011 | | MOZ_ASSERT(mOwner); |
2012 | | MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); |
2013 | | // We allow aParent.mOwner to be null, for cases when we're not starting at |
2014 | | // the root of the tree. We also allow aParent.mOwner to be somewhere up our |
2015 | | // expected owner chain not our immediate owner, which allows us creating long |
2016 | | // chains of ServoRestyleStates in some cases where it's just not worth it. |
2017 | | #ifdef DEBUG |
2018 | | if (aParent.mOwner) { |
2019 | | const nsIFrame* owner = ExpectedOwnerForChild(mOwner); |
2020 | | if (owner != aParent.mOwner) { |
2021 | | MOZ_ASSERT(IsAnonBox(owner), |
2022 | | "Should only have expected owner weirdness when anon boxes are involved"); |
2023 | | bool found = false; |
2024 | | for (; owner; owner = ExpectedOwnerForChild(owner)) { |
2025 | | if (owner == aParent.mOwner) { |
2026 | | found = true; |
2027 | | break; |
2028 | | } |
2029 | | } |
2030 | | MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain"); |
2031 | | } |
2032 | | } |
2033 | | #endif |
2034 | | } |
2035 | | |
2036 | | nsChangeHint |
2037 | | ServoRestyleState::ChangesHandledFor(const nsIFrame* aFrame) const |
2038 | | { |
2039 | | if (!mOwner) { |
2040 | | MOZ_ASSERT(!mChangesHandled); |
2041 | | return mChangesHandled; |
2042 | | } |
2043 | | |
2044 | | MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame), |
2045 | | "Missed some frame in the hierarchy?"); |
2046 | | return mChangesHandled; |
2047 | | } |
2048 | | #endif |
2049 | | |
2050 | | void |
2051 | | ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) |
2052 | 0 | { |
2053 | 0 | MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(), |
2054 | 0 | "All our wrappers are anon boxes, and why would we restyle " |
2055 | 0 | "non-inheriting ones?"); |
2056 | 0 | MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(), |
2057 | 0 | "All our wrappers are anon boxes, and why would we restyle " |
2058 | 0 | "non-inheriting ones?"); |
2059 | 0 | MOZ_ASSERT(aWrapperFrame->Style()->GetPseudo() != |
2060 | 0 | nsCSSAnonBoxes::cellContent(), |
2061 | 0 | "Someone should be using TableAwareParentFor"); |
2062 | 0 | MOZ_ASSERT(aWrapperFrame->Style()->GetPseudo() != |
2063 | 0 | nsCSSAnonBoxes::tableWrapper(), |
2064 | 0 | "Someone should be using TableAwareParentFor"); |
2065 | 0 | // Make sure we only add first continuations. |
2066 | 0 | aWrapperFrame = aWrapperFrame->FirstContinuation(); |
2067 | 0 | nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr); |
2068 | 0 | if (last == aWrapperFrame) { |
2069 | 0 | // Already queued up, nothing to do. |
2070 | 0 | return; |
2071 | 0 | } |
2072 | 0 | |
2073 | 0 | // Make sure to queue up parents before children. But don't queue up |
2074 | 0 | // ancestors of non-anonymous boxes here; those are handled when we traverse |
2075 | 0 | // their non-anonymous kids. |
2076 | 0 | if (aWrapperFrame->ParentIsWrapperAnonBox()) { |
2077 | 0 | AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame)); |
2078 | 0 | } |
2079 | 0 |
|
2080 | 0 | // If the append fails, we'll fail to restyle properly, but that's probably |
2081 | 0 | // better than crashing. |
2082 | 0 | if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) { |
2083 | 0 | aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true); |
2084 | 0 | } |
2085 | 0 | } |
2086 | | |
2087 | | void |
2088 | | ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) |
2089 | 0 | { |
2090 | 0 | size_t i = mPendingWrapperRestyleOffset; |
2091 | 0 | while (i < mPendingWrapperRestyles.Length()) { |
2092 | 0 | i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i); |
2093 | 0 | } |
2094 | 0 |
|
2095 | 0 | mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset); |
2096 | 0 | } |
2097 | | |
2098 | | size_t |
2099 | | ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, |
2100 | | size_t aIndex) |
2101 | 0 | { |
2102 | 0 | // The frame at index aIndex is something we should restyle ourselves, but |
2103 | 0 | // following frames may need separate ServoRestyleStates to restyle. |
2104 | 0 | MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length()); |
2105 | 0 |
|
2106 | 0 | nsIFrame* cur = mPendingWrapperRestyles[aIndex]; |
2107 | 0 | MOZ_ASSERT(cur->Style()->IsWrapperAnonBox()); |
2108 | 0 |
|
2109 | 0 | // Where is cur supposed to inherit from? From its parent frame, except in |
2110 | 0 | // the case when cur is a table, in which case it should be its grandparent. |
2111 | 0 | // Also, not in the case when the resulting frame would be a first-line; in |
2112 | 0 | // that case we should be inheriting from the block, and the first-line will |
2113 | 0 | // do its fixup later if needed. |
2114 | 0 | // |
2115 | 0 | // Note that after we do all that fixup the parent we get might still not be |
2116 | 0 | // aParent; for example aParent could be a scrollframe, in which case we |
2117 | 0 | // should inherit from the scrollcontent frame. Or the parent might be some |
2118 | 0 | // continuation of aParent. |
2119 | 0 | // |
2120 | 0 | // Try to assert as much as we can about the parent we actually end up using |
2121 | 0 | // without triggering bogus asserts in all those various edge cases. |
2122 | 0 | nsIFrame* parent = cur->GetParent(); |
2123 | 0 | if (cur->IsTableFrame()) { |
2124 | 0 | MOZ_ASSERT(parent->IsTableWrapperFrame()); |
2125 | 0 | parent = parent->GetParent(); |
2126 | 0 | } |
2127 | 0 | if (parent->IsLineFrame()) { |
2128 | 0 | parent = parent->GetParent(); |
2129 | 0 | } |
2130 | 0 | MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent || |
2131 | 0 | (parent->Style()->IsInheritingAnonBox() && |
2132 | 0 | parent->GetContent() == aParent->GetContent())); |
2133 | 0 |
|
2134 | 0 | // Now "this" is a ServoRestyleState for aParent, so if parent is not a next |
2135 | 0 | // continuation (possibly across ib splits) of aParent we need a new |
2136 | 0 | // ServoRestyleState for the kid. |
2137 | 0 | Maybe<ServoRestyleState> parentRestyleState; |
2138 | 0 | nsIFrame* parentForRestyle = |
2139 | 0 | nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent); |
2140 | 0 | if (parentForRestyle != aParent) { |
2141 | 0 | parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty, |
2142 | 0 | Type::InFlow); |
2143 | 0 | } |
2144 | 0 | ServoRestyleState& curRestyleState = |
2145 | 0 | parentRestyleState ? *parentRestyleState : *this; |
2146 | 0 |
|
2147 | 0 | // This frame may already have been restyled. Even if it has, we can't just |
2148 | 0 | // return, because the next frame may be a kid of it that does need restyling. |
2149 | 0 | if (cur->IsWrapperAnonBoxNeedingRestyle()) { |
2150 | 0 | parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState); |
2151 | 0 | cur->SetIsWrapperAnonBoxNeedingRestyle(false); |
2152 | 0 | } |
2153 | 0 |
|
2154 | 0 | size_t numProcessed = 1; |
2155 | 0 |
|
2156 | 0 | // Note: no overflow possible here, since aIndex < length. |
2157 | 0 | if (aIndex + 1 < mPendingWrapperRestyles.Length()) { |
2158 | 0 | nsIFrame* next = mPendingWrapperRestyles[aIndex + 1]; |
2159 | 0 | if (TableAwareParentFor(next) == cur && |
2160 | 0 | next->IsWrapperAnonBoxNeedingRestyle()) { |
2161 | 0 | // It might be nice if we could do better than nsChangeHint_Empty. On |
2162 | 0 | // the other hand, presumably our mChangesHandled already has the bits |
2163 | 0 | // we really want here so in practice it doesn't matter. |
2164 | 0 | ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty, |
2165 | 0 | Type::InFlow, |
2166 | 0 | /* aAssertWrapperRestyleLength = */ false); |
2167 | 0 | numProcessed += childState.ProcessMaybeNestedWrapperRestyle(cur, |
2168 | 0 | aIndex + 1); |
2169 | 0 | } |
2170 | 0 | } |
2171 | 0 |
|
2172 | 0 | return numProcessed; |
2173 | 0 | } |
2174 | | |
2175 | | nsIFrame* |
2176 | | ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) |
2177 | 0 | { |
2178 | 0 | // We want to get the anon box parent for aChild. where aChild has |
2179 | 0 | // ParentIsWrapperAnonBox(). |
2180 | 0 | // |
2181 | 0 | // For the most part this is pretty straightforward, but there are two |
2182 | 0 | // wrinkles. First, if aChild is a table, then we really want the parent of |
2183 | 0 | // its table wrapper. |
2184 | 0 | if (aChild->IsTableFrame()) { |
2185 | 0 | aChild = aChild->GetParent(); |
2186 | 0 | MOZ_ASSERT(aChild->IsTableWrapperFrame()); |
2187 | 0 | } |
2188 | 0 |
|
2189 | 0 | nsIFrame* parent = aChild->GetParent(); |
2190 | 0 | // Now if parent is a cell-content frame, we actually want the cellframe. |
2191 | 0 | if (parent->Style()->GetPseudo() == nsCSSAnonBoxes::cellContent()) { |
2192 | 0 | parent = parent->GetParent(); |
2193 | 0 | } else if (parent->IsTableWrapperFrame()) { |
2194 | 0 | // Must be a caption. In that case we want the table here. |
2195 | 0 | MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption); |
2196 | 0 | parent = parent->PrincipalChildList().FirstChild(); |
2197 | 0 | } |
2198 | 0 | return parent; |
2199 | 0 | } |
2200 | | |
2201 | | void |
2202 | | RestyleManager::PostRestyleEvent(Element* aElement, |
2203 | | nsRestyleHint aRestyleHint, |
2204 | | nsChangeHint aMinChangeHint) |
2205 | 0 | { |
2206 | 0 | MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange), |
2207 | 0 | "Didn't expect explicit change hints to be neutral!"); |
2208 | 0 | if (MOZ_UNLIKELY(IsDisconnected()) || |
2209 | 0 | MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) { |
2210 | 0 | return; |
2211 | 0 | } |
2212 | 0 | |
2213 | 0 | // We allow posting restyles from within change hint handling, but not from |
2214 | 0 | // within the restyle algorithm itself. |
2215 | 0 | MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()); |
2216 | 0 |
|
2217 | 0 | if (aRestyleHint == 0 && !aMinChangeHint) { |
2218 | 0 | return; // Nothing to do. |
2219 | 0 | } |
2220 | 0 | |
2221 | 0 | // Assuming the restyle hints will invalidate cached style for |
2222 | 0 | // getComputedStyle, since we don't know if any of the restyling that we do |
2223 | 0 | // would affect undisplayed elements. |
2224 | 0 | if (aRestyleHint) { |
2225 | 0 | IncrementUndisplayedRestyleGeneration(); |
2226 | 0 | } |
2227 | 0 |
|
2228 | 0 | // Processing change hints sometimes causes new change hints to be generated, |
2229 | 0 | // and very occasionally, additional restyle hints. We collect the change |
2230 | 0 | // hints manually to avoid re-traversing the DOM to find them. |
2231 | 0 | if (mReentrantChanges && !aRestyleHint) { |
2232 | 0 | mReentrantChanges->AppendElement(ReentrantChange { aElement, aMinChangeHint }); |
2233 | 0 | return; |
2234 | 0 | } |
2235 | 0 | |
2236 | 0 | if (aRestyleHint & ~eRestyle_AllHintsWithAnimations) { |
2237 | 0 | mHaveNonAnimationRestyles = true; |
2238 | 0 | } |
2239 | 0 |
|
2240 | 0 | if (aRestyleHint & eRestyle_LaterSiblings) { |
2241 | 0 | aRestyleHint &= ~eRestyle_LaterSiblings; |
2242 | 0 |
|
2243 | 0 | nsRestyleHint siblingHint = eRestyle_Subtree; |
2244 | 0 | Element* current = aElement->GetNextElementSibling(); |
2245 | 0 | while (current) { |
2246 | 0 | Servo_NoteExplicitHints(current, siblingHint, nsChangeHint(0)); |
2247 | 0 | current = current->GetNextElementSibling(); |
2248 | 0 | } |
2249 | 0 | } |
2250 | 0 |
|
2251 | 0 | if (aRestyleHint || aMinChangeHint) { |
2252 | 0 | Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint); |
2253 | 0 | } |
2254 | 0 | } |
2255 | | |
2256 | | void |
2257 | | RestyleManager::PostRestyleEventForAnimations( |
2258 | | Element* aElement, |
2259 | | CSSPseudoElementType aPseudoType, |
2260 | | nsRestyleHint aRestyleHint) |
2261 | 0 | { |
2262 | 0 | Element* elementToRestyle = |
2263 | 0 | EffectCompositor::GetElementToRestyle(aElement, aPseudoType); |
2264 | 0 |
|
2265 | 0 | if (!elementToRestyle) { |
2266 | 0 | // FIXME: Bug 1371107: When reframing happens, |
2267 | 0 | // EffectCompositor::mElementsToRestyle still has unbound old pseudo |
2268 | 0 | // element. We should drop it. |
2269 | 0 | return; |
2270 | 0 | } |
2271 | 0 | |
2272 | 0 | AutoRestyleTimelineMarker marker(mPresContext->GetDocShell(), |
2273 | 0 | true /* animation-only */); |
2274 | 0 | Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0)); |
2275 | 0 | } |
2276 | | |
2277 | | void |
2278 | | RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint, |
2279 | | nsRestyleHint aRestyleHint) |
2280 | 0 | { |
2281 | 0 | // NOTE(emilio): GeckoRestlyeManager does a sync style flush, which seems not |
2282 | 0 | // to be needed in my testing. |
2283 | 0 | PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint); |
2284 | 0 | } |
2285 | | |
2286 | | void |
2287 | | RestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint, |
2288 | | nsRestyleHint aRestyleHint) |
2289 | 0 | { |
2290 | 0 | // NOTE(emilio): The semantics of these methods are quite funny, in the sense |
2291 | 0 | // that we're not supposed to need to rebuild the actual stylist data. |
2292 | 0 | // |
2293 | 0 | // That's handled as part of the MediumFeaturesChanged stuff, if needed. |
2294 | 0 | StyleSet()->ClearCachedStyleData(); |
2295 | 0 |
|
2296 | 0 | DocumentStyleRootIterator iter(mPresContext->Document()); |
2297 | 0 | while (Element* root = iter.GetNextStyleRoot()) { |
2298 | 0 | PostRestyleEvent(root, aRestyleHint, aExtraHint); |
2299 | 0 | } |
2300 | 0 |
|
2301 | 0 | // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect |
2302 | 0 | // non-inheriting anon boxes. It's not clear if we want to support that, but |
2303 | 0 | // if we do, we need to re-selector-match them here. |
2304 | 0 | } |
2305 | | |
2306 | | /* static */ void |
2307 | | RestyleManager::ClearServoDataFromSubtree(Element* aElement, IncludeRoot aIncludeRoot) |
2308 | 0 | { |
2309 | 0 | if (aElement->HasServoData()) { |
2310 | 0 | StyleChildrenIterator it(aElement); |
2311 | 0 | for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { |
2312 | 0 | if (n->IsElement()) { |
2313 | 0 | ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes); |
2314 | 0 | } |
2315 | 0 | } |
2316 | 0 | } |
2317 | 0 |
|
2318 | 0 | if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) { |
2319 | 0 | aElement->ClearServoData(); |
2320 | 0 | MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits | NODE_NEEDS_FRAME)); |
2321 | 0 | MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot()); |
2322 | 0 | } |
2323 | 0 | } |
2324 | | |
2325 | | /* static */ void |
2326 | | RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) |
2327 | 0 | { |
2328 | 0 | if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) { |
2329 | 0 | StyleChildrenIterator it(aElement); |
2330 | 0 | for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { |
2331 | 0 | if (n->IsElement()) { |
2332 | 0 | ClearRestyleStateFromSubtree(n->AsElement()); |
2333 | 0 | } |
2334 | 0 | } |
2335 | 0 | } |
2336 | 0 |
|
2337 | 0 | bool wasRestyled; |
2338 | 0 | Unused << Servo_TakeChangeHint(aElement, &wasRestyled); |
2339 | 0 | aElement->UnsetFlags(Element::kAllServoDescendantBits); |
2340 | 0 | } |
2341 | | |
2342 | | /** |
2343 | | * This struct takes care of encapsulating some common state that text nodes may |
2344 | | * need to track during the post-traversal. |
2345 | | * |
2346 | | * This is currently used to properly compute change hints when the parent |
2347 | | * element of this node is a display: contents node, and also to avoid computing |
2348 | | * the style for text children more than once per element. |
2349 | | */ |
2350 | | struct RestyleManager::TextPostTraversalState |
2351 | | { |
2352 | | public: |
2353 | | TextPostTraversalState(Element& aParentElement, |
2354 | | ComputedStyle* aParentContext, |
2355 | | bool aDisplayContentsParentStyleChanged, |
2356 | | ServoRestyleState& aParentRestyleState) |
2357 | | : mParentElement(aParentElement) |
2358 | | , mParentContext(aParentContext) |
2359 | | , mParentRestyleState(aParentRestyleState) |
2360 | | , mStyle(nullptr) |
2361 | | , mShouldPostHints(aDisplayContentsParentStyleChanged) |
2362 | | , mShouldComputeHints(aDisplayContentsParentStyleChanged) |
2363 | | , mComputedHint(nsChangeHint_Empty) |
2364 | 0 | {} |
2365 | | |
2366 | 0 | nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); } |
2367 | | |
2368 | | ComputedStyle& ComputeStyle(nsIContent* aTextNode) |
2369 | 0 | { |
2370 | 0 | if (!mStyle) { |
2371 | 0 | mStyle = mParentRestyleState.StyleSet().ResolveStyleForText( |
2372 | 0 | aTextNode, &ParentStyle()); |
2373 | 0 | } |
2374 | 0 | MOZ_ASSERT(mStyle); |
2375 | 0 | return *mStyle; |
2376 | 0 | } |
2377 | | |
2378 | | void ComputeHintIfNeeded(nsIContent* aContent, |
2379 | | nsIFrame* aTextFrame, |
2380 | | ComputedStyle& aNewStyle) |
2381 | 0 | { |
2382 | 0 | MOZ_ASSERT(aTextFrame); |
2383 | 0 | MOZ_ASSERT(aNewStyle.GetPseudo() == nsCSSAnonBoxes::mozText()); |
2384 | 0 |
|
2385 | 0 | if (MOZ_LIKELY(!mShouldPostHints)) { |
2386 | 0 | return; |
2387 | 0 | } |
2388 | 0 | |
2389 | 0 | ComputedStyle* oldStyle = aTextFrame->Style(); |
2390 | 0 | MOZ_ASSERT(oldStyle->GetPseudo() == nsCSSAnonBoxes::mozText()); |
2391 | 0 |
|
2392 | 0 | // We rely on the fact that all the text children for the same element share |
2393 | 0 | // style to avoid recomputing style differences for all of them. |
2394 | 0 | // |
2395 | 0 | // TODO(emilio): The above may not be true for ::first-{line,letter}, but |
2396 | 0 | // we'll cross that bridge when we support those in stylo. |
2397 | 0 | if (mShouldComputeHints) { |
2398 | 0 | mShouldComputeHints = false; |
2399 | 0 | uint32_t equalStructs; |
2400 | 0 | mComputedHint = oldStyle->CalcStyleDifference(&aNewStyle, &equalStructs); |
2401 | 0 | mComputedHint = NS_RemoveSubsumedHints( |
2402 | 0 | mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame)); |
2403 | 0 | } |
2404 | 0 |
|
2405 | 0 | if (mComputedHint) { |
2406 | 0 | mParentRestyleState.ChangeList().AppendChange( |
2407 | 0 | aTextFrame, aContent, mComputedHint); |
2408 | 0 | } |
2409 | 0 | } |
2410 | | |
2411 | | private: |
2412 | 0 | ComputedStyle& ParentStyle() { |
2413 | 0 | if (!mParentContext) { |
2414 | 0 | mLazilyResolvedParentContext = |
2415 | 0 | mParentRestyleState.StyleSet().ResolveServoStyle(mParentElement); |
2416 | 0 | mParentContext = mLazilyResolvedParentContext; |
2417 | 0 | } |
2418 | 0 | return *mParentContext; |
2419 | 0 | } |
2420 | | |
2421 | | Element& mParentElement; |
2422 | | ComputedStyle* mParentContext; |
2423 | | RefPtr<ComputedStyle> mLazilyResolvedParentContext; |
2424 | | ServoRestyleState& mParentRestyleState; |
2425 | | RefPtr<ComputedStyle> mStyle; |
2426 | | bool mShouldPostHints; |
2427 | | bool mShouldComputeHints; |
2428 | | nsChangeHint mComputedHint; |
2429 | | }; |
2430 | | |
2431 | | static void |
2432 | | UpdateBackdropIfNeeded(nsIFrame* aFrame, |
2433 | | ServoStyleSet& aStyleSet, |
2434 | | nsStyleChangeList& aChangeList) |
2435 | 0 | { |
2436 | 0 | const nsStyleDisplay* display = aFrame->Style()->StyleDisplay(); |
2437 | 0 | if (display->mTopLayer != NS_STYLE_TOP_LAYER_TOP) { |
2438 | 0 | return; |
2439 | 0 | } |
2440 | 0 | |
2441 | 0 | // Elements in the top layer are guaranteed to have absolute or fixed |
2442 | 0 | // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer. |
2443 | 0 | MOZ_ASSERT(display->IsAbsolutelyPositionedStyle()); |
2444 | 0 |
|
2445 | 0 | nsIFrame* backdropPlaceholder = |
2446 | 0 | aFrame->GetChildList(nsIFrame::kBackdropList).FirstChild(); |
2447 | 0 | if (!backdropPlaceholder) { |
2448 | 0 | return; |
2449 | 0 | } |
2450 | 0 | |
2451 | 0 | MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame()); |
2452 | 0 | nsIFrame* backdropFrame = |
2453 | 0 | nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder); |
2454 | 0 | MOZ_ASSERT(backdropFrame->IsBackdropFrame()); |
2455 | 0 | MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() == |
2456 | 0 | CSSPseudoElementType::backdrop); |
2457 | 0 |
|
2458 | 0 | RefPtr<ComputedStyle> newStyle = |
2459 | 0 | aStyleSet.ResolvePseudoElementStyle(aFrame->GetContent()->AsElement(), |
2460 | 0 | CSSPseudoElementType::backdrop, |
2461 | 0 | aFrame->Style(), |
2462 | 0 | /* aPseudoElement = */ nullptr); |
2463 | 0 |
|
2464 | 0 | // NOTE(emilio): We can't use the changes handled for the owner of the |
2465 | 0 | // backdrop frame, since it's out of flow, and parented to the viewport or |
2466 | 0 | // canvas frame (depending on the `position` value). |
2467 | 0 | MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() || |
2468 | 0 | backdropFrame->GetParent()->IsCanvasFrame()); |
2469 | 0 | nsTArray<nsIFrame*> wrappersToRestyle; |
2470 | 0 | ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle); |
2471 | 0 | nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state); |
2472 | 0 | } |
2473 | | |
2474 | | static void |
2475 | | UpdateFirstLetterIfNeeded(nsIFrame* aFrame, ServoRestyleState& aRestyleState) |
2476 | 0 | { |
2477 | 0 | MOZ_ASSERT(!aFrame->IsFrameOfType(nsIFrame::eBlockFrame), |
2478 | 0 | "You're probably duplicating work with UpdatePseudoElementStyles!"); |
2479 | 0 | if (!aFrame->HasFirstLetterChild()) { |
2480 | 0 | return; |
2481 | 0 | } |
2482 | 0 | |
2483 | 0 | // We need to find the block the first-letter is associated with so we can |
2484 | 0 | // find the right element for the first-letter's style resolution. Might as |
2485 | 0 | // well just delegate the whole thing to that block. |
2486 | 0 | nsIFrame* block = aFrame->GetParent(); |
2487 | 0 | while (!block->IsFrameOfType(nsIFrame::eBlockFrame)) { |
2488 | 0 | block = block->GetParent(); |
2489 | 0 | } |
2490 | 0 |
|
2491 | 0 | static_cast<nsBlockFrame*>(block->FirstContinuation())-> |
2492 | 0 | UpdateFirstLetterStyle(aRestyleState); |
2493 | 0 | } |
2494 | | |
2495 | | static void |
2496 | | UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, |
2497 | | uint32_t aIndex, |
2498 | | ComputedStyle& aOldContext, |
2499 | | ServoRestyleState& aRestyleState) |
2500 | 0 | { |
2501 | 0 | auto pseudoType = aOldContext.GetPseudoType(); |
2502 | 0 | MOZ_ASSERT(pseudoType != CSSPseudoElementType::NotPseudo); |
2503 | 0 | MOZ_ASSERT( |
2504 | 0 | !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType)); |
2505 | 0 |
|
2506 | 0 | RefPtr<ComputedStyle> newStyle = |
2507 | 0 | aRestyleState.StyleSet().ResolvePseudoElementStyle( |
2508 | 0 | aFrame->GetContent()->AsElement(), |
2509 | 0 | pseudoType, |
2510 | 0 | aFrame->Style(), |
2511 | 0 | /* aPseudoElement = */ nullptr); |
2512 | 0 |
|
2513 | 0 | uint32_t equalStructs; // Not used, actually. |
2514 | 0 | nsChangeHint childHint = |
2515 | 0 | aOldContext.CalcStyleDifference(newStyle, &equalStructs); |
2516 | 0 | if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { |
2517 | 0 | childHint = NS_RemoveSubsumedHints( |
2518 | 0 | childHint, aRestyleState.ChangesHandledFor(aFrame)); |
2519 | 0 | } |
2520 | 0 |
|
2521 | 0 | if (childHint) { |
2522 | 0 | if (childHint & nsChangeHint_ReconstructFrame) { |
2523 | 0 | // If we generate a reconstruct here, remove any non-reconstruct hints we |
2524 | 0 | // may have already generated for this content. |
2525 | 0 | aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent()); |
2526 | 0 | } |
2527 | 0 | aRestyleState.ChangeList().AppendChange( |
2528 | 0 | aFrame, aFrame->GetContent(), childHint); |
2529 | 0 | } |
2530 | 0 |
|
2531 | 0 | aFrame->SetAdditionalComputedStyle(aIndex, newStyle); |
2532 | 0 | } |
2533 | | |
2534 | | static void |
2535 | | UpdateAdditionalComputedStyles(nsIFrame* aFrame, |
2536 | | ServoRestyleState& aRestyleState) |
2537 | 0 | { |
2538 | 0 | MOZ_ASSERT(aFrame); |
2539 | 0 | MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement()); |
2540 | 0 |
|
2541 | 0 | // FIXME(emilio): Consider adding a bit or something to avoid the initial |
2542 | 0 | // virtual call? |
2543 | 0 | uint32_t index = 0; |
2544 | 0 | while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) { |
2545 | 0 | UpdateOneAdditionalComputedStyle( |
2546 | 0 | aFrame, index++, *oldStyle, aRestyleState); |
2547 | 0 | } |
2548 | 0 | } |
2549 | | |
2550 | | static void |
2551 | | UpdateFramePseudoElementStyles(nsIFrame* aFrame, |
2552 | | ServoRestyleState& aRestyleState) |
2553 | 0 | { |
2554 | 0 | if (aFrame->IsFrameOfType(nsIFrame::eBlockFrame)) { |
2555 | 0 | static_cast<nsBlockFrame*>(aFrame)->UpdatePseudoElementStyles(aRestyleState); |
2556 | 0 | } else { |
2557 | 0 | UpdateFirstLetterIfNeeded(aFrame, aRestyleState); |
2558 | 0 | } |
2559 | 0 |
|
2560 | 0 | UpdateBackdropIfNeeded( |
2561 | 0 | aFrame, aRestyleState.StyleSet(), aRestyleState.ChangeList()); |
2562 | 0 | } |
2563 | | |
2564 | | enum class ServoPostTraversalFlags : uint32_t |
2565 | | { |
2566 | | Empty = 0, |
2567 | | // Whether parent was restyled. |
2568 | | ParentWasRestyled = 1 << 0, |
2569 | | // Skip sending accessibility notifications for all descendants. |
2570 | | SkipA11yNotifications = 1 << 1, |
2571 | | // Always send accessibility notifications if the element is shown. |
2572 | | // The SkipA11yNotifications flag above overrides this flag. |
2573 | | SendA11yNotificationsIfShown = 1 << 2, |
2574 | | }; |
2575 | | |
2576 | | MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags) |
2577 | | |
2578 | | // Send proper accessibility notifications and return post traversal |
2579 | | // flags for kids. |
2580 | | static ServoPostTraversalFlags |
2581 | | SendA11yNotifications(nsPresContext* aPresContext, |
2582 | | Element* aElement, |
2583 | | ComputedStyle* aOldComputedStyle, |
2584 | | ComputedStyle* aNewComputedStyle, |
2585 | | ServoPostTraversalFlags aFlags) |
2586 | 0 | { |
2587 | 0 | using Flags = ServoPostTraversalFlags; |
2588 | 0 | MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) || |
2589 | 0 | !(aFlags & Flags::SendA11yNotificationsIfShown), |
2590 | 0 | "The two a11y flags should never be set together"); |
2591 | 0 |
|
2592 | 0 | #ifdef ACCESSIBILITY |
2593 | 0 | nsAccessibilityService* accService = GetAccService(); |
2594 | 0 | if (!accService) { |
2595 | 0 | // If we don't have accessibility service, accessibility is not |
2596 | 0 | // enabled. Just skip everything. |
2597 | 0 | return Flags::Empty; |
2598 | 0 | } |
2599 | 0 | if (aFlags & Flags::SkipA11yNotifications) { |
2600 | 0 | // Propogate the skipping flag to descendants. |
2601 | 0 | return Flags::SkipA11yNotifications; |
2602 | 0 | } |
2603 | 0 | |
2604 | 0 | bool needsNotify = false; |
2605 | 0 | bool isVisible = aNewComputedStyle->StyleVisibility()->IsVisible(); |
2606 | 0 | if (aFlags & Flags::SendA11yNotificationsIfShown) { |
2607 | 0 | if (!isVisible) { |
2608 | 0 | // Propagate the sending-if-shown flag to descendants. |
2609 | 0 | return Flags::SendA11yNotificationsIfShown; |
2610 | 0 | } |
2611 | 0 | // We have asked accessibility service to remove the whole subtree |
2612 | 0 | // of element which becomes invisible from the accessible tree, but |
2613 | 0 | // this element is visible, so we need to add it back. |
2614 | 0 | needsNotify = true; |
2615 | 0 | } else { |
2616 | 0 | // If we shouldn't skip in any case, we need to check whether our |
2617 | 0 | // own visibility has changed. |
2618 | 0 | bool wasVisible = aOldComputedStyle->StyleVisibility()->IsVisible(); |
2619 | 0 | needsNotify = wasVisible != isVisible; |
2620 | 0 | } |
2621 | 0 |
|
2622 | 0 | if (needsNotify) { |
2623 | 0 | nsIPresShell* presShell = aPresContext->PresShell(); |
2624 | 0 | if (isVisible) { |
2625 | 0 | accService->ContentRangeInserted( |
2626 | 0 | presShell, aElement, aElement->GetNextSibling()); |
2627 | 0 | // We are adding the subtree. Accessibility service would handle |
2628 | 0 | // descendants, so we should just skip them from notifying. |
2629 | 0 | return Flags::SkipA11yNotifications; |
2630 | 0 | } |
2631 | 0 | // Remove the subtree of this invisible element, and ask any shown |
2632 | 0 | // descendant to add themselves back. |
2633 | 0 | accService->ContentRemoved(presShell, aElement); |
2634 | 0 | return Flags::SendA11yNotificationsIfShown; |
2635 | 0 | } |
2636 | 0 | #endif |
2637 | 0 | |
2638 | 0 | return Flags::Empty; |
2639 | 0 | } |
2640 | | |
2641 | | bool |
2642 | | RestyleManager::ProcessPostTraversal( |
2643 | | Element* aElement, |
2644 | | ComputedStyle* aParentContext, |
2645 | | ServoRestyleState& aRestyleState, |
2646 | | ServoPostTraversalFlags aFlags) |
2647 | 0 | { |
2648 | 0 | nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement); |
2649 | 0 | nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); |
2650 | 0 |
|
2651 | 0 | MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(), |
2652 | 0 | "Element without Servo data on a post-traversal? How?"); |
2653 | 0 |
|
2654 | 0 | // NOTE(emilio): This is needed because for table frames the bit is set on the |
2655 | 0 | // table wrapper (which is the primary frame), not on the table itself. |
2656 | 0 | const bool isOutOfFlow = |
2657 | 0 | primaryFrame && |
2658 | 0 | primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); |
2659 | 0 |
|
2660 | 0 | // Grab the change hint from Servo. |
2661 | 0 | bool wasRestyled; |
2662 | 0 | nsChangeHint changeHint = |
2663 | 0 | static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled)); |
2664 | 0 |
|
2665 | 0 | // We should really fix the weird primary frame mapping for image maps |
2666 | 0 | // (bug 135040)... |
2667 | 0 | if (styleFrame && styleFrame->GetContent() != aElement) { |
2668 | 0 | MOZ_ASSERT(static_cast<nsImageFrame*>(do_QueryFrame(styleFrame))); |
2669 | 0 | styleFrame = nullptr; |
2670 | 0 | } |
2671 | 0 |
|
2672 | 0 | // Handle lazy frame construction by posting a reconstruct for any lazily- |
2673 | 0 | // constructed roots. |
2674 | 0 | if (aElement->HasFlag(NODE_NEEDS_FRAME)) { |
2675 | 0 | changeHint |= nsChangeHint_ReconstructFrame; |
2676 | 0 | MOZ_ASSERT(!styleFrame); |
2677 | 0 | } |
2678 | 0 |
|
2679 | 0 | if (styleFrame) { |
2680 | 0 | MOZ_ASSERT(primaryFrame); |
2681 | 0 |
|
2682 | 0 | nsIFrame* maybeAnonBoxChild; |
2683 | 0 | if (isOutOfFlow) { |
2684 | 0 | maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame(); |
2685 | 0 | } else { |
2686 | 0 | maybeAnonBoxChild = primaryFrame; |
2687 | 0 | changeHint = NS_RemoveSubsumedHints( |
2688 | 0 | changeHint, aRestyleState.ChangesHandledFor(styleFrame)); |
2689 | 0 | } |
2690 | 0 |
|
2691 | 0 | // If the parent wasn't restyled, the styles of our anon box parents won't |
2692 | 0 | // change either. |
2693 | 0 | if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && |
2694 | 0 | maybeAnonBoxChild->ParentIsWrapperAnonBox()) { |
2695 | 0 | aRestyleState.AddPendingWrapperRestyle( |
2696 | 0 | ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild)); |
2697 | 0 | } |
2698 | 0 | } |
2699 | 0 |
|
2700 | 0 | // Although we shouldn't generate non-ReconstructFrame hints for elements with |
2701 | 0 | // no frames, we can still get them here if they were explicitly posted by |
2702 | 0 | // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be |
2703 | 0 | // :visited. Skip processing these hints if there is no frame. |
2704 | 0 | if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) && changeHint) { |
2705 | 0 | aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint); |
2706 | 0 | } |
2707 | 0 |
|
2708 | 0 | // If our change hint is reconstruct, we delegate to the frame constructor, |
2709 | 0 | // which consumes the new style and expects the old style to be on the frame. |
2710 | 0 | // |
2711 | 0 | // XXXbholley: We should teach the frame constructor how to clear the dirty |
2712 | 0 | // descendants bit to avoid the traversal here. |
2713 | 0 | if (changeHint & nsChangeHint_ReconstructFrame) { |
2714 | 0 | ClearRestyleStateFromSubtree(aElement); |
2715 | 0 | return true; |
2716 | 0 | } |
2717 | 0 | |
2718 | 0 | // TODO(emilio): We could avoid some refcount traffic here, specially in the |
2719 | 0 | // ComputedStyle case, which uses atomic refcounting. |
2720 | 0 | // |
2721 | 0 | // Hold the ComputedStyle alive, because it could become a dangling pointer |
2722 | 0 | // during the replacement. In practice it's not a huge deal, but better not |
2723 | 0 | // playing with dangling pointers if not needed. |
2724 | 0 | // |
2725 | 0 | // NOTE(emilio): We could keep around the old computed style for display: |
2726 | 0 | // contents elements too, but we don't really need it right now. |
2727 | 0 | RefPtr<ComputedStyle> oldOrDisplayContentsStyle = |
2728 | 0 | styleFrame ? styleFrame->Style() : nullptr; |
2729 | 0 |
|
2730 | 0 | MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)), |
2731 | 0 | "display: contents node has a frame, yet we didn't reframe it" |
2732 | 0 | " above?"); |
2733 | 0 | const bool isDisplayContents = |
2734 | 0 | !styleFrame && aElement->HasServoData() && |
2735 | 0 | Servo_Element_IsDisplayContents(aElement); |
2736 | 0 | if (isDisplayContents) { |
2737 | 0 | oldOrDisplayContentsStyle = |
2738 | 0 | aRestyleState.StyleSet().ResolveServoStyle(*aElement); |
2739 | 0 | } |
2740 | 0 |
|
2741 | 0 | Maybe<ServoRestyleState> thisFrameRestyleState; |
2742 | 0 | if (styleFrame) { |
2743 | 0 | auto type = isOutOfFlow |
2744 | 0 | ? ServoRestyleState::Type::OutOfFlow |
2745 | 0 | : ServoRestyleState::Type::InFlow; |
2746 | 0 |
|
2747 | 0 | thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type); |
2748 | 0 | } |
2749 | 0 |
|
2750 | 0 | // We can't really assume as used changes from display: contents elements (or |
2751 | 0 | // other elements without frames). |
2752 | 0 | ServoRestyleState& childrenRestyleState = |
2753 | 0 | thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState; |
2754 | 0 |
|
2755 | 0 | RefPtr<ComputedStyle> upToDateContext = |
2756 | 0 | wasRestyled |
2757 | 0 | ? aRestyleState.StyleSet().ResolveServoStyle(*aElement) |
2758 | 0 | : oldOrDisplayContentsStyle; |
2759 | 0 |
|
2760 | 0 | ServoPostTraversalFlags childrenFlags = |
2761 | 0 | wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled |
2762 | 0 | : ServoPostTraversalFlags::Empty; |
2763 | 0 |
|
2764 | 0 | if (wasRestyled && oldOrDisplayContentsStyle) { |
2765 | 0 | MOZ_ASSERT(styleFrame || isDisplayContents); |
2766 | 0 |
|
2767 | 0 | // Note that upToDateContext could be the same as oldOrDisplayContentsStyle, |
2768 | 0 | // but it doesn't matter, since the only point of it is calling FinishStyle |
2769 | 0 | // on the relevant structs, and those don't matter for display: contents. |
2770 | 0 | upToDateContext->ResolveSameStructsAs(oldOrDisplayContentsStyle); |
2771 | 0 |
|
2772 | 0 | // We want to walk all the continuations here, even the ones with different |
2773 | 0 | // styles. In practice, the only reason we get continuations with different |
2774 | 0 | // styles here is ::first-line (::first-letter never affects element |
2775 | 0 | // styles). But in that case, newStyle is the right context for the |
2776 | 0 | // _later_ continuations anyway (the ones not affected by ::first-line), not |
2777 | 0 | // the earlier ones, so there is no point stopping right at the point when |
2778 | 0 | // we'd actually be setting the right ComputedStyle. |
2779 | 0 | // |
2780 | 0 | // This does mean that we may be setting the wrong ComputedStyle on our |
2781 | 0 | // initial continuations; ::first-line fixes that up after the fact. |
2782 | 0 | for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) { |
2783 | 0 | MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0)); |
2784 | 0 | f->SetComputedStyle(upToDateContext); |
2785 | 0 | } |
2786 | 0 |
|
2787 | 0 | if (styleFrame) { |
2788 | 0 | UpdateAdditionalComputedStyles(styleFrame, aRestyleState); |
2789 | 0 | } |
2790 | 0 |
|
2791 | 0 | if (!aElement->GetParent()) { |
2792 | 0 | // This is the root. Update styles on the viewport as needed. |
2793 | 0 | ViewportFrame* viewport = |
2794 | 0 | do_QueryFrame(mPresContext->PresShell()->GetRootFrame()); |
2795 | 0 | if (viewport) { |
2796 | 0 | // NB: The root restyle state, not the one for our children! |
2797 | 0 | viewport->UpdateStyle(aRestyleState); |
2798 | 0 | } |
2799 | 0 | } |
2800 | 0 |
|
2801 | 0 | // Some changes to animations don't affect the computed style and yet still |
2802 | 0 | // require the layer to be updated. For example, pausing an animation via |
2803 | 0 | // the Web Animations API won't affect an element's style but still |
2804 | 0 | // requires to update the animation on the layer. |
2805 | 0 | // |
2806 | 0 | // We can sometimes reach this when the animated style is being removed. |
2807 | 0 | // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform |
2808 | 0 | // style or not, we need to call it *after* setting |newStyle| to |
2809 | 0 | // |styleFrame| to ensure the animated transform has been removed first. |
2810 | 0 | AddLayerChangesForAnimation( |
2811 | 0 | styleFrame, aElement, changeHint, aRestyleState.ChangeList()); |
2812 | 0 |
|
2813 | 0 | childrenFlags |= SendA11yNotifications(mPresContext, |
2814 | 0 | aElement, |
2815 | 0 | oldOrDisplayContentsStyle, |
2816 | 0 | upToDateContext, |
2817 | 0 | aFlags); |
2818 | 0 | } |
2819 | 0 |
|
2820 | 0 | const bool traverseElementChildren = |
2821 | 0 | aElement->HasAnyOfFlags(Element::kAllServoDescendantBits); |
2822 | 0 | const bool traverseTextChildren = |
2823 | 0 | wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES); |
2824 | 0 | bool recreatedAnyContext = wasRestyled; |
2825 | 0 | if (traverseElementChildren || traverseTextChildren) { |
2826 | 0 | StyleChildrenIterator it(aElement); |
2827 | 0 | TextPostTraversalState textState(*aElement, |
2828 | 0 | upToDateContext, |
2829 | 0 | isDisplayContents && wasRestyled, |
2830 | 0 | childrenRestyleState); |
2831 | 0 | for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { |
2832 | 0 | if (traverseElementChildren && n->IsElement()) { |
2833 | 0 | recreatedAnyContext |= ProcessPostTraversal(n->AsElement(), |
2834 | 0 | upToDateContext, |
2835 | 0 | childrenRestyleState, |
2836 | 0 | childrenFlags); |
2837 | 0 | } else if (traverseTextChildren && n->IsText()) { |
2838 | 0 | recreatedAnyContext |= ProcessPostTraversalForText(n, textState, |
2839 | 0 | childrenRestyleState, |
2840 | 0 | childrenFlags); |
2841 | 0 | } |
2842 | 0 | } |
2843 | 0 | } |
2844 | 0 |
|
2845 | 0 | // We want to update frame pseudo-element styles after we've traversed our |
2846 | 0 | // kids, because some of those updates (::first-line/::first-letter) need to |
2847 | 0 | // modify the styles of the kids, and the child traversal above would just |
2848 | 0 | // clobber those modifications. |
2849 | 0 | if (styleFrame) { |
2850 | 0 | if (wasRestyled) { |
2851 | 0 | // Make sure to update anon boxes and pseudo bits after updating text, |
2852 | 0 | // otherwise ProcessPostTraversalForText could clobber first-letter |
2853 | 0 | // styles, for example. |
2854 | 0 | styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState); |
2855 | 0 | } |
2856 | 0 | // Process anon box wrapper frames before ::first-line bits, but _after_ |
2857 | 0 | // owned anon boxes, since the children wrapper anon boxes could be |
2858 | 0 | // inheriting from our own owned anon boxes. |
2859 | 0 | childrenRestyleState.ProcessWrapperRestyles(styleFrame); |
2860 | 0 | if (wasRestyled) { |
2861 | 0 | UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState); |
2862 | 0 | } else if (traverseElementChildren && |
2863 | 0 | styleFrame->IsFrameOfType(nsIFrame::eBlockFrame)) { |
2864 | 0 | // Even if we were not restyled, if we're a block with a first-line and |
2865 | 0 | // one of our descendant elements which is on the first line was restyled, |
2866 | 0 | // we need to update the styles of things on the first line, because |
2867 | 0 | // they're wrong now. |
2868 | 0 | // |
2869 | 0 | // FIXME(bz) Could we do better here? For example, could we keep track of |
2870 | 0 | // frames that are "block with a ::first-line so we could avoid |
2871 | 0 | // IsFrameOfType() and digging about for the first-line frame if not? |
2872 | 0 | // Could we keep track of whether the element children we actually restyle |
2873 | 0 | // are affected by first-line? Something else? Bug 1385443 tracks making |
2874 | 0 | // this better. |
2875 | 0 | nsIFrame* firstLineFrame = |
2876 | 0 | static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame(); |
2877 | 0 | if (firstLineFrame) { |
2878 | 0 | for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { |
2879 | 0 | ReparentComputedStyleForFirstLine(kid); |
2880 | 0 | } |
2881 | 0 | } |
2882 | 0 | } |
2883 | 0 | } |
2884 | 0 |
|
2885 | 0 | aElement->UnsetFlags(Element::kAllServoDescendantBits); |
2886 | 0 | return recreatedAnyContext; |
2887 | 0 | } |
2888 | | |
2889 | | bool |
2890 | | RestyleManager::ProcessPostTraversalForText( |
2891 | | nsIContent* aTextNode, |
2892 | | TextPostTraversalState& aPostTraversalState, |
2893 | | ServoRestyleState& aRestyleState, |
2894 | | ServoPostTraversalFlags aFlags) |
2895 | 0 | { |
2896 | 0 | // Handle lazy frame construction. |
2897 | 0 | if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) { |
2898 | 0 | aPostTraversalState.ChangeList().AppendChange( |
2899 | 0 | nullptr, aTextNode, nsChangeHint_ReconstructFrame); |
2900 | 0 | return true; |
2901 | 0 | } |
2902 | 0 | |
2903 | 0 | // Handle restyle. |
2904 | 0 | nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame(); |
2905 | 0 | if (!primaryFrame) { |
2906 | 0 | return false; |
2907 | 0 | } |
2908 | 0 | |
2909 | 0 | // If the parent wasn't restyled, the styles of our anon box parents won't |
2910 | 0 | // change either. |
2911 | 0 | if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && |
2912 | 0 | primaryFrame->ParentIsWrapperAnonBox()) { |
2913 | 0 | aRestyleState.AddPendingWrapperRestyle( |
2914 | 0 | ServoRestyleState::TableAwareParentFor(primaryFrame)); |
2915 | 0 | } |
2916 | 0 |
|
2917 | 0 | ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode); |
2918 | 0 | aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle); |
2919 | 0 |
|
2920 | 0 | // We want to walk all the continuations here, even the ones with different |
2921 | 0 | // styles. In practice, the only reasons we get continuations with different |
2922 | 0 | // styles are ::first-line and ::first-letter. But in those cases, |
2923 | 0 | // newStyle is the right context for the _later_ continuations anyway (the |
2924 | 0 | // ones not affected by ::first-line/::first-letter), not the earlier ones, |
2925 | 0 | // so there is no point stopping right at the point when we'd actually be |
2926 | 0 | // setting the right ComputedStyle. |
2927 | 0 | // |
2928 | 0 | // This does mean that we may be setting the wrong ComputedStyle on our |
2929 | 0 | // initial continuations; ::first-line/::first-letter fix that up after the |
2930 | 0 | // fact. |
2931 | 0 | for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) { |
2932 | 0 | f->SetComputedStyle(&newStyle); |
2933 | 0 | } |
2934 | 0 |
|
2935 | 0 | return true; |
2936 | 0 | } |
2937 | | |
2938 | | void |
2939 | | RestyleManager::ClearSnapshots() |
2940 | 0 | { |
2941 | 0 | for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) { |
2942 | 0 | iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT); |
2943 | 0 | iter.Remove(); |
2944 | 0 | } |
2945 | 0 | } |
2946 | | |
2947 | | ServoElementSnapshot& |
2948 | | RestyleManager::SnapshotFor(Element& aElement) |
2949 | 0 | { |
2950 | 0 | MOZ_ASSERT(!mInStyleRefresh); |
2951 | 0 |
|
2952 | 0 | // NOTE(emilio): We can handle snapshots from a one-off restyle of those that |
2953 | 0 | // we do to restyle stuff for reconstruction, for example. |
2954 | 0 | // |
2955 | 0 | // It seems to be the case that we always flush in between that happens and |
2956 | 0 | // the next attribute change, so we can assert that we haven't handled the |
2957 | 0 | // snapshot here yet. If this assertion didn't hold, we'd need to unset that |
2958 | 0 | // flag from here too. |
2959 | 0 | // |
2960 | 0 | // Can't wait to make ProcessPendingRestyles the only entry-point for styling, |
2961 | 0 | // so this becomes much easier to reason about. Today is not that day though. |
2962 | 0 | MOZ_ASSERT(aElement.HasServoData()); |
2963 | 0 | MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT)); |
2964 | 0 |
|
2965 | 0 | ServoElementSnapshot* snapshot = mSnapshots.LookupOrAdd(&aElement, aElement); |
2966 | 0 | aElement.SetFlags(ELEMENT_HAS_SNAPSHOT); |
2967 | 0 |
|
2968 | 0 | // Now that we have a snapshot, make sure a restyle is triggered. |
2969 | 0 | aElement.NoteDirtyForServo(); |
2970 | 0 | return *snapshot; |
2971 | 0 | } |
2972 | | |
2973 | | void |
2974 | | RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) |
2975 | 0 | { |
2976 | 0 | nsPresContext* presContext = PresContext(); |
2977 | 0 |
|
2978 | 0 | MOZ_ASSERT(presContext->Document(), "No document? Pshaw!"); |
2979 | 0 | // FIXME(emilio): In the "flush animations" case, ideally, we should only |
2980 | 0 | // recascade animation styles running on the compositor, so we shouldn't care |
2981 | 0 | // about other styles, or new rules that apply to the page... |
2982 | 0 | // |
2983 | 0 | // However, that's not true as of right now, see bug 1388031 and bug 1388692. |
2984 | 0 | MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) || |
2985 | 0 | !presContext->HasPendingMediaQueryUpdates(), |
2986 | 0 | "Someone forgot to update media queries?"); |
2987 | 0 | MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!"); |
2988 | 0 | MOZ_ASSERT(!mInStyleRefresh, "Reentrant call?"); |
2989 | 0 |
|
2990 | 0 |
|
2991 | 0 | if (MOZ_UNLIKELY(!presContext->PresShell()->DidInitialize())) { |
2992 | 0 | // PresShell::FlushPendingNotifications doesn't early-return in the case |
2993 | 0 | // where the PresShell hasn't yet been initialized (and therefore we haven't |
2994 | 0 | // yet done the initial style traversal of the DOM tree). We should arguably |
2995 | 0 | // fix up the callers and assert against this case, but we just detect and |
2996 | 0 | // handle it for now. |
2997 | 0 | return; |
2998 | 0 | } |
2999 | 0 | |
3000 | 0 | // Create a AnimationsWithDestroyedFrame during restyling process to |
3001 | 0 | // stop animations and transitions on elements that have no frame at the end |
3002 | 0 | // of the restyling process. |
3003 | 0 | AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this); |
3004 | 0 |
|
3005 | 0 | ServoStyleSet* styleSet = StyleSet(); |
3006 | 0 | nsIDocument* doc = presContext->Document(); |
3007 | 0 |
|
3008 | 0 | // Ensure the refresh driver is active during traversal to avoid mutating |
3009 | 0 | // mActiveTimer and mMostRecentRefresh time. |
3010 | 0 | presContext->RefreshDriver()->MostRecentRefresh(); |
3011 | 0 |
|
3012 | 0 |
|
3013 | 0 | // Perform the Servo traversal, and the post-traversal if required. We do this |
3014 | 0 | // in a loop because certain rare paths in the frame constructor (like |
3015 | 0 | // uninstalling XBL bindings) can trigger additional style validations. |
3016 | 0 | mInStyleRefresh = true; |
3017 | 0 | if (mHaveNonAnimationRestyles) { |
3018 | 0 | ++mAnimationGeneration; |
3019 | 0 | } |
3020 | 0 |
|
3021 | 0 | if (mRestyleForCSSRuleChanges) { |
3022 | 0 | aFlags |= ServoTraversalFlags::ForCSSRuleChanges; |
3023 | 0 | } |
3024 | 0 |
|
3025 | 0 | while (styleSet->StyleDocument(aFlags)) { |
3026 | 0 | ClearSnapshots(); |
3027 | 0 |
|
3028 | 0 | nsStyleChangeList currentChanges; |
3029 | 0 | bool anyStyleChanged = false; |
3030 | 0 |
|
3031 | 0 | // Recreate styles , and queue up change hints (which also handle lazy frame |
3032 | 0 | // construction). |
3033 | 0 | { |
3034 | 0 | AutoRestyleTimelineMarker marker(presContext->GetDocShell(), false); |
3035 | 0 | DocumentStyleRootIterator iter(doc->GetServoRestyleRoot()); |
3036 | 0 | while (Element* root = iter.GetNextStyleRoot()) { |
3037 | 0 | nsTArray<nsIFrame*> wrappersToRestyle; |
3038 | 0 | ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle); |
3039 | 0 | ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty; |
3040 | 0 | anyStyleChanged |= ProcessPostTraversal(root, nullptr, state, flags); |
3041 | 0 | } |
3042 | 0 | } |
3043 | 0 |
|
3044 | 0 | doc->ClearServoRestyleRoot(); |
3045 | 0 |
|
3046 | 0 | // Process the change hints. |
3047 | 0 | // |
3048 | 0 | // Unfortunately, the frame constructor can generate new change hints while |
3049 | 0 | // processing existing ones. We redirect those into a secondary queue and |
3050 | 0 | // iterate until there's nothing left. |
3051 | 0 | { |
3052 | 0 | AutoTimelineMarker marker( |
3053 | 0 | presContext->GetDocShell(), "StylesApplyChanges"); |
3054 | 0 | ReentrantChangeList newChanges; |
3055 | 0 | mReentrantChanges = &newChanges; |
3056 | 0 | while (!currentChanges.IsEmpty()) { |
3057 | 0 | ProcessRestyledFrames(currentChanges); |
3058 | 0 | MOZ_ASSERT(currentChanges.IsEmpty()); |
3059 | 0 | for (ReentrantChange& change: newChanges) { |
3060 | 0 | if (!(change.mHint & nsChangeHint_ReconstructFrame) && |
3061 | 0 | !change.mContent->GetPrimaryFrame()) { |
3062 | 0 | // SVG Elements post change hints without ensuring that the primary |
3063 | 0 | // frame will be there after that (see bug 1366142). |
3064 | 0 | // |
3065 | 0 | // Just ignore those, since we can't really process them. |
3066 | 0 | continue; |
3067 | 0 | } |
3068 | 0 | currentChanges.AppendChange(change.mContent->GetPrimaryFrame(), |
3069 | 0 | change.mContent, change.mHint); |
3070 | 0 | } |
3071 | 0 | newChanges.Clear(); |
3072 | 0 | } |
3073 | 0 | mReentrantChanges = nullptr; |
3074 | 0 | } |
3075 | 0 |
|
3076 | 0 | if (anyStyleChanged) { |
3077 | 0 | // Maybe no styles changed when: |
3078 | 0 | // |
3079 | 0 | // * Only explicit change hints were posted in the first place. |
3080 | 0 | // * When an attribute or state change in the content happens not to need |
3081 | 0 | // a restyle after all. |
3082 | 0 | // |
3083 | 0 | // In any case, we don't need to increment the restyle generation in that |
3084 | 0 | // case. |
3085 | 0 | IncrementRestyleGeneration(); |
3086 | 0 | } |
3087 | 0 | } |
3088 | 0 |
|
3089 | 0 | doc->ClearServoRestyleRoot(); |
3090 | 0 |
|
3091 | 0 | FlushOverflowChangedTracker(); |
3092 | 0 |
|
3093 | 0 | ClearSnapshots(); |
3094 | 0 | styleSet->AssertTreeIsClean(); |
3095 | 0 | mHaveNonAnimationRestyles = false; |
3096 | 0 | mRestyleForCSSRuleChanges = false; |
3097 | 0 | mInStyleRefresh = false; |
3098 | 0 |
|
3099 | 0 | // Now that everything has settled, see if we have enough free rule nodes in |
3100 | 0 | // the tree to warrant sweeping them. |
3101 | 0 | styleSet->MaybeGCRuleTree(); |
3102 | 0 |
|
3103 | 0 | // Note: We are in the scope of |animationsWithDestroyedFrame|, so |
3104 | 0 | // |mAnimationsWithDestroyedFrame| is still valid. |
3105 | 0 | MOZ_ASSERT(mAnimationsWithDestroyedFrame); |
3106 | 0 | mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames(); |
3107 | 0 | } |
3108 | | |
3109 | | #ifdef DEBUG |
3110 | | static void |
3111 | | VerifyFlatTree(const nsIContent& aContent) |
3112 | | { |
3113 | | StyleChildrenIterator iter(&aContent); |
3114 | | |
3115 | | for (auto* content = iter.GetNextChild(); |
3116 | | content; |
3117 | | content = iter.GetNextChild()) { |
3118 | | MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent); |
3119 | | MOZ_ASSERT(!content->IsActiveChildrenElement()); |
3120 | | VerifyFlatTree(*content); |
3121 | | } |
3122 | | } |
3123 | | #endif |
3124 | | |
3125 | | void |
3126 | | RestyleManager::ProcessPendingRestyles() |
3127 | 0 | { |
3128 | | #ifdef DEBUG |
3129 | | if (auto* root = mPresContext->Document()->GetRootElement()) { |
3130 | | VerifyFlatTree(*root); |
3131 | | } |
3132 | | #endif |
3133 | |
|
3134 | 0 | DoProcessPendingRestyles(ServoTraversalFlags::Empty); |
3135 | 0 | } |
3136 | | |
3137 | | void |
3138 | | RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() |
3139 | 0 | { |
3140 | 0 | if (mSnapshots.IsEmpty()) { |
3141 | 0 | return; |
3142 | 0 | } |
3143 | 0 | for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) { |
3144 | 0 | // Servo data for the element might have been dropped. (e.g. by removing |
3145 | 0 | // from its document) |
3146 | 0 | if (iter.Key()->HasFlag(ELEMENT_HAS_SNAPSHOT)) { |
3147 | 0 | Servo_ProcessInvalidations(StyleSet()->RawSet(), iter.Key(), &mSnapshots); |
3148 | 0 | } |
3149 | 0 | } |
3150 | 0 | ClearSnapshots(); |
3151 | 0 | } |
3152 | | |
3153 | | bool |
3154 | | RestyleManager::HasPendingRestyleAncestor(Element* aElement) const |
3155 | 0 | { |
3156 | 0 | return Servo_HasPendingRestyleAncestor(aElement); |
3157 | 0 | } |
3158 | | |
3159 | | void |
3160 | | RestyleManager::UpdateOnlyAnimationStyles() |
3161 | 0 | { |
3162 | 0 | bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates(); |
3163 | 0 | if (!doCSS) { |
3164 | 0 | return; |
3165 | 0 | } |
3166 | 0 | |
3167 | 0 | DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations); |
3168 | 0 | } |
3169 | | |
3170 | | void |
3171 | | RestyleManager::ContentStateChanged(nsIContent* aContent, |
3172 | | EventStates aChangedBits) |
3173 | 0 | { |
3174 | 0 | MOZ_ASSERT(!mInStyleRefresh); |
3175 | 0 |
|
3176 | 0 | if (!aContent->IsElement()) { |
3177 | 0 | return; |
3178 | 0 | } |
3179 | 0 | |
3180 | 0 | Element& element = *aContent->AsElement(); |
3181 | 0 | if (!element.HasServoData()) { |
3182 | 0 | return; |
3183 | 0 | } |
3184 | 0 | |
3185 | 0 | const EventStates kVisitedAndUnvisited = |
3186 | 0 | NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED; |
3187 | 0 | // NOTE: We want to return ASAP for visitedness changes, but we don't want to |
3188 | 0 | // mess up the situation where the element became a link or stopped being one. |
3189 | 0 | if (aChangedBits.HasAllStates(kVisitedAndUnvisited) && |
3190 | 0 | !Gecko_VisitedStylesEnabled(element.OwnerDoc())) { |
3191 | 0 | aChangedBits &= ~kVisitedAndUnvisited; |
3192 | 0 | if (aChangedBits.IsEmpty()) { |
3193 | 0 | return; |
3194 | 0 | } |
3195 | 0 | } |
3196 | 0 | |
3197 | 0 | nsChangeHint changeHint; |
3198 | 0 | ContentStateChangedInternal(element, aChangedBits, &changeHint); |
3199 | 0 |
|
3200 | 0 | // Don't bother taking a snapshot if no rules depend on these state bits. |
3201 | 0 | // |
3202 | 0 | // We always take a snapshot for the LTR/RTL event states, since Servo doesn't |
3203 | 0 | // track those bits in the same way, and we know that :dir() rules are always |
3204 | 0 | // present in UA style sheets. |
3205 | 0 | // |
3206 | 0 | // FIXME(emilio): Doesn't this early-return drop the change hint on the floor? |
3207 | 0 | // Should it? |
3208 | 0 | if (!aChangedBits.HasAtLeastOneOfStates(DIRECTION_STATES) && |
3209 | 0 | !StyleSet()->HasStateDependency(element, aChangedBits)) { |
3210 | 0 | return; |
3211 | 0 | } |
3212 | 0 | |
3213 | 0 | ServoElementSnapshot& snapshot = SnapshotFor(element); |
3214 | 0 | EventStates previousState = element.StyleState() ^ aChangedBits; |
3215 | 0 | snapshot.AddState(previousState); |
3216 | 0 |
|
3217 | 0 | if (changeHint) { |
3218 | 0 | Servo_NoteExplicitHints(&element, nsRestyleHint(0), changeHint); |
3219 | 0 | } |
3220 | 0 |
|
3221 | 0 | // Assuming we need to invalidate cached style in getComputedStyle for |
3222 | 0 | // undisplayed elements, since we don't know if it is needed. |
3223 | 0 | IncrementUndisplayedRestyleGeneration(); |
3224 | 0 | } |
3225 | | |
3226 | | static inline bool |
3227 | | AttributeInfluencesOtherPseudoClassState(const Element& aElement, |
3228 | | const nsAtom* aAttribute) |
3229 | 0 | { |
3230 | 0 | // We must record some state for :-moz-browser-frame and |
3231 | 0 | // :-moz-table-border-nonzero. |
3232 | 0 | if (aAttribute == nsGkAtoms::mozbrowser) { |
3233 | 0 | return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame); |
3234 | 0 | } |
3235 | 0 | |
3236 | 0 | if (aAttribute == nsGkAtoms::border) { |
3237 | 0 | return aElement.IsHTMLElement(nsGkAtoms::table); |
3238 | 0 | } |
3239 | 0 | |
3240 | 0 | return false; |
3241 | 0 | } |
3242 | | |
3243 | | static inline bool |
3244 | | NeedToRecordAttrChange(const ServoStyleSet& aStyleSet, |
3245 | | const Element& aElement, |
3246 | | int32_t aNameSpaceID, |
3247 | | nsAtom* aAttribute, |
3248 | | bool* aInfluencesOtherPseudoClassState) |
3249 | 0 | { |
3250 | 0 | *aInfluencesOtherPseudoClassState = |
3251 | 0 | AttributeInfluencesOtherPseudoClassState(aElement, aAttribute); |
3252 | 0 |
|
3253 | 0 | // If the attribute influences one of the pseudo-classes that are backed by |
3254 | 0 | // attributes, we just record it. |
3255 | 0 | if (*aInfluencesOtherPseudoClassState) { |
3256 | 0 | return true; |
3257 | 0 | } |
3258 | 0 | |
3259 | 0 | // We assume that id and class attributes are used in class/id selectors, and |
3260 | 0 | // thus record them. |
3261 | 0 | // |
3262 | 0 | // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet, |
3263 | 0 | // presumably we could try to filter the old and new id, but it's not clear |
3264 | 0 | // it's worth it. |
3265 | 0 | if (aNameSpaceID == kNameSpaceID_None && |
3266 | 0 | (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) { |
3267 | 0 | return true; |
3268 | 0 | } |
3269 | 0 | |
3270 | 0 | // We always record lang="", even though we force a subtree restyle when it |
3271 | 0 | // changes, since it can change how its siblings match :lang(..) due to |
3272 | 0 | // selectors like :lang(..) + div. |
3273 | 0 | if (aAttribute == nsGkAtoms::lang) { |
3274 | 0 | return true; |
3275 | 0 | } |
3276 | 0 | |
3277 | 0 | // Otherwise, just record the attribute change if a selector in the page may |
3278 | 0 | // reference it from an attribute selector. |
3279 | 0 | return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute); |
3280 | 0 | } |
3281 | | |
3282 | | void |
3283 | | RestyleManager::AttributeWillChange(Element* aElement, |
3284 | | int32_t aNameSpaceID, |
3285 | | nsAtom* aAttribute, |
3286 | | int32_t aModType, |
3287 | | const nsAttrValue* aNewValue) |
3288 | 0 | { |
3289 | 0 | TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute); |
3290 | 0 | } |
3291 | | |
3292 | | void |
3293 | | RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) |
3294 | 0 | { |
3295 | 0 | TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None, |
3296 | 0 | nsGkAtoms::_class); |
3297 | 0 | } |
3298 | | |
3299 | | void |
3300 | | RestyleManager::TakeSnapshotForAttributeChange(Element& aElement, |
3301 | | int32_t aNameSpaceID, |
3302 | | nsAtom* aAttribute) |
3303 | 0 | { |
3304 | 0 | MOZ_ASSERT(!mInStyleRefresh); |
3305 | 0 |
|
3306 | 0 | if (!aElement.HasServoData()) { |
3307 | 0 | return; |
3308 | 0 | } |
3309 | 0 | |
3310 | 0 | bool influencesOtherPseudoClassState; |
3311 | 0 | if (!NeedToRecordAttrChange(*StyleSet(), |
3312 | 0 | aElement, |
3313 | 0 | aNameSpaceID, |
3314 | 0 | aAttribute, |
3315 | 0 | &influencesOtherPseudoClassState)) { |
3316 | 0 | return; |
3317 | 0 | } |
3318 | 0 | |
3319 | 0 | // We cannot tell if the attribute change will affect the styles of |
3320 | 0 | // undisplayed elements, because we don't actually restyle those elements |
3321 | 0 | // during the restyle traversal. So just assume that the attribute change can |
3322 | 0 | // cause the style to change. |
3323 | 0 | IncrementUndisplayedRestyleGeneration(); |
3324 | 0 |
|
3325 | 0 | // Some other random attribute changes may also affect the transitions, |
3326 | 0 | // so we also set this true here. |
3327 | 0 | mHaveNonAnimationRestyles = true; |
3328 | 0 |
|
3329 | 0 | ServoElementSnapshot& snapshot = SnapshotFor(aElement); |
3330 | 0 | snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute); |
3331 | 0 |
|
3332 | 0 | if (influencesOtherPseudoClassState) { |
3333 | 0 | snapshot.AddOtherPseudoClassState(aElement); |
3334 | 0 | } |
3335 | 0 | } |
3336 | | |
3337 | | // For some attribute changes we must restyle the whole subtree: |
3338 | | // |
3339 | | // * <td> is affected by the cellpadding on its ancestor table |
3340 | | // * lwtheme and lwthemetextcolor on root element of XUL document |
3341 | | // affects all descendants due to :-moz-lwtheme* pseudo-classes |
3342 | | // * lang="" and xml:lang="" can affect all descendants due to :lang() |
3343 | | // |
3344 | | static inline bool |
3345 | | AttributeChangeRequiresSubtreeRestyle(const Element& aElement, nsAtom* aAttr) |
3346 | 0 | { |
3347 | 0 | if (aAttr == nsGkAtoms::cellpadding) { |
3348 | 0 | return aElement.IsHTMLElement(nsGkAtoms::table); |
3349 | 0 | } |
3350 | 0 | if (aAttr == nsGkAtoms::lwtheme || |
3351 | 0 | aAttr == nsGkAtoms::lwthemetextcolor) { |
3352 | 0 | return aElement.GetNameSpaceID() == kNameSpaceID_XUL && |
3353 | 0 | &aElement == aElement.OwnerDoc()->GetRootElement(); |
3354 | 0 | } |
3355 | 0 |
|
3356 | 0 | return aAttr == nsGkAtoms::lang; |
3357 | 0 | } |
3358 | | |
3359 | | void |
3360 | | RestyleManager::AttributeChanged(Element* aElement, |
3361 | | int32_t aNameSpaceID, |
3362 | | nsAtom* aAttribute, |
3363 | | int32_t aModType, |
3364 | | const nsAttrValue* aOldValue) |
3365 | 0 | { |
3366 | 0 | MOZ_ASSERT(!mInStyleRefresh); |
3367 | 0 |
|
3368 | 0 | auto changeHint = nsChangeHint(0); |
3369 | 0 | auto restyleHint = nsRestyleHint(0); |
3370 | 0 |
|
3371 | 0 | changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType); |
3372 | 0 |
|
3373 | 0 | if (aAttribute == nsGkAtoms::style) { |
3374 | 0 | restyleHint |= eRestyle_StyleAttribute; |
3375 | 0 | } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) { |
3376 | 0 | restyleHint |= eRestyle_Subtree; |
3377 | 0 | } else if (aElement->IsAttributeMapped(aAttribute)) { |
3378 | 0 | restyleHint |= eRestyle_Self; |
3379 | 0 | } |
3380 | 0 |
|
3381 | 0 | if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) { |
3382 | 0 | // See if we have appearance information for a theme. |
3383 | 0 | const nsStyleDisplay* disp = primaryFrame->StyleDisplay(); |
3384 | 0 | if (disp->HasAppearance()) { |
3385 | 0 | nsITheme* theme = PresContext()->GetTheme(); |
3386 | 0 | if (theme && theme->ThemeSupportsWidget(PresContext(), primaryFrame, |
3387 | 0 | disp->mAppearance)) { |
3388 | 0 | bool repaint = false; |
3389 | 0 | theme->WidgetStateChanged(primaryFrame, disp->mAppearance, |
3390 | 0 | aAttribute, &repaint, aOldValue); |
3391 | 0 | if (repaint) { |
3392 | 0 | changeHint |= nsChangeHint_RepaintFrame; |
3393 | 0 | } |
3394 | 0 | } |
3395 | 0 | } |
3396 | 0 |
|
3397 | 0 | primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); |
3398 | 0 | } |
3399 | 0 |
|
3400 | 0 | if (restyleHint || changeHint) { |
3401 | 0 | Servo_NoteExplicitHints(aElement, restyleHint, changeHint); |
3402 | 0 | } |
3403 | 0 |
|
3404 | 0 | if (restyleHint) { |
3405 | 0 | // Assuming we need to invalidate cached style in getComputedStyle for |
3406 | 0 | // undisplayed elements, since we don't know if it is needed. |
3407 | 0 | IncrementUndisplayedRestyleGeneration(); |
3408 | 0 |
|
3409 | 0 | // If we change attributes, we have to mark this to be true, so we will |
3410 | 0 | // increase the animation generation for the new created transition if any. |
3411 | 0 | mHaveNonAnimationRestyles = true; |
3412 | 0 | } |
3413 | 0 | } |
3414 | | |
3415 | | void |
3416 | | RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) |
3417 | 0 | { |
3418 | 0 | // This is only called when moving frames in or out of the first-line |
3419 | 0 | // pseudo-element (or one of its descendants). We can't say much about |
3420 | 0 | // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into |
3421 | 0 | // something inside an inline-block on the first line the ancestors could be |
3422 | 0 | // totally arbitrary), but we will definitely find a line frame on the |
3423 | 0 | // ancestor chain. Note that the lineframe may not actually be the one that |
3424 | 0 | // corresponds to ::first-line; when we're moving _out_ of the ::first-line it |
3425 | 0 | // will be one of the continuations instead. |
3426 | | #ifdef DEBUG |
3427 | | { |
3428 | | nsIFrame* f = aFrame->GetParent(); |
3429 | | while (f && !f->IsLineFrame()) { |
3430 | | f = f->GetParent(); |
3431 | | } |
3432 | | MOZ_ASSERT(f, "Must have found a first-line frame"); |
3433 | | } |
3434 | | #endif |
3435 | |
|
3436 | 0 | DoReparentComputedStyleForFirstLine(aFrame, *StyleSet()); |
3437 | 0 | } |
3438 | | |
3439 | | void |
3440 | | RestyleManager::DoReparentComputedStyleForFirstLine(nsIFrame* aFrame, |
3441 | | ServoStyleSet& aStyleSet) |
3442 | 0 | { |
3443 | 0 | if (aFrame->IsBackdropFrame()) { |
3444 | 0 | // Style context of backdrop frame has no parent style, and thus we do not |
3445 | 0 | // need to reparent it. |
3446 | 0 | return; |
3447 | 0 | } |
3448 | 0 | |
3449 | 0 | if (aFrame->IsPlaceholderFrame()) { |
3450 | 0 | // Also reparent the out-of-flow and all its continuations. We're doing |
3451 | 0 | // this to match Gecko for now, but it's not clear that this behavior is |
3452 | 0 | // correct per spec. It's certainly pretty odd for out-of-flows whose |
3453 | 0 | // containing block is not within the first line. |
3454 | 0 | // |
3455 | 0 | // Right now we're somewhat inconsistent in this testcase: |
3456 | 0 | // |
3457 | 0 | // <style> |
3458 | 0 | // div { color: orange; clear: left; } |
3459 | 0 | // div::first-line { color: blue; } |
3460 | 0 | // </style> |
3461 | 0 | // <div> |
3462 | 0 | // <span style="float: left">What color is this text?</span> |
3463 | 0 | // </div> |
3464 | 0 | // <div> |
3465 | 0 | // <span><span style="float: left">What color is this text?</span></span> |
3466 | 0 | // </div> |
3467 | 0 | // |
3468 | 0 | // We make the first float orange and the second float blue. On the other |
3469 | 0 | // hand, if the float were within an inline-block that was on the first |
3470 | 0 | // line, arguably it _should_ inherit from the ::first-line... |
3471 | 0 | nsIFrame* outOfFlow = |
3472 | 0 | nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); |
3473 | 0 | MOZ_ASSERT(outOfFlow, "no out-of-flow frame"); |
3474 | 0 | for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) { |
3475 | 0 | DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet); |
3476 | 0 | } |
3477 | 0 | } |
3478 | 0 |
|
3479 | 0 | // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try |
3480 | 0 | // to remove it? |
3481 | 0 | nsIFrame* providerFrame; |
3482 | 0 | ComputedStyle* newParentStyle = |
3483 | 0 | aFrame->GetParentComputedStyle(&providerFrame); |
3484 | 0 | // If our provider is our child, we want to reparent it first, because we |
3485 | 0 | // inherit style from it. |
3486 | 0 | bool isChild = providerFrame && providerFrame->GetParent() == aFrame; |
3487 | 0 | nsIFrame* providerChild = nullptr; |
3488 | 0 | if (isChild) { |
3489 | 0 | DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet); |
3490 | 0 | // Get the style again after ReparentComputedStyle() which might have |
3491 | 0 | // changed it. |
3492 | 0 | newParentStyle = providerFrame->Style(); |
3493 | 0 | providerChild = providerFrame; |
3494 | 0 | MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), |
3495 | 0 | "Out of flow provider?"); |
3496 | 0 | } |
3497 | 0 |
|
3498 | 0 | if (!newParentStyle) { |
3499 | 0 | // No need to do anything here for this frame, but we should still reparent |
3500 | 0 | // its descendants, because those may have styles that inherit from the |
3501 | 0 | // parent of this frame (e.g. non-anonymous columns in an anonymous |
3502 | 0 | // colgroup). |
3503 | 0 | MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(), |
3504 | 0 | "Why did this frame not end up with a parent context?"); |
3505 | 0 | ReparentFrameDescendants(aFrame, providerChild, aStyleSet); |
3506 | 0 | return; |
3507 | 0 | } |
3508 | 0 |
|
3509 | 0 | bool isElement = aFrame->GetContent()->IsElement(); |
3510 | 0 |
|
3511 | 0 | // We probably don't want to initiate transitions from ReparentComputedStyle, |
3512 | 0 | // since we call it during frame construction rather than in response to |
3513 | 0 | // dynamic changes. |
3514 | 0 | // Also see the comment at the start of |
3515 | 0 | // nsTransitionManager::ConsiderInitiatingTransition. |
3516 | 0 | // |
3517 | 0 | // We don't try to do the fancy copying from previous continuations that |
3518 | 0 | // GeckoRestyleManager does here, because that relies on knowing the parents |
3519 | 0 | // of ComputedStyles, and we don't know those. |
3520 | 0 | ComputedStyle* oldStyle = aFrame->Style(); |
3521 | 0 | Element* ourElement = |
3522 | 0 | oldStyle->GetPseudoType() == CSSPseudoElementType::NotPseudo && |
3523 | 0 | isElement ? |
3524 | 0 | aFrame->GetContent()->AsElement() : |
3525 | 0 | nullptr; |
3526 | 0 | ComputedStyle* newParent = newParentStyle; |
3527 | 0 |
|
3528 | 0 | ComputedStyle* newParentIgnoringFirstLine; |
3529 | 0 | if (newParent->GetPseudoType() == CSSPseudoElementType::firstLine) { |
3530 | 0 | MOZ_ASSERT(providerFrame && providerFrame->GetParent()-> |
3531 | 0 | IsFrameOfType(nsIFrame::eBlockFrame), |
3532 | 0 | "How could we get a ::first-line parent style without having " |
3533 | 0 | "a ::first-line provider frame?"); |
3534 | 0 | // If newParent is a ::first-line style, get the parent blockframe, and then |
3535 | 0 | // correct it for our pseudo as needed (e.g. stepping out of anon boxes). |
3536 | 0 | // Use the resulting style for the "parent style ignoring ::first-line". |
3537 | 0 | nsIFrame* blockFrame = providerFrame->GetParent(); |
3538 | 0 | nsIFrame* correctedFrame = |
3539 | 0 | nsFrame::CorrectStyleParentFrame(blockFrame, oldStyle->GetPseudo()); |
3540 | 0 | newParentIgnoringFirstLine = correctedFrame->Style(); |
3541 | 0 | } else { |
3542 | 0 | newParentIgnoringFirstLine = newParent; |
3543 | 0 | } |
3544 | 0 |
|
3545 | 0 | if (!providerFrame) { |
3546 | 0 | // No providerFrame means we inherited from a display:contents thing. Our |
3547 | 0 | // layout parent style is the style of our nearest ancestor frame. But we have |
3548 | 0 | // to be careful to do that with our placeholder, not with us, if we're out of |
3549 | 0 | // flow. |
3550 | 0 | if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { |
3551 | 0 | aFrame->FirstContinuation()->GetPlaceholderFrame()->GetLayoutParentStyleForOutOfFlow(&providerFrame); |
3552 | 0 | } else { |
3553 | 0 | providerFrame = nsFrame::CorrectStyleParentFrame(aFrame->GetParent(), |
3554 | 0 | oldStyle->GetPseudo()); |
3555 | 0 | } |
3556 | 0 | } |
3557 | 0 | ComputedStyle* layoutParent = providerFrame->Style(); |
3558 | 0 |
|
3559 | 0 | RefPtr<ComputedStyle> newStyle = |
3560 | 0 | aStyleSet.ReparentComputedStyle(oldStyle, |
3561 | 0 | newParent, |
3562 | 0 | newParentIgnoringFirstLine, |
3563 | 0 | layoutParent, |
3564 | 0 | ourElement); |
3565 | 0 | aFrame->SetComputedStyle(newStyle); |
3566 | 0 |
|
3567 | 0 | // This logic somewhat mirrors the logic in |
3568 | 0 | // RestyleManager::ProcessPostTraversal. |
3569 | 0 | if (isElement) { |
3570 | 0 | // We can't use UpdateAdditionalComputedStyles as-is because it needs a |
3571 | 0 | // ServoRestyleState and maintaining one of those during a _frametree_ |
3572 | 0 | // traversal is basically impossible. |
3573 | 0 | uint32_t index = 0; |
3574 | 0 | while (auto* oldAdditionalStyle = aFrame->GetAdditionalComputedStyle(index)) { |
3575 | 0 | RefPtr<ComputedStyle> newAdditionalContext = |
3576 | 0 | aStyleSet.ReparentComputedStyle(oldAdditionalStyle, |
3577 | 0 | newStyle, |
3578 | 0 | newStyle, |
3579 | 0 | newStyle, |
3580 | 0 | nullptr); |
3581 | 0 | aFrame->SetAdditionalComputedStyle(index, newAdditionalContext); |
3582 | 0 | ++index; |
3583 | 0 | } |
3584 | 0 | } |
3585 | 0 |
|
3586 | 0 | // Generally, owned anon boxes are our descendants. The only exceptions are |
3587 | 0 | // tables (for the table wrapper) and inline frames (for the block part of the |
3588 | 0 | // block-in-inline split). We're going to update our descendants when looping |
3589 | 0 | // over kids, and we don't want to update the block part of a block-in-inline |
3590 | 0 | // split if the inline is on the first line but the block is not (and if the |
3591 | 0 | // block is, it's the child of something else on the first line and will get |
3592 | 0 | // updated as a child). And given how this method ends up getting called, if |
3593 | 0 | // we reach here for a table frame, we are already in the middle of |
3594 | 0 | // reparenting the table wrapper frame. So no need to |
3595 | 0 | // UpdateStyleOfOwnedAnonBoxes() here. |
3596 | 0 |
|
3597 | 0 | ReparentFrameDescendants(aFrame, providerChild, aStyleSet); |
3598 | 0 |
|
3599 | 0 | // We do not need to do the equivalent of UpdateFramePseudoElementStyles, |
3600 | 0 | // because those are handled by our descendant walk. |
3601 | 0 | } |
3602 | | |
3603 | | void |
3604 | | RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame, |
3605 | | nsIFrame* aProviderChild, |
3606 | | ServoStyleSet& aStyleSet) |
3607 | 0 | { |
3608 | 0 | if (aFrame->GetContent()->IsElement() && |
3609 | 0 | !aFrame->GetContent()->AsElement()->HasServoData()) { |
3610 | 0 | // We're getting into a display: none subtree, avoid reparenting into stuff |
3611 | 0 | // that is going to go away anyway in seconds. |
3612 | 0 | return; |
3613 | 0 | } |
3614 | 0 | nsIFrame::ChildListIterator lists(aFrame); |
3615 | 0 | for (; !lists.IsDone(); lists.Next()) { |
3616 | 0 | for (nsIFrame* child : lists.CurrentList()) { |
3617 | 0 | // only do frames that are in flow |
3618 | 0 | if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && |
3619 | 0 | child != aProviderChild) { |
3620 | 0 | DoReparentComputedStyleForFirstLine(child, aStyleSet); |
3621 | 0 | } |
3622 | 0 | } |
3623 | 0 | } |
3624 | 0 | } |
3625 | | |
3626 | | } // namespace mozilla |