no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / base / RestyleManager.cpp
blob81ffebf89a575b34c3f350c878d26b5fabd33878
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/. */
7 #include "mozilla/RestyleManager.h"
9 #include "mozilla/AnimationUtils.h"
10 #include "mozilla/Assertions.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 #include "mozilla/dom/HTMLInputElement.h"
38 #include "ScrollSnap.h"
39 #include "nsAnimationManager.h"
40 #include "nsBlockFrame.h"
41 #include "nsIScrollableFrame.h"
42 #include "nsContentUtils.h"
43 #include "nsCSSFrameConstructor.h"
44 #include "nsCSSRendering.h"
45 #include "nsDocShell.h"
46 #include "nsIFrame.h"
47 #include "nsIFrameInlines.h"
48 #include "nsImageFrame.h"
49 #include "nsPlaceholderFrame.h"
50 #include "nsPrintfCString.h"
51 #include "nsRefreshDriver.h"
52 #include "nsStyleChangeList.h"
53 #include "nsStyleUtil.h"
54 #include "nsTransitionManager.h"
55 #include "StickyScrollContainer.h"
56 #include "ActiveLayerTracker.h"
58 #ifdef ACCESSIBILITY
59 # include "nsAccessibilityService.h"
60 #endif
62 using mozilla::layers::AnimationInfo;
63 using mozilla::layout::ScrollAnchorContainer;
65 using namespace mozilla::dom;
66 using namespace mozilla::layers;
68 namespace mozilla {
70 RestyleManager::RestyleManager(nsPresContext* aPresContext)
71 : mPresContext(aPresContext),
72 mRestyleGeneration(1),
73 mUndisplayedRestyleGeneration(1),
74 mInStyleRefresh(false),
75 mAnimationGeneration(0) {
76 MOZ_ASSERT(mPresContext);
79 void RestyleManager::ContentInserted(nsIContent* aChild) {
80 MOZ_ASSERT(aChild->GetParentNode());
81 if (aChild->IsElement()) {
82 StyleSet()->MaybeInvalidateForElementInsertion(*aChild->AsElement());
84 RestyleForInsertOrChange(aChild);
87 void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
88 auto* container = aFirstNewContent->GetParentNode();
89 MOZ_ASSERT(container);
91 #ifdef DEBUG
93 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
94 NS_ASSERTION(!cur->IsRootOfNativeAnonymousSubtree(),
95 "anonymous nodes should not be in child lists");
98 #endif
99 StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent);
101 const auto selectorFlags = container->GetSelectorFlags() &
102 NodeSelectorFlags::AllSimpleRestyleFlagsForAppend;
103 if (!selectorFlags) {
104 return;
107 // The container cannot be a document.
108 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
110 if (selectorFlags & NodeSelectorFlags::HasEmptySelector) {
111 // see whether we need to restyle the container
112 bool wasEmpty = true; // :empty or :-moz-only-whitespace
113 for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
114 cur = cur->GetNextSibling()) {
115 // We don't know whether we're testing :empty or :-moz-only-whitespace,
116 // so be conservative and assume :-moz-only-whitespace (i.e., make
117 // IsSignificantChild less likely to be true, and thus make us more
118 // likely to restyle).
119 if (nsStyleUtil::IsSignificantChild(cur, false)) {
120 wasEmpty = false;
121 break;
124 if (wasEmpty && container->IsElement()) {
125 RestyleForEmptyChange(container->AsElement());
126 return;
130 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
131 if (container->IsElement()) {
132 auto* containerElement = container->AsElement();
133 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
134 nsChangeHint(0));
135 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
136 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
137 containerElement->GetFirstElementChild());
139 } else {
140 RestylePreviousSiblings(aFirstNewContent);
141 RestyleSiblingsStartingWith(aFirstNewContent);
143 // Restyling the container is the most we can do here, so we're done.
144 return;
147 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
148 // restyle the last element child before this node
149 for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
150 cur = cur->GetPreviousSibling()) {
151 if (cur->IsElement()) {
152 auto* element = cur->AsElement();
153 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
154 nsChangeHint(0));
155 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
156 *element);
157 break;
163 void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) {
164 for (nsIContent* sibling = aStartingSibling; sibling;
165 sibling = sibling->GetPreviousSibling()) {
166 if (auto* element = Element::FromNode(sibling)) {
167 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
172 void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) {
173 for (nsIContent* sibling = aStartingSibling; sibling;
174 sibling = sibling->GetNextSibling()) {
175 if (auto* element = Element::FromNode(sibling)) {
176 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
181 void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
182 PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0));
183 StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer);
185 // In some cases (:empty + E, :empty ~ E), a change in the content of
186 // an element requires restyling its parent's siblings.
187 nsIContent* grandparent = aContainer->GetParent();
188 if (!grandparent || !(grandparent->GetSelectorFlags() &
189 NodeSelectorFlags::HasSlowSelectorLaterSiblings)) {
190 return;
192 RestyleSiblingsStartingWith(aContainer->GetNextSibling());
195 void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
196 nsIContent* aChangedChild) {
197 MOZ_ASSERT(aContainer->GetSelectorFlags() &
198 NodeSelectorFlags::HasEdgeChildSelector);
199 MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
200 // restyle the previously-first element child if it is after this node
201 bool passedChild = false;
202 for (nsIContent* content = aContainer->GetFirstChild(); content;
203 content = content->GetNextSibling()) {
204 if (content == aChangedChild) {
205 passedChild = true;
206 continue;
208 if (content->IsElement()) {
209 if (passedChild) {
210 auto* element = content->AsElement();
211 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
212 nsChangeHint(0));
213 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
214 *element);
216 break;
219 // restyle the previously-last element child if it is before this node
220 passedChild = false;
221 for (nsIContent* content = aContainer->GetLastChild(); content;
222 content = content->GetPreviousSibling()) {
223 if (content == aChangedChild) {
224 passedChild = true;
225 continue;
227 if (content->IsElement()) {
228 if (passedChild) {
229 auto* element = content->AsElement();
230 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
231 nsChangeHint(0));
232 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
233 *element);
235 break;
240 template <typename CharT>
241 bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) {
242 for (auto index : IntegerRange(aUpTo)) {
243 if (!dom::IsSpaceCharacter(aBuffer[index])) {
244 return false;
247 return true;
250 template <typename CharT>
251 bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
252 size_t aNewLength) {
253 MOZ_ASSERT(aOldLength <= aNewLength);
254 if (!WhitespaceOnly(aBuffer, aOldLength)) {
255 // The old text was already not whitespace-only.
256 return false;
259 return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength);
262 static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
263 MOZ_ASSERT(aChild->GetParent() == aContainer);
264 for (nsIContent* child = aContainer->GetFirstChild(); child;
265 child = child->GetNextSibling()) {
266 if (child == aChild) {
267 continue;
269 // We don't know whether we're testing :empty or :-moz-only-whitespace,
270 // so be conservative and assume :-moz-only-whitespace (i.e., make
271 // IsSignificantChild less likely to be true, and thus make us more
272 // likely to restyle).
273 if (nsStyleUtil::IsSignificantChild(child, false)) {
274 return true;
278 return false;
281 void RestyleManager::CharacterDataChanged(
282 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
283 nsINode* parent = aContent->GetParentNode();
284 MOZ_ASSERT(parent, "How were we notified of a stray node?");
286 const auto slowSelectorFlags =
287 parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
288 if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
289 NodeSelectorFlags::HasEdgeChildSelector))) {
290 // Nothing to do, no other slow selector can change as a result of this.
291 return;
294 if (!aContent->IsText()) {
295 // Doesn't matter to styling (could be a processing instruction or a
296 // comment), it can't change whether any selectors match or don't.
297 return;
300 if (MOZ_UNLIKELY(!parent->IsElement())) {
301 MOZ_ASSERT(parent->IsShadowRoot());
302 return;
305 if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) {
306 // This is an anonymous node and thus isn't in child lists, so isn't taken
307 // into account for selector matching the relevant selectors here.
308 return;
311 // Handle appends specially since they're common and we can know both the old
312 // and the new text exactly.
314 // TODO(emilio): This could be made much more general if :-moz-only-whitespace
315 // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only
316 // need to know whether we went from empty to non-empty, and that's trivial to
317 // know, with CharacterDataChangeInfo...
318 if (!aInfo.mAppend) {
319 // FIXME(emilio): This restyles unnecessarily if the text node is the only
320 // child of the parent element. Fortunately, it's uncommon to have such
321 // nodes and this not being an append.
323 // See the testcase in bug 1427625 for a test-case that triggers this.
324 RestyleForInsertOrChange(aContent);
325 return;
328 const nsTextFragment* text = &aContent->AsText()->TextFragment();
330 const size_t oldLength = aInfo.mChangeStart;
331 const size_t newLength = text->GetLength();
333 const bool emptyChanged = !oldLength && newLength;
335 const bool whitespaceOnlyChanged =
336 text->Is2b()
337 ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength)
338 : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength);
340 if (!emptyChanged && !whitespaceOnlyChanged) {
341 return;
344 if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) {
345 if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
346 // We used to be empty, restyle the parent.
347 RestyleForEmptyChange(parent->AsElement());
348 return;
352 if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
353 MaybeRestyleForEdgeChildChange(parent, aContent);
357 // Restyling for a ContentInserted or CharacterDataChanged notification.
358 // This could be used for ContentRemoved as well if we got the
359 // notification before the removal happened (and sometimes
360 // CharacterDataChanged is more like a removal than an addition).
361 // The comments are written and variables are named in terms of it being
362 // a ContentInserted notification.
363 void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
364 nsINode* container = aChild->GetParentNode();
365 MOZ_ASSERT(container);
367 const auto selectorFlags =
368 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
369 if (!selectorFlags) {
370 return;
373 NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(),
374 "anonymous nodes should not be in child lists");
376 // The container cannot be a document.
377 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
379 if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
380 container->IsElement()) {
381 // See whether we need to restyle the container due to :empty /
382 // :-moz-only-whitespace.
383 const bool wasEmpty =
384 !HasAnySignificantSibling(container->AsElement(), aChild);
385 if (wasEmpty) {
386 // FIXME(emilio): When coming from CharacterDataChanged this can restyle
387 // unnecessarily. Also can restyle unnecessarily if aChild is not
388 // significant anyway, though that's more unlikely.
389 RestyleForEmptyChange(container->AsElement());
390 return;
394 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
395 if (container->IsElement()) {
396 auto* containerElement = container->AsElement();
397 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
398 nsChangeHint(0));
399 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
400 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
401 containerElement->GetFirstElementChild());
403 } else {
404 RestylePreviousSiblings(aChild);
405 RestyleSiblingsStartingWith(aChild);
407 // Restyling the container is the most we can do here, so we're done.
408 return;
411 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
412 // Restyle all later siblings.
413 RestyleSiblingsStartingWith(aChild->GetNextSibling());
414 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
415 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
416 aChild->GetNextElementSibling());
420 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
421 MaybeRestyleForEdgeChildChange(container, aChild);
425 void RestyleManager::ContentRemoved(nsIContent* aOldChild,
426 nsIContent* aFollowingSibling) {
427 auto* container = aOldChild->GetParentNode();
428 MOZ_ASSERT(container);
430 // Computed style data isn't useful for detached nodes, and we'll need to
431 // recompute it anyway if we ever insert the nodes back into a document.
432 if (auto* element = Element::FromNode(aOldChild)) {
433 RestyleManager::ClearServoDataFromSubtree(element);
434 // If this element is undisplayed or may have undisplayed descendants, we
435 // need to invalidate the cache, since there's the unlikely event of those
436 // elements getting destroyed and their addresses reused in a way that we
437 // look up the cache with their address for a different element before it's
438 // invalidated.
439 IncrementUndisplayedRestyleGeneration();
441 if (aOldChild->IsElement()) {
442 StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement(),
443 aFollowingSibling);
446 const auto selectorFlags =
447 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
448 if (!selectorFlags) {
449 return;
452 if (aOldChild->IsRootOfNativeAnonymousSubtree()) {
453 // This should be an assert, but this is called incorrectly in
454 // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
455 // up the logs. Make it an assert again when that's fixed.
456 MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
457 "anonymous nodes should not be in child lists (bug 439258)");
460 // The container cannot be a document.
461 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
463 if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
464 container->IsElement()) {
465 // see whether we need to restyle the container
466 bool isEmpty = true; // :empty or :-moz-only-whitespace
467 for (nsIContent* child = container->GetFirstChild(); child;
468 child = child->GetNextSibling()) {
469 // We don't know whether we're testing :empty or :-moz-only-whitespace,
470 // so be conservative and assume :-moz-only-whitespace (i.e., make
471 // IsSignificantChild less likely to be true, and thus make us more
472 // likely to restyle).
473 if (nsStyleUtil::IsSignificantChild(child, false)) {
474 isEmpty = false;
475 break;
478 if (isEmpty && container->IsElement()) {
479 RestyleForEmptyChange(container->AsElement());
480 return;
484 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
485 if (container->IsElement()) {
486 auto* containerElement = container->AsElement();
487 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
488 nsChangeHint(0));
489 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
490 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
491 containerElement->GetFirstElementChild());
493 } else {
494 RestylePreviousSiblings(aOldChild);
495 RestyleSiblingsStartingWith(aOldChild);
497 // Restyling the container is the most we can do here, so we're done.
498 return;
501 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
502 // Restyle all later siblings.
503 RestyleSiblingsStartingWith(aFollowingSibling);
504 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
505 Element* nextSibling =
506 aFollowingSibling ? aFollowingSibling->IsElement()
507 ? aFollowingSibling->AsElement()
508 : aFollowingSibling->GetNextElementSibling()
509 : nullptr;
510 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
511 nextSibling);
515 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
516 // restyle the now-first element child if it was after aOldChild
517 bool reachedFollowingSibling = false;
518 for (nsIContent* content = container->GetFirstChild(); content;
519 content = content->GetNextSibling()) {
520 if (content == aFollowingSibling) {
521 reachedFollowingSibling = true;
522 // do NOT continue here; we might want to restyle this node
524 if (content->IsElement()) {
525 if (reachedFollowingSibling) {
526 auto* element = content->AsElement();
527 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
528 nsChangeHint(0));
529 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
530 *element);
532 break;
535 // restyle the now-last element child if it was before aOldChild
536 reachedFollowingSibling = (aFollowingSibling == nullptr);
537 for (nsIContent* content = container->GetLastChild(); content;
538 content = content->GetPreviousSibling()) {
539 if (content->IsElement()) {
540 if (reachedFollowingSibling) {
541 auto* element = content->AsElement();
542 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
543 nsChangeHint(0));
544 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
545 *element);
547 break;
549 if (content == aFollowingSibling) {
550 reachedFollowingSibling = true;
556 static bool StateChangeMayAffectFrame(const Element& aElement,
557 const nsIFrame& aFrame,
558 ElementState aStates) {
559 const bool brokenChanged = aStates.HasState(ElementState::BROKEN);
560 if (!brokenChanged) {
561 return false;
564 if (aFrame.IsGeneratedContentFrame()) {
565 // If it's other generated content, ignore state changes on it.
566 return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage);
569 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed)) {
570 // Broken affects object fallback behavior.
571 return true;
574 const bool mightChange = [&] {
575 if (aElement.IsHTMLElement(nsGkAtoms::img)) {
576 return true;
578 const auto* input = HTMLInputElement::FromNode(aElement);
579 return input && input->ControlType() == FormControlType::InputImage;
580 }();
582 if (!mightChange) {
583 return false;
586 const bool needsImageFrame =
587 nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) !=
588 nsImageFrame::ImageFrameType::None;
589 return needsImageFrame != aFrame.IsImageFrameOrSubclass();
592 static bool RepaintForAppearance(nsIFrame& aFrame, const Element& aElement,
593 ElementState aStateMask) {
594 if (aStateMask.HasAtLeastOneOfStates(ElementState::HOVER |
595 ElementState::ACTIVE) &&
596 aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) {
597 // The checkbox inside these elements inherit hover state and so on, see
598 // nsNativeTheme::GetContentState.
599 // FIXME(emilio): Would be nice to not have these hard-coded.
600 return true;
602 auto appearance = aFrame.StyleDisplay()->EffectiveAppearance();
603 if (appearance == StyleAppearance::None) {
604 return false;
606 nsPresContext* pc = aFrame.PresContext();
607 nsITheme* theme = pc->Theme();
608 if (!theme->ThemeSupportsWidget(pc, &aFrame, appearance)) {
609 return false;
611 bool repaint = false;
612 theme->WidgetStateChanged(&aFrame, appearance, nullptr, &repaint, nullptr);
613 return repaint;
617 * Calculates the change hint and the restyle hint for a given content state
618 * change.
620 static nsChangeHint ChangeForContentStateChange(const Element& aElement,
621 ElementState aStateMask) {
622 auto changeHint = nsChangeHint(0);
624 // Any change to a content state that affects which frames we construct
625 // must lead to a frame reconstruct here if we already have a frame.
626 // Note that we never decide through non-CSS means to not create frames
627 // based on content states, so if we already don't have a frame we don't
628 // need to force a reframe -- if it's needed, the HasStateDependentStyle
629 // call will handle things.
630 if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) {
631 if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) {
632 return nsChangeHint_ReconstructFrame;
634 if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) {
635 changeHint |= nsChangeHint_RepaintFrame;
637 primaryFrame->ElementStateChanged(aStateMask);
640 if (aStateMask.HasState(ElementState::VISITED)) {
641 // Exposing information to the page about whether the link is
642 // visited or not isn't really something we can worry about here.
643 // FIXME: We could probably do this a bit better.
644 changeHint |= nsChangeHint_RepaintFrame;
647 // This changes the applicable text-transform in the editor root.
648 if (aStateMask.HasState(ElementState::REVEALED)) {
649 // This is the same change hint as tweaking text-transform.
650 changeHint |= NS_STYLE_HINT_REFLOW;
653 return changeHint;
656 #ifdef DEBUG
657 /* static */
658 nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
659 nsCString result;
660 bool any = false;
661 const char* names[] = {"RepaintFrame",
662 "NeedReflow",
663 "ClearAncestorIntrinsics",
664 "ClearDescendantIntrinsics",
665 "NeedDirtyReflow",
666 "UpdateCursor",
667 "UpdateEffects",
668 "UpdateOpacityLayer",
669 "UpdateTransformLayer",
670 "ReconstructFrame",
671 "UpdateOverflow",
672 "UpdateSubtreeOverflow",
673 "UpdatePostTransformOverflow",
674 "UpdateParentOverflow",
675 "ChildrenOnlyTransform",
676 "RecomputePosition",
677 "UpdateContainingBlock",
678 "BorderStyleNoneChange",
679 "SchedulePaint",
680 "NeutralChange",
681 "InvalidateRenderingObservers",
682 "ReflowChangesSizeOrPosition",
683 "UpdateComputedBSize",
684 "UpdateUsesOpacity",
685 "UpdateBackgroundPosition",
686 "AddOrRemoveTransform",
687 "ScrollbarChange",
688 "UpdateTableCellSpans",
689 "VisibilityChange"};
690 static_assert(nsChangeHint_AllHints ==
691 static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
692 "Name list doesn't match change hints.");
693 uint32_t hint =
694 aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
695 uint32_t rest =
696 aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
697 if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
698 result.AppendLiteral("NS_STYLE_HINT_REFLOW");
699 hint = hint & ~NS_STYLE_HINT_REFLOW;
700 any = true;
701 } else if ((hint & nsChangeHint_AllReflowHints) ==
702 nsChangeHint_AllReflowHints) {
703 result.AppendLiteral("nsChangeHint_AllReflowHints");
704 hint = hint & ~nsChangeHint_AllReflowHints;
705 any = true;
706 } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
707 result.AppendLiteral("NS_STYLE_HINT_VISUAL");
708 hint = hint & ~NS_STYLE_HINT_VISUAL;
709 any = true;
711 for (uint32_t i = 0; i < ArrayLength(names); i++) {
712 if (hint & (1u << i)) {
713 if (any) {
714 result.AppendLiteral(" | ");
716 result.AppendPrintf("nsChangeHint_%s", names[i]);
717 any = true;
720 if (rest) {
721 if (any) {
722 result.AppendLiteral(" | ");
724 result.AppendPrintf("0x%0x", rest);
725 } else {
726 if (!any) {
727 result.AppendLiteral("nsChangeHint(0)");
730 return result;
732 #endif
735 * Frame construction helpers follow.
737 #ifdef DEBUG
738 static bool gInApplyRenderingChangeToTree = false;
739 #endif
742 * Sync views on the frame and all of it's descendants (following placeholders).
743 * The change hint should be some combination of nsChangeHint_RepaintFrame,
744 * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else.
746 static void SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint);
748 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
751 * This helper function is used to find the correct SVG frame to target when we
752 * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end
753 * up handling that hint while processing hints for one of the SVG frame's
754 * ancestor frames.
756 * The reason that we sometimes end up trying to process the hint for an
757 * ancestor of the SVG frame that the hint is intended for is due to the way we
758 * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
759 * the restyled element's principle frame to one of its ancestor frames based
760 * on what nsCSSRendering::FindBackground returns, since the background style
761 * may have been propagated up to an ancestor frame. Processing hints using an
762 * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
763 * a special case since it is intended to update a specific frame.
765 static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) {
766 if (aFrame->IsViewportFrame()) {
767 // This happens if the root-<svg> is fixed positioned, in which case we
768 // can't use aFrame->GetContent() to find the primary frame, since
769 // GetContent() returns nullptr for ViewportFrame.
770 aFrame = aFrame->PrincipalChildList().FirstChild();
772 // For an nsHTMLScrollFrame, this will get the SVG frame that has the
773 // children-only transforms:
774 aFrame = aFrame->GetContent()->GetPrimaryFrame();
775 if (aFrame->IsSVGOuterSVGFrame()) {
776 aFrame = aFrame->PrincipalChildList().FirstChild();
777 MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(),
778 "Where is the SVGOuterSVGFrame's anon child??");
780 MOZ_ASSERT(aFrame->IsSVGContainerFrame(),
781 "Children-only transforms only expected on SVG frames");
782 return aFrame;
785 // This function tries to optimize a position style change by either
786 // moving aFrame or ignoring the style change when it's safe to do so.
787 // It returns true when that succeeds, otherwise it posts a reflow request
788 // and returns false.
789 static bool RecomputePosition(nsIFrame* aFrame) {
790 // It's pointless to move around frames that have never been reflowed or
791 // are dirty (i.e. they will be reflowed), or aren't affected by position
792 // styles.
793 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
794 NS_FRAME_SVG_LAYOUT)) {
795 return true;
798 // Don't process position changes on table frames, since we already handle
799 // the dynamic position change on the table wrapper frame, and the
800 // reflow-based fallback code path also ignores positions on inner table
801 // frames.
802 if (aFrame->IsTableFrame()) {
803 return true;
806 const nsStyleDisplay* display = aFrame->StyleDisplay();
807 // Changes to the offsets of a non-positioned element can safely be ignored.
808 if (display->mPosition == StylePositionProperty::Static) {
809 return true;
812 // Don't process position changes on frames which have views or the ones which
813 // have a view somewhere in their descendants, because the corresponding view
814 // needs to be repositioned properly as well.
815 if (aFrame->HasView() ||
816 aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
817 return false;
820 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
821 // If the frame has an intrinsic block-size, we resolve its 'auto' margins
822 // after doing layout, since we need to know the frame's block size. See
823 // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout().
825 // Since the size of the frame doesn't change, we could modify the below
826 // computation to compute the margin correctly without doing a full reflow,
827 // however we decided to try doing a full reflow for now.
828 if (aFrame->HasIntrinsicKeywordForBSize()) {
829 WritingMode wm = aFrame->GetWritingMode();
830 const auto* styleMargin = aFrame->StyleMargin();
831 if (styleMargin->HasBlockAxisAuto(wm)) {
832 return false;
835 // Flexbox and Grid layout supports CSS Align and the optimizations below
836 // don't support that yet.
837 nsIFrame* ph = aFrame->GetPlaceholderFrame();
838 if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
839 return false;
843 // If we need to reposition any descendant that depends on our static
844 // position, then we also can't take the optimized path.
846 // TODO(emilio): It may be worth trying to find them and try to call
847 // RecomputePosition on them too instead of disabling the optimization...
848 if (aFrame->DescendantMayDependOnItsStaticPosition()) {
849 return false;
852 aFrame->SchedulePaint();
854 auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) {
855 if (frame->IsInScrollAnchorChain()) {
856 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame);
857 frame->PresShell()->PostPendingScrollAnchorAdjustment(container);
860 // We need to trigger re-snapping to this content if we snapped to the
861 // content on the last scroll operation.
862 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
865 // For relative positioning, we can simply update the frame rect
866 if (display->IsRelativelyOrStickyPositionedStyle()) {
867 if (aFrame->IsGridItem()) {
868 // A grid item's CB is its grid area, not the parent frame content area
869 // as is assumed below.
870 return false;
872 // Move the frame
873 if (display->mPosition == StylePositionProperty::Sticky) {
874 // Update sticky positioning for an entire element at once, starting with
875 // the first continuation or ib-split sibling.
876 // It's rare that the frame we already have isn't already the first
877 // continuation or ib-split sibling, but it can happen when styles differ
878 // across continuations such as ::first-line or ::first-letter, and in
879 // those cases we will generally (but maybe not always) do the work twice.
880 nsIFrame* firstContinuation =
881 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
883 StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
884 StickyScrollContainer* ssc =
885 StickyScrollContainer::GetStickyScrollContainerForFrame(
886 firstContinuation);
887 if (ssc) {
888 ssc->PositionContinuations(firstContinuation);
890 } else {
891 MOZ_ASSERT(display->IsRelativelyPositionedStyle(),
892 "Unexpected type of positioning");
893 for (nsIFrame* cont = aFrame; cont;
894 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
895 nsIFrame* cb = cont->GetContainingBlock();
896 WritingMode wm = cb->GetWritingMode();
897 const LogicalSize cbSize = cb->ContentSize();
898 const LogicalMargin newLogicalOffsets =
899 ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize);
900 const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm);
902 // ReflowInput::ApplyRelativePositioning would work here, but
903 // since we've already checked mPosition and aren't changing the frame's
904 // normal position, go ahead and add the offsets directly.
905 // First, we need to ensure that the normal position is stored though.
906 bool hasProperty;
907 nsPoint normalPosition = cont->GetNormalPosition(&hasProperty);
908 if (!hasProperty) {
909 cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition);
911 cont->SetPosition(normalPosition +
912 nsPoint(newOffsets.left, newOffsets.top));
916 postPendingScrollAnchorOrResnap(aFrame);
917 return true;
920 // For the absolute positioning case, set up a fake HTML reflow input for
921 // the frame, and then get the offsets and size from it. If the frame's size
922 // doesn't need to change, we can simply update the frame position. Otherwise
923 // we fall back to a reflow.
924 UniquePtr<gfxContext> rc =
925 aFrame->PresShell()->CreateReferenceRenderingContext();
927 // Construct a bogus parent reflow input so that there's a usable reflow input
928 // for the containing block.
929 nsIFrame* parentFrame = aFrame->GetParent();
930 WritingMode parentWM = parentFrame->GetWritingMode();
931 WritingMode frameWM = aFrame->GetWritingMode();
932 LogicalSize parentSize = parentFrame->GetLogicalSize();
934 nsFrameState savedState = parentFrame->GetStateBits();
935 ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(),
936 parentSize);
937 parentFrame->RemoveStateBits(~nsFrameState(0));
938 parentFrame->AddStateBits(savedState);
940 // The bogus parent state here was created with no parent state of its own,
941 // and therefore it won't have an mCBReflowInput set up.
942 // But we may need one (for InitCBReflowInput in a child state), so let's
943 // try to create one here for the cases where it will be needed.
944 Maybe<ReflowInput> cbReflowInput;
945 nsIFrame* cbFrame = parentFrame->GetContainingBlock();
946 if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
947 parentFrame->IsTableFrame())) {
948 const auto cbWM = cbFrame->GetWritingMode();
949 LogicalSize cbSize = cbFrame->GetLogicalSize();
950 cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize);
951 cbReflowInput->SetComputedLogicalMargin(
952 cbWM, cbFrame->GetLogicalUsedMargin(cbWM));
953 cbReflowInput->SetComputedLogicalPadding(
954 cbWM, cbFrame->GetLogicalUsedPadding(cbWM));
955 cbReflowInput->SetComputedLogicalBorderPadding(
956 cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM));
957 parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
960 NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE &&
961 parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE,
962 "parentSize should be valid");
963 parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
964 parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
965 parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM));
967 parentReflowInput.SetComputedLogicalPadding(
968 parentWM, parentFrame->GetLogicalUsedPadding(parentWM));
969 parentReflowInput.SetComputedLogicalBorderPadding(
970 parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM));
971 LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
972 availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
974 ViewportFrame* viewport = do_QueryFrame(parentFrame);
975 nsSize cbSize =
976 viewport
977 ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
978 .Size()
979 : aFrame->GetContainingBlock()->GetSize();
980 const nsMargin& parentBorder =
981 parentReflowInput.mStyleBorder->GetComputedBorder();
982 cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
983 LogicalSize lcbSize(frameWM, cbSize);
984 ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
985 availSize, Some(lcbSize));
986 nscoord computedISize = reflowInput.ComputedISize();
987 nscoord computedBSize = reflowInput.ComputedBSize();
988 const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM);
989 computedISize += frameBP.IStartEnd(frameWM);
990 if (computedBSize != NS_UNCONSTRAINEDSIZE) {
991 computedBSize += frameBP.BStartEnd(frameWM);
993 LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM);
994 nsSize size = aFrame->GetSize();
995 // The RecomputePosition hint is not used if any offset changed between auto
996 // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new
997 // element height will be its intrinsic height, and since 'top' and 'bottom''s
998 // auto-ness hasn't changed, the old height must also be its intrinsic
999 // height, which we can assume hasn't changed (or reflow would have
1000 // been triggered).
1001 if (computedISize == logicalSize.ISize(frameWM) &&
1002 (computedBSize == NS_UNCONSTRAINEDSIZE ||
1003 computedBSize == logicalSize.BSize(frameWM))) {
1004 // If we're solving for 'left' or 'top', then compute it here, in order to
1005 // match the reflow code path.
1007 // TODO(emilio): It'd be nice if this did logical math instead, but it seems
1008 // to me the math should work out on vertical writing modes as well. See Bug
1009 // 1675861 for some hints.
1010 const nsMargin offset = reflowInput.ComputedPhysicalOffsets();
1011 const nsMargin margin = reflowInput.ComputedPhysicalMargin();
1013 nscoord left = offset.left;
1014 if (left == NS_AUTOOFFSET) {
1015 left =
1016 cbSize.width - offset.right - margin.right - size.width - margin.left;
1019 nscoord top = offset.top;
1020 if (top == NS_AUTOOFFSET) {
1021 top = cbSize.height - offset.bottom - margin.bottom - size.height -
1022 margin.top;
1025 // Move the frame
1026 nsPoint pos(parentBorder.left + left + margin.left,
1027 parentBorder.top + top + margin.top);
1028 aFrame->SetPosition(pos);
1030 postPendingScrollAnchorOrResnap(aFrame);
1031 return true;
1034 // Fall back to a reflow
1035 return false;
1039 * Return true if aFrame's subtree has placeholders for out-of-flow content
1040 * that would be affected due to the change to
1041 * `aPossiblyChangingContainingBlock` (and thus would need to get reframed).
1043 * In particular, this function returns true if there are placeholders whose OOF
1044 * frames may need to be reparented (via reframing) as a result of whatever
1045 * change actually happened.
1047 * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether
1048 * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed
1049 * pos stuff, respectively, for the _new_ style that the frame already has, not
1050 * the old one.
1052 static bool ContainingBlockChangeAffectsDescendants(
1053 nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame,
1054 bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) {
1055 // All fixed-pos containing blocks should also be abs-pos containing blocks.
1056 MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock);
1058 for (const auto& childList : aFrame->ChildLists()) {
1059 for (nsIFrame* f : childList.mList) {
1060 if (f->IsPlaceholderFrame()) {
1061 nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
1062 // If SVG text frames could appear here, they could confuse us since
1063 // they ignore their position style ... but they can't.
1064 NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(),
1065 "SVG text frames can't be out of flow");
1066 // Top-layer frames don't change containing block based on direct
1067 // ancestors.
1068 auto* display = outOfFlow->StyleDisplay();
1069 if (display->IsAbsolutelyPositionedStyle() &&
1070 display->mTopLayer == StyleTopLayer::None) {
1071 const bool isContainingBlock =
1072 aIsFixedPosContainingBlock ||
1073 (aIsAbsPosContainingBlock &&
1074 display->mPosition == StylePositionProperty::Absolute);
1075 // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be
1076 // a first continuation, see the assertion in the caller.
1077 nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation();
1078 if (isContainingBlock) {
1079 // If we are becoming a containing block, we only need to reframe if
1080 // this oof's current containing block is an ancestor of the new
1081 // frame.
1082 if (parent != aPossiblyChangingContainingBlock &&
1083 nsLayoutUtils::IsProperAncestorFrame(
1084 parent, aPossiblyChangingContainingBlock)) {
1085 return true;
1087 } else {
1088 // If we are not a containing block anymore, we only need to reframe
1089 // if we are the current containing block of the oof frame.
1090 if (parent == aPossiblyChangingContainingBlock) {
1091 return true;
1096 // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or
1097 // f->IsFixedPosContainingBlock() here. However, that would only
1098 // be testing the *new* style of the frame, which might exclude
1099 // descendants that currently have this frame as an abs-pos
1100 // containing block. Taking the codepath where we don't reframe
1101 // could lead to an unsafe call to
1102 // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
1103 // the descendant and taken it off the absolute list.
1104 if (ContainingBlockChangeAffectsDescendants(
1105 aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock,
1106 aIsFixedPosContainingBlock)) {
1107 return true;
1111 return false;
1114 // Returns the frame that would serve as the containing block for aFrame's
1115 // positioned descendants, if aFrame had styles to make it a CB for such
1116 // descendants. (Typically this is just aFrame itself, or its insertion frame).
1118 // Returns nullptr if this frame can't be easily determined.
1119 static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) {
1120 if (aFrame->IsFieldSetFrame()) {
1121 // FIXME: This should be easily implementable.
1122 return nullptr;
1124 nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame();
1125 if (insertionFrame == aFrame) {
1126 return insertionFrame;
1128 // Generally frames with a different insertion frame are hard to deal with,
1129 // but scrollframes are easy because the containing block is just the
1130 // insertion frame.
1131 if (aFrame->IsScrollFrame()) {
1132 return insertionFrame;
1134 // Combobox frames are easy as well because they can't have positioned
1135 // children anyways.
1136 // Button and table cell frames are also easy because the containing block is
1137 // the frame itself.
1138 if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() ||
1139 aFrame->IsTableCellFrame()) {
1140 return aFrame;
1142 return nullptr;
1145 static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame,
1146 nsIFrame* aMaybeChangingCB) {
1147 // NOTE: This looks at the new style.
1148 const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock();
1149 MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock());
1151 const bool isAbsPosContainingBlock =
1152 isFixedContainingBlock || aFrame->IsAbsPosContainingBlock();
1154 for (nsIFrame* f = aFrame; f;
1155 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
1156 if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f,
1157 isAbsPosContainingBlock,
1158 isFixedContainingBlock)) {
1159 return true;
1162 return false;
1165 static void DoApplyRenderingChangeToTree(nsIFrame* aFrame,
1166 nsChangeHint aChange) {
1167 MOZ_ASSERT(gInApplyRenderingChangeToTree,
1168 "should only be called within ApplyRenderingChangeToTree");
1170 for (; aFrame;
1171 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
1172 // Invalidate and sync views on all descendant frames, following
1173 // placeholders. We don't need to update transforms in
1174 // SyncViewsAndInvalidateDescendants, because there can't be any
1175 // out-of-flows or popups that need to be transformed; all out-of-flow
1176 // descendants of the transformed element must also be descendants of the
1177 // transformed frame.
1178 SyncViewsAndInvalidateDescendants(
1179 aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
1180 nsChangeHint_UpdateOpacityLayer |
1181 nsChangeHint_SchedulePaint)));
1182 // This must be set to true if the rendering change needs to
1183 // invalidate content. If it's false, a composite-only paint
1184 // (empty transaction) will be scheduled.
1185 bool needInvalidatingPaint = false;
1187 // if frame has view, will already be invalidated
1188 if (aChange & nsChangeHint_RepaintFrame) {
1189 // Note that this whole block will be skipped when painting is suppressed
1190 // (due to our caller ApplyRendingChangeToTree() discarding the
1191 // nsChangeHint_RepaintFrame hint). If you add handling for any other
1192 // hints within this block, be sure that they too should be ignored when
1193 // painting is suppressed.
1194 needInvalidatingPaint = true;
1195 aFrame->InvalidateFrameSubtree();
1196 if ((aChange & nsChangeHint_UpdateEffects) &&
1197 aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1198 // Need to update our overflow rects:
1199 SVGUtils::ScheduleReflowSVG(aFrame);
1202 ActiveLayerTracker::NotifyNeedsRepaint(aFrame);
1204 if (aChange & nsChangeHint_UpdateOpacityLayer) {
1205 // FIXME/bug 796697: we can get away with empty transactions for
1206 // opacity updates in many cases.
1207 needInvalidatingPaint = true;
1209 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
1210 if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
1211 // SVG effects paints the opacity without using
1212 // nsDisplayOpacity. We need to invalidate manually.
1213 aFrame->InvalidateFrameSubtree();
1216 if ((aChange & nsChangeHint_UpdateTransformLayer) &&
1217 aFrame->IsTransformed()) {
1218 // Note: All the transform-like properties should map to the same
1219 // layer activity index, so does the restyle count. Therefore, using
1220 // eCSSProperty_transform should be fine.
1221 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
1222 needInvalidatingPaint = true;
1224 if (aChange & nsChangeHint_ChildrenOnlyTransform) {
1225 needInvalidatingPaint = true;
1226 nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
1227 ->PrincipalChildList()
1228 .FirstChild();
1229 for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1230 // Note: All the transform-like properties should map to the same
1231 // layer activity index, so does the restyle count. Therefore, using
1232 // eCSSProperty_transform should be fine.
1233 ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
1236 if (aChange & nsChangeHint_SchedulePaint) {
1237 needInvalidatingPaint = true;
1239 aFrame->SchedulePaint(needInvalidatingPaint
1240 ? nsIFrame::PAINT_DEFAULT
1241 : nsIFrame::PAINT_COMPOSITE_ONLY);
1245 static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
1246 nsChangeHint aChange) {
1247 MOZ_ASSERT(gInApplyRenderingChangeToTree,
1248 "should only be called within ApplyRenderingChangeToTree");
1250 NS_ASSERTION(nsChangeHint_size_t(aChange) ==
1251 (aChange & (nsChangeHint_RepaintFrame |
1252 nsChangeHint_UpdateOpacityLayer |
1253 nsChangeHint_SchedulePaint)),
1254 "Invalid change flag");
1256 aFrame->SyncFrameViewProperties();
1258 for (const auto& [list, listID] : aFrame->ChildLists()) {
1259 for (nsIFrame* child : list) {
1260 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
1261 // only do frames that don't have placeholders
1262 if (child->IsPlaceholderFrame()) {
1263 // do the out-of-flow frame and its continuations
1264 nsIFrame* outOfFlowFrame =
1265 nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
1266 DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
1267 } else if (listID == FrameChildListID::Popup) {
1268 DoApplyRenderingChangeToTree(child, aChange);
1269 } else { // regular frame
1270 SyncViewsAndInvalidateDescendants(child, aChange);
1277 static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
1278 nsChangeHint aChange) {
1279 // We check StyleDisplay()->HasTransformStyle() in addition to checking
1280 // IsTransformed() since we can get here for some frames that don't support
1281 // CSS transforms, and table frames, which are their own odd-ball, since the
1282 // transform is handled by their wrapper, which _also_ gets a separate hint.
1283 NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
1284 aFrame->IsTransformed() ||
1285 aFrame->StyleDisplay()->HasTransformStyle(),
1286 "Unexpected UpdateTransformLayer hint");
1288 if (aPresShell->IsPaintingSuppressed()) {
1289 // Don't allow synchronous rendering changes when painting is turned off.
1290 aChange &= ~nsChangeHint_RepaintFrame;
1291 if (!aChange) {
1292 return;
1296 // Trigger rendering updates by damaging this frame and any
1297 // continuations of this frame.
1298 #ifdef DEBUG
1299 gInApplyRenderingChangeToTree = true;
1300 #endif
1301 if (aChange & nsChangeHint_RepaintFrame) {
1302 // If the frame is the primary frame of either the body element or
1303 // the html element, we propagate the repaint change hint to the
1304 // viewport. This is necessary for background and scrollbar colors
1305 // propagation.
1306 if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
1307 nsIFrame* rootFrame = aPresShell->GetRootFrame();
1308 MOZ_ASSERT(rootFrame, "No root frame?");
1309 DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
1310 aChange &= ~nsChangeHint_RepaintFrame;
1311 if (!aChange) {
1312 return;
1316 DoApplyRenderingChangeToTree(aFrame, aChange);
1317 #ifdef DEBUG
1318 gInApplyRenderingChangeToTree = false;
1319 #endif
1322 static void AddSubtreeToOverflowTracker(
1323 nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) {
1324 if (aFrame->FrameMaintainsOverflow()) {
1325 aOverflowChangedTracker.AddFrame(aFrame,
1326 OverflowChangedTracker::CHILDREN_CHANGED);
1328 for (const auto& childList : aFrame->ChildLists()) {
1329 for (nsIFrame* child : childList.mList) {
1330 AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
1335 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) {
1336 IntrinsicDirty dirtyType;
1337 if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
1338 NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
1339 "Please read the comments in nsChangeHint.h");
1340 NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
1341 "ClearDescendantIntrinsics requires NeedDirtyReflow");
1342 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
1343 } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
1344 aFrame->HasAnyStateBits(
1345 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
1346 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
1347 } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
1348 dirtyType = IntrinsicDirty::FrameAndAncestors;
1349 } else {
1350 dirtyType = IntrinsicDirty::None;
1353 if (aHint & nsChangeHint_UpdateComputedBSize) {
1354 aFrame->SetHasBSizeChange(true);
1357 nsFrameState dirtyBits;
1358 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
1359 dirtyBits = nsFrameState(0);
1360 } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
1361 dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) {
1362 dirtyBits = NS_FRAME_IS_DIRTY;
1363 } else {
1364 dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
1367 // If we're not going to clear any intrinsic sizes on the frames, and
1368 // there are no dirty bits to set, then there's nothing to do.
1369 if (dirtyType == IntrinsicDirty::None && !dirtyBits) return;
1371 ReflowRootHandling rootHandling;
1372 if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
1373 rootHandling = ReflowRootHandling::PositionOrSizeChange;
1374 } else {
1375 rootHandling = ReflowRootHandling::NoPositionOrSizeChange;
1378 do {
1379 aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
1380 rootHandling);
1381 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
1382 } while (aFrame);
1385 // Get the next sibling which might have a frame. This only considers siblings
1386 // that stylo post-traversal looks at, so only elements and text. In
1387 // particular, it ignores comments.
1388 static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) {
1389 for (nsIContent* next = aContent->GetNextSibling(); next;
1390 next = next->GetNextSibling()) {
1391 if (next->IsElement() || next->IsText()) {
1392 return next;
1396 return nullptr;
1399 // If |aFrame| is dirty or has dirty children, then we can skip updating
1400 // overflows since that will happen when it's reflowed.
1401 static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) {
1402 return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY |
1403 NS_FRAME_HAS_DIRTY_CHILDREN);
1406 static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint,
1407 nsIContent* aContent,
1408 nsIFrame* aFrame,
1409 nsPresContext* aPc) {
1410 if (!(aHint & nsChangeHint_ScrollbarChange)) {
1411 return;
1413 aHint &= ~nsChangeHint_ScrollbarChange;
1414 if (aHint & nsChangeHint_ReconstructFrame) {
1415 return;
1418 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
1420 const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent();
1422 // Only bother with this if we're the root or the body element, since:
1423 // (a) It'd be *expensive* to reframe these particular nodes. They're
1424 // at the root, so reframing would mean rebuilding the world.
1425 // (b) It's often *unnecessary* to reframe for "overflow" changes on
1426 // these particular nodes. In general, the only reason we reframe
1427 // for "overflow" changes is so we can construct (or destroy) a
1428 // scrollframe & scrollbars -- and the html/body nodes often don't
1429 // need their own scrollframe/scrollbars because they coopt the ones
1430 // on the viewport (which always exist). So depending on whether
1431 // that's happening, we can skip the reframe for these nodes.
1432 if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) {
1433 // If the restyled element provided/provides the scrollbar styles for
1434 // the viewport before and/or after this restyle, AND it's not coopting
1435 // that responsibility from some other element (which would need
1436 // reconstruction to make its own scrollframe now), THEN: we don't need
1437 // to reconstruct - we can just reflow, because no scrollframe is being
1438 // added/removed.
1439 Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement();
1440 Element* newOverride = aPc->UpdateViewportScrollStylesOverride();
1442 const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) {
1443 if (aOverride) {
1444 return aOverride == aContent;
1446 return isRoot;
1449 if (ProvidesScrollbarStyles(prevOverride) ||
1450 ProvidesScrollbarStyles(newOverride)) {
1451 // If we get here, the restyled element provided the scrollbar styles
1452 // for viewport before this restyle, OR it will provide them after.
1453 if (!prevOverride || !newOverride || prevOverride == newOverride) {
1454 // If we get here, the restyled element is NOT replacing (or being
1455 // replaced by) some other element as the viewport's
1456 // scrollbar-styles provider. (If it were, we'd potentially need to
1457 // reframe to create a dedicated scrollframe for whichever element
1458 // is being booted from providing viewport scrollbar styles.)
1460 // Under these conditions, we're OK to assume that this "overflow"
1461 // change only impacts the root viewport's scrollframe, which
1462 // already exists, so we can simply reflow instead of reframing.
1463 if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
1464 sf->MarkScrollbarsDirtyForReflow();
1465 } else if (nsIScrollableFrame* sf =
1466 aPc->PresShell()->GetRootScrollFrameAsScrollable()) {
1467 sf->MarkScrollbarsDirtyForReflow();
1469 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1470 } else {
1471 // If we changed the override element, we need to reconstruct as the old
1472 // override element might start / stop being scrollable.
1473 aHint |= nsChangeHint_ReconstructFrame;
1475 return;
1479 const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow();
1480 if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
1481 if (scrollable && sf->HasAllNeededScrollbars()) {
1482 sf->MarkScrollbarsDirtyForReflow();
1483 // Once we've created scrollbars for a frame, don't bother reconstructing
1484 // it just to remove them if we still need a scroll frame.
1485 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1486 return;
1488 } else if (aFrame->IsTextInputFrame()) {
1489 // input / textarea for the most part don't honor overflow themselves, the
1490 // editor root will deal with the change if needed.
1491 // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(),
1492 // so we need to reflow the textarea itself, not just the inner control.
1493 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1494 return;
1495 } else if (!scrollable) {
1496 // Something changed, but we don't have nor will have a scroll frame,
1497 // there's nothing to do here.
1498 return;
1501 // Oh well, we couldn't optimize it out, just reconstruct frames for the
1502 // subtree.
1503 aHint |= nsChangeHint_ReconstructFrame;
1506 static void TryToHandleContainingBlockChange(nsChangeHint& aHint,
1507 nsIFrame* aFrame) {
1508 if (!(aHint & nsChangeHint_UpdateContainingBlock)) {
1509 return;
1511 if (aHint & nsChangeHint_ReconstructFrame) {
1512 return;
1514 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
1515 nsIFrame* containingBlock = ContainingBlockForFrame(aFrame);
1516 if (!containingBlock ||
1517 NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) {
1518 // The frame has positioned children that need to be reparented, or it can't
1519 // easily be converted to/from being an abs-pos container correctly.
1520 aHint |= nsChangeHint_ReconstructFrame;
1521 return;
1523 const bool isCb = aFrame->IsAbsPosContainingBlock();
1525 // The absolute container should be containingBlock.
1526 for (nsIFrame* cont = containingBlock; cont;
1527 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1528 // Normally frame construction would set state bits as needed,
1529 // but we're not going to reconstruct the frame so we need to set
1530 // them. It's because we need to set this state on each affected frame
1531 // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
1532 // to ancestors (i.e. it can't be an change hint that is handled for
1533 // descendants).
1534 if (isCb) {
1535 if (!cont->IsAbsoluteContainer() &&
1536 cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
1537 cont->MarkAsAbsoluteContainingBlock();
1539 } else if (cont->IsAbsoluteContainer()) {
1540 if (cont->HasAbsolutelyPositionedChildren()) {
1541 // If |cont| still has absolutely positioned children,
1542 // we can't call MarkAsNotAbsoluteContainingBlock. This
1543 // will remove a frame list that still has children in
1544 // it that we need to keep track of.
1545 // The optimization of removing it isn't particularly
1546 // important, although it does mean we skip some tests.
1547 NS_WARNING("skipping removal of absolute containing block");
1548 } else {
1549 cont->MarkAsNotAbsoluteContainingBlock();
1555 void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
1556 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1557 "Someone forgot a script blocker");
1559 // See bug 1378219 comment 9:
1560 // Recursive calls here are a bit worrying, but apparently do happen in the
1561 // wild (although not currently in any of our automated tests). Try to get a
1562 // stack from Nightly/Dev channel to figure out what's going on and whether
1563 // it's OK.
1564 MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
1566 if (aChangeList.IsEmpty()) {
1567 return;
1570 // If mDestroyedFrames is null, we want to create a new hashtable here
1571 // and destroy it on exit; but if it is already non-null (because we're in
1572 // a recursive call), we will continue to use the existing table to
1573 // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit.
1574 // We use a MaybeClearDestroyedFrames helper to conditionally reset the
1575 // mDestroyedFrames pointer when this method returns.
1576 typedef decltype(mDestroyedFrames) DestroyedFramesT;
1577 class MOZ_RAII MaybeClearDestroyedFrames {
1578 private:
1579 DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames
1580 const bool mResetOnDestruction;
1582 public:
1583 explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
1584 : mDestroyedFramesRef(aTarget),
1585 mResetOnDestruction(!aTarget) // reset only if target starts out null
1587 ~MaybeClearDestroyedFrames() {
1588 if (mResetOnDestruction) {
1589 mDestroyedFramesRef.reset(nullptr);
1594 MaybeClearDestroyedFrames maybeClear(mDestroyedFrames);
1595 if (!mDestroyedFrames) {
1596 mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>();
1599 AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT);
1601 nsPresContext* presContext = PresContext();
1602 nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
1604 bool didUpdateCursor = false;
1606 for (size_t i = 0; i < aChangeList.Length(); ++i) {
1607 // Collect and coalesce adjacent siblings for lazy frame construction.
1608 // Eventually it would be even better to make RecreateFramesForContent
1609 // accept a range and coalesce all adjacent reconstructs (bug 1344139).
1610 size_t lazyRangeStart = i;
1611 while (i < aChangeList.Length() && aChangeList[i].mContent &&
1612 aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
1613 (i == lazyRangeStart ||
1614 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) ==
1615 aChangeList[i].mContent)) {
1616 MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
1617 MOZ_ASSERT(!aChangeList[i].mFrame);
1618 ++i;
1620 if (i != lazyRangeStart) {
1621 nsIContent* start = aChangeList[lazyRangeStart].mContent;
1622 nsIContent* end =
1623 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent);
1624 if (!end) {
1625 frameConstructor->ContentAppended(
1626 start, nsCSSFrameConstructor::InsertionKind::Sync);
1627 } else {
1628 frameConstructor->ContentRangeInserted(
1629 start, end, nsCSSFrameConstructor::InsertionKind::Sync);
1632 for (size_t j = lazyRangeStart; j < i; ++j) {
1633 MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() ||
1634 !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
1636 if (i == aChangeList.Length()) {
1637 break;
1640 const nsStyleChangeData& data = aChangeList[i];
1641 nsIFrame* frame = data.mFrame;
1642 nsIContent* content = data.mContent;
1643 nsChangeHint hint = data.mHint;
1644 bool didReflowThisFrame = false;
1646 NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
1647 (hint & nsChangeHint_NeedReflow),
1648 "Reflow hint bits set without actually asking for a reflow");
1650 // skip any frame that has been destroyed due to a ripple effect
1651 if (frame && mDestroyedFrames->Contains(frame)) {
1652 continue;
1655 if (frame && frame->GetContent() != content) {
1656 // XXXbz this is due to image maps messing with the primary frame of
1657 // <area>s. See bug 135040. Remove this block once that's fixed.
1658 frame = nullptr;
1659 if (!(hint & nsChangeHint_ReconstructFrame)) {
1660 continue;
1664 TryToDealWithScrollbarChange(hint, content, frame, presContext);
1665 TryToHandleContainingBlockChange(hint, frame);
1667 if (hint & nsChangeHint_ReconstructFrame) {
1668 // If we ever start passing true here, be careful of restyles
1669 // that involve a reframe and animations. In particular, if the
1670 // restyle we're processing here is an animation restyle, but
1671 // the style resolution we will do for the frame construction
1672 // happens async when we're not in an animation restyle already,
1673 // problems could arise.
1674 // We could also have problems with triggering of CSS transitions
1675 // on elements whose frames are reconstructed, since we depend on
1676 // the reconstruction happening synchronously.
1677 frameConstructor->RecreateFramesForContent(
1678 content, nsCSSFrameConstructor::InsertionKind::Sync);
1679 continue;
1682 MOZ_ASSERT(frame, "This shouldn't happen");
1683 if (hint & nsChangeHint_AddOrRemoveTransform) {
1684 for (nsIFrame* cont = frame; cont;
1685 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1686 if (cont->StyleDisplay()->HasTransform(cont)) {
1687 cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
1689 // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
1690 // transformed by other means. It's OK to have the bit even if it's
1691 // not needed.
1693 // When dropping a running transform animation we will first add an
1694 // nsChangeHint_UpdateTransformLayer hint as part of the animation-only
1695 // restyle. During the subsequent regular restyle, if the animation was
1696 // the only reason the element had any transform applied, we will add
1697 // nsChangeHint_AddOrRemoveTransform as part of the regular restyle.
1699 // With the Gecko backend, these two change hints are processed
1700 // after each restyle but when using the Servo backend they accumulate
1701 // and are processed together after we have already removed the
1702 // transform as part of the regular restyle. Since we don't actually
1703 // need the nsChangeHint_UpdateTransformLayer hint if we already have
1704 // a nsChangeHint_AddOrRemoveTransform hint, and since we
1705 // will fail an assertion in ApplyRenderingChangeToTree if we try
1706 // specify nsChangeHint_UpdateTransformLayer but don't have any
1707 // transform style, we just drop the unneeded hint here.
1708 hint &= ~nsChangeHint_UpdateTransformLayer;
1711 if (!frame->FrameMaintainsOverflow()) {
1712 // frame does not maintain overflow rects, so avoid calling
1713 // FinishAndStoreOverflow on it:
1714 hint &=
1715 ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
1716 nsChangeHint_UpdatePostTransformOverflow |
1717 nsChangeHint_UpdateParentOverflow |
1718 nsChangeHint_UpdateSubtreeOverflow);
1721 if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
1722 // Frame can not be transformed, and thus a change in transform will
1723 // have no effect and we should not use either
1724 // nsChangeHint_UpdatePostTransformOverflow or
1725 // nsChangeHint_UpdateTransformLayerhint.
1726 hint &= ~(nsChangeHint_UpdatePostTransformOverflow |
1727 nsChangeHint_UpdateTransformLayer);
1730 if ((hint & nsChangeHint_UpdateEffects) &&
1731 frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
1732 SVGObserverUtils::UpdateEffects(frame);
1734 if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
1735 ((hint & nsChangeHint_UpdateOpacityLayer) &&
1736 frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) {
1737 SVGObserverUtils::InvalidateRenderingObservers(frame);
1738 frame->SchedulePaint();
1740 if (hint & nsChangeHint_NeedReflow) {
1741 StyleChangeReflow(frame, hint);
1742 didReflowThisFrame = true;
1745 // Here we need to propagate repaint frame change hint instead of update
1746 // opacity layer change hint when we do opacity optimization for SVG.
1747 // We can't do it in nsStyleEffects::CalcDifference() just like we do
1748 // for the optimization for 0.99 over opacity values since we have no way
1749 // to call SVGUtils::CanOptimizeOpacity() there.
1750 if ((hint & nsChangeHint_UpdateOpacityLayer) &&
1751 SVGUtils::CanOptimizeOpacity(frame)) {
1752 hint &= ~nsChangeHint_UpdateOpacityLayer;
1753 hint |= nsChangeHint_RepaintFrame;
1756 if ((hint & nsChangeHint_UpdateUsesOpacity) && frame->IsTablePart()) {
1757 NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
1758 "should only return UpdateUsesOpacity hint "
1759 "when also returning UpdateOpacityLayer hint");
1760 // When an internal table part (including cells) changes between
1761 // having opacity 1 and non-1, it changes whether its
1762 // backgrounds (and those of table parts inside of it) are
1763 // painted as part of the table's nsDisplayTableBorderBackground
1764 // display item, or part of its own display item. That requires
1765 // invalidation, so change UpdateOpacityLayer to RepaintFrame.
1766 hint &= ~nsChangeHint_UpdateOpacityLayer;
1767 hint |= nsChangeHint_RepaintFrame;
1770 // Opacity disables preserve-3d, so if we toggle it, then we also need
1771 // to update the overflow areas of all potentially affected frames.
1772 if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1773 frame->StyleDisplay()->mTransformStyle ==
1774 StyleTransformStyle::Preserve3d) {
1775 hint |= nsChangeHint_UpdateSubtreeOverflow;
1778 if (hint & nsChangeHint_UpdateBackgroundPosition) {
1779 // For most frame types, DLBI can detect background position changes,
1780 // so we only need to schedule a paint.
1781 hint |= nsChangeHint_SchedulePaint;
1782 if (frame->IsTablePart() || frame->IsMathMLFrame()) {
1783 // Table parts and MathML frames don't build display items for their
1784 // backgrounds, so DLBI can't detect background-position changes for
1785 // these frames. Repaint the whole frame.
1786 hint |= nsChangeHint_RepaintFrame;
1790 if (hint &
1791 (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer |
1792 nsChangeHint_UpdateTransformLayer |
1793 nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
1794 ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
1797 if (hint & (nsChangeHint_UpdateTransformLayer |
1798 nsChangeHint_AddOrRemoveTransform)) {
1799 // We need to trigger re-snapping to this content if we snapped to the
1800 // content on the last scroll operation.
1801 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
1804 if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
1805 // It is possible for this to fall back to a reflow
1806 if (!RecomputePosition(frame)) {
1807 StyleChangeReflow(frame, nsChangeHint_NeedReflow |
1808 nsChangeHint_ReflowChangesSizeOrPosition);
1809 didReflowThisFrame = true;
1812 NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
1813 (hint & nsChangeHint_UpdateOverflow),
1814 "nsChangeHint_UpdateOverflow should be passed too");
1815 if (!didReflowThisFrame &&
1816 (hint & (nsChangeHint_UpdateOverflow |
1817 nsChangeHint_UpdatePostTransformOverflow |
1818 nsChangeHint_UpdateParentOverflow |
1819 nsChangeHint_UpdateSubtreeOverflow))) {
1820 if (hint & nsChangeHint_UpdateSubtreeOverflow) {
1821 for (nsIFrame* cont = frame; cont;
1822 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1823 AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
1825 // The work we just did in AddSubtreeToOverflowTracker
1826 // subsumes some of the other hints:
1827 hint &= ~(nsChangeHint_UpdateOverflow |
1828 nsChangeHint_UpdatePostTransformOverflow);
1830 if (hint & nsChangeHint_ChildrenOnlyTransform) {
1831 // We need to update overflows. The correct frame(s) to update depends
1832 // on whether the ChangeHint came from an outer or an inner svg.
1833 nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
1834 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
1835 "SVG frames should not have continuations "
1836 "or ib-split siblings");
1837 NS_ASSERTION(
1838 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
1839 "SVG frames should not have continuations "
1840 "or ib-split siblings");
1841 if (hintFrame->IsSVGOuterSVGAnonChildFrame()) {
1842 // The children only transform of an outer svg frame is applied to
1843 // the outer svg's anonymous child frame (instead of to the
1844 // anonymous child's children).
1846 if (!CanSkipOverflowUpdates(hintFrame)) {
1847 mOverflowChangedTracker.AddFrame(
1848 hintFrame, OverflowChangedTracker::CHILDREN_CHANGED);
1850 } else {
1851 // The children only transform is applied to the child frames of an
1852 // inner svg frame, so update the child overflows.
1853 nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
1854 for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1855 MOZ_ASSERT(childFrame->IsSVGFrame(),
1856 "Not expecting non-SVG children");
1857 if (!CanSkipOverflowUpdates(childFrame)) {
1858 mOverflowChangedTracker.AddFrame(
1859 childFrame, OverflowChangedTracker::CHILDREN_CHANGED);
1861 NS_ASSERTION(
1862 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
1863 "SVG frames should not have continuations "
1864 "or ib-split siblings");
1865 NS_ASSERTION(
1866 childFrame->GetParent() == hintFrame,
1867 "SVG child frame not expected to have different parent");
1871 if (!CanSkipOverflowUpdates(frame)) {
1872 if (hint & (nsChangeHint_UpdateOverflow |
1873 nsChangeHint_UpdatePostTransformOverflow)) {
1874 OverflowChangedTracker::ChangeKind changeKind;
1875 // If we have both nsChangeHint_UpdateOverflow and
1876 // nsChangeHint_UpdatePostTransformOverflow,
1877 // CHILDREN_CHANGED is selected as it is
1878 // strictly stronger.
1879 if (hint & nsChangeHint_UpdateOverflow) {
1880 changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
1881 } else {
1882 changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
1884 for (nsIFrame* cont = frame; cont;
1885 cont =
1886 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1887 mOverflowChangedTracker.AddFrame(cont, changeKind);
1890 // UpdateParentOverflow hints need to be processed in addition
1891 // to the above, since if the processing of the above hints
1892 // yields no change, the update will not propagate to the
1893 // parent.
1894 if (hint & nsChangeHint_UpdateParentOverflow) {
1895 MOZ_ASSERT(frame->GetParent(),
1896 "shouldn't get style hints for the root frame");
1897 for (nsIFrame* cont = frame; cont;
1898 cont =
1899 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1900 mOverflowChangedTracker.AddFrame(
1901 cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED);
1906 if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
1907 presContext->PresShell()->SynthesizeMouseMove(false);
1908 didUpdateCursor = true;
1910 if (hint & nsChangeHint_UpdateTableCellSpans) {
1911 frameConstructor->UpdateTableCellSpans(content);
1913 if (hint & nsChangeHint_VisibilityChange) {
1914 frame->UpdateVisibleDescendantsState();
1918 aChangeList.Clear();
1919 FlushOverflowChangedTracker();
1922 /* static */
1923 uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) {
1924 EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame);
1925 return effectSet ? effectSet->GetAnimationGeneration() : 0;
1928 void RestyleManager::IncrementAnimationGeneration() {
1929 // We update the animation generation at start of each call to
1930 // ProcessPendingRestyles so we should ignore any subsequent (redundant)
1931 // calls that occur while we are still processing restyles.
1932 if (!mInStyleRefresh) {
1933 ++mAnimationGeneration;
1937 /* static */
1938 void RestyleManager::AddLayerChangesForAnimation(
1939 nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement,
1940 nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) {
1941 MOZ_ASSERT(aElement);
1942 MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame);
1943 if (!aStyleFrame) {
1944 return;
1947 uint64_t frameGeneration =
1948 RestyleManager::GetAnimationGenerationForFrame(aStyleFrame);
1950 Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties;
1952 nsChangeHint hint = nsChangeHint(0);
1953 auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration,
1954 DisplayItemType aDisplayItemType) -> bool {
1955 if (aGeneration && frameGeneration != *aGeneration) {
1956 // If we have a transform layer but don't have any transform style, we
1957 // probably just removed the transform but haven't destroyed the layer
1958 // yet. In this case we will typically add the appropriate change hint
1959 // (nsChangeHint_UpdateContainingBlock) when we compare styles so in
1960 // theory we could skip adding any change hint here.
1962 // However, sometimes when we compare styles we'll get no change. For
1963 // example, if the transform style was 'none' when we sent the transform
1964 // animation to the compositor and the current transform style is now
1965 // 'none' we'll think nothing changed but actually we still need to
1966 // trigger an update to clear whatever style the transform animation set
1967 // on the compositor. To handle this case we simply set all the change
1968 // hints relevant to removing transform style (since we don't know exactly
1969 // what changes happened while the animation was running on the
1970 // compositor).
1972 // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we
1973 // did, ApplyRenderingChangeToTree would complain that we're updating a
1974 // transform layer without a transform.
1975 if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM &&
1976 !aStyleFrame->StyleDisplay()->HasTransformStyle()) {
1977 // Add all the hints for a removing a transform if they are not already
1978 // set for this frame.
1979 if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform,
1980 aHintForThisFrame))) {
1981 hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
1983 return true;
1985 hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
1988 // We consider it's the first paint for the frame if we have an animation
1989 // for the property but have no layer, for the case of WebRender, no
1990 // corresponding animation info.
1991 // Note that in case of animations which has properties preventing running
1992 // on the compositor, e.g., width or height, corresponding layer is not
1993 // created at all, but even in such cases, we normally set valid change
1994 // hint for such animations in each tick, i.e. restyles in each tick. As
1995 // a result, we usually do restyles for such animations in every tick on
1996 // the main-thread. The only animations which will be affected by this
1997 // explicit change hint are animations that have opacity/transform but did
1998 // not have those properies just before. e.g, setting transform by
1999 // setKeyframes or changing target element from other target which prevents
2000 // running on the compositor, etc.
2001 if (!aGeneration) {
2002 nsChangeHint hintForDisplayItem =
2003 LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
2004 // We don't need to apply the corresponding change hint if we already have
2005 // it.
2006 if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) {
2007 return true;
2010 if (!effectiveAnimationProperties) {
2011 effectiveAnimationProperties.emplace(
2012 nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame));
2014 const nsCSSPropertyIDSet& propertiesForDisplayItem =
2015 LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType);
2016 if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) {
2017 hint |= hintForDisplayItem;
2020 return true;
2023 AnimationInfo::EnumerateGenerationOnFrame(
2024 aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes,
2025 maybeApplyChangeHint);
2027 if (hint) {
2028 // We apply the hint to the primary frame, not the style frame. Transform
2029 // and opacity hints apply to the table wrapper box, not the table box.
2030 aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint);
2034 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
2035 RestyleManager* aRestyleManager)
2036 : mRestyleManager(aRestyleManager),
2037 mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) {
2038 MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
2039 "shouldn't construct recursively");
2040 mRestyleManager->mAnimationsWithDestroyedFrame = this;
2043 void RestyleManager::AnimationsWithDestroyedFrame ::
2044 StopAnimationsForElementsWithoutFrames() {
2045 StopAnimationsWithoutFrame(mContents, PseudoStyleType::NotPseudo);
2046 StopAnimationsWithoutFrame(mBeforeContents, PseudoStyleType::before);
2047 StopAnimationsWithoutFrame(mAfterContents, PseudoStyleType::after);
2048 StopAnimationsWithoutFrame(mMarkerContents, PseudoStyleType::marker);
2051 void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
2052 nsTArray<RefPtr<nsIContent>>& aArray, PseudoStyleType aPseudoType) {
2053 nsAnimationManager* animationManager =
2054 mRestyleManager->PresContext()->AnimationManager();
2055 nsTransitionManager* transitionManager =
2056 mRestyleManager->PresContext()->TransitionManager();
2057 for (nsIContent* content : aArray) {
2058 if (aPseudoType == PseudoStyleType::NotPseudo) {
2059 if (content->GetPrimaryFrame()) {
2060 continue;
2062 } else if (aPseudoType == PseudoStyleType::before) {
2063 if (nsLayoutUtils::GetBeforeFrame(content)) {
2064 continue;
2066 } else if (aPseudoType == PseudoStyleType::after) {
2067 if (nsLayoutUtils::GetAfterFrame(content)) {
2068 continue;
2070 } else if (aPseudoType == PseudoStyleType::marker) {
2071 if (nsLayoutUtils::GetMarkerFrame(content)) {
2072 continue;
2075 dom::Element* element = content->AsElement();
2077 animationManager->StopAnimationsForElement(element, aPseudoType);
2078 transitionManager->StopAnimationsForElement(element, aPseudoType);
2080 // All other animations should keep running but not running on the
2081 // *compositor* at this point.
2082 if (EffectSet* effectSet = EffectSet::Get(element, aPseudoType)) {
2083 for (KeyframeEffect* effect : *effectSet) {
2084 effect->ResetIsRunningOnCompositor();
2090 // When using handled hints by an ancestor, we need to make sure that our
2091 // ancestor in the DOM tree is actually our ancestor in the flat tree.
2092 // Otherwise, we can't guarantee that e.g. a repaint from an ancestor in the DOM
2093 // will really end up repainting us.
2094 static bool CanUseHandledHintsFromAncestors(const nsIFrame* aFrame) {
2095 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
2096 // An out of flow can be parented in other part of the tree.
2097 return false;
2099 if (aFrame->IsColumnSpanInMulticolSubtree()) {
2100 // Any column-spanner's parent frame is not its DOM parent's primary frame.
2101 return false;
2103 if (aFrame->IsTableCaption()) {
2104 // This one is more subtle. captions are in-flow children of the table
2105 // frame. But they are parented to the table wrapper. So hints handled for
2106 // the inner table might not be applicable to us.
2107 return false;
2109 return true;
2112 #ifdef DEBUG
2113 static bool IsAnonBox(const nsIFrame* aFrame) {
2114 return aFrame->Style()->IsAnonBox();
2117 static const nsIFrame* FirstContinuationOrPartOfIBSplit(
2118 const nsIFrame* aFrame) {
2119 if (!aFrame) {
2120 return nullptr;
2123 return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
2126 static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
2127 const nsIFrame* parent = aFrame->GetParent();
2128 if (aFrame->IsTableFrame()) {
2129 MOZ_ASSERT(parent->IsTableWrapperFrame());
2130 parent = parent->GetParent();
2133 if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) {
2134 if (parent->IsLineFrame()) {
2135 parent = parent->GetParent();
2137 return parent->IsViewportFrame() ? nullptr
2138 : FirstContinuationOrPartOfIBSplit(parent);
2141 if (aFrame->IsLineFrame()) {
2142 // A ::first-line always ends up here via its block, which is therefore the
2143 // right expected owner. That block can be an
2144 // anonymous box. For example, we could have a ::first-line on a columnated
2145 // block; the blockframe is the column-content anonymous box in that case.
2146 // So we don't want to end up in the code below, which steps out of anon
2147 // boxes. Just return the parent of the line frame, which is the block.
2148 return parent;
2151 if (aFrame->IsLetterFrame()) {
2152 // Ditto for ::first-letter. A first-letter always arrives here via its
2153 // direct parent, except when it's parented to a ::first-line.
2154 if (parent->IsLineFrame()) {
2155 parent = parent->GetParent();
2157 return FirstContinuationOrPartOfIBSplit(parent);
2160 if (parent->IsLetterFrame()) {
2161 // Things never have ::first-letter as their expected parent. Go
2162 // on up to the ::first-letter's parent.
2163 parent = parent->GetParent();
2166 parent = FirstContinuationOrPartOfIBSplit(parent);
2168 // We've handled already anon boxes, so now we're looking at
2169 // a frame of a DOM element or pseudo. Hop through anon and line-boxes
2170 // generated by our DOM parent, and go find the owner frame for it.
2171 while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {
2172 auto pseudo = parent->Style()->GetPseudoType();
2173 if (pseudo == PseudoStyleType::tableWrapper) {
2174 const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
2175 MOZ_ASSERT(tableFrame->IsTableFrame());
2176 // Handle :-moz-table and :-moz-inline-table.
2177 parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame;
2178 } else {
2179 // We get the in-flow parent here so that we can handle the OOF anonymous
2180 // boxed to get the correct parent.
2181 parent = parent->GetInFlowParent();
2183 parent = FirstContinuationOrPartOfIBSplit(parent);
2186 return parent;
2189 // FIXME(emilio, bug 1633685): We should ideally figure out how to properly
2190 // restyle replicated fixed pos frames... We seem to assume everywhere that they
2191 // can't get restyled at the moment...
2192 static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
2193 if (!aFrame->PresContext()->IsPaginated()) {
2194 return false;
2197 for (; aFrame; aFrame = aFrame->GetParent()) {
2198 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2199 !aFrame->FirstContinuation()->IsPrimaryFrame() &&
2200 nsLayoutUtils::IsReallyFixedPos(aFrame)) {
2201 return true;
2205 return true;
2208 void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
2209 MOZ_ASSERT(mOwner);
2210 MOZ_ASSERT(CanUseHandledHintsFromAncestors(mOwner));
2211 // We allow aParent.mOwner to be null, for cases when we're not starting at
2212 // the root of the tree. We also allow aParent.mOwner to be somewhere up our
2213 // expected owner chain not our immediate owner, which allows us creating long
2214 // chains of ServoRestyleStates in some cases where it's just not worth it.
2215 if (aParent.mOwner) {
2216 const nsIFrame* owner = ExpectedOwnerForChild(mOwner);
2217 if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) {
2218 MOZ_ASSERT(IsAnonBox(owner),
2219 "Should only have expected owner weirdness when anon boxes "
2220 "are involved");
2221 bool found = false;
2222 for (; owner; owner = ExpectedOwnerForChild(owner)) {
2223 if (owner == aParent.mOwner) {
2224 found = true;
2225 break;
2228 MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain");
2233 nsChangeHint ServoRestyleState::ChangesHandledFor(
2234 const nsIFrame* aFrame) const {
2235 if (!mOwner) {
2236 MOZ_ASSERT(!mChangesHandled);
2237 return mChangesHandled;
2240 MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) ||
2241 IsInReplicatedFixedPosTree(aFrame),
2242 "Missed some frame in the hierarchy?");
2243 return mChangesHandled;
2245 #endif
2247 void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) {
2248 MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(),
2249 "All our wrappers are anon boxes, and why would we restyle "
2250 "non-inheriting ones?");
2251 MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(),
2252 "All our wrappers are anon boxes, and why would we restyle "
2253 "non-inheriting ones?");
2254 MOZ_ASSERT(
2255 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent,
2256 "Someone should be using TableAwareParentFor");
2257 MOZ_ASSERT(
2258 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper,
2259 "Someone should be using TableAwareParentFor");
2260 // Make sure we only add first continuations.
2261 aWrapperFrame = aWrapperFrame->FirstContinuation();
2262 nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
2263 if (last == aWrapperFrame) {
2264 // Already queued up, nothing to do.
2265 return;
2268 // Make sure to queue up parents before children. But don't queue up
2269 // ancestors of non-anonymous boxes here; those are handled when we traverse
2270 // their non-anonymous kids.
2271 if (aWrapperFrame->ParentIsWrapperAnonBox()) {
2272 AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
2275 // If the append fails, we'll fail to restyle properly, but that's probably
2276 // better than crashing.
2277 if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
2278 aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
2282 void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) {
2283 size_t i = mPendingWrapperRestyleOffset;
2284 while (i < mPendingWrapperRestyles.Length()) {
2285 i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
2288 mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
2291 size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
2292 size_t aIndex) {
2293 // The frame at index aIndex is something we should restyle ourselves, but
2294 // following frames may need separate ServoRestyleStates to restyle.
2295 MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
2297 nsIFrame* cur = mPendingWrapperRestyles[aIndex];
2298 MOZ_ASSERT(cur->Style()->IsWrapperAnonBox());
2300 // Where is cur supposed to inherit from? From its parent frame, except in
2301 // the case when cur is a table, in which case it should be its grandparent.
2302 // Also, not in the case when the resulting frame would be a first-line; in
2303 // that case we should be inheriting from the block, and the first-line will
2304 // do its fixup later if needed.
2306 // Note that after we do all that fixup the parent we get might still not be
2307 // aParent; for example aParent could be a scrollframe, in which case we
2308 // should inherit from the scrollcontent frame. Or the parent might be some
2309 // continuation of aParent.
2311 // Try to assert as much as we can about the parent we actually end up using
2312 // without triggering bogus asserts in all those various edge cases.
2313 nsIFrame* parent = cur->GetParent();
2314 if (cur->IsTableFrame()) {
2315 MOZ_ASSERT(parent->IsTableWrapperFrame());
2316 parent = parent->GetParent();
2318 if (parent->IsLineFrame()) {
2319 parent = parent->GetParent();
2321 MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent ||
2322 (parent->Style()->IsInheritingAnonBox() &&
2323 parent->GetContent() == aParent->GetContent()));
2325 // Now "this" is a ServoRestyleState for aParent, so if parent is not a next
2326 // continuation (possibly across ib splits) of aParent we need a new
2327 // ServoRestyleState for the kid.
2328 Maybe<ServoRestyleState> parentRestyleState;
2329 nsIFrame* parentForRestyle =
2330 nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
2331 if (parentForRestyle != aParent) {
2332 parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
2333 CanUseHandledHints::Yes);
2335 ServoRestyleState& curRestyleState =
2336 parentRestyleState ? *parentRestyleState : *this;
2338 // This frame may already have been restyled. Even if it has, we can't just
2339 // return, because the next frame may be a kid of it that does need restyling.
2340 if (cur->IsWrapperAnonBoxNeedingRestyle()) {
2341 parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState);
2342 cur->SetIsWrapperAnonBoxNeedingRestyle(false);
2345 size_t numProcessed = 1;
2347 // Note: no overflow possible here, since aIndex < length.
2348 if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
2349 nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
2350 if (TableAwareParentFor(next) == cur &&
2351 next->IsWrapperAnonBoxNeedingRestyle()) {
2352 // It might be nice if we could do better than nsChangeHint_Empty. On
2353 // the other hand, presumably our mChangesHandled already has the bits
2354 // we really want here so in practice it doesn't matter.
2355 ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
2356 CanUseHandledHints::Yes,
2357 /* aAssertWrapperRestyleLength = */ false);
2358 numProcessed +=
2359 childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
2363 return numProcessed;
2366 nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) {
2367 // We want to get the anon box parent for aChild. where aChild has
2368 // ParentIsWrapperAnonBox().
2370 // For the most part this is pretty straightforward, but there are two
2371 // wrinkles. First, if aChild is a table, then we really want the parent of
2372 // its table wrapper.
2373 if (aChild->IsTableFrame()) {
2374 aChild = aChild->GetParent();
2375 MOZ_ASSERT(aChild->IsTableWrapperFrame());
2378 nsIFrame* parent = aChild->GetParent();
2379 // Now if parent is a cell-content frame, we actually want the cellframe.
2380 if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
2381 parent = parent->GetParent();
2382 } else if (parent->IsTableWrapperFrame()) {
2383 // Must be a caption. In that case we want the table here.
2384 MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
2385 parent = parent->PrincipalChildList().FirstChild();
2387 return parent;
2390 void RestyleManager::PostRestyleEvent(Element* aElement,
2391 RestyleHint aRestyleHint,
2392 nsChangeHint aMinChangeHint) {
2393 MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange),
2394 "Didn't expect explicit change hints to be neutral!");
2395 if (MOZ_UNLIKELY(IsDisconnected()) ||
2396 MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
2397 return;
2400 // We allow posting restyles from within change hint handling, but not from
2401 // within the restyle algorithm itself.
2402 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
2404 if (!aRestyleHint && !aMinChangeHint) {
2405 // FIXME(emilio): we should assert against this instead.
2406 return; // Nothing to do.
2409 // Assuming the restyle hints will invalidate cached style for
2410 // getComputedStyle, since we don't know if any of the restyling that we do
2411 // would affect undisplayed elements.
2412 if (aRestyleHint) {
2413 if (!(aRestyleHint & RestyleHint::ForAnimations())) {
2414 mHaveNonAnimationRestyles = true;
2417 IncrementUndisplayedRestyleGeneration();
2420 // Processing change hints sometimes causes new change hints to be generated,
2421 // and very occasionally, additional restyle hints. We collect the change
2422 // hints manually to avoid re-traversing the DOM to find them.
2423 if (mReentrantChanges && !aRestyleHint) {
2424 mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint});
2425 return;
2428 if (aRestyleHint || aMinChangeHint) {
2429 Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
2433 void RestyleManager::PostRestyleEventForAnimations(Element* aElement,
2434 PseudoStyleType aPseudoType,
2435 RestyleHint aRestyleHint) {
2436 Element* elementToRestyle =
2437 AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
2439 if (!elementToRestyle) {
2440 // FIXME: Bug 1371107: When reframing happens,
2441 // EffectCompositor::mElementsToRestyle still has unbound old pseudo
2442 // element. We should drop it.
2443 return;
2446 mPresContext->TriggeredAnimationRestyle();
2448 Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0));
2451 void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
2452 RestyleHint aRestyleHint) {
2453 // NOTE(emilio): The semantics of these methods are quite funny, in the sense
2454 // that we're not supposed to need to rebuild the actual stylist data.
2456 // That's handled as part of the MediumFeaturesChanged stuff, if needed.
2458 // Clear the cached style data only if we are guaranteed to process the whole
2459 // DOM tree again.
2461 // FIXME(emilio): Decouple this, probably. This probably just wants to reset
2462 // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon
2463 // box styles and such... But it doesn't really always need to clear the
2464 // initial style of the document and similar...
2465 if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
2466 StyleSet()->ClearCachedStyleData();
2469 DocumentStyleRootIterator iter(mPresContext->Document());
2470 while (Element* root = iter.GetNextStyleRoot()) {
2471 PostRestyleEvent(root, aRestyleHint, aExtraHint);
2474 // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect
2475 // non-inheriting anon boxes. It's not clear if we want to support that, but
2476 // if we do, we need to re-selector-match them here.
2479 /* static */
2480 void RestyleManager::ClearServoDataFromSubtree(Element* aElement,
2481 IncludeRoot aIncludeRoot) {
2482 if (aElement->HasServoData()) {
2483 StyleChildrenIterator it(aElement);
2484 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2485 if (n->IsElement()) {
2486 ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes);
2491 if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) {
2492 aElement->ClearServoData();
2493 MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits |
2494 NODE_NEEDS_FRAME));
2495 MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot());
2499 /* static */
2500 void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) {
2501 if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) {
2502 StyleChildrenIterator it(aElement);
2503 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2504 if (n->IsElement()) {
2505 ClearRestyleStateFromSubtree(n->AsElement());
2510 bool wasRestyled = false;
2511 Unused << Servo_TakeChangeHint(aElement, &wasRestyled);
2512 aElement->UnsetFlags(Element::kAllServoDescendantBits);
2516 * This struct takes care of encapsulating some common state that text nodes may
2517 * need to track during the post-traversal.
2519 * This is currently used to properly compute change hints when the parent
2520 * element of this node is a display: contents node, and also to avoid computing
2521 * the style for text children more than once per element.
2523 struct RestyleManager::TextPostTraversalState {
2524 public:
2525 TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext,
2526 bool aDisplayContentsParentStyleChanged,
2527 ServoRestyleState& aParentRestyleState)
2528 : mParentElement(aParentElement),
2529 mParentContext(aParentContext),
2530 mParentRestyleState(aParentRestyleState),
2531 mStyle(nullptr),
2532 mShouldPostHints(aDisplayContentsParentStyleChanged),
2533 mShouldComputeHints(aDisplayContentsParentStyleChanged),
2534 mComputedHint(nsChangeHint_Empty) {}
2536 nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); }
2538 ComputedStyle& ComputeStyle(nsIContent* aTextNode) {
2539 if (!mStyle) {
2540 mStyle = mParentRestyleState.StyleSet().ResolveStyleForText(
2541 aTextNode, &ParentStyle());
2543 MOZ_ASSERT(mStyle);
2544 return *mStyle;
2547 void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame,
2548 ComputedStyle& aNewStyle) {
2549 MOZ_ASSERT(aTextFrame);
2550 MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText);
2552 if (MOZ_LIKELY(!mShouldPostHints)) {
2553 return;
2556 ComputedStyle* oldStyle = aTextFrame->Style();
2557 MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText);
2559 // We rely on the fact that all the text children for the same element share
2560 // style to avoid recomputing style differences for all of them.
2562 // TODO(emilio): The above may not be true for ::first-{line,letter}, but
2563 // we'll cross that bridge when we support those in stylo.
2564 if (mShouldComputeHints) {
2565 mShouldComputeHints = false;
2566 uint32_t equalStructs;
2567 mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs);
2568 mComputedHint = NS_RemoveSubsumedHints(
2569 mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame));
2572 if (mComputedHint) {
2573 mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent,
2574 mComputedHint);
2578 private:
2579 ComputedStyle& ParentStyle() {
2580 if (!mParentContext) {
2581 mLazilyResolvedParentContext =
2582 ServoStyleSet::ResolveServoStyle(mParentElement);
2583 mParentContext = mLazilyResolvedParentContext;
2585 return *mParentContext;
2588 Element& mParentElement;
2589 ComputedStyle* mParentContext;
2590 RefPtr<ComputedStyle> mLazilyResolvedParentContext;
2591 ServoRestyleState& mParentRestyleState;
2592 RefPtr<ComputedStyle> mStyle;
2593 bool mShouldPostHints;
2594 bool mShouldComputeHints;
2595 nsChangeHint mComputedHint;
2598 static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet,
2599 nsStyleChangeList& aChangeList) {
2600 const nsStyleDisplay* display = aFrame->Style()->StyleDisplay();
2601 if (display->mTopLayer != StyleTopLayer::Top) {
2602 return;
2605 // Elements in the top layer are guaranteed to have absolute or fixed
2606 // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer.
2607 MOZ_ASSERT(display->IsAbsolutelyPositionedStyle());
2609 nsIFrame* backdropPlaceholder =
2610 aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild();
2611 if (!backdropPlaceholder) {
2612 return;
2615 MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
2616 nsIFrame* backdropFrame =
2617 nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
2618 MOZ_ASSERT(backdropFrame->IsBackdropFrame());
2619 MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
2620 PseudoStyleType::backdrop);
2622 RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
2623 *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, nullptr,
2624 aFrame->Style());
2626 // NOTE(emilio): We can't use the changes handled for the owner of the
2627 // backdrop frame, since it's out of flow, and parented to the viewport or
2628 // canvas frame (depending on the `position` value).
2629 MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
2630 backdropFrame->GetParent()->IsCanvasFrame());
2631 nsTArray<nsIFrame*> wrappersToRestyle;
2632 nsTArray<RefPtr<Element>> anchorsToSuppress;
2633 ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle,
2634 anchorsToSuppress);
2635 nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state);
2636 MOZ_ASSERT(anchorsToSuppress.IsEmpty());
2639 static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame,
2640 ServoRestyleState& aRestyleState) {
2641 MOZ_ASSERT(
2642 !aFrame->IsBlockFrameOrSubclass(),
2643 "You're probably duplicating work with UpdatePseudoElementStyles!");
2644 if (!aFrame->HasFirstLetterChild()) {
2645 return;
2648 // We need to find the block the first-letter is associated with so we can
2649 // find the right element for the first-letter's style resolution. Might as
2650 // well just delegate the whole thing to that block.
2651 nsIFrame* block = aFrame->GetParent();
2652 while (!block->IsBlockFrameOrSubclass()) {
2653 block = block->GetParent();
2656 static_cast<nsBlockFrame*>(block->FirstContinuation())
2657 ->UpdateFirstLetterStyle(aRestyleState);
2660 static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
2661 ComputedStyle& aOldContext,
2662 ServoRestyleState& aRestyleState) {
2663 auto pseudoType = aOldContext.GetPseudoType();
2664 MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
2665 MOZ_ASSERT(
2666 !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
2668 RefPtr<ComputedStyle> newStyle =
2669 aRestyleState.StyleSet().ResolvePseudoElementStyle(
2670 *aFrame->GetContent()->AsElement(), pseudoType, nullptr,
2671 aFrame->Style());
2673 uint32_t equalStructs; // Not used, actually.
2674 nsChangeHint childHint =
2675 aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
2676 if (CanUseHandledHintsFromAncestors(aFrame)) {
2677 childHint = NS_RemoveSubsumedHints(childHint,
2678 aRestyleState.ChangesHandledFor(aFrame));
2681 if (childHint) {
2682 if (childHint & nsChangeHint_ReconstructFrame) {
2683 // If we generate a reconstruct here, remove any non-reconstruct hints we
2684 // may have already generated for this content.
2685 aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent());
2687 aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(),
2688 childHint);
2691 aFrame->SetAdditionalComputedStyle(aIndex, newStyle);
2694 static void UpdateAdditionalComputedStyles(nsIFrame* aFrame,
2695 ServoRestyleState& aRestyleState) {
2696 MOZ_ASSERT(aFrame);
2697 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement());
2699 // FIXME(emilio): Consider adding a bit or something to avoid the initial
2700 // virtual call?
2701 uint32_t index = 0;
2702 while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) {
2703 UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState);
2707 static void UpdateFramePseudoElementStyles(nsIFrame* aFrame,
2708 ServoRestyleState& aRestyleState) {
2709 if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
2710 blockFrame->UpdatePseudoElementStyles(aRestyleState);
2711 } else {
2712 UpdateFirstLetterIfNeeded(aFrame, aRestyleState);
2715 UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(),
2716 aRestyleState.ChangeList());
2719 enum class ServoPostTraversalFlags : uint32_t {
2720 Empty = 0,
2721 // Whether parent was restyled.
2722 ParentWasRestyled = 1 << 0,
2723 // Skip sending accessibility notifications for all descendants.
2724 SkipA11yNotifications = 1 << 1,
2725 // Always send accessibility notifications if the element is shown.
2726 // The SkipA11yNotifications flag above overrides this flag.
2727 SendA11yNotificationsIfShown = 1 << 2,
2730 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
2732 #ifdef ACCESSIBILITY
2733 static bool IsVisibleForA11y(const ComputedStyle& aStyle) {
2734 return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert();
2737 static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) {
2738 return aStyle.StyleDisplay()->mContentVisibility !=
2739 StyleContentVisibility::Hidden;
2741 #endif
2743 // Send proper accessibility notifications and return post traversal
2744 // flags for kids.
2745 static ServoPostTraversalFlags SendA11yNotifications(
2746 nsPresContext* aPresContext, Element* aElement,
2747 const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
2748 ServoPostTraversalFlags aFlags) {
2749 using Flags = ServoPostTraversalFlags;
2750 MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) ||
2751 !(aFlags & Flags::SendA11yNotificationsIfShown),
2752 "The two a11y flags should never be set together");
2754 #ifdef ACCESSIBILITY
2755 nsAccessibilityService* accService = GetAccService();
2756 if (!accService) {
2757 // If we don't have accessibility service, accessibility is not
2758 // enabled. Just skip everything.
2759 return Flags::Empty;
2762 if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually !=
2763 aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
2764 if (aElement->GetParent() &&
2765 aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) {
2766 accService->NotifyOfTabPanelVisibilityChange(
2767 aPresContext->PresShell(), aElement,
2768 aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually);
2772 if (aFlags & Flags::SkipA11yNotifications) {
2773 // Propagate the skipping flag to descendants.
2774 return Flags::SkipA11yNotifications;
2777 bool needsNotify = false;
2778 const bool isVisible = IsVisibleForA11y(aNewStyle);
2779 const bool wasVisible = IsVisibleForA11y(aOldStyle);
2781 if (aFlags & Flags::SendA11yNotificationsIfShown) {
2782 if (!isVisible) {
2783 // Propagate the sending-if-shown flag to descendants.
2784 return Flags::SendA11yNotificationsIfShown;
2786 // We have asked accessibility service to remove the whole subtree
2787 // of element which becomes invisible from the accessible tree, but
2788 // this element is visible, so we need to add it back.
2789 needsNotify = true;
2790 } else {
2791 // If we shouldn't skip in any case, we need to check whether our own
2792 // visibility has changed.
2793 // Also notify if the subtree visibility change due to content-visibility.
2794 const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle);
2795 const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle);
2796 needsNotify =
2797 wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible;
2800 if (needsNotify) {
2801 PresShell* presShell = aPresContext->PresShell();
2802 if (isVisible) {
2803 accService->ContentRangeInserted(presShell, aElement,
2804 aElement->GetNextSibling());
2805 // We are adding the subtree. Accessibility service would handle
2806 // descendants, so we should just skip them from notifying.
2807 return Flags::SkipA11yNotifications;
2809 if (wasVisible) {
2810 // Remove the subtree of this invisible element, and ask any shown
2811 // descendant to add themselves back.
2812 accService->ContentRemoved(presShell, aElement);
2813 return Flags::SendA11yNotificationsIfShown;
2816 #endif
2818 return Flags::Empty;
2821 bool RestyleManager::ProcessPostTraversal(Element* aElement,
2822 ServoRestyleState& aRestyleState,
2823 ServoPostTraversalFlags aFlags) {
2824 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
2825 nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
2827 MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(),
2828 "Element without Servo data on a post-traversal? How?");
2830 // NOTE(emilio): This is needed because for table frames the bit is set on the
2831 // table wrapper (which is the primary frame), not on the table itself.
2832 const bool isOutOfFlow =
2833 primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
2835 const bool canUseHandledHints =
2836 primaryFrame && CanUseHandledHintsFromAncestors(primaryFrame);
2838 // Grab the change hint from Servo.
2839 bool wasRestyled = false;
2840 nsChangeHint changeHint =
2841 static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled));
2843 RefPtr<ComputedStyle> upToDateStyleIfRestyled =
2844 wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr;
2846 // We should really fix the weird primary frame mapping for image maps
2847 // (bug 135040)...
2848 if (styleFrame && styleFrame->GetContent() != aElement) {
2849 MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass());
2850 styleFrame = nullptr;
2853 // Handle lazy frame construction by posting a reconstruct for any lazily-
2854 // constructed roots.
2855 if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
2856 changeHint |= nsChangeHint_ReconstructFrame;
2857 MOZ_ASSERT(!styleFrame);
2860 if (styleFrame) {
2861 MOZ_ASSERT(primaryFrame);
2863 nsIFrame* maybeAnonBoxChild;
2864 if (isOutOfFlow) {
2865 maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
2866 } else {
2867 maybeAnonBoxChild = primaryFrame;
2870 // Do not subsume change hints for the column-spanner.
2871 if (canUseHandledHints) {
2872 changeHint = NS_RemoveSubsumedHints(
2873 changeHint, aRestyleState.ChangesHandledFor(styleFrame));
2876 // If the parent wasn't restyled, the styles of our anon box parents won't
2877 // change either.
2878 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
2879 maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
2880 aRestyleState.AddPendingWrapperRestyle(
2881 ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
2884 // If we don't have a ::marker pseudo-element, but need it, then
2885 // reconstruct the frame. (The opposite situation implies 'display'
2886 // changes so doesn't need to be handled explicitly here.)
2887 if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() &&
2888 styleFrame->IsBlockFrameOrSubclass() &&
2889 !nsLayoutUtils::GetMarkerPseudo(aElement)) {
2890 RefPtr<ComputedStyle> pseudoStyle =
2891 aRestyleState.StyleSet().ProbePseudoElementStyle(
2892 *aElement, PseudoStyleType::marker, nullptr,
2893 upToDateStyleIfRestyled);
2894 if (pseudoStyle) {
2895 changeHint |= nsChangeHint_ReconstructFrame;
2900 // Although we shouldn't generate non-ReconstructFrame hints for elements with
2901 // no frames, we can still get them here if they were explicitly posted by
2902 // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
2903 // :visited. Skip processing these hints if there is no frame.
2904 if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) &&
2905 changeHint) {
2906 aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
2909 // If our change hint is reconstruct, we delegate to the frame constructor,
2910 // which consumes the new style and expects the old style to be on the frame.
2912 // XXXbholley: We should teach the frame constructor how to clear the dirty
2913 // descendants bit to avoid the traversal here.
2914 if (changeHint & nsChangeHint_ReconstructFrame) {
2915 if (wasRestyled &&
2916 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
2917 const bool wasAbsPos =
2918 styleFrame &&
2919 styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
2920 auto* newDisp = upToDateStyleIfRestyled->StyleDisplay();
2921 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
2923 // We need to do the position check here rather than in
2924 // DidSetComputedStyle because changing position reframes.
2926 // We suppress adjustments whenever we change from being display: none to
2927 // be an abspos.
2929 // Similarly, for other changes from abspos to non-abspos styles.
2931 // TODO(emilio): I _think_ chrome won't suppress adjustments whenever
2932 // `display` changes. But that causes some infinite loops in cases like
2933 // bug 1568778.
2934 if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) {
2935 aRestyleState.AddPendingScrollAnchorSuppression(aElement);
2938 ClearRestyleStateFromSubtree(aElement);
2939 return true;
2942 // TODO(emilio): We could avoid some refcount traffic here, specially in the
2943 // ComputedStyle case, which uses atomic refcounting.
2945 // Hold the ComputedStyle alive, because it could become a dangling pointer
2946 // during the replacement. In practice it's not a huge deal, but better not
2947 // playing with dangling pointers if not needed.
2949 // NOTE(emilio): We could keep around the old computed style for display:
2950 // contents elements too, but we don't really need it right now.
2951 RefPtr<ComputedStyle> oldOrDisplayContentsStyle =
2952 styleFrame ? styleFrame->Style() : nullptr;
2954 MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)),
2955 "display: contents node has a frame, yet we didn't reframe it"
2956 " above?");
2957 const bool isDisplayContents = !styleFrame && aElement->HasServoData() &&
2958 Servo_Element_IsDisplayContents(aElement);
2959 if (isDisplayContents) {
2960 oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement);
2963 Maybe<ServoRestyleState> thisFrameRestyleState;
2964 if (styleFrame) {
2965 thisFrameRestyleState.emplace(
2966 *styleFrame, aRestyleState, changeHint,
2967 ServoRestyleState::CanUseHandledHints(canUseHandledHints));
2970 // We can't really assume as used changes from display: contents elements (or
2971 // other elements without frames).
2972 ServoRestyleState& childrenRestyleState =
2973 thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
2975 ComputedStyle* upToDateStyle =
2976 wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle;
2978 ServoPostTraversalFlags childrenFlags =
2979 wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled
2980 : ServoPostTraversalFlags::Empty;
2982 if (wasRestyled && oldOrDisplayContentsStyle) {
2983 MOZ_ASSERT(styleFrame || isDisplayContents);
2985 // We want to walk all the continuations here, even the ones with different
2986 // styles. In practice, the only reason we get continuations with different
2987 // styles here is ::first-line (::first-letter never affects element
2988 // styles). But in that case, newStyle is the right context for the
2989 // _later_ continuations anyway (the ones not affected by ::first-line), not
2990 // the earlier ones, so there is no point stopping right at the point when
2991 // we'd actually be setting the right ComputedStyle.
2993 // This does mean that we may be setting the wrong ComputedStyle on our
2994 // initial continuations; ::first-line fixes that up after the fact.
2995 for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) {
2996 MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0));
2997 f->SetComputedStyle(upToDateStyle);
3000 if (styleFrame) {
3001 UpdateAdditionalComputedStyles(styleFrame, aRestyleState);
3004 if (!aElement->GetParent()) {
3005 // This is the root. Update styles on the viewport as needed.
3006 ViewportFrame* viewport =
3007 do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
3008 if (viewport) {
3009 // NB: The root restyle state, not the one for our children!
3010 viewport->UpdateStyle(aRestyleState);
3014 // Some changes to animations don't affect the computed style and yet still
3015 // require the layer to be updated. For example, pausing an animation via
3016 // the Web Animations API won't affect an element's style but still
3017 // requires to update the animation on the layer.
3019 // We can sometimes reach this when the animated style is being removed.
3020 // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform
3021 // style or not, we need to call it *after* setting |newStyle| to
3022 // |styleFrame| to ensure the animated transform has been removed first.
3023 AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint,
3024 aRestyleState.ChangeList());
3026 childrenFlags |= SendA11yNotifications(mPresContext, aElement,
3027 *oldOrDisplayContentsStyle,
3028 *upToDateStyle, aFlags);
3031 const bool traverseElementChildren =
3032 aElement->HasAnyOfFlags(Element::kAllServoDescendantBits);
3033 const bool traverseTextChildren =
3034 wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
3035 bool recreatedAnyContext = wasRestyled;
3036 if (traverseElementChildren || traverseTextChildren) {
3037 StyleChildrenIterator it(aElement);
3038 TextPostTraversalState textState(*aElement, upToDateStyle,
3039 isDisplayContents && wasRestyled,
3040 childrenRestyleState);
3041 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
3042 if (traverseElementChildren && n->IsElement()) {
3043 recreatedAnyContext |= ProcessPostTraversal(
3044 n->AsElement(), childrenRestyleState, childrenFlags);
3045 } else if (traverseTextChildren && n->IsText()) {
3046 recreatedAnyContext |= ProcessPostTraversalForText(
3047 n, textState, childrenRestyleState, childrenFlags);
3052 // We want to update frame pseudo-element styles after we've traversed our
3053 // kids, because some of those updates (::first-line/::first-letter) need to
3054 // modify the styles of the kids, and the child traversal above would just
3055 // clobber those modifications.
3056 if (styleFrame) {
3057 if (wasRestyled) {
3058 // Make sure to update anon boxes and pseudo bits after updating text,
3059 // otherwise ProcessPostTraversalForText could clobber first-letter
3060 // styles, for example.
3061 styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
3063 // Process anon box wrapper frames before ::first-line bits, but _after_
3064 // owned anon boxes, since the children wrapper anon boxes could be
3065 // inheriting from our own owned anon boxes.
3066 childrenRestyleState.ProcessWrapperRestyles(styleFrame);
3067 if (wasRestyled) {
3068 UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
3069 } else if (traverseElementChildren &&
3070 styleFrame->IsBlockFrameOrSubclass()) {
3071 // Even if we were not restyled, if we're a block with a first-line and
3072 // one of our descendant elements which is on the first line was restyled,
3073 // we need to update the styles of things on the first line, because
3074 // they're wrong now.
3076 // FIXME(bz) Could we do better here? For example, could we keep track of
3077 // frames that are "block with a ::first-line so we could avoid
3078 // IsFrameOfType() and digging about for the first-line frame if not?
3079 // Could we keep track of whether the element children we actually restyle
3080 // are affected by first-line? Something else? Bug 1385443 tracks making
3081 // this better.
3082 nsIFrame* firstLineFrame =
3083 static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame();
3084 if (firstLineFrame) {
3085 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
3086 ReparentComputedStyleForFirstLine(kid);
3092 aElement->UnsetFlags(Element::kAllServoDescendantBits);
3093 return recreatedAnyContext;
3096 bool RestyleManager::ProcessPostTraversalForText(
3097 nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState,
3098 ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) {
3099 // Handle lazy frame construction.
3100 if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
3101 aPostTraversalState.ChangeList().AppendChange(
3102 nullptr, aTextNode, nsChangeHint_ReconstructFrame);
3103 return true;
3106 // Handle restyle.
3107 nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
3108 if (!primaryFrame) {
3109 return false;
3112 // If the parent wasn't restyled, the styles of our anon box parents won't
3113 // change either.
3114 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
3115 primaryFrame->ParentIsWrapperAnonBox()) {
3116 aRestyleState.AddPendingWrapperRestyle(
3117 ServoRestyleState::TableAwareParentFor(primaryFrame));
3120 ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode);
3121 aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle);
3123 // We want to walk all the continuations here, even the ones with different
3124 // styles. In practice, the only reasons we get continuations with different
3125 // styles are ::first-line and ::first-letter. But in those cases,
3126 // newStyle is the right context for the _later_ continuations anyway (the
3127 // ones not affected by ::first-line/::first-letter), not the earlier ones,
3128 // so there is no point stopping right at the point when we'd actually be
3129 // setting the right ComputedStyle.
3131 // This does mean that we may be setting the wrong ComputedStyle on our
3132 // initial continuations; ::first-line/::first-letter fix that up after the
3133 // fact.
3134 for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) {
3135 f->SetComputedStyle(&newStyle);
3138 return true;
3141 void RestyleManager::ClearSnapshots() {
3142 for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
3143 iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
3144 iter.Remove();
3148 ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) {
3149 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3151 // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
3152 // we do to restyle stuff for reconstruction, for example.
3154 // It seems to be the case that we always flush in between that happens and
3155 // the next attribute change, so we can assert that we haven't handled the
3156 // snapshot here yet. If this assertion didn't hold, we'd need to unset that
3157 // flag from here too.
3159 // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
3160 // so this becomes much easier to reason about. Today is not that day though.
3161 MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT));
3163 ServoElementSnapshot* snapshot =
3164 mSnapshots.GetOrInsertNew(&aElement, aElement);
3165 aElement.SetFlags(ELEMENT_HAS_SNAPSHOT);
3167 // Now that we have a snapshot, make sure a restyle is triggered.
3168 aElement.NoteDirtyForServo();
3169 return *snapshot;
3172 void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
3173 nsPresContext* presContext = PresContext();
3174 PresShell* presShell = presContext->PresShell();
3176 MOZ_ASSERT(presContext->Document(), "No document? Pshaw!");
3177 // FIXME(emilio): In the "flush animations" case, ideally, we should only
3178 // recascade animation styles running on the compositor, so we shouldn't care
3179 // about other styles, or new rules that apply to the page...
3181 // However, that's not true as of right now, see bug 1388031 and bug 1388692.
3182 MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) ||
3183 !presContext->HasPendingMediaQueryUpdates(),
3184 "Someone forgot to update media queries?");
3185 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
3186 MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?");
3188 if (MOZ_UNLIKELY(!presShell->DidInitialize())) {
3189 // PresShell::FlushPendingNotifications doesn't early-return in the case
3190 // where the PresShell hasn't yet been initialized (and therefore we haven't
3191 // yet done the initial style traversal of the DOM tree). We should arguably
3192 // fix up the callers and assert against this case, but we just detect and
3193 // handle it for now.
3194 return;
3197 // It'd be bad!
3198 PresShell::AutoAssertNoFlush noReentrantFlush(*presShell);
3200 // Create a AnimationsWithDestroyedFrame during restyling process to
3201 // stop animations and transitions on elements that have no frame at the end
3202 // of the restyling process.
3203 AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
3205 ServoStyleSet* styleSet = StyleSet();
3206 Document* doc = presContext->Document();
3208 // Ensure the refresh driver is active during traversal to avoid mutating
3209 // mActiveTimer and mMostRecentRefresh time.
3210 presContext->RefreshDriver()->MostRecentRefresh();
3212 if (!doc->GetServoRestyleRoot()) {
3213 // This might post new restyles, so need to do it here. Don't do it if we're
3214 // already going to restyle tho, so that we don't potentially reflow with
3215 // dirty styling.
3216 presContext->UpdateContainerQueryStyles();
3217 presContext->FinishedContainerQueryUpdate();
3220 // Perform the Servo traversal, and the post-traversal if required. We do this
3221 // in a loop because certain rare paths in the frame constructor can trigger
3222 // additional style invalidations.
3224 // FIXME(emilio): Confirm whether that's still true now that XBL is gone.
3225 mInStyleRefresh = true;
3226 if (mHaveNonAnimationRestyles) {
3227 ++mAnimationGeneration;
3230 if (mRestyleForCSSRuleChanges) {
3231 aFlags |= ServoTraversalFlags::ForCSSRuleChanges;
3234 while (styleSet->StyleDocument(aFlags)) {
3235 ClearSnapshots();
3237 // Select scroll anchors for frames that have been scrolled. Do this
3238 // before processing restyled frames so that anchor nodes are correctly
3239 // marked when directly moving frames with RecomputePosition.
3240 presContext->PresShell()->FlushPendingScrollAnchorSelections();
3242 nsStyleChangeList currentChanges;
3243 bool anyStyleChanged = false;
3245 // Recreate styles , and queue up change hints (which also handle lazy frame
3246 // construction).
3247 nsTArray<RefPtr<Element>> anchorsToSuppress;
3250 DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
3251 while (Element* root = iter.GetNextStyleRoot()) {
3252 nsTArray<nsIFrame*> wrappersToRestyle;
3253 ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle,
3254 anchorsToSuppress);
3255 ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty;
3256 anyStyleChanged |= ProcessPostTraversal(root, state, flags);
3259 // We want to suppress adjustments the current (before-change) scroll
3260 // anchor container now, and save a reference to the content node so that
3261 // we can suppress them in the after-change scroll anchor .
3262 for (Element* element : anchorsToSuppress) {
3263 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3264 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3265 container->SuppressAdjustments();
3271 doc->ClearServoRestyleRoot();
3272 ClearSnapshots();
3274 // Process the change hints.
3276 // Unfortunately, the frame constructor can generate new change hints while
3277 // processing existing ones. We redirect those into a secondary queue and
3278 // iterate until there's nothing left.
3280 ReentrantChangeList newChanges;
3281 mReentrantChanges = &newChanges;
3282 while (!currentChanges.IsEmpty()) {
3283 ProcessRestyledFrames(currentChanges);
3284 MOZ_ASSERT(currentChanges.IsEmpty());
3285 for (ReentrantChange& change : newChanges) {
3286 if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
3287 !change.mContent->GetPrimaryFrame()) {
3288 // SVG Elements post change hints without ensuring that the primary
3289 // frame will be there after that (see bug 1366142).
3291 // Just ignore those, since we can't really process them.
3292 continue;
3294 currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
3295 change.mContent, change.mHint);
3297 newChanges.Clear();
3299 mReentrantChanges = nullptr;
3302 // Suppress adjustments in the after-change scroll anchors if needed, now
3303 // that we're done reframing everything.
3304 for (Element* element : anchorsToSuppress) {
3305 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3306 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3307 container->SuppressAdjustments();
3312 if (anyStyleChanged) {
3313 // Maybe no styles changed when:
3315 // * Only explicit change hints were posted in the first place.
3316 // * When an attribute or state change in the content happens not to need
3317 // a restyle after all.
3319 // In any case, we don't need to increment the restyle generation in that
3320 // case.
3321 IncrementRestyleGeneration();
3324 mInStyleRefresh = false;
3325 presContext->UpdateContainerQueryStyles();
3326 mInStyleRefresh = true;
3329 doc->ClearServoRestyleRoot();
3330 presContext->FinishedContainerQueryUpdate();
3331 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
3332 ClearSnapshots();
3333 styleSet->AssertTreeIsClean();
3335 mHaveNonAnimationRestyles = false;
3336 mRestyleForCSSRuleChanges = false;
3337 mInStyleRefresh = false;
3339 // Now that everything has settled, see if we have enough free rule nodes in
3340 // the tree to warrant sweeping them.
3341 styleSet->MaybeGCRuleTree();
3343 // Note: We are in the scope of |animationsWithDestroyedFrame|, so
3344 // |mAnimationsWithDestroyedFrame| is still valid.
3345 MOZ_ASSERT(mAnimationsWithDestroyedFrame);
3346 mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
3349 #ifdef DEBUG
3350 static void VerifyFlatTree(const nsIContent& aContent) {
3351 StyleChildrenIterator iter(&aContent);
3353 for (auto* content = iter.GetNextChild(); content;
3354 content = iter.GetNextChild()) {
3355 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
3356 VerifyFlatTree(*content);
3359 #endif
3361 void RestyleManager::ProcessPendingRestyles() {
3362 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT);
3363 #ifdef DEBUG
3364 if (auto* root = mPresContext->Document()->GetRootElement()) {
3365 VerifyFlatTree(*root);
3367 #endif
3369 DoProcessPendingRestyles(ServoTraversalFlags::Empty);
3372 void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() {
3373 if (mSnapshots.IsEmpty()) {
3374 return;
3376 for (const auto& key : mSnapshots.Keys()) {
3377 // Servo data for the element might have been dropped. (e.g. by removing
3378 // from its document)
3379 if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3380 Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots);
3383 ClearSnapshots();
3386 void RestyleManager::UpdateOnlyAnimationStyles() {
3387 bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
3388 if (!doCSS) {
3389 return;
3392 DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations);
3395 void RestyleManager::ElementStateChanged(Element* aElement,
3396 ElementState aChangedBits) {
3397 #ifdef EARLY_BETA_OR_EARLIER
3398 if (MOZ_UNLIKELY(mInStyleRefresh)) {
3399 MOZ_CRASH_UNSAFE_PRINTF(
3400 "Element state change during style refresh (%" PRIu64 ")",
3401 aChangedBits.GetInternalValue());
3403 #endif
3405 const ElementState kVisitedAndUnvisited =
3406 ElementState::VISITED | ElementState::UNVISITED;
3408 // We'll restyle when the relevant visited query finishes, regardless of the
3409 // style (see Link::VisitedQueryFinished). So there's no need to do anything
3410 // as a result of this state change just yet.
3412 // Note that this check checks for _both_ bits: This is only true when visited
3413 // changes to unvisited or vice-versa, but not when we start or stop being a
3414 // link itself.
3415 if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) {
3416 aChangedBits &= ~kVisitedAndUnvisited;
3417 if (aChangedBits.IsEmpty()) {
3418 return;
3422 if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) {
3423 Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint);
3426 // Don't bother taking a snapshot if no rules depend on these state bits.
3428 // We always take a snapshot for the LTR/RTL event states, since Servo doesn't
3429 // track those bits in the same way, and we know that :dir() rules are always
3430 // present in UA style sheets.
3431 if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) &&
3432 !StyleSet()->HasStateDependency(*aElement, aChangedBits)) {
3433 return;
3436 // Assuming we need to invalidate cached style in getComputedStyle for
3437 // undisplayed elements, since we don't know if it is needed.
3438 IncrementUndisplayedRestyleGeneration();
3440 if (!aElement->HasServoData() &&
3441 !(aElement->GetSelectorFlags() &
3442 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3443 return;
3446 ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
3447 ElementState previousState = aElement->StyleState() ^ aChangedBits;
3448 snapshot.AddState(previousState);
3450 ServoStyleSet& styleSet = *StyleSet();
3451 MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
3452 MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
3455 void RestyleManager::CustomStatesWillChange(Element& aElement) {
3456 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3458 IncrementUndisplayedRestyleGeneration();
3460 // Relative selector invalidation travels ancestor and earlier sibling
3461 // direction, so it's very possible that it invalidates a styled element.
3462 if (!aElement.HasServoData() &&
3463 !(aElement.GetSelectorFlags() &
3464 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3465 return;
3468 ServoElementSnapshot& snapshot = SnapshotFor(aElement);
3469 snapshot.AddCustomStates(aElement);
3472 void RestyleManager::CustomStateChanged(Element& aElement, nsAtom* aState) {
3473 ServoStyleSet& styleSet = *StyleSet();
3474 MaybeRestyleForNthOfCustomState(styleSet, aElement, aState);
3475 styleSet.MaybeInvalidateRelativeSelectorCustomStateDependency(
3476 aElement, aState, Snapshots());
3479 void RestyleManager::MaybeRestyleForNthOfCustomState(ServoStyleSet& aStyleSet,
3480 Element& aChild,
3481 nsAtom* aState) {
3482 const auto* parentNode = aChild.GetParentNode();
3483 MOZ_ASSERT(parentNode);
3484 const auto parentFlags = parentNode->GetSelectorFlags();
3485 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3486 return;
3489 if (aStyleSet.HasNthOfCustomStateDependency(aChild, aState)) {
3490 RestyleSiblingsForNthOf(&aChild, parentFlags);
3494 void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
3495 Element* aChild,
3496 ElementState aChangedBits) {
3497 const auto* parentNode = aChild->GetParentNode();
3498 MOZ_ASSERT(parentNode);
3499 const auto parentFlags = parentNode->GetSelectorFlags();
3500 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3501 return;
3504 if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
3505 RestyleSiblingsForNthOf(aChild, parentFlags);
3509 static inline bool AttributeInfluencesOtherPseudoClassState(
3510 const Element& aElement, const nsAtom* aAttribute) {
3511 // We must record some state for :-moz-table-border-nonzero and
3512 // :-moz-select-list-box.
3514 if (aAttribute == nsGkAtoms::border) {
3515 return aElement.IsHTMLElement(nsGkAtoms::table);
3518 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
3519 return aElement.IsHTMLElement(nsGkAtoms::select);
3522 return false;
3525 static inline bool NeedToRecordAttrChange(
3526 const ServoStyleSet& aStyleSet, const Element& aElement,
3527 int32_t aNameSpaceID, nsAtom* aAttribute,
3528 bool* aInfluencesOtherPseudoClassState) {
3529 *aInfluencesOtherPseudoClassState =
3530 AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
3532 // If the attribute influences one of the pseudo-classes that are backed by
3533 // attributes, we just record it.
3534 if (*aInfluencesOtherPseudoClassState) {
3535 return true;
3538 // We assume that id and class attributes are used in class/id selectors, and
3539 // thus record them.
3541 // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
3542 // presumably we could try to filter the old and new id, but it's not clear
3543 // it's worth it.
3544 if (aNameSpaceID == kNameSpaceID_None &&
3545 (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
3546 return true;
3549 // We always record lang="", even though we force a subtree restyle when it
3550 // changes, since it can change how its siblings match :lang(..) due to
3551 // selectors like :lang(..) + div.
3552 if (aAttribute == nsGkAtoms::lang) {
3553 return true;
3556 // Otherwise, just record the attribute change if a selector in the page may
3557 // reference it from an attribute selector.
3558 return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
3561 void RestyleManager::AttributeWillChange(Element* aElement,
3562 int32_t aNameSpaceID,
3563 nsAtom* aAttribute, int32_t aModType) {
3564 TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute);
3567 void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) {
3568 TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None,
3569 nsGkAtoms::_class);
3572 void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement,
3573 int32_t aNameSpaceID,
3574 nsAtom* aAttribute) {
3575 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3577 bool influencesOtherPseudoClassState;
3578 if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute,
3579 &influencesOtherPseudoClassState)) {
3580 return;
3583 // We cannot tell if the attribute change will affect the styles of
3584 // undisplayed elements, because we don't actually restyle those elements
3585 // during the restyle traversal. So just assume that the attribute change can
3586 // cause the style to change.
3587 IncrementUndisplayedRestyleGeneration();
3589 // Relative selector invalidation travels ancestor and earlier sibling
3590 // direction, so it's very possible that it invalidates a styled element.
3591 if (!aElement.HasServoData() &&
3592 !(aElement.GetSelectorFlags() &
3593 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3594 return;
3597 // Some other random attribute changes may also affect the transitions,
3598 // so we also set this true here.
3599 mHaveNonAnimationRestyles = true;
3601 ServoElementSnapshot& snapshot = SnapshotFor(aElement);
3602 snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
3604 if (influencesOtherPseudoClassState) {
3605 snapshot.AddOtherPseudoClassState(aElement);
3609 // For some attribute changes we must restyle the whole subtree:
3611 // * lang="" and xml:lang="" can affect all descendants due to :lang()
3612 // * exportparts can affect all descendant parts. We could certainly integrate
3613 // it better in the invalidation machinery if it was necessary.
3614 static inline bool AttributeChangeRequiresSubtreeRestyle(
3615 const Element& aElement, nsAtom* aAttr) {
3616 if (aAttr == nsGkAtoms::exportparts) {
3617 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for
3618 // exportparts attribute changes?
3619 return !!aElement.GetShadowRoot();
3621 return aAttr == nsGkAtoms::lang;
3624 void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
3625 nsAtom* aAttribute, int32_t aModType,
3626 const nsAttrValue* aOldValue) {
3627 MOZ_ASSERT(!mInStyleRefresh);
3629 auto changeHint = nsChangeHint(0);
3630 auto restyleHint = RestyleHint{0};
3632 changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
3634 MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
3635 MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
3637 if (aAttribute == nsGkAtoms::style) {
3638 restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
3639 } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
3640 restyleHint |= RestyleHint::RestyleSubtree();
3641 } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) {
3642 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part
3643 // attribute changes?
3644 restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS;
3647 if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
3648 // See if we have appearance information for a theme.
3649 StyleAppearance appearance =
3650 primaryFrame->StyleDisplay()->EffectiveAppearance();
3651 if (appearance != StyleAppearance::None) {
3652 nsITheme* theme = PresContext()->Theme();
3653 if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) {
3654 bool repaint = false;
3655 theme->WidgetStateChanged(primaryFrame, appearance, aAttribute,
3656 &repaint, aOldValue);
3657 if (repaint) {
3658 changeHint |= nsChangeHint_RepaintFrame;
3663 primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
3666 if (restyleHint || changeHint) {
3667 Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
3670 if (restyleHint) {
3671 // Assuming we need to invalidate cached style in getComputedStyle for
3672 // undisplayed elements, since we don't know if it is needed.
3673 IncrementUndisplayedRestyleGeneration();
3675 // If we change attributes, we have to mark this to be true, so we will
3676 // increase the animation generation for the new created transition if any.
3677 mHaveNonAnimationRestyles = true;
3681 void RestyleManager::RestyleSiblingsForNthOf(Element* aChild,
3682 NodeSelectorFlags aParentFlags) {
3683 StyleSet()->RestyleSiblingsForNthOf(*aChild,
3684 static_cast<uint32_t>(aParentFlags));
3687 void RestyleManager::MaybeRestyleForNthOfAttribute(
3688 Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3689 const auto* parentNode = aChild->GetParentNode();
3690 MOZ_ASSERT(parentNode);
3691 const auto parentFlags = parentNode->GetSelectorFlags();
3692 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3693 return;
3695 if (!aChild->HasServoData()) {
3696 return;
3699 bool mightHaveNthOfDependency;
3700 auto& styleSet = *StyleSet();
3701 if (aAttribute == nsGkAtoms::id) {
3702 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3703 ? aOldValue->GetAtomValue()
3704 : nullptr;
3705 mightHaveNthOfDependency =
3706 styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
3707 } else if (aAttribute == nsGkAtoms::_class) {
3708 mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
3709 } else {
3710 mightHaveNthOfDependency =
3711 styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
3714 if (mightHaveNthOfDependency) {
3715 RestyleSiblingsForNthOf(aChild, parentFlags);
3719 void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
3720 Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3721 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3722 return;
3724 auto& styleSet = *StyleSet();
3725 if (aAttribute == nsGkAtoms::id) {
3726 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3727 ? aOldValue->GetAtomValue()
3728 : nullptr;
3729 styleSet.MaybeInvalidateRelativeSelectorIDDependency(
3730 *aElement, oldAtom, aElement->GetID(), Snapshots());
3731 } else if (aAttribute == nsGkAtoms::_class) {
3732 styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement,
3733 Snapshots());
3734 } else {
3735 styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(
3736 *aElement, aAttribute, Snapshots());
3740 void RestyleManager::MaybeRestyleForRelativeSelectorState(
3741 ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
3742 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3743 return;
3745 aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(
3746 *aElement, aChangedBits, Snapshots());
3749 void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
3750 // This is only called when moving frames in or out of the first-line
3751 // pseudo-element (or one of its descendants). We can't say much about
3752 // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into
3753 // something inside an inline-block on the first line the ancestors could be
3754 // totally arbitrary), but we will definitely find a line frame on the
3755 // ancestor chain. Note that the lineframe may not actually be the one that
3756 // corresponds to ::first-line; when we're moving _out_ of the ::first-line it
3757 // will be one of the continuations instead.
3758 #ifdef DEBUG
3760 nsIFrame* f = aFrame->GetParent();
3761 while (f && !f->IsLineFrame()) {
3762 f = f->GetParent();
3764 MOZ_ASSERT(f, "Must have found a first-line frame");
3766 #endif
3768 DoReparentComputedStyleForFirstLine(aFrame, *StyleSet());
3771 static bool IsFrameAboutToGoAway(nsIFrame* aFrame) {
3772 auto* element = Element::FromNode(aFrame->GetContent());
3773 if (!element) {
3774 return false;
3776 return !element->HasServoData();
3779 void RestyleManager::DoReparentComputedStyleForFirstLine(
3780 nsIFrame* aFrame, ServoStyleSet& aStyleSet) {
3781 if (aFrame->IsBackdropFrame()) {
3782 // Style context of backdrop frame has no parent style, and thus we do not
3783 // need to reparent it.
3784 return;
3787 if (IsFrameAboutToGoAway(aFrame)) {
3788 // We're entering a display: none subtree, which we know it's going to get
3789 // rebuilt. Don't bother reparenting.
3790 return;
3793 if (aFrame->IsPlaceholderFrame()) {
3794 // Also reparent the out-of-flow and all its continuations. We're doing
3795 // this to match Gecko for now, but it's not clear that this behavior is
3796 // correct per spec. It's certainly pretty odd for out-of-flows whose
3797 // containing block is not within the first line.
3799 // Right now we're somewhat inconsistent in this testcase:
3801 // <style>
3802 // div { color: orange; clear: left; }
3803 // div::first-line { color: blue; }
3804 // </style>
3805 // <div>
3806 // <span style="float: left">What color is this text?</span>
3807 // </div>
3808 // <div>
3809 // <span><span style="float: left">What color is this text?</span></span>
3810 // </div>
3812 // We make the first float orange and the second float blue. On the other
3813 // hand, if the float were within an inline-block that was on the first
3814 // line, arguably it _should_ inherit from the ::first-line...
3815 nsIFrame* outOfFlow =
3816 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
3817 MOZ_ASSERT(outOfFlow, "no out-of-flow frame");
3818 for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) {
3819 DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet);
3823 // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try
3824 // to remove it?
3825 nsIFrame* providerFrame;
3826 ComputedStyle* newParentStyle =
3827 aFrame->GetParentComputedStyle(&providerFrame);
3828 // If our provider is our child, we want to reparent it first, because we
3829 // inherit style from it.
3830 bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
3831 nsIFrame* providerChild = nullptr;
3832 if (isChild) {
3833 DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet);
3834 // Get the style again after ReparentComputedStyle() which might have
3835 // changed it.
3836 newParentStyle = providerFrame->Style();
3837 providerChild = providerFrame;
3838 MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
3839 "Out of flow provider?");
3842 if (!newParentStyle) {
3843 // No need to do anything here for this frame, but we should still reparent
3844 // its descendants, because those may have styles that inherit from the
3845 // parent of this frame (e.g. non-anonymous columns in an anonymous
3846 // colgroup).
3847 MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(),
3848 "Why did this frame not end up with a parent context?");
3849 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3850 return;
3853 bool isElement = aFrame->GetContent()->IsElement();
3855 // We probably don't want to initiate transitions from ReparentComputedStyle,
3856 // since we call it during frame construction rather than in response to
3857 // dynamic changes.
3858 // Also see the comment at the start of
3859 // nsTransitionManager::ConsiderInitiatingTransition.
3861 // We don't try to do the fancy copying from previous continuations that
3862 // GeckoRestyleManager does here, because that relies on knowing the parents
3863 // of ComputedStyles, and we don't know those.
3864 ComputedStyle* oldStyle = aFrame->Style();
3865 Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr;
3866 ComputedStyle* newParent = newParentStyle;
3868 if (!providerFrame) {
3869 // No providerFrame means we inherited from a display:contents thing. Our
3870 // layout parent style is the style of our nearest ancestor frame. But we
3871 // have to be careful to do that with our placeholder, not with us, if we're
3872 // out of flow.
3873 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
3874 aFrame->FirstContinuation()
3875 ->GetPlaceholderFrame()
3876 ->GetLayoutParentStyleForOutOfFlow(&providerFrame);
3877 } else {
3878 providerFrame = nsIFrame::CorrectStyleParentFrame(
3879 aFrame->GetParent(), oldStyle->GetPseudoType());
3882 ComputedStyle* layoutParent = providerFrame->Style();
3884 RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle(
3885 oldStyle, newParent, layoutParent, ourElement);
3886 aFrame->SetComputedStyle(newStyle);
3888 // This logic somewhat mirrors the logic in
3889 // RestyleManager::ProcessPostTraversal.
3890 if (isElement) {
3891 // We can't use UpdateAdditionalComputedStyles as-is because it needs a
3892 // ServoRestyleState and maintaining one of those during a _frametree_
3893 // traversal is basically impossible.
3894 int32_t index = 0;
3895 while (auto* oldAdditionalStyle =
3896 aFrame->GetAdditionalComputedStyle(index)) {
3897 RefPtr<ComputedStyle> newAdditionalContext =
3898 aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle,
3899 newStyle, nullptr);
3900 aFrame->SetAdditionalComputedStyle(index, newAdditionalContext);
3901 ++index;
3905 // Generally, owned anon boxes are our descendants. The only exceptions are
3906 // tables (for the table wrapper) and inline frames (for the block part of the
3907 // block-in-inline split). We're going to update our descendants when looping
3908 // over kids, and we don't want to update the block part of a block-in-inline
3909 // split if the inline is on the first line but the block is not (and if the
3910 // block is, it's the child of something else on the first line and will get
3911 // updated as a child). And given how this method ends up getting called, if
3912 // we reach here for a table frame, we are already in the middle of
3913 // reparenting the table wrapper frame. So no need to
3914 // UpdateStyleOfOwnedAnonBoxes() here.
3916 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3918 // We do not need to do the equivalent of UpdateFramePseudoElementStyles,
3919 // because those are handled by our descendant walk.
3922 void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame,
3923 nsIFrame* aProviderChild,
3924 ServoStyleSet& aStyleSet) {
3925 for (const auto& childList : aFrame->ChildLists()) {
3926 for (nsIFrame* child : childList.mList) {
3927 // only do frames that are in flow
3928 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
3929 child != aProviderChild) {
3930 DoReparentComputedStyleForFirstLine(child, aStyleSet);
3936 } // namespace mozilla