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