Bug 1879449 [wpt PR 44489] - [wptrunner] Add `infrastructure/expected-fail/` test...
[gecko.git] / layout / base / RestyleManager.cpp
blob8c0751209382b614588ae72f1f06564c9e301aa7
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 #ifdef DEBUG
2091 static bool IsAnonBox(const nsIFrame* aFrame) {
2092 return aFrame->Style()->IsAnonBox();
2095 static const nsIFrame* FirstContinuationOrPartOfIBSplit(
2096 const nsIFrame* aFrame) {
2097 if (!aFrame) {
2098 return nullptr;
2101 return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
2104 static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
2105 const nsIFrame* parent = aFrame->GetParent();
2106 if (aFrame->IsTableFrame()) {
2107 MOZ_ASSERT(parent->IsTableWrapperFrame());
2108 parent = parent->GetParent();
2111 if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) {
2112 if (parent->IsLineFrame()) {
2113 parent = parent->GetParent();
2115 return parent->IsViewportFrame() ? nullptr
2116 : FirstContinuationOrPartOfIBSplit(parent);
2119 if (aFrame->IsLineFrame()) {
2120 // A ::first-line always ends up here via its block, which is therefore the
2121 // right expected owner. That block can be an
2122 // anonymous box. For example, we could have a ::first-line on a columnated
2123 // block; the blockframe is the column-content anonymous box in that case.
2124 // So we don't want to end up in the code below, which steps out of anon
2125 // boxes. Just return the parent of the line frame, which is the block.
2126 return parent;
2129 if (aFrame->IsLetterFrame()) {
2130 // Ditto for ::first-letter. A first-letter always arrives here via its
2131 // direct parent, except when it's parented to a ::first-line.
2132 if (parent->IsLineFrame()) {
2133 parent = parent->GetParent();
2135 return FirstContinuationOrPartOfIBSplit(parent);
2138 if (parent->IsLetterFrame()) {
2139 // Things never have ::first-letter as their expected parent. Go
2140 // on up to the ::first-letter's parent.
2141 parent = parent->GetParent();
2144 parent = FirstContinuationOrPartOfIBSplit(parent);
2146 // We've handled already anon boxes, so now we're looking at
2147 // a frame of a DOM element or pseudo. Hop through anon and line-boxes
2148 // generated by our DOM parent, and go find the owner frame for it.
2149 while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {
2150 auto pseudo = parent->Style()->GetPseudoType();
2151 if (pseudo == PseudoStyleType::tableWrapper) {
2152 const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
2153 MOZ_ASSERT(tableFrame->IsTableFrame());
2154 // Handle :-moz-table and :-moz-inline-table.
2155 parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame;
2156 } else {
2157 // We get the in-flow parent here so that we can handle the OOF anonymous
2158 // boxed to get the correct parent.
2159 parent = parent->GetInFlowParent();
2161 parent = FirstContinuationOrPartOfIBSplit(parent);
2164 return parent;
2167 // FIXME(emilio, bug 1633685): We should ideally figure out how to properly
2168 // restyle replicated fixed pos frames... We seem to assume everywhere that they
2169 // can't get restyled at the moment...
2170 static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
2171 if (!aFrame->PresContext()->IsPaginated()) {
2172 return false;
2175 for (; aFrame; aFrame = aFrame->GetParent()) {
2176 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2177 !aFrame->FirstContinuation()->IsPrimaryFrame() &&
2178 nsLayoutUtils::IsReallyFixedPos(aFrame)) {
2179 return true;
2183 return true;
2186 void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
2187 MOZ_ASSERT(mOwner);
2188 MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
2189 MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree());
2190 // We allow aParent.mOwner to be null, for cases when we're not starting at
2191 // the root of the tree. We also allow aParent.mOwner to be somewhere up our
2192 // expected owner chain not our immediate owner, which allows us creating long
2193 // chains of ServoRestyleStates in some cases where it's just not worth it.
2194 if (aParent.mOwner) {
2195 const nsIFrame* owner = ExpectedOwnerForChild(mOwner);
2196 if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) {
2197 MOZ_ASSERT(IsAnonBox(owner),
2198 "Should only have expected owner weirdness when anon boxes "
2199 "are involved");
2200 bool found = false;
2201 for (; owner; owner = ExpectedOwnerForChild(owner)) {
2202 if (owner == aParent.mOwner) {
2203 found = true;
2204 break;
2207 MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain");
2212 nsChangeHint ServoRestyleState::ChangesHandledFor(
2213 const nsIFrame* aFrame) const {
2214 if (!mOwner) {
2215 MOZ_ASSERT(!mChangesHandled);
2216 return mChangesHandled;
2219 MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) ||
2220 IsInReplicatedFixedPosTree(aFrame),
2221 "Missed some frame in the hierarchy?");
2222 return mChangesHandled;
2224 #endif
2226 void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) {
2227 MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(),
2228 "All our wrappers are anon boxes, and why would we restyle "
2229 "non-inheriting ones?");
2230 MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(),
2231 "All our wrappers are anon boxes, and why would we restyle "
2232 "non-inheriting ones?");
2233 MOZ_ASSERT(
2234 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent,
2235 "Someone should be using TableAwareParentFor");
2236 MOZ_ASSERT(
2237 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper,
2238 "Someone should be using TableAwareParentFor");
2239 // Make sure we only add first continuations.
2240 aWrapperFrame = aWrapperFrame->FirstContinuation();
2241 nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
2242 if (last == aWrapperFrame) {
2243 // Already queued up, nothing to do.
2244 return;
2247 // Make sure to queue up parents before children. But don't queue up
2248 // ancestors of non-anonymous boxes here; those are handled when we traverse
2249 // their non-anonymous kids.
2250 if (aWrapperFrame->ParentIsWrapperAnonBox()) {
2251 AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
2254 // If the append fails, we'll fail to restyle properly, but that's probably
2255 // better than crashing.
2256 if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
2257 aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
2261 void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) {
2262 size_t i = mPendingWrapperRestyleOffset;
2263 while (i < mPendingWrapperRestyles.Length()) {
2264 i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
2267 mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
2270 size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
2271 size_t aIndex) {
2272 // The frame at index aIndex is something we should restyle ourselves, but
2273 // following frames may need separate ServoRestyleStates to restyle.
2274 MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
2276 nsIFrame* cur = mPendingWrapperRestyles[aIndex];
2277 MOZ_ASSERT(cur->Style()->IsWrapperAnonBox());
2279 // Where is cur supposed to inherit from? From its parent frame, except in
2280 // the case when cur is a table, in which case it should be its grandparent.
2281 // Also, not in the case when the resulting frame would be a first-line; in
2282 // that case we should be inheriting from the block, and the first-line will
2283 // do its fixup later if needed.
2285 // Note that after we do all that fixup the parent we get might still not be
2286 // aParent; for example aParent could be a scrollframe, in which case we
2287 // should inherit from the scrollcontent frame. Or the parent might be some
2288 // continuation of aParent.
2290 // Try to assert as much as we can about the parent we actually end up using
2291 // without triggering bogus asserts in all those various edge cases.
2292 nsIFrame* parent = cur->GetParent();
2293 if (cur->IsTableFrame()) {
2294 MOZ_ASSERT(parent->IsTableWrapperFrame());
2295 parent = parent->GetParent();
2297 if (parent->IsLineFrame()) {
2298 parent = parent->GetParent();
2300 MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent ||
2301 (parent->Style()->IsInheritingAnonBox() &&
2302 parent->GetContent() == aParent->GetContent()));
2304 // Now "this" is a ServoRestyleState for aParent, so if parent is not a next
2305 // continuation (possibly across ib splits) of aParent we need a new
2306 // ServoRestyleState for the kid.
2307 Maybe<ServoRestyleState> parentRestyleState;
2308 nsIFrame* parentForRestyle =
2309 nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
2310 if (parentForRestyle != aParent) {
2311 parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
2312 Type::InFlow);
2314 ServoRestyleState& curRestyleState =
2315 parentRestyleState ? *parentRestyleState : *this;
2317 // This frame may already have been restyled. Even if it has, we can't just
2318 // return, because the next frame may be a kid of it that does need restyling.
2319 if (cur->IsWrapperAnonBoxNeedingRestyle()) {
2320 parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState);
2321 cur->SetIsWrapperAnonBoxNeedingRestyle(false);
2324 size_t numProcessed = 1;
2326 // Note: no overflow possible here, since aIndex < length.
2327 if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
2328 nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
2329 if (TableAwareParentFor(next) == cur &&
2330 next->IsWrapperAnonBoxNeedingRestyle()) {
2331 // It might be nice if we could do better than nsChangeHint_Empty. On
2332 // the other hand, presumably our mChangesHandled already has the bits
2333 // we really want here so in practice it doesn't matter.
2334 ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
2335 Type::InFlow,
2336 /* aAssertWrapperRestyleLength = */ false);
2337 numProcessed +=
2338 childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
2342 return numProcessed;
2345 nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) {
2346 // We want to get the anon box parent for aChild. where aChild has
2347 // ParentIsWrapperAnonBox().
2349 // For the most part this is pretty straightforward, but there are two
2350 // wrinkles. First, if aChild is a table, then we really want the parent of
2351 // its table wrapper.
2352 if (aChild->IsTableFrame()) {
2353 aChild = aChild->GetParent();
2354 MOZ_ASSERT(aChild->IsTableWrapperFrame());
2357 nsIFrame* parent = aChild->GetParent();
2358 // Now if parent is a cell-content frame, we actually want the cellframe.
2359 if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
2360 parent = parent->GetParent();
2361 } else if (parent->IsTableWrapperFrame()) {
2362 // Must be a caption. In that case we want the table here.
2363 MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
2364 parent = parent->PrincipalChildList().FirstChild();
2366 return parent;
2369 void RestyleManager::PostRestyleEvent(Element* aElement,
2370 RestyleHint aRestyleHint,
2371 nsChangeHint aMinChangeHint) {
2372 MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange),
2373 "Didn't expect explicit change hints to be neutral!");
2374 if (MOZ_UNLIKELY(IsDisconnected()) ||
2375 MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
2376 return;
2379 // We allow posting restyles from within change hint handling, but not from
2380 // within the restyle algorithm itself.
2381 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
2383 if (!aRestyleHint && !aMinChangeHint) {
2384 // FIXME(emilio): we should assert against this instead.
2385 return; // Nothing to do.
2388 // Assuming the restyle hints will invalidate cached style for
2389 // getComputedStyle, since we don't know if any of the restyling that we do
2390 // would affect undisplayed elements.
2391 if (aRestyleHint) {
2392 if (!(aRestyleHint & RestyleHint::ForAnimations())) {
2393 mHaveNonAnimationRestyles = true;
2396 IncrementUndisplayedRestyleGeneration();
2399 // Processing change hints sometimes causes new change hints to be generated,
2400 // and very occasionally, additional restyle hints. We collect the change
2401 // hints manually to avoid re-traversing the DOM to find them.
2402 if (mReentrantChanges && !aRestyleHint) {
2403 mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint});
2404 return;
2407 if (aRestyleHint || aMinChangeHint) {
2408 Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
2412 void RestyleManager::PostRestyleEventForAnimations(Element* aElement,
2413 PseudoStyleType aPseudoType,
2414 RestyleHint aRestyleHint) {
2415 Element* elementToRestyle =
2416 AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
2418 if (!elementToRestyle) {
2419 // FIXME: Bug 1371107: When reframing happens,
2420 // EffectCompositor::mElementsToRestyle still has unbound old pseudo
2421 // element. We should drop it.
2422 return;
2425 mPresContext->TriggeredAnimationRestyle();
2427 Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0));
2430 void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
2431 RestyleHint aRestyleHint) {
2432 // NOTE(emilio): The semantics of these methods are quite funny, in the sense
2433 // that we're not supposed to need to rebuild the actual stylist data.
2435 // That's handled as part of the MediumFeaturesChanged stuff, if needed.
2437 // Clear the cached style data only if we are guaranteed to process the whole
2438 // DOM tree again.
2440 // FIXME(emilio): Decouple this, probably. This probably just wants to reset
2441 // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon
2442 // box styles and such... But it doesn't really always need to clear the
2443 // initial style of the document and similar...
2444 if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
2445 StyleSet()->ClearCachedStyleData();
2448 DocumentStyleRootIterator iter(mPresContext->Document());
2449 while (Element* root = iter.GetNextStyleRoot()) {
2450 PostRestyleEvent(root, aRestyleHint, aExtraHint);
2453 // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect
2454 // non-inheriting anon boxes. It's not clear if we want to support that, but
2455 // if we do, we need to re-selector-match them here.
2458 /* static */
2459 void RestyleManager::ClearServoDataFromSubtree(Element* aElement,
2460 IncludeRoot aIncludeRoot) {
2461 if (aElement->HasServoData()) {
2462 StyleChildrenIterator it(aElement);
2463 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2464 if (n->IsElement()) {
2465 ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes);
2470 if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) {
2471 aElement->ClearServoData();
2472 MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits |
2473 NODE_NEEDS_FRAME));
2474 MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot());
2478 /* static */
2479 void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) {
2480 if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) {
2481 StyleChildrenIterator it(aElement);
2482 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2483 if (n->IsElement()) {
2484 ClearRestyleStateFromSubtree(n->AsElement());
2489 bool wasRestyled = false;
2490 Unused << Servo_TakeChangeHint(aElement, &wasRestyled);
2491 aElement->UnsetFlags(Element::kAllServoDescendantBits);
2495 * This struct takes care of encapsulating some common state that text nodes may
2496 * need to track during the post-traversal.
2498 * This is currently used to properly compute change hints when the parent
2499 * element of this node is a display: contents node, and also to avoid computing
2500 * the style for text children more than once per element.
2502 struct RestyleManager::TextPostTraversalState {
2503 public:
2504 TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext,
2505 bool aDisplayContentsParentStyleChanged,
2506 ServoRestyleState& aParentRestyleState)
2507 : mParentElement(aParentElement),
2508 mParentContext(aParentContext),
2509 mParentRestyleState(aParentRestyleState),
2510 mStyle(nullptr),
2511 mShouldPostHints(aDisplayContentsParentStyleChanged),
2512 mShouldComputeHints(aDisplayContentsParentStyleChanged),
2513 mComputedHint(nsChangeHint_Empty) {}
2515 nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); }
2517 ComputedStyle& ComputeStyle(nsIContent* aTextNode) {
2518 if (!mStyle) {
2519 mStyle = mParentRestyleState.StyleSet().ResolveStyleForText(
2520 aTextNode, &ParentStyle());
2522 MOZ_ASSERT(mStyle);
2523 return *mStyle;
2526 void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame,
2527 ComputedStyle& aNewStyle) {
2528 MOZ_ASSERT(aTextFrame);
2529 MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText);
2531 if (MOZ_LIKELY(!mShouldPostHints)) {
2532 return;
2535 ComputedStyle* oldStyle = aTextFrame->Style();
2536 MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText);
2538 // We rely on the fact that all the text children for the same element share
2539 // style to avoid recomputing style differences for all of them.
2541 // TODO(emilio): The above may not be true for ::first-{line,letter}, but
2542 // we'll cross that bridge when we support those in stylo.
2543 if (mShouldComputeHints) {
2544 mShouldComputeHints = false;
2545 uint32_t equalStructs;
2546 mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs);
2547 mComputedHint = NS_RemoveSubsumedHints(
2548 mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame));
2551 if (mComputedHint) {
2552 mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent,
2553 mComputedHint);
2557 private:
2558 ComputedStyle& ParentStyle() {
2559 if (!mParentContext) {
2560 mLazilyResolvedParentContext =
2561 ServoStyleSet::ResolveServoStyle(mParentElement);
2562 mParentContext = mLazilyResolvedParentContext;
2564 return *mParentContext;
2567 Element& mParentElement;
2568 ComputedStyle* mParentContext;
2569 RefPtr<ComputedStyle> mLazilyResolvedParentContext;
2570 ServoRestyleState& mParentRestyleState;
2571 RefPtr<ComputedStyle> mStyle;
2572 bool mShouldPostHints;
2573 bool mShouldComputeHints;
2574 nsChangeHint mComputedHint;
2577 static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet,
2578 nsStyleChangeList& aChangeList) {
2579 const nsStyleDisplay* display = aFrame->Style()->StyleDisplay();
2580 if (display->mTopLayer != StyleTopLayer::Top) {
2581 return;
2584 // Elements in the top layer are guaranteed to have absolute or fixed
2585 // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer.
2586 MOZ_ASSERT(display->IsAbsolutelyPositionedStyle());
2588 nsIFrame* backdropPlaceholder =
2589 aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild();
2590 if (!backdropPlaceholder) {
2591 return;
2594 MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
2595 nsIFrame* backdropFrame =
2596 nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
2597 MOZ_ASSERT(backdropFrame->IsBackdropFrame());
2598 MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
2599 PseudoStyleType::backdrop);
2601 RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
2602 *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, nullptr,
2603 aFrame->Style());
2605 // NOTE(emilio): We can't use the changes handled for the owner of the
2606 // backdrop frame, since it's out of flow, and parented to the viewport or
2607 // canvas frame (depending on the `position` value).
2608 MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
2609 backdropFrame->GetParent()->IsCanvasFrame());
2610 nsTArray<nsIFrame*> wrappersToRestyle;
2611 nsTArray<RefPtr<Element>> anchorsToSuppress;
2612 ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle,
2613 anchorsToSuppress);
2614 nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state);
2615 MOZ_ASSERT(anchorsToSuppress.IsEmpty());
2618 static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame,
2619 ServoRestyleState& aRestyleState) {
2620 MOZ_ASSERT(
2621 !aFrame->IsBlockFrameOrSubclass(),
2622 "You're probably duplicating work with UpdatePseudoElementStyles!");
2623 if (!aFrame->HasFirstLetterChild()) {
2624 return;
2627 // We need to find the block the first-letter is associated with so we can
2628 // find the right element for the first-letter's style resolution. Might as
2629 // well just delegate the whole thing to that block.
2630 nsIFrame* block = aFrame->GetParent();
2631 while (!block->IsBlockFrameOrSubclass()) {
2632 block = block->GetParent();
2635 static_cast<nsBlockFrame*>(block->FirstContinuation())
2636 ->UpdateFirstLetterStyle(aRestyleState);
2639 static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
2640 ComputedStyle& aOldContext,
2641 ServoRestyleState& aRestyleState) {
2642 auto pseudoType = aOldContext.GetPseudoType();
2643 MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
2644 MOZ_ASSERT(
2645 !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
2647 RefPtr<ComputedStyle> newStyle =
2648 aRestyleState.StyleSet().ResolvePseudoElementStyle(
2649 *aFrame->GetContent()->AsElement(), pseudoType, nullptr,
2650 aFrame->Style());
2652 uint32_t equalStructs; // Not used, actually.
2653 nsChangeHint childHint =
2654 aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
2655 if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
2656 !aFrame->IsColumnSpanInMulticolSubtree()) {
2657 childHint = NS_RemoveSubsumedHints(childHint,
2658 aRestyleState.ChangesHandledFor(aFrame));
2661 if (childHint) {
2662 if (childHint & nsChangeHint_ReconstructFrame) {
2663 // If we generate a reconstruct here, remove any non-reconstruct hints we
2664 // may have already generated for this content.
2665 aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent());
2667 aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(),
2668 childHint);
2671 aFrame->SetAdditionalComputedStyle(aIndex, newStyle);
2674 static void UpdateAdditionalComputedStyles(nsIFrame* aFrame,
2675 ServoRestyleState& aRestyleState) {
2676 MOZ_ASSERT(aFrame);
2677 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement());
2679 // FIXME(emilio): Consider adding a bit or something to avoid the initial
2680 // virtual call?
2681 uint32_t index = 0;
2682 while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) {
2683 UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState);
2687 static void UpdateFramePseudoElementStyles(nsIFrame* aFrame,
2688 ServoRestyleState& aRestyleState) {
2689 if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
2690 blockFrame->UpdatePseudoElementStyles(aRestyleState);
2691 } else {
2692 UpdateFirstLetterIfNeeded(aFrame, aRestyleState);
2695 UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(),
2696 aRestyleState.ChangeList());
2699 enum class ServoPostTraversalFlags : uint32_t {
2700 Empty = 0,
2701 // Whether parent was restyled.
2702 ParentWasRestyled = 1 << 0,
2703 // Skip sending accessibility notifications for all descendants.
2704 SkipA11yNotifications = 1 << 1,
2705 // Always send accessibility notifications if the element is shown.
2706 // The SkipA11yNotifications flag above overrides this flag.
2707 SendA11yNotificationsIfShown = 1 << 2,
2710 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
2712 #ifdef ACCESSIBILITY
2713 static bool IsVisibleForA11y(const ComputedStyle& aStyle) {
2714 return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert();
2717 static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) {
2718 return aStyle.StyleDisplay()->mContentVisibility !=
2719 StyleContentVisibility::Hidden;
2721 #endif
2723 // Send proper accessibility notifications and return post traversal
2724 // flags for kids.
2725 static ServoPostTraversalFlags SendA11yNotifications(
2726 nsPresContext* aPresContext, Element* aElement,
2727 const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
2728 ServoPostTraversalFlags aFlags) {
2729 using Flags = ServoPostTraversalFlags;
2730 MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) ||
2731 !(aFlags & Flags::SendA11yNotificationsIfShown),
2732 "The two a11y flags should never be set together");
2734 #ifdef ACCESSIBILITY
2735 nsAccessibilityService* accService = GetAccService();
2736 if (!accService) {
2737 // If we don't have accessibility service, accessibility is not
2738 // enabled. Just skip everything.
2739 return Flags::Empty;
2742 if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually !=
2743 aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
2744 if (aElement->GetParent() &&
2745 aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) {
2746 accService->NotifyOfTabPanelVisibilityChange(
2747 aPresContext->PresShell(), aElement,
2748 aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually);
2752 if (aFlags & Flags::SkipA11yNotifications) {
2753 // Propagate the skipping flag to descendants.
2754 return Flags::SkipA11yNotifications;
2757 bool needsNotify = false;
2758 const bool isVisible = IsVisibleForA11y(aNewStyle);
2759 const bool wasVisible = IsVisibleForA11y(aOldStyle);
2761 if (aFlags & Flags::SendA11yNotificationsIfShown) {
2762 if (!isVisible) {
2763 // Propagate the sending-if-shown flag to descendants.
2764 return Flags::SendA11yNotificationsIfShown;
2766 // We have asked accessibility service to remove the whole subtree
2767 // of element which becomes invisible from the accessible tree, but
2768 // this element is visible, so we need to add it back.
2769 needsNotify = true;
2770 } else {
2771 // If we shouldn't skip in any case, we need to check whether our own
2772 // visibility has changed.
2773 // Also notify if the subtree visibility change due to content-visibility.
2774 const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle);
2775 const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle);
2776 needsNotify =
2777 wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible;
2780 if (needsNotify) {
2781 PresShell* presShell = aPresContext->PresShell();
2782 if (isVisible) {
2783 accService->ContentRangeInserted(presShell, aElement,
2784 aElement->GetNextSibling());
2785 // We are adding the subtree. Accessibility service would handle
2786 // descendants, so we should just skip them from notifying.
2787 return Flags::SkipA11yNotifications;
2789 if (wasVisible) {
2790 // Remove the subtree of this invisible element, and ask any shown
2791 // descendant to add themselves back.
2792 accService->ContentRemoved(presShell, aElement);
2793 return Flags::SendA11yNotificationsIfShown;
2796 #endif
2798 return Flags::Empty;
2801 bool RestyleManager::ProcessPostTraversal(Element* aElement,
2802 ServoRestyleState& aRestyleState,
2803 ServoPostTraversalFlags aFlags) {
2804 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
2805 nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
2807 MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(),
2808 "Element without Servo data on a post-traversal? How?");
2810 // NOTE(emilio): This is needed because for table frames the bit is set on the
2811 // table wrapper (which is the primary frame), not on the table itself.
2812 const bool isOutOfFlow =
2813 primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
2815 // We need this because any column-spanner's parent frame is not its DOM
2816 // parent's primary frame. We need some special check similar to out-of-flow
2817 // frames.
2818 const bool isColumnSpan =
2819 primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree();
2821 // Grab the change hint from Servo.
2822 bool wasRestyled = false;
2823 nsChangeHint changeHint =
2824 static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled));
2826 RefPtr<ComputedStyle> upToDateStyleIfRestyled =
2827 wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr;
2829 // We should really fix the weird primary frame mapping for image maps
2830 // (bug 135040)...
2831 if (styleFrame && styleFrame->GetContent() != aElement) {
2832 MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass());
2833 styleFrame = nullptr;
2836 // Handle lazy frame construction by posting a reconstruct for any lazily-
2837 // constructed roots.
2838 if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
2839 changeHint |= nsChangeHint_ReconstructFrame;
2840 MOZ_ASSERT(!styleFrame);
2843 if (styleFrame) {
2844 MOZ_ASSERT(primaryFrame);
2846 nsIFrame* maybeAnonBoxChild;
2847 if (isOutOfFlow) {
2848 maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
2849 } else {
2850 maybeAnonBoxChild = primaryFrame;
2851 // Do not subsume change hints for the column-spanner.
2852 if (!isColumnSpan) {
2853 changeHint = NS_RemoveSubsumedHints(
2854 changeHint, aRestyleState.ChangesHandledFor(styleFrame));
2858 // If the parent wasn't restyled, the styles of our anon box parents won't
2859 // change either.
2860 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
2861 maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
2862 aRestyleState.AddPendingWrapperRestyle(
2863 ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
2866 // If we don't have a ::marker pseudo-element, but need it, then
2867 // reconstruct the frame. (The opposite situation implies 'display'
2868 // changes so doesn't need to be handled explicitly here.)
2869 if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() &&
2870 styleFrame->IsBlockFrameOrSubclass() &&
2871 !nsLayoutUtils::GetMarkerPseudo(aElement)) {
2872 RefPtr<ComputedStyle> pseudoStyle =
2873 aRestyleState.StyleSet().ProbePseudoElementStyle(
2874 *aElement, PseudoStyleType::marker, nullptr,
2875 upToDateStyleIfRestyled);
2876 if (pseudoStyle) {
2877 changeHint |= nsChangeHint_ReconstructFrame;
2882 // Although we shouldn't generate non-ReconstructFrame hints for elements with
2883 // no frames, we can still get them here if they were explicitly posted by
2884 // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
2885 // :visited. Skip processing these hints if there is no frame.
2886 if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) &&
2887 changeHint) {
2888 aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
2891 // If our change hint is reconstruct, we delegate to the frame constructor,
2892 // which consumes the new style and expects the old style to be on the frame.
2894 // XXXbholley: We should teach the frame constructor how to clear the dirty
2895 // descendants bit to avoid the traversal here.
2896 if (changeHint & nsChangeHint_ReconstructFrame) {
2897 if (wasRestyled &&
2898 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
2899 const bool wasAbsPos =
2900 styleFrame &&
2901 styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
2902 auto* newDisp = upToDateStyleIfRestyled->StyleDisplay();
2903 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
2905 // We need to do the position check here rather than in
2906 // DidSetComputedStyle because changing position reframes.
2908 // We suppress adjustments whenever we change from being display: none to
2909 // be an abspos.
2911 // Similarly, for other changes from abspos to non-abspos styles.
2913 // TODO(emilio): I _think_ chrome won't suppress adjustments whenever
2914 // `display` changes. But that causes some infinite loops in cases like
2915 // bug 1568778.
2916 if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) {
2917 aRestyleState.AddPendingScrollAnchorSuppression(aElement);
2920 ClearRestyleStateFromSubtree(aElement);
2921 return true;
2924 // TODO(emilio): We could avoid some refcount traffic here, specially in the
2925 // ComputedStyle case, which uses atomic refcounting.
2927 // Hold the ComputedStyle alive, because it could become a dangling pointer
2928 // during the replacement. In practice it's not a huge deal, but better not
2929 // playing with dangling pointers if not needed.
2931 // NOTE(emilio): We could keep around the old computed style for display:
2932 // contents elements too, but we don't really need it right now.
2933 RefPtr<ComputedStyle> oldOrDisplayContentsStyle =
2934 styleFrame ? styleFrame->Style() : nullptr;
2936 MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)),
2937 "display: contents node has a frame, yet we didn't reframe it"
2938 " above?");
2939 const bool isDisplayContents = !styleFrame && aElement->HasServoData() &&
2940 Servo_Element_IsDisplayContents(aElement);
2941 if (isDisplayContents) {
2942 oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement);
2945 Maybe<ServoRestyleState> thisFrameRestyleState;
2946 if (styleFrame) {
2947 auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow
2948 : ServoRestyleState::Type::InFlow;
2950 thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type);
2953 // We can't really assume as used changes from display: contents elements (or
2954 // other elements without frames).
2955 ServoRestyleState& childrenRestyleState =
2956 thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
2958 ComputedStyle* upToDateStyle =
2959 wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle;
2961 ServoPostTraversalFlags childrenFlags =
2962 wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled
2963 : ServoPostTraversalFlags::Empty;
2965 if (wasRestyled && oldOrDisplayContentsStyle) {
2966 MOZ_ASSERT(styleFrame || isDisplayContents);
2968 // We want to walk all the continuations here, even the ones with different
2969 // styles. In practice, the only reason we get continuations with different
2970 // styles here is ::first-line (::first-letter never affects element
2971 // styles). But in that case, newStyle is the right context for the
2972 // _later_ continuations anyway (the ones not affected by ::first-line), not
2973 // the earlier ones, so there is no point stopping right at the point when
2974 // we'd actually be setting the right ComputedStyle.
2976 // This does mean that we may be setting the wrong ComputedStyle on our
2977 // initial continuations; ::first-line fixes that up after the fact.
2978 for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) {
2979 MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0));
2980 f->SetComputedStyle(upToDateStyle);
2983 if (styleFrame) {
2984 UpdateAdditionalComputedStyles(styleFrame, aRestyleState);
2987 if (!aElement->GetParent()) {
2988 // This is the root. Update styles on the viewport as needed.
2989 ViewportFrame* viewport =
2990 do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
2991 if (viewport) {
2992 // NB: The root restyle state, not the one for our children!
2993 viewport->UpdateStyle(aRestyleState);
2997 // Some changes to animations don't affect the computed style and yet still
2998 // require the layer to be updated. For example, pausing an animation via
2999 // the Web Animations API won't affect an element's style but still
3000 // requires to update the animation on the layer.
3002 // We can sometimes reach this when the animated style is being removed.
3003 // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform
3004 // style or not, we need to call it *after* setting |newStyle| to
3005 // |styleFrame| to ensure the animated transform has been removed first.
3006 AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint,
3007 aRestyleState.ChangeList());
3009 childrenFlags |= SendA11yNotifications(mPresContext, aElement,
3010 *oldOrDisplayContentsStyle,
3011 *upToDateStyle, aFlags);
3014 const bool traverseElementChildren =
3015 aElement->HasAnyOfFlags(Element::kAllServoDescendantBits);
3016 const bool traverseTextChildren =
3017 wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
3018 bool recreatedAnyContext = wasRestyled;
3019 if (traverseElementChildren || traverseTextChildren) {
3020 StyleChildrenIterator it(aElement);
3021 TextPostTraversalState textState(*aElement, upToDateStyle,
3022 isDisplayContents && wasRestyled,
3023 childrenRestyleState);
3024 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
3025 if (traverseElementChildren && n->IsElement()) {
3026 recreatedAnyContext |= ProcessPostTraversal(
3027 n->AsElement(), childrenRestyleState, childrenFlags);
3028 } else if (traverseTextChildren && n->IsText()) {
3029 recreatedAnyContext |= ProcessPostTraversalForText(
3030 n, textState, childrenRestyleState, childrenFlags);
3035 // We want to update frame pseudo-element styles after we've traversed our
3036 // kids, because some of those updates (::first-line/::first-letter) need to
3037 // modify the styles of the kids, and the child traversal above would just
3038 // clobber those modifications.
3039 if (styleFrame) {
3040 if (wasRestyled) {
3041 // Make sure to update anon boxes and pseudo bits after updating text,
3042 // otherwise ProcessPostTraversalForText could clobber first-letter
3043 // styles, for example.
3044 styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
3046 // Process anon box wrapper frames before ::first-line bits, but _after_
3047 // owned anon boxes, since the children wrapper anon boxes could be
3048 // inheriting from our own owned anon boxes.
3049 childrenRestyleState.ProcessWrapperRestyles(styleFrame);
3050 if (wasRestyled) {
3051 UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
3052 } else if (traverseElementChildren &&
3053 styleFrame->IsBlockFrameOrSubclass()) {
3054 // Even if we were not restyled, if we're a block with a first-line and
3055 // one of our descendant elements which is on the first line was restyled,
3056 // we need to update the styles of things on the first line, because
3057 // they're wrong now.
3059 // FIXME(bz) Could we do better here? For example, could we keep track of
3060 // frames that are "block with a ::first-line so we could avoid
3061 // IsFrameOfType() and digging about for the first-line frame if not?
3062 // Could we keep track of whether the element children we actually restyle
3063 // are affected by first-line? Something else? Bug 1385443 tracks making
3064 // this better.
3065 nsIFrame* firstLineFrame =
3066 static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame();
3067 if (firstLineFrame) {
3068 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
3069 ReparentComputedStyleForFirstLine(kid);
3075 aElement->UnsetFlags(Element::kAllServoDescendantBits);
3076 return recreatedAnyContext;
3079 bool RestyleManager::ProcessPostTraversalForText(
3080 nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState,
3081 ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) {
3082 // Handle lazy frame construction.
3083 if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
3084 aPostTraversalState.ChangeList().AppendChange(
3085 nullptr, aTextNode, nsChangeHint_ReconstructFrame);
3086 return true;
3089 // Handle restyle.
3090 nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
3091 if (!primaryFrame) {
3092 return false;
3095 // If the parent wasn't restyled, the styles of our anon box parents won't
3096 // change either.
3097 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
3098 primaryFrame->ParentIsWrapperAnonBox()) {
3099 aRestyleState.AddPendingWrapperRestyle(
3100 ServoRestyleState::TableAwareParentFor(primaryFrame));
3103 ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode);
3104 aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle);
3106 // We want to walk all the continuations here, even the ones with different
3107 // styles. In practice, the only reasons we get continuations with different
3108 // styles are ::first-line and ::first-letter. But in those cases,
3109 // newStyle is the right context for the _later_ continuations anyway (the
3110 // ones not affected by ::first-line/::first-letter), not the earlier ones,
3111 // so there is no point stopping right at the point when we'd actually be
3112 // setting the right ComputedStyle.
3114 // This does mean that we may be setting the wrong ComputedStyle on our
3115 // initial continuations; ::first-line/::first-letter fix that up after the
3116 // fact.
3117 for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) {
3118 f->SetComputedStyle(&newStyle);
3121 return true;
3124 void RestyleManager::ClearSnapshots() {
3125 for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
3126 iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
3127 iter.Remove();
3131 ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) {
3132 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3134 // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
3135 // we do to restyle stuff for reconstruction, for example.
3137 // It seems to be the case that we always flush in between that happens and
3138 // the next attribute change, so we can assert that we haven't handled the
3139 // snapshot here yet. If this assertion didn't hold, we'd need to unset that
3140 // flag from here too.
3142 // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
3143 // so this becomes much easier to reason about. Today is not that day though.
3144 MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT));
3146 ServoElementSnapshot* snapshot =
3147 mSnapshots.GetOrInsertNew(&aElement, aElement);
3148 aElement.SetFlags(ELEMENT_HAS_SNAPSHOT);
3150 // Now that we have a snapshot, make sure a restyle is triggered.
3151 aElement.NoteDirtyForServo();
3152 return *snapshot;
3155 void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
3156 nsPresContext* presContext = PresContext();
3157 PresShell* presShell = presContext->PresShell();
3159 MOZ_ASSERT(presContext->Document(), "No document? Pshaw!");
3160 // FIXME(emilio): In the "flush animations" case, ideally, we should only
3161 // recascade animation styles running on the compositor, so we shouldn't care
3162 // about other styles, or new rules that apply to the page...
3164 // However, that's not true as of right now, see bug 1388031 and bug 1388692.
3165 MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) ||
3166 !presContext->HasPendingMediaQueryUpdates(),
3167 "Someone forgot to update media queries?");
3168 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
3169 MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?");
3171 if (MOZ_UNLIKELY(!presShell->DidInitialize())) {
3172 // PresShell::FlushPendingNotifications doesn't early-return in the case
3173 // where the PresShell hasn't yet been initialized (and therefore we haven't
3174 // yet done the initial style traversal of the DOM tree). We should arguably
3175 // fix up the callers and assert against this case, but we just detect and
3176 // handle it for now.
3177 return;
3180 // It'd be bad!
3181 PresShell::AutoAssertNoFlush noReentrantFlush(*presShell);
3183 // Create a AnimationsWithDestroyedFrame during restyling process to
3184 // stop animations and transitions on elements that have no frame at the end
3185 // of the restyling process.
3186 AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
3188 ServoStyleSet* styleSet = StyleSet();
3189 Document* doc = presContext->Document();
3191 // Ensure the refresh driver is active during traversal to avoid mutating
3192 // mActiveTimer and mMostRecentRefresh time.
3193 presContext->RefreshDriver()->MostRecentRefresh();
3195 if (!doc->GetServoRestyleRoot()) {
3196 // This might post new restyles, so need to do it here. Don't do it if we're
3197 // already going to restyle tho, so that we don't potentially reflow with
3198 // dirty styling.
3199 presContext->UpdateContainerQueryStyles();
3200 presContext->FinishedContainerQueryUpdate();
3203 // Perform the Servo traversal, and the post-traversal if required. We do this
3204 // in a loop because certain rare paths in the frame constructor can trigger
3205 // additional style invalidations.
3207 // FIXME(emilio): Confirm whether that's still true now that XBL is gone.
3208 mInStyleRefresh = true;
3209 if (mHaveNonAnimationRestyles) {
3210 ++mAnimationGeneration;
3213 if (mRestyleForCSSRuleChanges) {
3214 aFlags |= ServoTraversalFlags::ForCSSRuleChanges;
3217 while (styleSet->StyleDocument(aFlags)) {
3218 ClearSnapshots();
3220 // Select scroll anchors for frames that have been scrolled. Do this
3221 // before processing restyled frames so that anchor nodes are correctly
3222 // marked when directly moving frames with RecomputePosition.
3223 presContext->PresShell()->FlushPendingScrollAnchorSelections();
3225 nsStyleChangeList currentChanges;
3226 bool anyStyleChanged = false;
3228 // Recreate styles , and queue up change hints (which also handle lazy frame
3229 // construction).
3230 nsTArray<RefPtr<Element>> anchorsToSuppress;
3233 DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
3234 while (Element* root = iter.GetNextStyleRoot()) {
3235 nsTArray<nsIFrame*> wrappersToRestyle;
3236 ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle,
3237 anchorsToSuppress);
3238 ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty;
3239 anyStyleChanged |= ProcessPostTraversal(root, state, flags);
3242 // We want to suppress adjustments the current (before-change) scroll
3243 // anchor container now, and save a reference to the content node so that
3244 // we can suppress them in the after-change scroll anchor .
3245 for (Element* element : anchorsToSuppress) {
3246 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3247 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3248 container->SuppressAdjustments();
3254 doc->ClearServoRestyleRoot();
3255 ClearSnapshots();
3257 // Process the change hints.
3259 // Unfortunately, the frame constructor can generate new change hints while
3260 // processing existing ones. We redirect those into a secondary queue and
3261 // iterate until there's nothing left.
3263 ReentrantChangeList newChanges;
3264 mReentrantChanges = &newChanges;
3265 while (!currentChanges.IsEmpty()) {
3266 ProcessRestyledFrames(currentChanges);
3267 MOZ_ASSERT(currentChanges.IsEmpty());
3268 for (ReentrantChange& change : newChanges) {
3269 if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
3270 !change.mContent->GetPrimaryFrame()) {
3271 // SVG Elements post change hints without ensuring that the primary
3272 // frame will be there after that (see bug 1366142).
3274 // Just ignore those, since we can't really process them.
3275 continue;
3277 currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
3278 change.mContent, change.mHint);
3280 newChanges.Clear();
3282 mReentrantChanges = nullptr;
3285 // Suppress adjustments in the after-change scroll anchors if needed, now
3286 // that we're done reframing everything.
3287 for (Element* element : anchorsToSuppress) {
3288 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3289 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3290 container->SuppressAdjustments();
3295 if (anyStyleChanged) {
3296 // Maybe no styles changed when:
3298 // * Only explicit change hints were posted in the first place.
3299 // * When an attribute or state change in the content happens not to need
3300 // a restyle after all.
3302 // In any case, we don't need to increment the restyle generation in that
3303 // case.
3304 IncrementRestyleGeneration();
3307 mInStyleRefresh = false;
3308 presContext->UpdateContainerQueryStyles();
3309 mInStyleRefresh = true;
3312 doc->ClearServoRestyleRoot();
3313 presContext->FinishedContainerQueryUpdate();
3314 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
3315 ClearSnapshots();
3316 styleSet->AssertTreeIsClean();
3318 mHaveNonAnimationRestyles = false;
3319 mRestyleForCSSRuleChanges = false;
3320 mInStyleRefresh = false;
3322 // Now that everything has settled, see if we have enough free rule nodes in
3323 // the tree to warrant sweeping them.
3324 styleSet->MaybeGCRuleTree();
3326 // Note: We are in the scope of |animationsWithDestroyedFrame|, so
3327 // |mAnimationsWithDestroyedFrame| is still valid.
3328 MOZ_ASSERT(mAnimationsWithDestroyedFrame);
3329 mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
3332 #ifdef DEBUG
3333 static void VerifyFlatTree(const nsIContent& aContent) {
3334 StyleChildrenIterator iter(&aContent);
3336 for (auto* content = iter.GetNextChild(); content;
3337 content = iter.GetNextChild()) {
3338 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
3339 VerifyFlatTree(*content);
3342 #endif
3344 void RestyleManager::ProcessPendingRestyles() {
3345 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT);
3346 #ifdef DEBUG
3347 if (auto* root = mPresContext->Document()->GetRootElement()) {
3348 VerifyFlatTree(*root);
3350 #endif
3352 DoProcessPendingRestyles(ServoTraversalFlags::Empty);
3355 void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() {
3356 if (mSnapshots.IsEmpty()) {
3357 return;
3359 for (const auto& key : mSnapshots.Keys()) {
3360 // Servo data for the element might have been dropped. (e.g. by removing
3361 // from its document)
3362 if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3363 Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots);
3366 ClearSnapshots();
3369 void RestyleManager::UpdateOnlyAnimationStyles() {
3370 bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
3371 if (!doCSS) {
3372 return;
3375 DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations);
3378 void RestyleManager::ElementStateChanged(Element* aElement,
3379 ElementState aChangedBits) {
3380 #ifdef EARLY_BETA_OR_EARLIER
3381 if (MOZ_UNLIKELY(mInStyleRefresh)) {
3382 MOZ_CRASH_UNSAFE_PRINTF(
3383 "Element state change during style refresh (%" PRIu64 ")",
3384 aChangedBits.GetInternalValue());
3386 #endif
3388 const ElementState kVisitedAndUnvisited =
3389 ElementState::VISITED | ElementState::UNVISITED;
3391 // We'll restyle when the relevant visited query finishes, regardless of the
3392 // style (see Link::VisitedQueryFinished). So there's no need to do anything
3393 // as a result of this state change just yet.
3395 // Note that this check checks for _both_ bits: This is only true when visited
3396 // changes to unvisited or vice-versa, but not when we start or stop being a
3397 // link itself.
3398 if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) {
3399 aChangedBits &= ~kVisitedAndUnvisited;
3400 if (aChangedBits.IsEmpty()) {
3401 return;
3405 if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) {
3406 Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint);
3409 // Don't bother taking a snapshot if no rules depend on these state bits.
3411 // We always take a snapshot for the LTR/RTL event states, since Servo doesn't
3412 // track those bits in the same way, and we know that :dir() rules are always
3413 // present in UA style sheets.
3414 if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) &&
3415 !StyleSet()->HasStateDependency(*aElement, aChangedBits)) {
3416 return;
3419 // Assuming we need to invalidate cached style in getComputedStyle for
3420 // undisplayed elements, since we don't know if it is needed.
3421 IncrementUndisplayedRestyleGeneration();
3423 if (!aElement->HasServoData() &&
3424 !(aElement->GetSelectorFlags() &
3425 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3426 return;
3429 ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
3430 ElementState previousState = aElement->StyleState() ^ aChangedBits;
3431 snapshot.AddState(previousState);
3433 ServoStyleSet& styleSet = *StyleSet();
3434 MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
3435 MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
3438 void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
3439 Element* aChild,
3440 ElementState aChangedBits) {
3441 const auto* parentNode = aChild->GetParentNode();
3442 MOZ_ASSERT(parentNode);
3443 const auto parentFlags = parentNode->GetSelectorFlags();
3444 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3445 return;
3448 if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
3449 RestyleSiblingsForNthOf(aChild, parentFlags);
3453 static inline bool AttributeInfluencesOtherPseudoClassState(
3454 const Element& aElement, const nsAtom* aAttribute) {
3455 // We must record some state for :-moz-table-border-nonzero and
3456 // :-moz-select-list-box.
3458 if (aAttribute == nsGkAtoms::border) {
3459 return aElement.IsHTMLElement(nsGkAtoms::table);
3462 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
3463 return aElement.IsHTMLElement(nsGkAtoms::select);
3466 return false;
3469 static inline bool NeedToRecordAttrChange(
3470 const ServoStyleSet& aStyleSet, const Element& aElement,
3471 int32_t aNameSpaceID, nsAtom* aAttribute,
3472 bool* aInfluencesOtherPseudoClassState) {
3473 *aInfluencesOtherPseudoClassState =
3474 AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
3476 // If the attribute influences one of the pseudo-classes that are backed by
3477 // attributes, we just record it.
3478 if (*aInfluencesOtherPseudoClassState) {
3479 return true;
3482 // We assume that id and class attributes are used in class/id selectors, and
3483 // thus record them.
3485 // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
3486 // presumably we could try to filter the old and new id, but it's not clear
3487 // it's worth it.
3488 if (aNameSpaceID == kNameSpaceID_None &&
3489 (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
3490 return true;
3493 // We always record lang="", even though we force a subtree restyle when it
3494 // changes, since it can change how its siblings match :lang(..) due to
3495 // selectors like :lang(..) + div.
3496 if (aAttribute == nsGkAtoms::lang) {
3497 return true;
3500 // Otherwise, just record the attribute change if a selector in the page may
3501 // reference it from an attribute selector.
3502 return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
3505 void RestyleManager::AttributeWillChange(Element* aElement,
3506 int32_t aNameSpaceID,
3507 nsAtom* aAttribute, int32_t aModType) {
3508 TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute);
3511 void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) {
3512 TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None,
3513 nsGkAtoms::_class);
3516 void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement,
3517 int32_t aNameSpaceID,
3518 nsAtom* aAttribute) {
3519 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3521 bool influencesOtherPseudoClassState;
3522 if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute,
3523 &influencesOtherPseudoClassState)) {
3524 return;
3527 // We cannot tell if the attribute change will affect the styles of
3528 // undisplayed elements, because we don't actually restyle those elements
3529 // during the restyle traversal. So just assume that the attribute change can
3530 // cause the style to change.
3531 IncrementUndisplayedRestyleGeneration();
3533 // Relative selector invalidation travels ancestor and earlier sibling
3534 // direction, so it's very possible that it invalidates a styled element.
3535 if (!aElement.HasServoData() &&
3536 !(aElement.GetSelectorFlags() &
3537 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3538 return;
3541 // Some other random attribute changes may also affect the transitions,
3542 // so we also set this true here.
3543 mHaveNonAnimationRestyles = true;
3545 ServoElementSnapshot& snapshot = SnapshotFor(aElement);
3546 snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
3548 if (influencesOtherPseudoClassState) {
3549 snapshot.AddOtherPseudoClassState(aElement);
3553 // For some attribute changes we must restyle the whole subtree:
3555 // * lang="" and xml:lang="" can affect all descendants due to :lang()
3556 // * exportparts can affect all descendant parts. We could certainly integrate
3557 // it better in the invalidation machinery if it was necessary.
3558 static inline bool AttributeChangeRequiresSubtreeRestyle(
3559 const Element& aElement, nsAtom* aAttr) {
3560 if (aAttr == nsGkAtoms::exportparts) {
3561 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for
3562 // exportparts attribute changes?
3563 return !!aElement.GetShadowRoot();
3565 return aAttr == nsGkAtoms::lang;
3568 void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
3569 nsAtom* aAttribute, int32_t aModType,
3570 const nsAttrValue* aOldValue) {
3571 MOZ_ASSERT(!mInStyleRefresh);
3573 auto changeHint = nsChangeHint(0);
3574 auto restyleHint = RestyleHint{0};
3576 changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
3578 MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
3579 MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
3581 if (aAttribute == nsGkAtoms::style) {
3582 restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
3583 } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
3584 restyleHint |= RestyleHint::RestyleSubtree();
3585 } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) {
3586 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part
3587 // attribute changes?
3588 restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS;
3591 if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
3592 // See if we have appearance information for a theme.
3593 StyleAppearance appearance =
3594 primaryFrame->StyleDisplay()->EffectiveAppearance();
3595 if (appearance != StyleAppearance::None) {
3596 nsITheme* theme = PresContext()->Theme();
3597 if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) {
3598 bool repaint = false;
3599 theme->WidgetStateChanged(primaryFrame, appearance, aAttribute,
3600 &repaint, aOldValue);
3601 if (repaint) {
3602 changeHint |= nsChangeHint_RepaintFrame;
3607 primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
3610 if (restyleHint || changeHint) {
3611 Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
3614 if (restyleHint) {
3615 // Assuming we need to invalidate cached style in getComputedStyle for
3616 // undisplayed elements, since we don't know if it is needed.
3617 IncrementUndisplayedRestyleGeneration();
3619 // If we change attributes, we have to mark this to be true, so we will
3620 // increase the animation generation for the new created transition if any.
3621 mHaveNonAnimationRestyles = true;
3625 void RestyleManager::RestyleSiblingsForNthOf(Element* aChild,
3626 NodeSelectorFlags aParentFlags) {
3627 StyleSet()->RestyleSiblingsForNthOf(*aChild,
3628 static_cast<uint32_t>(aParentFlags));
3631 void RestyleManager::MaybeRestyleForNthOfAttribute(
3632 Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3633 const auto* parentNode = aChild->GetParentNode();
3634 MOZ_ASSERT(parentNode);
3635 const auto parentFlags = parentNode->GetSelectorFlags();
3636 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3637 return;
3639 if (!aChild->HasServoData()) {
3640 return;
3643 bool mightHaveNthOfDependency;
3644 auto& styleSet = *StyleSet();
3645 if (aAttribute == nsGkAtoms::id) {
3646 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3647 ? aOldValue->GetAtomValue()
3648 : nullptr;
3649 mightHaveNthOfDependency =
3650 styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
3651 } else if (aAttribute == nsGkAtoms::_class) {
3652 mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
3653 } else {
3654 mightHaveNthOfDependency =
3655 styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
3658 if (mightHaveNthOfDependency) {
3659 RestyleSiblingsForNthOf(aChild, parentFlags);
3663 void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
3664 Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3665 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3666 return;
3668 auto& styleSet = *StyleSet();
3669 if (aAttribute == nsGkAtoms::id) {
3670 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3671 ? aOldValue->GetAtomValue()
3672 : nullptr;
3673 styleSet.MaybeInvalidateRelativeSelectorIDDependency(
3674 *aElement, oldAtom, aElement->GetID(), Snapshots());
3675 } else if (aAttribute == nsGkAtoms::_class) {
3676 styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement,
3677 Snapshots());
3678 } else {
3679 styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(
3680 *aElement, aAttribute, Snapshots());
3684 void RestyleManager::MaybeRestyleForRelativeSelectorState(
3685 ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
3686 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3687 return;
3689 aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(
3690 *aElement, aChangedBits, Snapshots());
3693 void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
3694 // This is only called when moving frames in or out of the first-line
3695 // pseudo-element (or one of its descendants). We can't say much about
3696 // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into
3697 // something inside an inline-block on the first line the ancestors could be
3698 // totally arbitrary), but we will definitely find a line frame on the
3699 // ancestor chain. Note that the lineframe may not actually be the one that
3700 // corresponds to ::first-line; when we're moving _out_ of the ::first-line it
3701 // will be one of the continuations instead.
3702 #ifdef DEBUG
3704 nsIFrame* f = aFrame->GetParent();
3705 while (f && !f->IsLineFrame()) {
3706 f = f->GetParent();
3708 MOZ_ASSERT(f, "Must have found a first-line frame");
3710 #endif
3712 DoReparentComputedStyleForFirstLine(aFrame, *StyleSet());
3715 static bool IsFrameAboutToGoAway(nsIFrame* aFrame) {
3716 auto* element = Element::FromNode(aFrame->GetContent());
3717 if (!element) {
3718 return false;
3720 return !element->HasServoData();
3723 void RestyleManager::DoReparentComputedStyleForFirstLine(
3724 nsIFrame* aFrame, ServoStyleSet& aStyleSet) {
3725 if (aFrame->IsBackdropFrame()) {
3726 // Style context of backdrop frame has no parent style, and thus we do not
3727 // need to reparent it.
3728 return;
3731 if (IsFrameAboutToGoAway(aFrame)) {
3732 // We're entering a display: none subtree, which we know it's going to get
3733 // rebuilt. Don't bother reparenting.
3734 return;
3737 if (aFrame->IsPlaceholderFrame()) {
3738 // Also reparent the out-of-flow and all its continuations. We're doing
3739 // this to match Gecko for now, but it's not clear that this behavior is
3740 // correct per spec. It's certainly pretty odd for out-of-flows whose
3741 // containing block is not within the first line.
3743 // Right now we're somewhat inconsistent in this testcase:
3745 // <style>
3746 // div { color: orange; clear: left; }
3747 // div::first-line { color: blue; }
3748 // </style>
3749 // <div>
3750 // <span style="float: left">What color is this text?</span>
3751 // </div>
3752 // <div>
3753 // <span><span style="float: left">What color is this text?</span></span>
3754 // </div>
3756 // We make the first float orange and the second float blue. On the other
3757 // hand, if the float were within an inline-block that was on the first
3758 // line, arguably it _should_ inherit from the ::first-line...
3759 nsIFrame* outOfFlow =
3760 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
3761 MOZ_ASSERT(outOfFlow, "no out-of-flow frame");
3762 for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) {
3763 DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet);
3767 // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try
3768 // to remove it?
3769 nsIFrame* providerFrame;
3770 ComputedStyle* newParentStyle =
3771 aFrame->GetParentComputedStyle(&providerFrame);
3772 // If our provider is our child, we want to reparent it first, because we
3773 // inherit style from it.
3774 bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
3775 nsIFrame* providerChild = nullptr;
3776 if (isChild) {
3777 DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet);
3778 // Get the style again after ReparentComputedStyle() which might have
3779 // changed it.
3780 newParentStyle = providerFrame->Style();
3781 providerChild = providerFrame;
3782 MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
3783 "Out of flow provider?");
3786 if (!newParentStyle) {
3787 // No need to do anything here for this frame, but we should still reparent
3788 // its descendants, because those may have styles that inherit from the
3789 // parent of this frame (e.g. non-anonymous columns in an anonymous
3790 // colgroup).
3791 MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(),
3792 "Why did this frame not end up with a parent context?");
3793 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3794 return;
3797 bool isElement = aFrame->GetContent()->IsElement();
3799 // We probably don't want to initiate transitions from ReparentComputedStyle,
3800 // since we call it during frame construction rather than in response to
3801 // dynamic changes.
3802 // Also see the comment at the start of
3803 // nsTransitionManager::ConsiderInitiatingTransition.
3805 // We don't try to do the fancy copying from previous continuations that
3806 // GeckoRestyleManager does here, because that relies on knowing the parents
3807 // of ComputedStyles, and we don't know those.
3808 ComputedStyle* oldStyle = aFrame->Style();
3809 Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr;
3810 ComputedStyle* newParent = newParentStyle;
3812 if (!providerFrame) {
3813 // No providerFrame means we inherited from a display:contents thing. Our
3814 // layout parent style is the style of our nearest ancestor frame. But we
3815 // have to be careful to do that with our placeholder, not with us, if we're
3816 // out of flow.
3817 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
3818 aFrame->FirstContinuation()
3819 ->GetPlaceholderFrame()
3820 ->GetLayoutParentStyleForOutOfFlow(&providerFrame);
3821 } else {
3822 providerFrame = nsIFrame::CorrectStyleParentFrame(
3823 aFrame->GetParent(), oldStyle->GetPseudoType());
3826 ComputedStyle* layoutParent = providerFrame->Style();
3828 RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle(
3829 oldStyle, newParent, layoutParent, ourElement);
3830 aFrame->SetComputedStyle(newStyle);
3832 // This logic somewhat mirrors the logic in
3833 // RestyleManager::ProcessPostTraversal.
3834 if (isElement) {
3835 // We can't use UpdateAdditionalComputedStyles as-is because it needs a
3836 // ServoRestyleState and maintaining one of those during a _frametree_
3837 // traversal is basically impossible.
3838 int32_t index = 0;
3839 while (auto* oldAdditionalStyle =
3840 aFrame->GetAdditionalComputedStyle(index)) {
3841 RefPtr<ComputedStyle> newAdditionalContext =
3842 aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle,
3843 newStyle, nullptr);
3844 aFrame->SetAdditionalComputedStyle(index, newAdditionalContext);
3845 ++index;
3849 // Generally, owned anon boxes are our descendants. The only exceptions are
3850 // tables (for the table wrapper) and inline frames (for the block part of the
3851 // block-in-inline split). We're going to update our descendants when looping
3852 // over kids, and we don't want to update the block part of a block-in-inline
3853 // split if the inline is on the first line but the block is not (and if the
3854 // block is, it's the child of something else on the first line and will get
3855 // updated as a child). And given how this method ends up getting called, if
3856 // we reach here for a table frame, we are already in the middle of
3857 // reparenting the table wrapper frame. So no need to
3858 // UpdateStyleOfOwnedAnonBoxes() here.
3860 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3862 // We do not need to do the equivalent of UpdateFramePseudoElementStyles,
3863 // because those are handled by our descendant walk.
3866 void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame,
3867 nsIFrame* aProviderChild,
3868 ServoStyleSet& aStyleSet) {
3869 for (const auto& childList : aFrame->ChildLists()) {
3870 for (nsIFrame* child : childList.mList) {
3871 // only do frames that are in flow
3872 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
3873 child != aProviderChild) {
3874 DoReparentComputedStyleForFirstLine(child, aStyleSet);
3880 } // namespace mozilla