Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / base / RestyleManager.cpp
blob86420bb4055d77494771972a441bd30cf8cc8832
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 if (aFirstNewContent->IsElement()) {
100 StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent->AsElement());
103 const auto selectorFlags = container->GetSelectorFlags() &
104 NodeSelectorFlags::AllSimpleRestyleFlagsForAppend;
105 if (!selectorFlags) {
106 return;
109 // The container cannot be a document.
110 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
112 if (selectorFlags & NodeSelectorFlags::HasEmptySelector) {
113 // see whether we need to restyle the container
114 bool wasEmpty = true; // :empty or :-moz-only-whitespace
115 for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
116 cur = cur->GetNextSibling()) {
117 // We don't know whether we're testing :empty or :-moz-only-whitespace,
118 // so be conservative and assume :-moz-only-whitespace (i.e., make
119 // IsSignificantChild less likely to be true, and thus make us more
120 // likely to restyle).
121 if (nsStyleUtil::IsSignificantChild(cur, false)) {
122 wasEmpty = false;
123 break;
126 if (wasEmpty && container->IsElement()) {
127 RestyleForEmptyChange(container->AsElement());
128 return;
132 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
133 if (container->IsElement()) {
134 auto* containerElement = container->AsElement();
135 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
136 nsChangeHint(0));
137 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
138 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
139 containerElement->GetFirstElementChild());
141 } else {
142 RestylePreviousSiblings(aFirstNewContent);
143 RestyleSiblingsStartingWith(aFirstNewContent);
145 // Restyling the container is the most we can do here, so we're done.
146 return;
149 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
150 // restyle the last element child before this node
151 for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
152 cur = cur->GetPreviousSibling()) {
153 if (cur->IsElement()) {
154 auto* element = cur->AsElement();
155 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
156 nsChangeHint(0));
157 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
158 *element);
159 break;
165 void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) {
166 for (nsIContent* sibling = aStartingSibling; sibling;
167 sibling = sibling->GetPreviousSibling()) {
168 if (auto* element = Element::FromNode(sibling)) {
169 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
174 void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) {
175 for (nsIContent* sibling = aStartingSibling; sibling;
176 sibling = sibling->GetNextSibling()) {
177 if (auto* element = Element::FromNode(sibling)) {
178 PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
183 void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
184 PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0));
185 StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer);
187 // In some cases (:empty + E, :empty ~ E), a change in the content of
188 // an element requires restyling its parent's siblings.
189 nsIContent* grandparent = aContainer->GetParent();
190 if (!grandparent || !(grandparent->GetSelectorFlags() &
191 NodeSelectorFlags::HasSlowSelectorLaterSiblings)) {
192 return;
194 RestyleSiblingsStartingWith(aContainer->GetNextSibling());
197 void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
198 nsIContent* aChangedChild) {
199 MOZ_ASSERT(aContainer->GetSelectorFlags() &
200 NodeSelectorFlags::HasEdgeChildSelector);
201 MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
202 // restyle the previously-first element child if it is after this node
203 bool passedChild = false;
204 for (nsIContent* content = aContainer->GetFirstChild(); content;
205 content = content->GetNextSibling()) {
206 if (content == aChangedChild) {
207 passedChild = true;
208 continue;
210 if (content->IsElement()) {
211 if (passedChild) {
212 auto* element = content->AsElement();
213 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
214 nsChangeHint(0));
215 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
216 *element);
218 break;
221 // restyle the previously-last element child if it is before this node
222 passedChild = false;
223 for (nsIContent* content = aContainer->GetLastChild(); content;
224 content = content->GetPreviousSibling()) {
225 if (content == aChangedChild) {
226 passedChild = true;
227 continue;
229 if (content->IsElement()) {
230 if (passedChild) {
231 auto* element = content->AsElement();
232 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
233 nsChangeHint(0));
234 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
235 *element);
237 break;
242 template <typename CharT>
243 bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) {
244 for (auto index : IntegerRange(aUpTo)) {
245 if (!dom::IsSpaceCharacter(aBuffer[index])) {
246 return false;
249 return true;
252 template <typename CharT>
253 bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
254 size_t aNewLength) {
255 MOZ_ASSERT(aOldLength <= aNewLength);
256 if (!WhitespaceOnly(aBuffer, aOldLength)) {
257 // The old text was already not whitespace-only.
258 return false;
261 return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength);
264 static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
265 MOZ_ASSERT(aChild->GetParent() == aContainer);
266 for (nsIContent* child = aContainer->GetFirstChild(); child;
267 child = child->GetNextSibling()) {
268 if (child == aChild) {
269 continue;
271 // We don't know whether we're testing :empty or :-moz-only-whitespace,
272 // so be conservative and assume :-moz-only-whitespace (i.e., make
273 // IsSignificantChild less likely to be true, and thus make us more
274 // likely to restyle).
275 if (nsStyleUtil::IsSignificantChild(child, false)) {
276 return true;
280 return false;
283 void RestyleManager::CharacterDataChanged(
284 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
285 nsINode* parent = aContent->GetParentNode();
286 MOZ_ASSERT(parent, "How were we notified of a stray node?");
288 const auto slowSelectorFlags =
289 parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
290 if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
291 NodeSelectorFlags::HasEdgeChildSelector))) {
292 // Nothing to do, no other slow selector can change as a result of this.
293 return;
296 if (!aContent->IsText()) {
297 // Doesn't matter to styling (could be a processing instruction or a
298 // comment), it can't change whether any selectors match or don't.
299 return;
302 if (MOZ_UNLIKELY(!parent->IsElement())) {
303 MOZ_ASSERT(parent->IsShadowRoot());
304 return;
307 if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) {
308 // This is an anonymous node and thus isn't in child lists, so isn't taken
309 // into account for selector matching the relevant selectors here.
310 return;
313 // Handle appends specially since they're common and we can know both the old
314 // and the new text exactly.
316 // TODO(emilio): This could be made much more general if :-moz-only-whitespace
317 // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only
318 // need to know whether we went from empty to non-empty, and that's trivial to
319 // know, with CharacterDataChangeInfo...
320 if (!aInfo.mAppend) {
321 // FIXME(emilio): This restyles unnecessarily if the text node is the only
322 // child of the parent element. Fortunately, it's uncommon to have such
323 // nodes and this not being an append.
325 // See the testcase in bug 1427625 for a test-case that triggers this.
326 RestyleForInsertOrChange(aContent);
327 return;
330 const nsTextFragment* text = &aContent->AsText()->TextFragment();
332 const size_t oldLength = aInfo.mChangeStart;
333 const size_t newLength = text->GetLength();
335 const bool emptyChanged = !oldLength && newLength;
337 const bool whitespaceOnlyChanged =
338 text->Is2b()
339 ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength)
340 : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength);
342 if (!emptyChanged && !whitespaceOnlyChanged) {
343 return;
346 if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) {
347 if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
348 // We used to be empty, restyle the parent.
349 RestyleForEmptyChange(parent->AsElement());
350 return;
354 if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
355 MaybeRestyleForEdgeChildChange(parent, aContent);
359 // Restyling for a ContentInserted or CharacterDataChanged notification.
360 // This could be used for ContentRemoved as well if we got the
361 // notification before the removal happened (and sometimes
362 // CharacterDataChanged is more like a removal than an addition).
363 // The comments are written and variables are named in terms of it being
364 // a ContentInserted notification.
365 void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
366 nsINode* container = aChild->GetParentNode();
367 MOZ_ASSERT(container);
369 const auto selectorFlags =
370 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
371 if (!selectorFlags) {
372 return;
375 NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(),
376 "anonymous nodes should not be in child lists");
378 // The container cannot be a document.
379 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
381 if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
382 container->IsElement()) {
383 // See whether we need to restyle the container due to :empty /
384 // :-moz-only-whitespace.
385 const bool wasEmpty =
386 !HasAnySignificantSibling(container->AsElement(), aChild);
387 if (wasEmpty) {
388 // FIXME(emilio): When coming from CharacterDataChanged this can restyle
389 // unnecessarily. Also can restyle unnecessarily if aChild is not
390 // significant anyway, though that's more unlikely.
391 RestyleForEmptyChange(container->AsElement());
392 return;
396 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
397 if (container->IsElement()) {
398 auto* containerElement = container->AsElement();
399 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
400 nsChangeHint(0));
401 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
402 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
403 containerElement->GetFirstElementChild());
405 } else {
406 RestylePreviousSiblings(aChild);
407 RestyleSiblingsStartingWith(aChild);
409 // Restyling the container is the most we can do here, so we're done.
410 return;
413 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
414 // Restyle all later siblings.
415 RestyleSiblingsStartingWith(aChild->GetNextSibling());
416 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
417 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
418 aChild->GetNextElementSibling());
422 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
423 MaybeRestyleForEdgeChildChange(container, aChild);
427 void RestyleManager::ContentRemoved(nsIContent* aOldChild,
428 nsIContent* aFollowingSibling) {
429 auto* container = aOldChild->GetParentNode();
430 MOZ_ASSERT(container);
432 // Computed style data isn't useful for detached nodes, and we'll need to
433 // recompute it anyway if we ever insert the nodes back into a document.
434 if (auto* element = Element::FromNode(aOldChild)) {
435 RestyleManager::ClearServoDataFromSubtree(element);
436 // If this element is undisplayed or may have undisplayed descendants, we
437 // need to invalidate the cache, since there's the unlikely event of those
438 // elements getting destroyed and their addresses reused in a way that we
439 // look up the cache with their address for a different element before it's
440 // invalidated.
441 IncrementUndisplayedRestyleGeneration();
443 if (aOldChild->IsElement()) {
444 StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement(),
445 aFollowingSibling);
448 const auto selectorFlags =
449 container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
450 if (!selectorFlags) {
451 return;
454 if (aOldChild->IsRootOfNativeAnonymousSubtree()) {
455 // This should be an assert, but this is called incorrectly in
456 // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
457 // up the logs. Make it an assert again when that's fixed.
458 MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
459 "anonymous nodes should not be in child lists (bug 439258)");
462 // The container cannot be a document.
463 MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
465 if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
466 container->IsElement()) {
467 // see whether we need to restyle the container
468 bool isEmpty = true; // :empty or :-moz-only-whitespace
469 for (nsIContent* child = container->GetFirstChild(); child;
470 child = child->GetNextSibling()) {
471 // We don't know whether we're testing :empty or :-moz-only-whitespace,
472 // so be conservative and assume :-moz-only-whitespace (i.e., make
473 // IsSignificantChild less likely to be true, and thus make us more
474 // likely to restyle).
475 if (nsStyleUtil::IsSignificantChild(child, false)) {
476 isEmpty = false;
477 break;
480 if (isEmpty && container->IsElement()) {
481 RestyleForEmptyChange(container->AsElement());
482 return;
486 if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
487 if (container->IsElement()) {
488 auto* containerElement = container->AsElement();
489 PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
490 nsChangeHint(0));
491 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
492 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
493 containerElement->GetFirstElementChild());
495 } else {
496 RestylePreviousSiblings(aOldChild);
497 RestyleSiblingsStartingWith(aOldChild);
499 // Restyling the container is the most we can do here, so we're done.
500 return;
503 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
504 // Restyle all later siblings.
505 RestyleSiblingsStartingWith(aFollowingSibling);
506 if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
507 Element* nextSibling =
508 aFollowingSibling ? aFollowingSibling->IsElement()
509 ? aFollowingSibling->AsElement()
510 : aFollowingSibling->GetNextElementSibling()
511 : nullptr;
512 StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
513 nextSibling);
517 if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
518 // restyle the now-first element child if it was after aOldChild
519 bool reachedFollowingSibling = false;
520 for (nsIContent* content = container->GetFirstChild(); content;
521 content = content->GetNextSibling()) {
522 if (content == aFollowingSibling) {
523 reachedFollowingSibling = true;
524 // do NOT continue here; we might want to restyle this node
526 if (content->IsElement()) {
527 if (reachedFollowingSibling) {
528 auto* element = content->AsElement();
529 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
530 nsChangeHint(0));
531 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
532 *element);
534 break;
537 // restyle the now-last element child if it was before aOldChild
538 reachedFollowingSibling = (aFollowingSibling == nullptr);
539 for (nsIContent* content = container->GetLastChild(); content;
540 content = content->GetPreviousSibling()) {
541 if (content->IsElement()) {
542 if (reachedFollowingSibling) {
543 auto* element = content->AsElement();
544 PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
545 nsChangeHint(0));
546 StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
547 *element);
549 break;
551 if (content == aFollowingSibling) {
552 reachedFollowingSibling = true;
558 static bool StateChangeMayAffectFrame(const Element& aElement,
559 const nsIFrame& aFrame,
560 ElementState aStates) {
561 const bool brokenChanged = aStates.HasState(ElementState::BROKEN);
562 if (!brokenChanged) {
563 return false;
566 if (aFrame.IsGeneratedContentFrame()) {
567 // If it's other generated content, ignore state changes on it.
568 return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage);
571 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed)) {
572 // Broken affects object fallback behavior.
573 return true;
576 const bool mightChange = [&] {
577 if (aElement.IsHTMLElement(nsGkAtoms::img)) {
578 return true;
580 const auto* input = HTMLInputElement::FromNode(aElement);
581 return input && input->ControlType() == FormControlType::InputImage;
582 }();
584 if (!mightChange) {
585 return false;
588 const bool needsImageFrame =
589 nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) !=
590 nsImageFrame::ImageFrameType::None;
591 return needsImageFrame != aFrame.IsImageFrameOrSubclass();
594 static bool RepaintForAppearance(nsIFrame& aFrame, const Element& aElement,
595 ElementState aStateMask) {
596 if (aStateMask.HasAtLeastOneOfStates(ElementState::HOVER |
597 ElementState::ACTIVE) &&
598 aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) {
599 // The checkbox inside these elements inherit hover state and so on, see
600 // nsNativeTheme::GetContentState.
601 // FIXME(emilio): Would be nice to not have these hard-coded.
602 return true;
604 auto appearance = aFrame.StyleDisplay()->EffectiveAppearance();
605 if (appearance == StyleAppearance::None) {
606 return false;
608 nsPresContext* pc = aFrame.PresContext();
609 nsITheme* theme = pc->Theme();
610 if (!theme->ThemeSupportsWidget(pc, &aFrame, appearance)) {
611 return false;
613 bool repaint = false;
614 theme->WidgetStateChanged(&aFrame, appearance, nullptr, &repaint, nullptr);
615 return repaint;
619 * Calculates the change hint and the restyle hint for a given content state
620 * change.
622 static nsChangeHint ChangeForContentStateChange(const Element& aElement,
623 ElementState aStateMask) {
624 auto changeHint = nsChangeHint(0);
626 // Any change to a content state that affects which frames we construct
627 // must lead to a frame reconstruct here if we already have a frame.
628 // Note that we never decide through non-CSS means to not create frames
629 // based on content states, so if we already don't have a frame we don't
630 // need to force a reframe -- if it's needed, the HasStateDependentStyle
631 // call will handle things.
632 if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) {
633 if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) {
634 return nsChangeHint_ReconstructFrame;
636 if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) {
637 changeHint |= nsChangeHint_RepaintFrame;
639 primaryFrame->ElementStateChanged(aStateMask);
642 if (aStateMask.HasState(ElementState::VISITED)) {
643 // Exposing information to the page about whether the link is
644 // visited or not isn't really something we can worry about here.
645 // FIXME: We could probably do this a bit better.
646 changeHint |= nsChangeHint_RepaintFrame;
649 // This changes the applicable text-transform in the editor root.
650 if (aStateMask.HasState(ElementState::REVEALED)) {
651 // This is the same change hint as tweaking text-transform.
652 changeHint |= NS_STYLE_HINT_REFLOW;
655 return changeHint;
658 #ifdef DEBUG
659 /* static */
660 nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
661 nsCString result;
662 bool any = false;
663 const char* names[] = {"RepaintFrame",
664 "NeedReflow",
665 "ClearAncestorIntrinsics",
666 "ClearDescendantIntrinsics",
667 "NeedDirtyReflow",
668 "UpdateCursor",
669 "UpdateEffects",
670 "UpdateOpacityLayer",
671 "UpdateTransformLayer",
672 "ReconstructFrame",
673 "UpdateOverflow",
674 "UpdateSubtreeOverflow",
675 "UpdatePostTransformOverflow",
676 "UpdateParentOverflow",
677 "ChildrenOnlyTransform",
678 "RecomputePosition",
679 "UpdateContainingBlock",
680 "BorderStyleNoneChange",
681 "SchedulePaint",
682 "NeutralChange",
683 "InvalidateRenderingObservers",
684 "ReflowChangesSizeOrPosition",
685 "UpdateComputedBSize",
686 "UpdateUsesOpacity",
687 "UpdateBackgroundPosition",
688 "AddOrRemoveTransform",
689 "ScrollbarChange",
690 "UpdateTableCellSpans",
691 "VisibilityChange"};
692 static_assert(nsChangeHint_AllHints ==
693 static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
694 "Name list doesn't match change hints.");
695 uint32_t hint =
696 aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
697 uint32_t rest =
698 aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
699 if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
700 result.AppendLiteral("NS_STYLE_HINT_REFLOW");
701 hint = hint & ~NS_STYLE_HINT_REFLOW;
702 any = true;
703 } else if ((hint & nsChangeHint_AllReflowHints) ==
704 nsChangeHint_AllReflowHints) {
705 result.AppendLiteral("nsChangeHint_AllReflowHints");
706 hint = hint & ~nsChangeHint_AllReflowHints;
707 any = true;
708 } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
709 result.AppendLiteral("NS_STYLE_HINT_VISUAL");
710 hint = hint & ~NS_STYLE_HINT_VISUAL;
711 any = true;
713 for (uint32_t i = 0; i < ArrayLength(names); i++) {
714 if (hint & (1u << i)) {
715 if (any) {
716 result.AppendLiteral(" | ");
718 result.AppendPrintf("nsChangeHint_%s", names[i]);
719 any = true;
722 if (rest) {
723 if (any) {
724 result.AppendLiteral(" | ");
726 result.AppendPrintf("0x%0x", rest);
727 } else {
728 if (!any) {
729 result.AppendLiteral("nsChangeHint(0)");
732 return result;
734 #endif
737 * Frame construction helpers follow.
739 #ifdef DEBUG
740 static bool gInApplyRenderingChangeToTree = false;
741 #endif
744 * Sync views on the frame and all of it's descendants (following placeholders).
745 * The change hint should be some combination of nsChangeHint_RepaintFrame,
746 * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else.
748 static void SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint);
750 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
753 * This helper function is used to find the correct SVG frame to target when we
754 * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end
755 * up handling that hint while processing hints for one of the SVG frame's
756 * ancestor frames.
758 * The reason that we sometimes end up trying to process the hint for an
759 * ancestor of the SVG frame that the hint is intended for is due to the way we
760 * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
761 * the restyled element's principle frame to one of its ancestor frames based
762 * on what nsCSSRendering::FindBackground returns, since the background style
763 * may have been propagated up to an ancestor frame. Processing hints using an
764 * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
765 * a special case since it is intended to update a specific frame.
767 static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) {
768 if (aFrame->IsViewportFrame()) {
769 // This happens if the root-<svg> is fixed positioned, in which case we
770 // can't use aFrame->GetContent() to find the primary frame, since
771 // GetContent() returns nullptr for ViewportFrame.
772 aFrame = aFrame->PrincipalChildList().FirstChild();
774 // For an nsHTMLScrollFrame, this will get the SVG frame that has the
775 // children-only transforms:
776 aFrame = aFrame->GetContent()->GetPrimaryFrame();
777 if (aFrame->IsSVGOuterSVGFrame()) {
778 aFrame = aFrame->PrincipalChildList().FirstChild();
779 MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(),
780 "Where is the SVGOuterSVGFrame's anon child??");
782 MOZ_ASSERT(aFrame->IsSVGContainerFrame(),
783 "Children-only transforms only expected on SVG frames");
784 return aFrame;
787 // This function tries to optimize a position style change by either
788 // moving aFrame or ignoring the style change when it's safe to do so.
789 // It returns true when that succeeds, otherwise it posts a reflow request
790 // and returns false.
791 static bool RecomputePosition(nsIFrame* aFrame) {
792 // It's pointless to move around frames that have never been reflowed or
793 // are dirty (i.e. they will be reflowed), or aren't affected by position
794 // styles.
795 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
796 NS_FRAME_SVG_LAYOUT)) {
797 return true;
800 // Don't process position changes on table frames, since we already handle
801 // the dynamic position change on the table wrapper frame, and the
802 // reflow-based fallback code path also ignores positions on inner table
803 // frames.
804 if (aFrame->IsTableFrame()) {
805 return true;
808 const nsStyleDisplay* display = aFrame->StyleDisplay();
809 // Changes to the offsets of a non-positioned element can safely be ignored.
810 if (display->mPosition == StylePositionProperty::Static) {
811 return true;
814 // Don't process position changes on frames which have views or the ones which
815 // have a view somewhere in their descendants, because the corresponding view
816 // needs to be repositioned properly as well.
817 if (aFrame->HasView() ||
818 aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
819 return false;
822 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
823 // If the frame has an intrinsic block-size, we resolve its 'auto' margins
824 // after doing layout, since we need to know the frame's block size. See
825 // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout().
827 // Since the size of the frame doesn't change, we could modify the below
828 // computation to compute the margin correctly without doing a full reflow,
829 // however we decided to try doing a full reflow for now.
830 if (aFrame->HasIntrinsicKeywordForBSize()) {
831 WritingMode wm = aFrame->GetWritingMode();
832 const auto* styleMargin = aFrame->StyleMargin();
833 if (styleMargin->HasBlockAxisAuto(wm)) {
834 return false;
837 // Flexbox and Grid layout supports CSS Align and the optimizations below
838 // don't support that yet.
839 nsIFrame* ph = aFrame->GetPlaceholderFrame();
840 if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
841 return false;
845 // If we need to reposition any descendant that depends on our static
846 // position, then we also can't take the optimized path.
848 // TODO(emilio): It may be worth trying to find them and try to call
849 // RecomputePosition on them too instead of disabling the optimization...
850 if (aFrame->DescendantMayDependOnItsStaticPosition()) {
851 return false;
854 aFrame->SchedulePaint();
856 auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) {
857 if (frame->IsInScrollAnchorChain()) {
858 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame);
859 frame->PresShell()->PostPendingScrollAnchorAdjustment(container);
862 // We need to trigger re-snapping to this content if we snapped to the
863 // content on the last scroll operation.
864 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
867 // For relative positioning, we can simply update the frame rect
868 if (display->IsRelativelyOrStickyPositionedStyle()) {
869 if (aFrame->IsGridItem()) {
870 // A grid item's CB is its grid area, not the parent frame content area
871 // as is assumed below.
872 return false;
874 // Move the frame
875 if (display->mPosition == StylePositionProperty::Sticky) {
876 // Update sticky positioning for an entire element at once, starting with
877 // the first continuation or ib-split sibling.
878 // It's rare that the frame we already have isn't already the first
879 // continuation or ib-split sibling, but it can happen when styles differ
880 // across continuations such as ::first-line or ::first-letter, and in
881 // those cases we will generally (but maybe not always) do the work twice.
882 nsIFrame* firstContinuation =
883 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
885 StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
886 StickyScrollContainer* ssc =
887 StickyScrollContainer::GetStickyScrollContainerForFrame(
888 firstContinuation);
889 if (ssc) {
890 ssc->PositionContinuations(firstContinuation);
892 } else {
893 MOZ_ASSERT(display->IsRelativelyPositionedStyle(),
894 "Unexpected type of positioning");
895 for (nsIFrame* cont = aFrame; cont;
896 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
897 nsIFrame* cb = cont->GetContainingBlock();
898 WritingMode wm = cb->GetWritingMode();
899 const LogicalSize cbSize = cb->ContentSize();
900 const LogicalMargin newLogicalOffsets =
901 ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize);
902 const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm);
904 // ReflowInput::ApplyRelativePositioning would work here, but
905 // since we've already checked mPosition and aren't changing the frame's
906 // normal position, go ahead and add the offsets directly.
907 // First, we need to ensure that the normal position is stored though.
908 bool hasProperty;
909 nsPoint normalPosition = cont->GetNormalPosition(&hasProperty);
910 if (!hasProperty) {
911 cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition);
913 cont->SetPosition(normalPosition +
914 nsPoint(newOffsets.left, newOffsets.top));
918 postPendingScrollAnchorOrResnap(aFrame);
919 return true;
922 // For the absolute positioning case, set up a fake HTML reflow input for
923 // the frame, and then get the offsets and size from it. If the frame's size
924 // doesn't need to change, we can simply update the frame position. Otherwise
925 // we fall back to a reflow.
926 UniquePtr<gfxContext> rc =
927 aFrame->PresShell()->CreateReferenceRenderingContext();
929 // Construct a bogus parent reflow input so that there's a usable reflow input
930 // for the containing block.
931 nsIFrame* parentFrame = aFrame->GetParent();
932 WritingMode parentWM = parentFrame->GetWritingMode();
933 WritingMode frameWM = aFrame->GetWritingMode();
934 LogicalSize parentSize = parentFrame->GetLogicalSize();
936 nsFrameState savedState = parentFrame->GetStateBits();
937 ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(),
938 parentSize);
939 parentFrame->RemoveStateBits(~nsFrameState(0));
940 parentFrame->AddStateBits(savedState);
942 // The bogus parent state here was created with no parent state of its own,
943 // and therefore it won't have an mCBReflowInput set up.
944 // But we may need one (for InitCBReflowInput in a child state), so let's
945 // try to create one here for the cases where it will be needed.
946 Maybe<ReflowInput> cbReflowInput;
947 nsIFrame* cbFrame = parentFrame->GetContainingBlock();
948 if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
949 parentFrame->IsTableFrame())) {
950 const auto cbWM = cbFrame->GetWritingMode();
951 LogicalSize cbSize = cbFrame->GetLogicalSize();
952 cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize);
953 cbReflowInput->SetComputedLogicalMargin(
954 cbWM, cbFrame->GetLogicalUsedMargin(cbWM));
955 cbReflowInput->SetComputedLogicalPadding(
956 cbWM, cbFrame->GetLogicalUsedPadding(cbWM));
957 cbReflowInput->SetComputedLogicalBorderPadding(
958 cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM));
959 parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
962 NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE &&
963 parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE,
964 "parentSize should be valid");
965 parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
966 parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
967 parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM));
969 parentReflowInput.SetComputedLogicalPadding(
970 parentWM, parentFrame->GetLogicalUsedPadding(parentWM));
971 parentReflowInput.SetComputedLogicalBorderPadding(
972 parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM));
973 LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
974 availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
976 ViewportFrame* viewport = do_QueryFrame(parentFrame);
977 nsSize cbSize =
978 viewport
979 ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
980 .Size()
981 : aFrame->GetContainingBlock()->GetSize();
982 const nsMargin& parentBorder =
983 parentReflowInput.mStyleBorder->GetComputedBorder();
984 cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
985 LogicalSize lcbSize(frameWM, cbSize);
986 ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
987 availSize, Some(lcbSize));
988 nscoord computedISize = reflowInput.ComputedISize();
989 nscoord computedBSize = reflowInput.ComputedBSize();
990 const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM);
991 computedISize += frameBP.IStartEnd(frameWM);
992 if (computedBSize != NS_UNCONSTRAINEDSIZE) {
993 computedBSize += frameBP.BStartEnd(frameWM);
995 LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM);
996 nsSize size = aFrame->GetSize();
997 // The RecomputePosition hint is not used if any offset changed between auto
998 // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new
999 // element height will be its intrinsic height, and since 'top' and 'bottom''s
1000 // auto-ness hasn't changed, the old height must also be its intrinsic
1001 // height, which we can assume hasn't changed (or reflow would have
1002 // been triggered).
1003 if (computedISize == logicalSize.ISize(frameWM) &&
1004 (computedBSize == NS_UNCONSTRAINEDSIZE ||
1005 computedBSize == logicalSize.BSize(frameWM))) {
1006 // If we're solving for 'left' or 'top', then compute it here, in order to
1007 // match the reflow code path.
1009 // TODO(emilio): It'd be nice if this did logical math instead, but it seems
1010 // to me the math should work out on vertical writing modes as well. See Bug
1011 // 1675861 for some hints.
1012 const nsMargin offset = reflowInput.ComputedPhysicalOffsets();
1013 const nsMargin margin = reflowInput.ComputedPhysicalMargin();
1015 nscoord left = offset.left;
1016 if (left == NS_AUTOOFFSET) {
1017 left =
1018 cbSize.width - offset.right - margin.right - size.width - margin.left;
1021 nscoord top = offset.top;
1022 if (top == NS_AUTOOFFSET) {
1023 top = cbSize.height - offset.bottom - margin.bottom - size.height -
1024 margin.top;
1027 // Move the frame
1028 nsPoint pos(parentBorder.left + left + margin.left,
1029 parentBorder.top + top + margin.top);
1030 aFrame->SetPosition(pos);
1032 postPendingScrollAnchorOrResnap(aFrame);
1033 return true;
1036 // Fall back to a reflow
1037 return false;
1041 * Return true if aFrame's subtree has placeholders for out-of-flow content
1042 * that would be affected due to the change to
1043 * `aPossiblyChangingContainingBlock` (and thus would need to get reframed).
1045 * In particular, this function returns true if there are placeholders whose OOF
1046 * frames may need to be reparented (via reframing) as a result of whatever
1047 * change actually happened.
1049 * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether
1050 * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed
1051 * pos stuff, respectively, for the _new_ style that the frame already has, not
1052 * the old one.
1054 static bool ContainingBlockChangeAffectsDescendants(
1055 nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame,
1056 bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) {
1057 // All fixed-pos containing blocks should also be abs-pos containing blocks.
1058 MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock);
1060 for (const auto& childList : aFrame->ChildLists()) {
1061 for (nsIFrame* f : childList.mList) {
1062 if (f->IsPlaceholderFrame()) {
1063 nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
1064 // If SVG text frames could appear here, they could confuse us since
1065 // they ignore their position style ... but they can't.
1066 NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(),
1067 "SVG text frames can't be out of flow");
1068 // Top-layer frames don't change containing block based on direct
1069 // ancestors.
1070 auto* display = outOfFlow->StyleDisplay();
1071 if (display->IsAbsolutelyPositionedStyle() &&
1072 display->mTopLayer == StyleTopLayer::None) {
1073 const bool isContainingBlock =
1074 aIsFixedPosContainingBlock ||
1075 (aIsAbsPosContainingBlock &&
1076 display->mPosition == StylePositionProperty::Absolute);
1077 // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be
1078 // a first continuation, see the assertion in the caller.
1079 nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation();
1080 if (isContainingBlock) {
1081 // If we are becoming a containing block, we only need to reframe if
1082 // this oof's current containing block is an ancestor of the new
1083 // frame.
1084 if (parent != aPossiblyChangingContainingBlock &&
1085 nsLayoutUtils::IsProperAncestorFrame(
1086 parent, aPossiblyChangingContainingBlock)) {
1087 return true;
1089 } else {
1090 // If we are not a containing block anymore, we only need to reframe
1091 // if we are the current containing block of the oof frame.
1092 if (parent == aPossiblyChangingContainingBlock) {
1093 return true;
1098 // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or
1099 // f->IsFixedPosContainingBlock() here. However, that would only
1100 // be testing the *new* style of the frame, which might exclude
1101 // descendants that currently have this frame as an abs-pos
1102 // containing block. Taking the codepath where we don't reframe
1103 // could lead to an unsafe call to
1104 // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
1105 // the descendant and taken it off the absolute list.
1106 if (ContainingBlockChangeAffectsDescendants(
1107 aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock,
1108 aIsFixedPosContainingBlock)) {
1109 return true;
1113 return false;
1116 // Returns the frame that would serve as the containing block for aFrame's
1117 // positioned descendants, if aFrame had styles to make it a CB for such
1118 // descendants. (Typically this is just aFrame itself, or its insertion frame).
1120 // Returns nullptr if this frame can't be easily determined.
1121 static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) {
1122 if (aFrame->IsFieldSetFrame()) {
1123 // FIXME: This should be easily implementable.
1124 return nullptr;
1126 nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame();
1127 if (insertionFrame == aFrame) {
1128 return insertionFrame;
1130 // Generally frames with a different insertion frame are hard to deal with,
1131 // but scrollframes are easy because the containing block is just the
1132 // insertion frame.
1133 if (aFrame->IsScrollFrame()) {
1134 return insertionFrame;
1136 // Combobox frames are easy as well because they can't have positioned
1137 // children anyways.
1138 // Button and table cell frames are also easy because the containing block is
1139 // the frame itself.
1140 if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() ||
1141 aFrame->IsTableCellFrame()) {
1142 return aFrame;
1144 return nullptr;
1147 static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame,
1148 nsIFrame* aMaybeChangingCB) {
1149 // NOTE: This looks at the new style.
1150 const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock();
1151 MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock());
1153 const bool isAbsPosContainingBlock =
1154 isFixedContainingBlock || aFrame->IsAbsPosContainingBlock();
1156 for (nsIFrame* f = aFrame; f;
1157 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
1158 if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f,
1159 isAbsPosContainingBlock,
1160 isFixedContainingBlock)) {
1161 return true;
1164 return false;
1167 static void DoApplyRenderingChangeToTree(nsIFrame* aFrame,
1168 nsChangeHint aChange) {
1169 MOZ_ASSERT(gInApplyRenderingChangeToTree,
1170 "should only be called within ApplyRenderingChangeToTree");
1172 for (; aFrame;
1173 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
1174 // Invalidate and sync views on all descendant frames, following
1175 // placeholders. We don't need to update transforms in
1176 // SyncViewsAndInvalidateDescendants, because there can't be any
1177 // out-of-flows or popups that need to be transformed; all out-of-flow
1178 // descendants of the transformed element must also be descendants of the
1179 // transformed frame.
1180 SyncViewsAndInvalidateDescendants(
1181 aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
1182 nsChangeHint_UpdateOpacityLayer |
1183 nsChangeHint_SchedulePaint)));
1184 // This must be set to true if the rendering change needs to
1185 // invalidate content. If it's false, a composite-only paint
1186 // (empty transaction) will be scheduled.
1187 bool needInvalidatingPaint = false;
1189 // if frame has view, will already be invalidated
1190 if (aChange & nsChangeHint_RepaintFrame) {
1191 // Note that this whole block will be skipped when painting is suppressed
1192 // (due to our caller ApplyRendingChangeToTree() discarding the
1193 // nsChangeHint_RepaintFrame hint). If you add handling for any other
1194 // hints within this block, be sure that they too should be ignored when
1195 // painting is suppressed.
1196 needInvalidatingPaint = true;
1197 aFrame->InvalidateFrameSubtree();
1198 if ((aChange & nsChangeHint_UpdateEffects) &&
1199 aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1200 // Need to update our overflow rects:
1201 SVGUtils::ScheduleReflowSVG(aFrame);
1204 ActiveLayerTracker::NotifyNeedsRepaint(aFrame);
1206 if (aChange & nsChangeHint_UpdateOpacityLayer) {
1207 // FIXME/bug 796697: we can get away with empty transactions for
1208 // opacity updates in many cases.
1209 needInvalidatingPaint = true;
1211 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
1212 if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
1213 // SVG effects paints the opacity without using
1214 // nsDisplayOpacity. We need to invalidate manually.
1215 aFrame->InvalidateFrameSubtree();
1218 if ((aChange & nsChangeHint_UpdateTransformLayer) &&
1219 aFrame->IsTransformed()) {
1220 // Note: All the transform-like properties should map to the same
1221 // layer activity index, so does the restyle count. Therefore, using
1222 // eCSSProperty_transform should be fine.
1223 ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
1224 needInvalidatingPaint = true;
1226 if (aChange & nsChangeHint_ChildrenOnlyTransform) {
1227 needInvalidatingPaint = true;
1228 nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
1229 ->PrincipalChildList()
1230 .FirstChild();
1231 for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1232 // Note: All the transform-like properties should map to the same
1233 // layer activity index, so does the restyle count. Therefore, using
1234 // eCSSProperty_transform should be fine.
1235 ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
1238 if (aChange & nsChangeHint_SchedulePaint) {
1239 needInvalidatingPaint = true;
1241 aFrame->SchedulePaint(needInvalidatingPaint
1242 ? nsIFrame::PAINT_DEFAULT
1243 : nsIFrame::PAINT_COMPOSITE_ONLY);
1247 static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
1248 nsChangeHint aChange) {
1249 MOZ_ASSERT(gInApplyRenderingChangeToTree,
1250 "should only be called within ApplyRenderingChangeToTree");
1252 NS_ASSERTION(nsChangeHint_size_t(aChange) ==
1253 (aChange & (nsChangeHint_RepaintFrame |
1254 nsChangeHint_UpdateOpacityLayer |
1255 nsChangeHint_SchedulePaint)),
1256 "Invalid change flag");
1258 aFrame->SyncFrameViewProperties();
1260 for (const auto& [list, listID] : aFrame->ChildLists()) {
1261 for (nsIFrame* child : list) {
1262 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
1263 // only do frames that don't have placeholders
1264 if (child->IsPlaceholderFrame()) {
1265 // do the out-of-flow frame and its continuations
1266 nsIFrame* outOfFlowFrame =
1267 nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
1268 DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
1269 } else if (listID == FrameChildListID::Popup) {
1270 DoApplyRenderingChangeToTree(child, aChange);
1271 } else { // regular frame
1272 SyncViewsAndInvalidateDescendants(child, aChange);
1279 static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
1280 nsChangeHint aChange) {
1281 // We check StyleDisplay()->HasTransformStyle() in addition to checking
1282 // IsTransformed() since we can get here for some frames that don't support
1283 // CSS transforms, and table frames, which are their own odd-ball, since the
1284 // transform is handled by their wrapper, which _also_ gets a separate hint.
1285 NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
1286 aFrame->IsTransformed() ||
1287 aFrame->StyleDisplay()->HasTransformStyle(),
1288 "Unexpected UpdateTransformLayer hint");
1290 if (aPresShell->IsPaintingSuppressed()) {
1291 // Don't allow synchronous rendering changes when painting is turned off.
1292 aChange &= ~nsChangeHint_RepaintFrame;
1293 if (!aChange) {
1294 return;
1298 // Trigger rendering updates by damaging this frame and any
1299 // continuations of this frame.
1300 #ifdef DEBUG
1301 gInApplyRenderingChangeToTree = true;
1302 #endif
1303 if (aChange & nsChangeHint_RepaintFrame) {
1304 // If the frame is the primary frame of either the body element or
1305 // the html element, we propagate the repaint change hint to the
1306 // viewport. This is necessary for background and scrollbar colors
1307 // propagation.
1308 if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
1309 nsIFrame* rootFrame = aPresShell->GetRootFrame();
1310 MOZ_ASSERT(rootFrame, "No root frame?");
1311 DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
1312 aChange &= ~nsChangeHint_RepaintFrame;
1313 if (!aChange) {
1314 return;
1318 DoApplyRenderingChangeToTree(aFrame, aChange);
1319 #ifdef DEBUG
1320 gInApplyRenderingChangeToTree = false;
1321 #endif
1324 static void AddSubtreeToOverflowTracker(
1325 nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) {
1326 if (aFrame->FrameMaintainsOverflow()) {
1327 aOverflowChangedTracker.AddFrame(aFrame,
1328 OverflowChangedTracker::CHILDREN_CHANGED);
1330 for (const auto& childList : aFrame->ChildLists()) {
1331 for (nsIFrame* child : childList.mList) {
1332 AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
1337 static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) {
1338 IntrinsicDirty dirtyType;
1339 if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
1340 NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
1341 "Please read the comments in nsChangeHint.h");
1342 NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
1343 "ClearDescendantIntrinsics requires NeedDirtyReflow");
1344 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
1345 } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
1346 aFrame->HasAnyStateBits(
1347 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
1348 dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
1349 } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
1350 dirtyType = IntrinsicDirty::FrameAndAncestors;
1351 } else {
1352 dirtyType = IntrinsicDirty::None;
1355 if (aHint & nsChangeHint_UpdateComputedBSize) {
1356 aFrame->SetHasBSizeChange(true);
1359 nsFrameState dirtyBits;
1360 if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
1361 dirtyBits = nsFrameState(0);
1362 } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
1363 dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) {
1364 dirtyBits = NS_FRAME_IS_DIRTY;
1365 } else {
1366 dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
1369 // If we're not going to clear any intrinsic sizes on the frames, and
1370 // there are no dirty bits to set, then there's nothing to do.
1371 if (dirtyType == IntrinsicDirty::None && !dirtyBits) return;
1373 ReflowRootHandling rootHandling;
1374 if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
1375 rootHandling = ReflowRootHandling::PositionOrSizeChange;
1376 } else {
1377 rootHandling = ReflowRootHandling::NoPositionOrSizeChange;
1380 do {
1381 aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
1382 rootHandling);
1383 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
1384 } while (aFrame);
1387 // Get the next sibling which might have a frame. This only considers siblings
1388 // that stylo post-traversal looks at, so only elements and text. In
1389 // particular, it ignores comments.
1390 static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) {
1391 for (nsIContent* next = aContent->GetNextSibling(); next;
1392 next = next->GetNextSibling()) {
1393 if (next->IsElement() || next->IsText()) {
1394 return next;
1398 return nullptr;
1401 // If |aFrame| is dirty or has dirty children, then we can skip updating
1402 // overflows since that will happen when it's reflowed.
1403 static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) {
1404 return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY |
1405 NS_FRAME_HAS_DIRTY_CHILDREN);
1408 static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint,
1409 nsIContent* aContent,
1410 nsIFrame* aFrame,
1411 nsPresContext* aPc) {
1412 if (!(aHint & nsChangeHint_ScrollbarChange)) {
1413 return;
1415 aHint &= ~nsChangeHint_ScrollbarChange;
1416 if (aHint & nsChangeHint_ReconstructFrame) {
1417 return;
1420 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
1422 const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent();
1424 // Only bother with this if we're the root or the body element, since:
1425 // (a) It'd be *expensive* to reframe these particular nodes. They're
1426 // at the root, so reframing would mean rebuilding the world.
1427 // (b) It's often *unnecessary* to reframe for "overflow" changes on
1428 // these particular nodes. In general, the only reason we reframe
1429 // for "overflow" changes is so we can construct (or destroy) a
1430 // scrollframe & scrollbars -- and the html/body nodes often don't
1431 // need their own scrollframe/scrollbars because they coopt the ones
1432 // on the viewport (which always exist). So depending on whether
1433 // that's happening, we can skip the reframe for these nodes.
1434 if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) {
1435 // If the restyled element provided/provides the scrollbar styles for
1436 // the viewport before and/or after this restyle, AND it's not coopting
1437 // that responsibility from some other element (which would need
1438 // reconstruction to make its own scrollframe now), THEN: we don't need
1439 // to reconstruct - we can just reflow, because no scrollframe is being
1440 // added/removed.
1441 Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement();
1442 Element* newOverride = aPc->UpdateViewportScrollStylesOverride();
1444 const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) {
1445 if (aOverride) {
1446 return aOverride == aContent;
1448 return isRoot;
1451 if (ProvidesScrollbarStyles(prevOverride) ||
1452 ProvidesScrollbarStyles(newOverride)) {
1453 // If we get here, the restyled element provided the scrollbar styles
1454 // for viewport before this restyle, OR it will provide them after.
1455 if (!prevOverride || !newOverride || prevOverride == newOverride) {
1456 // If we get here, the restyled element is NOT replacing (or being
1457 // replaced by) some other element as the viewport's
1458 // scrollbar-styles provider. (If it were, we'd potentially need to
1459 // reframe to create a dedicated scrollframe for whichever element
1460 // is being booted from providing viewport scrollbar styles.)
1462 // Under these conditions, we're OK to assume that this "overflow"
1463 // change only impacts the root viewport's scrollframe, which
1464 // already exists, so we can simply reflow instead of reframing.
1465 if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
1466 sf->MarkScrollbarsDirtyForReflow();
1467 } else if (nsIScrollableFrame* sf =
1468 aPc->PresShell()->GetRootScrollFrameAsScrollable()) {
1469 sf->MarkScrollbarsDirtyForReflow();
1471 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1472 } else {
1473 // If we changed the override element, we need to reconstruct as the old
1474 // override element might start / stop being scrollable.
1475 aHint |= nsChangeHint_ReconstructFrame;
1477 return;
1481 const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow();
1482 if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
1483 if (scrollable && sf->HasAllNeededScrollbars()) {
1484 sf->MarkScrollbarsDirtyForReflow();
1485 // Once we've created scrollbars for a frame, don't bother reconstructing
1486 // it just to remove them if we still need a scroll frame.
1487 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1488 return;
1490 } else if (aFrame->IsTextInputFrame()) {
1491 // input / textarea for the most part don't honor overflow themselves, the
1492 // editor root will deal with the change if needed.
1493 // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(),
1494 // so we need to reflow the textarea itself, not just the inner control.
1495 aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
1496 return;
1497 } else if (!scrollable) {
1498 // Something changed, but we don't have nor will have a scroll frame,
1499 // there's nothing to do here.
1500 return;
1503 // Oh well, we couldn't optimize it out, just reconstruct frames for the
1504 // subtree.
1505 aHint |= nsChangeHint_ReconstructFrame;
1508 static void TryToHandleContainingBlockChange(nsChangeHint& aHint,
1509 nsIFrame* aFrame) {
1510 if (!(aHint & nsChangeHint_UpdateContainingBlock)) {
1511 return;
1513 if (aHint & nsChangeHint_ReconstructFrame) {
1514 return;
1516 MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
1517 nsIFrame* containingBlock = ContainingBlockForFrame(aFrame);
1518 if (!containingBlock ||
1519 NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) {
1520 // The frame has positioned children that need to be reparented, or it can't
1521 // easily be converted to/from being an abs-pos container correctly.
1522 aHint |= nsChangeHint_ReconstructFrame;
1523 return;
1525 const bool isCb = aFrame->IsAbsPosContainingBlock();
1527 // The absolute container should be containingBlock.
1528 for (nsIFrame* cont = containingBlock; cont;
1529 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1530 // Normally frame construction would set state bits as needed,
1531 // but we're not going to reconstruct the frame so we need to set
1532 // them. It's because we need to set this state on each affected frame
1533 // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
1534 // to ancestors (i.e. it can't be an change hint that is handled for
1535 // descendants).
1536 if (isCb) {
1537 if (!cont->IsAbsoluteContainer() &&
1538 cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
1539 cont->MarkAsAbsoluteContainingBlock();
1541 } else if (cont->IsAbsoluteContainer()) {
1542 if (cont->HasAbsolutelyPositionedChildren()) {
1543 // If |cont| still has absolutely positioned children,
1544 // we can't call MarkAsNotAbsoluteContainingBlock. This
1545 // will remove a frame list that still has children in
1546 // it that we need to keep track of.
1547 // The optimization of removing it isn't particularly
1548 // important, although it does mean we skip some tests.
1549 NS_WARNING("skipping removal of absolute containing block");
1550 } else {
1551 cont->MarkAsNotAbsoluteContainingBlock();
1557 void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
1558 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1559 "Someone forgot a script blocker");
1561 // See bug 1378219 comment 9:
1562 // Recursive calls here are a bit worrying, but apparently do happen in the
1563 // wild (although not currently in any of our automated tests). Try to get a
1564 // stack from Nightly/Dev channel to figure out what's going on and whether
1565 // it's OK.
1566 MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
1568 if (aChangeList.IsEmpty()) {
1569 return;
1572 // If mDestroyedFrames is null, we want to create a new hashtable here
1573 // and destroy it on exit; but if it is already non-null (because we're in
1574 // a recursive call), we will continue to use the existing table to
1575 // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit.
1576 // We use a MaybeClearDestroyedFrames helper to conditionally reset the
1577 // mDestroyedFrames pointer when this method returns.
1578 typedef decltype(mDestroyedFrames) DestroyedFramesT;
1579 class MOZ_RAII MaybeClearDestroyedFrames {
1580 private:
1581 DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames
1582 const bool mResetOnDestruction;
1584 public:
1585 explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
1586 : mDestroyedFramesRef(aTarget),
1587 mResetOnDestruction(!aTarget) // reset only if target starts out null
1589 ~MaybeClearDestroyedFrames() {
1590 if (mResetOnDestruction) {
1591 mDestroyedFramesRef.reset(nullptr);
1596 MaybeClearDestroyedFrames maybeClear(mDestroyedFrames);
1597 if (!mDestroyedFrames) {
1598 mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>();
1601 AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT);
1603 nsPresContext* presContext = PresContext();
1604 nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
1606 bool didUpdateCursor = false;
1608 for (size_t i = 0; i < aChangeList.Length(); ++i) {
1609 // Collect and coalesce adjacent siblings for lazy frame construction.
1610 // Eventually it would be even better to make RecreateFramesForContent
1611 // accept a range and coalesce all adjacent reconstructs (bug 1344139).
1612 size_t lazyRangeStart = i;
1613 while (i < aChangeList.Length() && aChangeList[i].mContent &&
1614 aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
1615 (i == lazyRangeStart ||
1616 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) ==
1617 aChangeList[i].mContent)) {
1618 MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
1619 MOZ_ASSERT(!aChangeList[i].mFrame);
1620 ++i;
1622 if (i != lazyRangeStart) {
1623 nsIContent* start = aChangeList[lazyRangeStart].mContent;
1624 nsIContent* end =
1625 NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent);
1626 if (!end) {
1627 frameConstructor->ContentAppended(
1628 start, nsCSSFrameConstructor::InsertionKind::Sync);
1629 } else {
1630 frameConstructor->ContentRangeInserted(
1631 start, end, nsCSSFrameConstructor::InsertionKind::Sync);
1634 for (size_t j = lazyRangeStart; j < i; ++j) {
1635 MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() ||
1636 !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
1638 if (i == aChangeList.Length()) {
1639 break;
1642 const nsStyleChangeData& data = aChangeList[i];
1643 nsIFrame* frame = data.mFrame;
1644 nsIContent* content = data.mContent;
1645 nsChangeHint hint = data.mHint;
1646 bool didReflowThisFrame = false;
1648 NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
1649 (hint & nsChangeHint_NeedReflow),
1650 "Reflow hint bits set without actually asking for a reflow");
1652 // skip any frame that has been destroyed due to a ripple effect
1653 if (frame && mDestroyedFrames->Contains(frame)) {
1654 continue;
1657 if (frame && frame->GetContent() != content) {
1658 // XXXbz this is due to image maps messing with the primary frame of
1659 // <area>s. See bug 135040. Remove this block once that's fixed.
1660 frame = nullptr;
1661 if (!(hint & nsChangeHint_ReconstructFrame)) {
1662 continue;
1666 TryToDealWithScrollbarChange(hint, content, frame, presContext);
1667 TryToHandleContainingBlockChange(hint, frame);
1669 if (hint & nsChangeHint_ReconstructFrame) {
1670 // If we ever start passing true here, be careful of restyles
1671 // that involve a reframe and animations. In particular, if the
1672 // restyle we're processing here is an animation restyle, but
1673 // the style resolution we will do for the frame construction
1674 // happens async when we're not in an animation restyle already,
1675 // problems could arise.
1676 // We could also have problems with triggering of CSS transitions
1677 // on elements whose frames are reconstructed, since we depend on
1678 // the reconstruction happening synchronously.
1679 frameConstructor->RecreateFramesForContent(
1680 content, nsCSSFrameConstructor::InsertionKind::Sync);
1681 continue;
1684 MOZ_ASSERT(frame, "This shouldn't happen");
1685 if (hint & nsChangeHint_AddOrRemoveTransform) {
1686 for (nsIFrame* cont = frame; cont;
1687 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1688 if (cont->StyleDisplay()->HasTransform(cont)) {
1689 cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
1691 // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
1692 // transformed by other means. It's OK to have the bit even if it's
1693 // not needed.
1695 // When dropping a running transform animation we will first add an
1696 // nsChangeHint_UpdateTransformLayer hint as part of the animation-only
1697 // restyle. During the subsequent regular restyle, if the animation was
1698 // the only reason the element had any transform applied, we will add
1699 // nsChangeHint_AddOrRemoveTransform as part of the regular restyle.
1701 // With the Gecko backend, these two change hints are processed
1702 // after each restyle but when using the Servo backend they accumulate
1703 // and are processed together after we have already removed the
1704 // transform as part of the regular restyle. Since we don't actually
1705 // need the nsChangeHint_UpdateTransformLayer hint if we already have
1706 // a nsChangeHint_AddOrRemoveTransform hint, and since we
1707 // will fail an assertion in ApplyRenderingChangeToTree if we try
1708 // specify nsChangeHint_UpdateTransformLayer but don't have any
1709 // transform style, we just drop the unneeded hint here.
1710 hint &= ~nsChangeHint_UpdateTransformLayer;
1713 if (!frame->FrameMaintainsOverflow()) {
1714 // frame does not maintain overflow rects, so avoid calling
1715 // FinishAndStoreOverflow on it:
1716 hint &=
1717 ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
1718 nsChangeHint_UpdatePostTransformOverflow |
1719 nsChangeHint_UpdateParentOverflow |
1720 nsChangeHint_UpdateSubtreeOverflow);
1723 if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
1724 // Frame can not be transformed, and thus a change in transform will
1725 // have no effect and we should not use either
1726 // nsChangeHint_UpdatePostTransformOverflow or
1727 // nsChangeHint_UpdateTransformLayerhint.
1728 hint &= ~(nsChangeHint_UpdatePostTransformOverflow |
1729 nsChangeHint_UpdateTransformLayer);
1732 if ((hint & nsChangeHint_UpdateEffects) &&
1733 frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
1734 SVGObserverUtils::UpdateEffects(frame);
1736 if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
1737 ((hint & nsChangeHint_UpdateOpacityLayer) &&
1738 frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) {
1739 SVGObserverUtils::InvalidateRenderingObservers(frame);
1740 frame->SchedulePaint();
1742 if (hint & nsChangeHint_NeedReflow) {
1743 StyleChangeReflow(frame, hint);
1744 didReflowThisFrame = true;
1747 // Here we need to propagate repaint frame change hint instead of update
1748 // opacity layer change hint when we do opacity optimization for SVG.
1749 // We can't do it in nsStyleEffects::CalcDifference() just like we do
1750 // for the optimization for 0.99 over opacity values since we have no way
1751 // to call SVGUtils::CanOptimizeOpacity() there.
1752 if ((hint & nsChangeHint_UpdateOpacityLayer) &&
1753 SVGUtils::CanOptimizeOpacity(frame)) {
1754 hint &= ~nsChangeHint_UpdateOpacityLayer;
1755 hint |= nsChangeHint_RepaintFrame;
1758 if ((hint & nsChangeHint_UpdateUsesOpacity) && frame->IsTablePart()) {
1759 NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
1760 "should only return UpdateUsesOpacity hint "
1761 "when also returning UpdateOpacityLayer hint");
1762 // When an internal table part (including cells) changes between
1763 // having opacity 1 and non-1, it changes whether its
1764 // backgrounds (and those of table parts inside of it) are
1765 // painted as part of the table's nsDisplayTableBorderBackground
1766 // display item, or part of its own display item. That requires
1767 // invalidation, so change UpdateOpacityLayer to RepaintFrame.
1768 hint &= ~nsChangeHint_UpdateOpacityLayer;
1769 hint |= nsChangeHint_RepaintFrame;
1772 // Opacity disables preserve-3d, so if we toggle it, then we also need
1773 // to update the overflow areas of all potentially affected frames.
1774 if ((hint & nsChangeHint_UpdateUsesOpacity) &&
1775 frame->StyleDisplay()->mTransformStyle ==
1776 StyleTransformStyle::Preserve3d) {
1777 hint |= nsChangeHint_UpdateSubtreeOverflow;
1780 if (hint & nsChangeHint_UpdateBackgroundPosition) {
1781 // For most frame types, DLBI can detect background position changes,
1782 // so we only need to schedule a paint.
1783 hint |= nsChangeHint_SchedulePaint;
1784 if (frame->IsTablePart() || frame->IsMathMLFrame()) {
1785 // Table parts and MathML frames don't build display items for their
1786 // backgrounds, so DLBI can't detect background-position changes for
1787 // these frames. Repaint the whole frame.
1788 hint |= nsChangeHint_RepaintFrame;
1792 if (hint &
1793 (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer |
1794 nsChangeHint_UpdateTransformLayer |
1795 nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
1796 ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
1799 if (hint & (nsChangeHint_UpdateTransformLayer |
1800 nsChangeHint_AddOrRemoveTransform)) {
1801 // We need to trigger re-snapping to this content if we snapped to the
1802 // content on the last scroll operation.
1803 ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
1806 if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
1807 // It is possible for this to fall back to a reflow
1808 if (!RecomputePosition(frame)) {
1809 StyleChangeReflow(frame, nsChangeHint_NeedReflow |
1810 nsChangeHint_ReflowChangesSizeOrPosition);
1811 didReflowThisFrame = true;
1814 NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
1815 (hint & nsChangeHint_UpdateOverflow),
1816 "nsChangeHint_UpdateOverflow should be passed too");
1817 if (!didReflowThisFrame &&
1818 (hint & (nsChangeHint_UpdateOverflow |
1819 nsChangeHint_UpdatePostTransformOverflow |
1820 nsChangeHint_UpdateParentOverflow |
1821 nsChangeHint_UpdateSubtreeOverflow))) {
1822 if (hint & nsChangeHint_UpdateSubtreeOverflow) {
1823 for (nsIFrame* cont = frame; cont;
1824 cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1825 AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
1827 // The work we just did in AddSubtreeToOverflowTracker
1828 // subsumes some of the other hints:
1829 hint &= ~(nsChangeHint_UpdateOverflow |
1830 nsChangeHint_UpdatePostTransformOverflow);
1832 if (hint & nsChangeHint_ChildrenOnlyTransform) {
1833 // We need to update overflows. The correct frame(s) to update depends
1834 // on whether the ChangeHint came from an outer or an inner svg.
1835 nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
1836 NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
1837 "SVG frames should not have continuations "
1838 "or ib-split siblings");
1839 NS_ASSERTION(
1840 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
1841 "SVG frames should not have continuations "
1842 "or ib-split siblings");
1843 if (hintFrame->IsSVGOuterSVGAnonChildFrame()) {
1844 // The children only transform of an outer svg frame is applied to
1845 // the outer svg's anonymous child frame (instead of to the
1846 // anonymous child's children).
1848 if (!CanSkipOverflowUpdates(hintFrame)) {
1849 mOverflowChangedTracker.AddFrame(
1850 hintFrame, OverflowChangedTracker::CHILDREN_CHANGED);
1852 } else {
1853 // The children only transform is applied to the child frames of an
1854 // inner svg frame, so update the child overflows.
1855 nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
1856 for (; childFrame; childFrame = childFrame->GetNextSibling()) {
1857 MOZ_ASSERT(childFrame->IsSVGFrame(),
1858 "Not expecting non-SVG children");
1859 if (!CanSkipOverflowUpdates(childFrame)) {
1860 mOverflowChangedTracker.AddFrame(
1861 childFrame, OverflowChangedTracker::CHILDREN_CHANGED);
1863 NS_ASSERTION(
1864 !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
1865 "SVG frames should not have continuations "
1866 "or ib-split siblings");
1867 NS_ASSERTION(
1868 childFrame->GetParent() == hintFrame,
1869 "SVG child frame not expected to have different parent");
1873 if (!CanSkipOverflowUpdates(frame)) {
1874 if (hint & (nsChangeHint_UpdateOverflow |
1875 nsChangeHint_UpdatePostTransformOverflow)) {
1876 OverflowChangedTracker::ChangeKind changeKind;
1877 // If we have both nsChangeHint_UpdateOverflow and
1878 // nsChangeHint_UpdatePostTransformOverflow,
1879 // CHILDREN_CHANGED is selected as it is
1880 // strictly stronger.
1881 if (hint & nsChangeHint_UpdateOverflow) {
1882 changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
1883 } else {
1884 changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
1886 for (nsIFrame* cont = frame; cont;
1887 cont =
1888 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1889 mOverflowChangedTracker.AddFrame(cont, changeKind);
1892 // UpdateParentOverflow hints need to be processed in addition
1893 // to the above, since if the processing of the above hints
1894 // yields no change, the update will not propagate to the
1895 // parent.
1896 if (hint & nsChangeHint_UpdateParentOverflow) {
1897 MOZ_ASSERT(frame->GetParent(),
1898 "shouldn't get style hints for the root frame");
1899 for (nsIFrame* cont = frame; cont;
1900 cont =
1901 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
1902 mOverflowChangedTracker.AddFrame(
1903 cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED);
1908 if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
1909 presContext->PresShell()->SynthesizeMouseMove(false);
1910 didUpdateCursor = true;
1912 if (hint & nsChangeHint_UpdateTableCellSpans) {
1913 frameConstructor->UpdateTableCellSpans(content);
1915 if (hint & nsChangeHint_VisibilityChange) {
1916 frame->UpdateVisibleDescendantsState();
1920 aChangeList.Clear();
1921 FlushOverflowChangedTracker();
1924 /* static */
1925 uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) {
1926 EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame);
1927 return effectSet ? effectSet->GetAnimationGeneration() : 0;
1930 void RestyleManager::IncrementAnimationGeneration() {
1931 // We update the animation generation at start of each call to
1932 // ProcessPendingRestyles so we should ignore any subsequent (redundant)
1933 // calls that occur while we are still processing restyles.
1934 if (!mInStyleRefresh) {
1935 ++mAnimationGeneration;
1939 /* static */
1940 void RestyleManager::AddLayerChangesForAnimation(
1941 nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement,
1942 nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) {
1943 MOZ_ASSERT(aElement);
1944 MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame);
1945 if (!aStyleFrame) {
1946 return;
1949 uint64_t frameGeneration =
1950 RestyleManager::GetAnimationGenerationForFrame(aStyleFrame);
1952 Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties;
1954 nsChangeHint hint = nsChangeHint(0);
1955 auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration,
1956 DisplayItemType aDisplayItemType) -> bool {
1957 if (aGeneration && frameGeneration != *aGeneration) {
1958 // If we have a transform layer but don't have any transform style, we
1959 // probably just removed the transform but haven't destroyed the layer
1960 // yet. In this case we will typically add the appropriate change hint
1961 // (nsChangeHint_UpdateContainingBlock) when we compare styles so in
1962 // theory we could skip adding any change hint here.
1964 // However, sometimes when we compare styles we'll get no change. For
1965 // example, if the transform style was 'none' when we sent the transform
1966 // animation to the compositor and the current transform style is now
1967 // 'none' we'll think nothing changed but actually we still need to
1968 // trigger an update to clear whatever style the transform animation set
1969 // on the compositor. To handle this case we simply set all the change
1970 // hints relevant to removing transform style (since we don't know exactly
1971 // what changes happened while the animation was running on the
1972 // compositor).
1974 // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we
1975 // did, ApplyRenderingChangeToTree would complain that we're updating a
1976 // transform layer without a transform.
1977 if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM &&
1978 !aStyleFrame->StyleDisplay()->HasTransformStyle()) {
1979 // Add all the hints for a removing a transform if they are not already
1980 // set for this frame.
1981 if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform,
1982 aHintForThisFrame))) {
1983 hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
1985 return true;
1987 hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
1990 // We consider it's the first paint for the frame if we have an animation
1991 // for the property but have no layer, for the case of WebRender, no
1992 // corresponding animation info.
1993 // Note that in case of animations which has properties preventing running
1994 // on the compositor, e.g., width or height, corresponding layer is not
1995 // created at all, but even in such cases, we normally set valid change
1996 // hint for such animations in each tick, i.e. restyles in each tick. As
1997 // a result, we usually do restyles for such animations in every tick on
1998 // the main-thread. The only animations which will be affected by this
1999 // explicit change hint are animations that have opacity/transform but did
2000 // not have those properies just before. e.g, setting transform by
2001 // setKeyframes or changing target element from other target which prevents
2002 // running on the compositor, etc.
2003 if (!aGeneration) {
2004 nsChangeHint hintForDisplayItem =
2005 LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
2006 // We don't need to apply the corresponding change hint if we already have
2007 // it.
2008 if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) {
2009 return true;
2012 if (!effectiveAnimationProperties) {
2013 effectiveAnimationProperties.emplace(
2014 nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame));
2016 const nsCSSPropertyIDSet& propertiesForDisplayItem =
2017 LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType);
2018 if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) {
2019 hint |= hintForDisplayItem;
2022 return true;
2025 AnimationInfo::EnumerateGenerationOnFrame(
2026 aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes,
2027 maybeApplyChangeHint);
2029 if (hint) {
2030 // We apply the hint to the primary frame, not the style frame. Transform
2031 // and opacity hints apply to the table wrapper box, not the table box.
2032 aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint);
2036 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
2037 RestyleManager* aRestyleManager)
2038 : mRestyleManager(aRestyleManager),
2039 mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) {
2040 MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
2041 "shouldn't construct recursively");
2042 mRestyleManager->mAnimationsWithDestroyedFrame = this;
2045 void RestyleManager::AnimationsWithDestroyedFrame ::
2046 StopAnimationsForElementsWithoutFrames() {
2047 StopAnimationsWithoutFrame(mContents, PseudoStyleType::NotPseudo);
2048 StopAnimationsWithoutFrame(mBeforeContents, PseudoStyleType::before);
2049 StopAnimationsWithoutFrame(mAfterContents, PseudoStyleType::after);
2050 StopAnimationsWithoutFrame(mMarkerContents, PseudoStyleType::marker);
2053 void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
2054 nsTArray<RefPtr<nsIContent>>& aArray, PseudoStyleType aPseudoType) {
2055 nsAnimationManager* animationManager =
2056 mRestyleManager->PresContext()->AnimationManager();
2057 nsTransitionManager* transitionManager =
2058 mRestyleManager->PresContext()->TransitionManager();
2059 for (nsIContent* content : aArray) {
2060 if (aPseudoType == PseudoStyleType::NotPseudo) {
2061 if (content->GetPrimaryFrame()) {
2062 continue;
2064 } else if (aPseudoType == PseudoStyleType::before) {
2065 if (nsLayoutUtils::GetBeforeFrame(content)) {
2066 continue;
2068 } else if (aPseudoType == PseudoStyleType::after) {
2069 if (nsLayoutUtils::GetAfterFrame(content)) {
2070 continue;
2072 } else if (aPseudoType == PseudoStyleType::marker) {
2073 if (nsLayoutUtils::GetMarkerFrame(content)) {
2074 continue;
2077 dom::Element* element = content->AsElement();
2079 animationManager->StopAnimationsForElement(element, aPseudoType);
2080 transitionManager->StopAnimationsForElement(element, aPseudoType);
2082 // All other animations should keep running but not running on the
2083 // *compositor* at this point.
2084 if (EffectSet* effectSet = EffectSet::Get(element, aPseudoType)) {
2085 for (KeyframeEffect* effect : *effectSet) {
2086 effect->ResetIsRunningOnCompositor();
2092 #ifdef DEBUG
2093 static bool IsAnonBox(const nsIFrame* aFrame) {
2094 return aFrame->Style()->IsAnonBox();
2097 static const nsIFrame* FirstContinuationOrPartOfIBSplit(
2098 const nsIFrame* aFrame) {
2099 if (!aFrame) {
2100 return nullptr;
2103 return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
2106 static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
2107 const nsIFrame* parent = aFrame->GetParent();
2108 if (aFrame->IsTableFrame()) {
2109 MOZ_ASSERT(parent->IsTableWrapperFrame());
2110 parent = parent->GetParent();
2113 if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) {
2114 if (parent->IsLineFrame()) {
2115 parent = parent->GetParent();
2117 return parent->IsViewportFrame() ? nullptr
2118 : FirstContinuationOrPartOfIBSplit(parent);
2121 if (aFrame->IsLineFrame()) {
2122 // A ::first-line always ends up here via its block, which is therefore the
2123 // right expected owner. That block can be an
2124 // anonymous box. For example, we could have a ::first-line on a columnated
2125 // block; the blockframe is the column-content anonymous box in that case.
2126 // So we don't want to end up in the code below, which steps out of anon
2127 // boxes. Just return the parent of the line frame, which is the block.
2128 return parent;
2131 if (aFrame->IsLetterFrame()) {
2132 // Ditto for ::first-letter. A first-letter always arrives here via its
2133 // direct parent, except when it's parented to a ::first-line.
2134 if (parent->IsLineFrame()) {
2135 parent = parent->GetParent();
2137 return FirstContinuationOrPartOfIBSplit(parent);
2140 if (parent->IsLetterFrame()) {
2141 // Things never have ::first-letter as their expected parent. Go
2142 // on up to the ::first-letter's parent.
2143 parent = parent->GetParent();
2146 parent = FirstContinuationOrPartOfIBSplit(parent);
2148 // We've handled already anon boxes, so now we're looking at
2149 // a frame of a DOM element or pseudo. Hop through anon and line-boxes
2150 // generated by our DOM parent, and go find the owner frame for it.
2151 while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {
2152 auto pseudo = parent->Style()->GetPseudoType();
2153 if (pseudo == PseudoStyleType::tableWrapper) {
2154 const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
2155 MOZ_ASSERT(tableFrame->IsTableFrame());
2156 // Handle :-moz-table and :-moz-inline-table.
2157 parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame;
2158 } else {
2159 // We get the in-flow parent here so that we can handle the OOF anonymous
2160 // boxed to get the correct parent.
2161 parent = parent->GetInFlowParent();
2163 parent = FirstContinuationOrPartOfIBSplit(parent);
2166 return parent;
2169 // FIXME(emilio, bug 1633685): We should ideally figure out how to properly
2170 // restyle replicated fixed pos frames... We seem to assume everywhere that they
2171 // can't get restyled at the moment...
2172 static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
2173 if (!aFrame->PresContext()->IsPaginated()) {
2174 return false;
2177 for (; aFrame; aFrame = aFrame->GetParent()) {
2178 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
2179 !aFrame->FirstContinuation()->IsPrimaryFrame() &&
2180 nsLayoutUtils::IsReallyFixedPos(aFrame)) {
2181 return true;
2185 return true;
2188 void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
2189 MOZ_ASSERT(mOwner);
2190 MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
2191 MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree());
2192 // We allow aParent.mOwner to be null, for cases when we're not starting at
2193 // the root of the tree. We also allow aParent.mOwner to be somewhere up our
2194 // expected owner chain not our immediate owner, which allows us creating long
2195 // chains of ServoRestyleStates in some cases where it's just not worth it.
2196 if (aParent.mOwner) {
2197 const nsIFrame* owner = ExpectedOwnerForChild(mOwner);
2198 if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) {
2199 MOZ_ASSERT(IsAnonBox(owner),
2200 "Should only have expected owner weirdness when anon boxes "
2201 "are involved");
2202 bool found = false;
2203 for (; owner; owner = ExpectedOwnerForChild(owner)) {
2204 if (owner == aParent.mOwner) {
2205 found = true;
2206 break;
2209 MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain");
2214 nsChangeHint ServoRestyleState::ChangesHandledFor(
2215 const nsIFrame* aFrame) const {
2216 if (!mOwner) {
2217 MOZ_ASSERT(!mChangesHandled);
2218 return mChangesHandled;
2221 MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) ||
2222 IsInReplicatedFixedPosTree(aFrame),
2223 "Missed some frame in the hierarchy?");
2224 return mChangesHandled;
2226 #endif
2228 void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) {
2229 MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(),
2230 "All our wrappers are anon boxes, and why would we restyle "
2231 "non-inheriting ones?");
2232 MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(),
2233 "All our wrappers are anon boxes, and why would we restyle "
2234 "non-inheriting ones?");
2235 MOZ_ASSERT(
2236 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent,
2237 "Someone should be using TableAwareParentFor");
2238 MOZ_ASSERT(
2239 aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper,
2240 "Someone should be using TableAwareParentFor");
2241 // Make sure we only add first continuations.
2242 aWrapperFrame = aWrapperFrame->FirstContinuation();
2243 nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
2244 if (last == aWrapperFrame) {
2245 // Already queued up, nothing to do.
2246 return;
2249 // Make sure to queue up parents before children. But don't queue up
2250 // ancestors of non-anonymous boxes here; those are handled when we traverse
2251 // their non-anonymous kids.
2252 if (aWrapperFrame->ParentIsWrapperAnonBox()) {
2253 AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
2256 // If the append fails, we'll fail to restyle properly, but that's probably
2257 // better than crashing.
2258 if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
2259 aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
2263 void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) {
2264 size_t i = mPendingWrapperRestyleOffset;
2265 while (i < mPendingWrapperRestyles.Length()) {
2266 i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
2269 mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
2272 size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
2273 size_t aIndex) {
2274 // The frame at index aIndex is something we should restyle ourselves, but
2275 // following frames may need separate ServoRestyleStates to restyle.
2276 MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
2278 nsIFrame* cur = mPendingWrapperRestyles[aIndex];
2279 MOZ_ASSERT(cur->Style()->IsWrapperAnonBox());
2281 // Where is cur supposed to inherit from? From its parent frame, except in
2282 // the case when cur is a table, in which case it should be its grandparent.
2283 // Also, not in the case when the resulting frame would be a first-line; in
2284 // that case we should be inheriting from the block, and the first-line will
2285 // do its fixup later if needed.
2287 // Note that after we do all that fixup the parent we get might still not be
2288 // aParent; for example aParent could be a scrollframe, in which case we
2289 // should inherit from the scrollcontent frame. Or the parent might be some
2290 // continuation of aParent.
2292 // Try to assert as much as we can about the parent we actually end up using
2293 // without triggering bogus asserts in all those various edge cases.
2294 nsIFrame* parent = cur->GetParent();
2295 if (cur->IsTableFrame()) {
2296 MOZ_ASSERT(parent->IsTableWrapperFrame());
2297 parent = parent->GetParent();
2299 if (parent->IsLineFrame()) {
2300 parent = parent->GetParent();
2302 MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent ||
2303 (parent->Style()->IsInheritingAnonBox() &&
2304 parent->GetContent() == aParent->GetContent()));
2306 // Now "this" is a ServoRestyleState for aParent, so if parent is not a next
2307 // continuation (possibly across ib splits) of aParent we need a new
2308 // ServoRestyleState for the kid.
2309 Maybe<ServoRestyleState> parentRestyleState;
2310 nsIFrame* parentForRestyle =
2311 nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
2312 if (parentForRestyle != aParent) {
2313 parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
2314 Type::InFlow);
2316 ServoRestyleState& curRestyleState =
2317 parentRestyleState ? *parentRestyleState : *this;
2319 // This frame may already have been restyled. Even if it has, we can't just
2320 // return, because the next frame may be a kid of it that does need restyling.
2321 if (cur->IsWrapperAnonBoxNeedingRestyle()) {
2322 parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState);
2323 cur->SetIsWrapperAnonBoxNeedingRestyle(false);
2326 size_t numProcessed = 1;
2328 // Note: no overflow possible here, since aIndex < length.
2329 if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
2330 nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
2331 if (TableAwareParentFor(next) == cur &&
2332 next->IsWrapperAnonBoxNeedingRestyle()) {
2333 // It might be nice if we could do better than nsChangeHint_Empty. On
2334 // the other hand, presumably our mChangesHandled already has the bits
2335 // we really want here so in practice it doesn't matter.
2336 ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
2337 Type::InFlow,
2338 /* aAssertWrapperRestyleLength = */ false);
2339 numProcessed +=
2340 childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
2344 return numProcessed;
2347 nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) {
2348 // We want to get the anon box parent for aChild. where aChild has
2349 // ParentIsWrapperAnonBox().
2351 // For the most part this is pretty straightforward, but there are two
2352 // wrinkles. First, if aChild is a table, then we really want the parent of
2353 // its table wrapper.
2354 if (aChild->IsTableFrame()) {
2355 aChild = aChild->GetParent();
2356 MOZ_ASSERT(aChild->IsTableWrapperFrame());
2359 nsIFrame* parent = aChild->GetParent();
2360 // Now if parent is a cell-content frame, we actually want the cellframe.
2361 if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
2362 parent = parent->GetParent();
2363 } else if (parent->IsTableWrapperFrame()) {
2364 // Must be a caption. In that case we want the table here.
2365 MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
2366 parent = parent->PrincipalChildList().FirstChild();
2368 return parent;
2371 void RestyleManager::PostRestyleEvent(Element* aElement,
2372 RestyleHint aRestyleHint,
2373 nsChangeHint aMinChangeHint) {
2374 MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange),
2375 "Didn't expect explicit change hints to be neutral!");
2376 if (MOZ_UNLIKELY(IsDisconnected()) ||
2377 MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
2378 return;
2381 // We allow posting restyles from within change hint handling, but not from
2382 // within the restyle algorithm itself.
2383 MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
2385 if (!aRestyleHint && !aMinChangeHint) {
2386 // FIXME(emilio): we should assert against this instead.
2387 return; // Nothing to do.
2390 // Assuming the restyle hints will invalidate cached style for
2391 // getComputedStyle, since we don't know if any of the restyling that we do
2392 // would affect undisplayed elements.
2393 if (aRestyleHint) {
2394 if (!(aRestyleHint & RestyleHint::ForAnimations())) {
2395 mHaveNonAnimationRestyles = true;
2398 IncrementUndisplayedRestyleGeneration();
2401 // Processing change hints sometimes causes new change hints to be generated,
2402 // and very occasionally, additional restyle hints. We collect the change
2403 // hints manually to avoid re-traversing the DOM to find them.
2404 if (mReentrantChanges && !aRestyleHint) {
2405 mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint});
2406 return;
2409 if (aRestyleHint || aMinChangeHint) {
2410 Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
2414 void RestyleManager::PostRestyleEventForAnimations(Element* aElement,
2415 PseudoStyleType aPseudoType,
2416 RestyleHint aRestyleHint) {
2417 Element* elementToRestyle =
2418 AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
2420 if (!elementToRestyle) {
2421 // FIXME: Bug 1371107: When reframing happens,
2422 // EffectCompositor::mElementsToRestyle still has unbound old pseudo
2423 // element. We should drop it.
2424 return;
2427 mPresContext->TriggeredAnimationRestyle();
2429 Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0));
2432 void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
2433 RestyleHint aRestyleHint) {
2434 // NOTE(emilio): The semantics of these methods are quite funny, in the sense
2435 // that we're not supposed to need to rebuild the actual stylist data.
2437 // That's handled as part of the MediumFeaturesChanged stuff, if needed.
2439 // Clear the cached style data only if we are guaranteed to process the whole
2440 // DOM tree again.
2442 // FIXME(emilio): Decouple this, probably. This probably just wants to reset
2443 // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon
2444 // box styles and such... But it doesn't really always need to clear the
2445 // initial style of the document and similar...
2446 if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
2447 StyleSet()->ClearCachedStyleData();
2450 DocumentStyleRootIterator iter(mPresContext->Document());
2451 while (Element* root = iter.GetNextStyleRoot()) {
2452 PostRestyleEvent(root, aRestyleHint, aExtraHint);
2455 // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect
2456 // non-inheriting anon boxes. It's not clear if we want to support that, but
2457 // if we do, we need to re-selector-match them here.
2460 /* static */
2461 void RestyleManager::ClearServoDataFromSubtree(Element* aElement,
2462 IncludeRoot aIncludeRoot) {
2463 if (aElement->HasServoData()) {
2464 StyleChildrenIterator it(aElement);
2465 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2466 if (n->IsElement()) {
2467 ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes);
2472 if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) {
2473 aElement->ClearServoData();
2474 MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits |
2475 NODE_NEEDS_FRAME));
2476 MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot());
2480 /* static */
2481 void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) {
2482 if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) {
2483 StyleChildrenIterator it(aElement);
2484 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
2485 if (n->IsElement()) {
2486 ClearRestyleStateFromSubtree(n->AsElement());
2491 bool wasRestyled = false;
2492 Unused << Servo_TakeChangeHint(aElement, &wasRestyled);
2493 aElement->UnsetFlags(Element::kAllServoDescendantBits);
2497 * This struct takes care of encapsulating some common state that text nodes may
2498 * need to track during the post-traversal.
2500 * This is currently used to properly compute change hints when the parent
2501 * element of this node is a display: contents node, and also to avoid computing
2502 * the style for text children more than once per element.
2504 struct RestyleManager::TextPostTraversalState {
2505 public:
2506 TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext,
2507 bool aDisplayContentsParentStyleChanged,
2508 ServoRestyleState& aParentRestyleState)
2509 : mParentElement(aParentElement),
2510 mParentContext(aParentContext),
2511 mParentRestyleState(aParentRestyleState),
2512 mStyle(nullptr),
2513 mShouldPostHints(aDisplayContentsParentStyleChanged),
2514 mShouldComputeHints(aDisplayContentsParentStyleChanged),
2515 mComputedHint(nsChangeHint_Empty) {}
2517 nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); }
2519 ComputedStyle& ComputeStyle(nsIContent* aTextNode) {
2520 if (!mStyle) {
2521 mStyle = mParentRestyleState.StyleSet().ResolveStyleForText(
2522 aTextNode, &ParentStyle());
2524 MOZ_ASSERT(mStyle);
2525 return *mStyle;
2528 void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame,
2529 ComputedStyle& aNewStyle) {
2530 MOZ_ASSERT(aTextFrame);
2531 MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText);
2533 if (MOZ_LIKELY(!mShouldPostHints)) {
2534 return;
2537 ComputedStyle* oldStyle = aTextFrame->Style();
2538 MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText);
2540 // We rely on the fact that all the text children for the same element share
2541 // style to avoid recomputing style differences for all of them.
2543 // TODO(emilio): The above may not be true for ::first-{line,letter}, but
2544 // we'll cross that bridge when we support those in stylo.
2545 if (mShouldComputeHints) {
2546 mShouldComputeHints = false;
2547 uint32_t equalStructs;
2548 mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs);
2549 mComputedHint = NS_RemoveSubsumedHints(
2550 mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame));
2553 if (mComputedHint) {
2554 mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent,
2555 mComputedHint);
2559 private:
2560 ComputedStyle& ParentStyle() {
2561 if (!mParentContext) {
2562 mLazilyResolvedParentContext =
2563 ServoStyleSet::ResolveServoStyle(mParentElement);
2564 mParentContext = mLazilyResolvedParentContext;
2566 return *mParentContext;
2569 Element& mParentElement;
2570 ComputedStyle* mParentContext;
2571 RefPtr<ComputedStyle> mLazilyResolvedParentContext;
2572 ServoRestyleState& mParentRestyleState;
2573 RefPtr<ComputedStyle> mStyle;
2574 bool mShouldPostHints;
2575 bool mShouldComputeHints;
2576 nsChangeHint mComputedHint;
2579 static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet,
2580 nsStyleChangeList& aChangeList) {
2581 const nsStyleDisplay* display = aFrame->Style()->StyleDisplay();
2582 if (display->mTopLayer != StyleTopLayer::Top) {
2583 return;
2586 // Elements in the top layer are guaranteed to have absolute or fixed
2587 // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer.
2588 MOZ_ASSERT(display->IsAbsolutelyPositionedStyle());
2590 nsIFrame* backdropPlaceholder =
2591 aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild();
2592 if (!backdropPlaceholder) {
2593 return;
2596 MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
2597 nsIFrame* backdropFrame =
2598 nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
2599 MOZ_ASSERT(backdropFrame->IsBackdropFrame());
2600 MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
2601 PseudoStyleType::backdrop);
2603 RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
2604 *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, nullptr,
2605 aFrame->Style());
2607 // NOTE(emilio): We can't use the changes handled for the owner of the
2608 // backdrop frame, since it's out of flow, and parented to the viewport or
2609 // canvas frame (depending on the `position` value).
2610 MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
2611 backdropFrame->GetParent()->IsCanvasFrame());
2612 nsTArray<nsIFrame*> wrappersToRestyle;
2613 nsTArray<RefPtr<Element>> anchorsToSuppress;
2614 ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle,
2615 anchorsToSuppress);
2616 nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state);
2617 MOZ_ASSERT(anchorsToSuppress.IsEmpty());
2620 static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame,
2621 ServoRestyleState& aRestyleState) {
2622 MOZ_ASSERT(
2623 !aFrame->IsBlockFrameOrSubclass(),
2624 "You're probably duplicating work with UpdatePseudoElementStyles!");
2625 if (!aFrame->HasFirstLetterChild()) {
2626 return;
2629 // We need to find the block the first-letter is associated with so we can
2630 // find the right element for the first-letter's style resolution. Might as
2631 // well just delegate the whole thing to that block.
2632 nsIFrame* block = aFrame->GetParent();
2633 while (!block->IsBlockFrameOrSubclass()) {
2634 block = block->GetParent();
2637 static_cast<nsBlockFrame*>(block->FirstContinuation())
2638 ->UpdateFirstLetterStyle(aRestyleState);
2641 static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
2642 ComputedStyle& aOldContext,
2643 ServoRestyleState& aRestyleState) {
2644 auto pseudoType = aOldContext.GetPseudoType();
2645 MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
2646 MOZ_ASSERT(
2647 !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
2649 RefPtr<ComputedStyle> newStyle =
2650 aRestyleState.StyleSet().ResolvePseudoElementStyle(
2651 *aFrame->GetContent()->AsElement(), pseudoType, nullptr,
2652 aFrame->Style());
2654 uint32_t equalStructs; // Not used, actually.
2655 nsChangeHint childHint =
2656 aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
2657 if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
2658 !aFrame->IsColumnSpanInMulticolSubtree()) {
2659 childHint = NS_RemoveSubsumedHints(childHint,
2660 aRestyleState.ChangesHandledFor(aFrame));
2663 if (childHint) {
2664 if (childHint & nsChangeHint_ReconstructFrame) {
2665 // If we generate a reconstruct here, remove any non-reconstruct hints we
2666 // may have already generated for this content.
2667 aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent());
2669 aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(),
2670 childHint);
2673 aFrame->SetAdditionalComputedStyle(aIndex, newStyle);
2676 static void UpdateAdditionalComputedStyles(nsIFrame* aFrame,
2677 ServoRestyleState& aRestyleState) {
2678 MOZ_ASSERT(aFrame);
2679 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement());
2681 // FIXME(emilio): Consider adding a bit or something to avoid the initial
2682 // virtual call?
2683 uint32_t index = 0;
2684 while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) {
2685 UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState);
2689 static void UpdateFramePseudoElementStyles(nsIFrame* aFrame,
2690 ServoRestyleState& aRestyleState) {
2691 if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
2692 blockFrame->UpdatePseudoElementStyles(aRestyleState);
2693 } else {
2694 UpdateFirstLetterIfNeeded(aFrame, aRestyleState);
2697 UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(),
2698 aRestyleState.ChangeList());
2701 enum class ServoPostTraversalFlags : uint32_t {
2702 Empty = 0,
2703 // Whether parent was restyled.
2704 ParentWasRestyled = 1 << 0,
2705 // Skip sending accessibility notifications for all descendants.
2706 SkipA11yNotifications = 1 << 1,
2707 // Always send accessibility notifications if the element is shown.
2708 // The SkipA11yNotifications flag above overrides this flag.
2709 SendA11yNotificationsIfShown = 1 << 2,
2712 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
2714 // Send proper accessibility notifications and return post traversal
2715 // flags for kids.
2716 static ServoPostTraversalFlags SendA11yNotifications(
2717 nsPresContext* aPresContext, Element* aElement,
2718 const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
2719 ServoPostTraversalFlags aFlags) {
2720 using Flags = ServoPostTraversalFlags;
2721 MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) ||
2722 !(aFlags & Flags::SendA11yNotificationsIfShown),
2723 "The two a11y flags should never be set together");
2725 #ifdef ACCESSIBILITY
2726 nsAccessibilityService* accService = GetAccService();
2727 if (!accService) {
2728 // If we don't have accessibility service, accessibility is not
2729 // enabled. Just skip everything.
2730 return Flags::Empty;
2733 if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually !=
2734 aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
2735 if (aElement->GetParent() &&
2736 aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) {
2737 accService->NotifyOfTabPanelVisibilityChange(
2738 aPresContext->PresShell(), aElement,
2739 aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually);
2743 if (aFlags & Flags::SkipA11yNotifications) {
2744 // Propagate the skipping flag to descendants.
2745 return Flags::SkipA11yNotifications;
2748 bool needsNotify = false;
2749 const bool isVisible = aNewStyle.StyleVisibility()->IsVisible() &&
2750 !aNewStyle.StyleUI()->IsInert();
2751 if (aFlags & Flags::SendA11yNotificationsIfShown) {
2752 if (!isVisible) {
2753 // Propagate the sending-if-shown flag to descendants.
2754 return Flags::SendA11yNotificationsIfShown;
2756 // We have asked accessibility service to remove the whole subtree
2757 // of element which becomes invisible from the accessible tree, but
2758 // this element is visible, so we need to add it back.
2759 needsNotify = true;
2760 } else {
2761 // If we shouldn't skip in any case, we need to check whether our
2762 // own visibility has changed.
2763 const bool wasVisible = aOldStyle.StyleVisibility()->IsVisible() &&
2764 !aOldStyle.StyleUI()->IsInert();
2765 needsNotify = wasVisible != isVisible;
2768 if (needsNotify) {
2769 PresShell* presShell = aPresContext->PresShell();
2770 if (isVisible) {
2771 accService->ContentRangeInserted(presShell, aElement,
2772 aElement->GetNextSibling());
2773 // We are adding the subtree. Accessibility service would handle
2774 // descendants, so we should just skip them from notifying.
2775 return Flags::SkipA11yNotifications;
2777 // Remove the subtree of this invisible element, and ask any shown
2778 // descendant to add themselves back.
2779 accService->ContentRemoved(presShell, aElement);
2780 return Flags::SendA11yNotificationsIfShown;
2782 #endif
2784 return Flags::Empty;
2787 bool RestyleManager::ProcessPostTraversal(Element* aElement,
2788 ServoRestyleState& aRestyleState,
2789 ServoPostTraversalFlags aFlags) {
2790 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
2791 nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
2793 MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(),
2794 "Element without Servo data on a post-traversal? How?");
2796 // NOTE(emilio): This is needed because for table frames the bit is set on the
2797 // table wrapper (which is the primary frame), not on the table itself.
2798 const bool isOutOfFlow =
2799 primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
2801 // We need this because any column-spanner's parent frame is not its DOM
2802 // parent's primary frame. We need some special check similar to out-of-flow
2803 // frames.
2804 const bool isColumnSpan =
2805 primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree();
2807 // Grab the change hint from Servo.
2808 bool wasRestyled = false;
2809 nsChangeHint changeHint =
2810 static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled));
2812 RefPtr<ComputedStyle> upToDateStyleIfRestyled =
2813 wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr;
2815 // We should really fix the weird primary frame mapping for image maps
2816 // (bug 135040)...
2817 if (styleFrame && styleFrame->GetContent() != aElement) {
2818 MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass());
2819 styleFrame = nullptr;
2822 // Handle lazy frame construction by posting a reconstruct for any lazily-
2823 // constructed roots.
2824 if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
2825 changeHint |= nsChangeHint_ReconstructFrame;
2826 MOZ_ASSERT(!styleFrame);
2829 if (styleFrame) {
2830 MOZ_ASSERT(primaryFrame);
2832 nsIFrame* maybeAnonBoxChild;
2833 if (isOutOfFlow) {
2834 maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
2835 } else {
2836 maybeAnonBoxChild = primaryFrame;
2837 // Do not subsume change hints for the column-spanner.
2838 if (!isColumnSpan) {
2839 changeHint = NS_RemoveSubsumedHints(
2840 changeHint, aRestyleState.ChangesHandledFor(styleFrame));
2844 // If the parent wasn't restyled, the styles of our anon box parents won't
2845 // change either.
2846 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
2847 maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
2848 aRestyleState.AddPendingWrapperRestyle(
2849 ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
2852 // If we don't have a ::marker pseudo-element, but need it, then
2853 // reconstruct the frame. (The opposite situation implies 'display'
2854 // changes so doesn't need to be handled explicitly here.)
2855 if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() &&
2856 styleFrame->IsBlockFrameOrSubclass() &&
2857 !nsLayoutUtils::GetMarkerPseudo(aElement)) {
2858 RefPtr<ComputedStyle> pseudoStyle =
2859 aRestyleState.StyleSet().ProbePseudoElementStyle(
2860 *aElement, PseudoStyleType::marker, nullptr,
2861 upToDateStyleIfRestyled);
2862 if (pseudoStyle) {
2863 changeHint |= nsChangeHint_ReconstructFrame;
2868 // Although we shouldn't generate non-ReconstructFrame hints for elements with
2869 // no frames, we can still get them here if they were explicitly posted by
2870 // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
2871 // :visited. Skip processing these hints if there is no frame.
2872 if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) &&
2873 changeHint) {
2874 aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
2877 // If our change hint is reconstruct, we delegate to the frame constructor,
2878 // which consumes the new style and expects the old style to be on the frame.
2880 // XXXbholley: We should teach the frame constructor how to clear the dirty
2881 // descendants bit to avoid the traversal here.
2882 if (changeHint & nsChangeHint_ReconstructFrame) {
2883 if (wasRestyled &&
2884 StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
2885 const bool wasAbsPos =
2886 styleFrame &&
2887 styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
2888 auto* newDisp = upToDateStyleIfRestyled->StyleDisplay();
2889 // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
2891 // We need to do the position check here rather than in
2892 // DidSetComputedStyle because changing position reframes.
2894 // We suppress adjustments whenever we change from being display: none to
2895 // be an abspos.
2897 // Similarly, for other changes from abspos to non-abspos styles.
2899 // TODO(emilio): I _think_ chrome won't suppress adjustments whenever
2900 // `display` changes. But that causes some infinite loops in cases like
2901 // bug 1568778.
2902 if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) {
2903 aRestyleState.AddPendingScrollAnchorSuppression(aElement);
2906 ClearRestyleStateFromSubtree(aElement);
2907 return true;
2910 // TODO(emilio): We could avoid some refcount traffic here, specially in the
2911 // ComputedStyle case, which uses atomic refcounting.
2913 // Hold the ComputedStyle alive, because it could become a dangling pointer
2914 // during the replacement. In practice it's not a huge deal, but better not
2915 // playing with dangling pointers if not needed.
2917 // NOTE(emilio): We could keep around the old computed style for display:
2918 // contents elements too, but we don't really need it right now.
2919 RefPtr<ComputedStyle> oldOrDisplayContentsStyle =
2920 styleFrame ? styleFrame->Style() : nullptr;
2922 MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)),
2923 "display: contents node has a frame, yet we didn't reframe it"
2924 " above?");
2925 const bool isDisplayContents = !styleFrame && aElement->HasServoData() &&
2926 Servo_Element_IsDisplayContents(aElement);
2927 if (isDisplayContents) {
2928 oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement);
2931 Maybe<ServoRestyleState> thisFrameRestyleState;
2932 if (styleFrame) {
2933 auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow
2934 : ServoRestyleState::Type::InFlow;
2936 thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type);
2939 // We can't really assume as used changes from display: contents elements (or
2940 // other elements without frames).
2941 ServoRestyleState& childrenRestyleState =
2942 thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
2944 ComputedStyle* upToDateStyle =
2945 wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle;
2947 ServoPostTraversalFlags childrenFlags =
2948 wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled
2949 : ServoPostTraversalFlags::Empty;
2951 if (wasRestyled && oldOrDisplayContentsStyle) {
2952 MOZ_ASSERT(styleFrame || isDisplayContents);
2954 // We want to walk all the continuations here, even the ones with different
2955 // styles. In practice, the only reason we get continuations with different
2956 // styles here is ::first-line (::first-letter never affects element
2957 // styles). But in that case, newStyle is the right context for the
2958 // _later_ continuations anyway (the ones not affected by ::first-line), not
2959 // the earlier ones, so there is no point stopping right at the point when
2960 // we'd actually be setting the right ComputedStyle.
2962 // This does mean that we may be setting the wrong ComputedStyle on our
2963 // initial continuations; ::first-line fixes that up after the fact.
2964 for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) {
2965 MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0));
2966 f->SetComputedStyle(upToDateStyle);
2969 if (styleFrame) {
2970 UpdateAdditionalComputedStyles(styleFrame, aRestyleState);
2973 if (!aElement->GetParent()) {
2974 // This is the root. Update styles on the viewport as needed.
2975 ViewportFrame* viewport =
2976 do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
2977 if (viewport) {
2978 // NB: The root restyle state, not the one for our children!
2979 viewport->UpdateStyle(aRestyleState);
2983 // Some changes to animations don't affect the computed style and yet still
2984 // require the layer to be updated. For example, pausing an animation via
2985 // the Web Animations API won't affect an element's style but still
2986 // requires to update the animation on the layer.
2988 // We can sometimes reach this when the animated style is being removed.
2989 // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform
2990 // style or not, we need to call it *after* setting |newStyle| to
2991 // |styleFrame| to ensure the animated transform has been removed first.
2992 AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint,
2993 aRestyleState.ChangeList());
2995 childrenFlags |= SendA11yNotifications(mPresContext, aElement,
2996 *oldOrDisplayContentsStyle,
2997 *upToDateStyle, aFlags);
3000 const bool traverseElementChildren =
3001 aElement->HasAnyOfFlags(Element::kAllServoDescendantBits);
3002 const bool traverseTextChildren =
3003 wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
3004 bool recreatedAnyContext = wasRestyled;
3005 if (traverseElementChildren || traverseTextChildren) {
3006 StyleChildrenIterator it(aElement);
3007 TextPostTraversalState textState(*aElement, upToDateStyle,
3008 isDisplayContents && wasRestyled,
3009 childrenRestyleState);
3010 for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
3011 if (traverseElementChildren && n->IsElement()) {
3012 recreatedAnyContext |= ProcessPostTraversal(
3013 n->AsElement(), childrenRestyleState, childrenFlags);
3014 } else if (traverseTextChildren && n->IsText()) {
3015 recreatedAnyContext |= ProcessPostTraversalForText(
3016 n, textState, childrenRestyleState, childrenFlags);
3021 // We want to update frame pseudo-element styles after we've traversed our
3022 // kids, because some of those updates (::first-line/::first-letter) need to
3023 // modify the styles of the kids, and the child traversal above would just
3024 // clobber those modifications.
3025 if (styleFrame) {
3026 if (wasRestyled) {
3027 // Make sure to update anon boxes and pseudo bits after updating text,
3028 // otherwise ProcessPostTraversalForText could clobber first-letter
3029 // styles, for example.
3030 styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
3032 // Process anon box wrapper frames before ::first-line bits, but _after_
3033 // owned anon boxes, since the children wrapper anon boxes could be
3034 // inheriting from our own owned anon boxes.
3035 childrenRestyleState.ProcessWrapperRestyles(styleFrame);
3036 if (wasRestyled) {
3037 UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
3038 } else if (traverseElementChildren &&
3039 styleFrame->IsBlockFrameOrSubclass()) {
3040 // Even if we were not restyled, if we're a block with a first-line and
3041 // one of our descendant elements which is on the first line was restyled,
3042 // we need to update the styles of things on the first line, because
3043 // they're wrong now.
3045 // FIXME(bz) Could we do better here? For example, could we keep track of
3046 // frames that are "block with a ::first-line so we could avoid
3047 // IsFrameOfType() and digging about for the first-line frame if not?
3048 // Could we keep track of whether the element children we actually restyle
3049 // are affected by first-line? Something else? Bug 1385443 tracks making
3050 // this better.
3051 nsIFrame* firstLineFrame =
3052 static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame();
3053 if (firstLineFrame) {
3054 for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
3055 ReparentComputedStyleForFirstLine(kid);
3061 aElement->UnsetFlags(Element::kAllServoDescendantBits);
3062 return recreatedAnyContext;
3065 bool RestyleManager::ProcessPostTraversalForText(
3066 nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState,
3067 ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) {
3068 // Handle lazy frame construction.
3069 if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
3070 aPostTraversalState.ChangeList().AppendChange(
3071 nullptr, aTextNode, nsChangeHint_ReconstructFrame);
3072 return true;
3075 // Handle restyle.
3076 nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
3077 if (!primaryFrame) {
3078 return false;
3081 // If the parent wasn't restyled, the styles of our anon box parents won't
3082 // change either.
3083 if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
3084 primaryFrame->ParentIsWrapperAnonBox()) {
3085 aRestyleState.AddPendingWrapperRestyle(
3086 ServoRestyleState::TableAwareParentFor(primaryFrame));
3089 ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode);
3090 aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle);
3092 // We want to walk all the continuations here, even the ones with different
3093 // styles. In practice, the only reasons we get continuations with different
3094 // styles are ::first-line and ::first-letter. But in those cases,
3095 // newStyle is the right context for the _later_ continuations anyway (the
3096 // ones not affected by ::first-line/::first-letter), not the earlier ones,
3097 // so there is no point stopping right at the point when we'd actually be
3098 // setting the right ComputedStyle.
3100 // This does mean that we may be setting the wrong ComputedStyle on our
3101 // initial continuations; ::first-line/::first-letter fix that up after the
3102 // fact.
3103 for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) {
3104 f->SetComputedStyle(&newStyle);
3107 return true;
3110 void RestyleManager::ClearSnapshots() {
3111 for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
3112 iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
3113 iter.Remove();
3117 ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) {
3118 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3120 // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
3121 // we do to restyle stuff for reconstruction, for example.
3123 // It seems to be the case that we always flush in between that happens and
3124 // the next attribute change, so we can assert that we haven't handled the
3125 // snapshot here yet. If this assertion didn't hold, we'd need to unset that
3126 // flag from here too.
3128 // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
3129 // so this becomes much easier to reason about. Today is not that day though.
3130 MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT));
3132 ServoElementSnapshot* snapshot =
3133 mSnapshots.GetOrInsertNew(&aElement, aElement);
3134 aElement.SetFlags(ELEMENT_HAS_SNAPSHOT);
3136 // Now that we have a snapshot, make sure a restyle is triggered.
3137 aElement.NoteDirtyForServo();
3138 return *snapshot;
3141 void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
3142 nsPresContext* presContext = PresContext();
3143 PresShell* presShell = presContext->PresShell();
3145 MOZ_ASSERT(presContext->Document(), "No document? Pshaw!");
3146 // FIXME(emilio): In the "flush animations" case, ideally, we should only
3147 // recascade animation styles running on the compositor, so we shouldn't care
3148 // about other styles, or new rules that apply to the page...
3150 // However, that's not true as of right now, see bug 1388031 and bug 1388692.
3151 MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) ||
3152 !presContext->HasPendingMediaQueryUpdates(),
3153 "Someone forgot to update media queries?");
3154 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
3155 MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?");
3157 if (MOZ_UNLIKELY(!presShell->DidInitialize())) {
3158 // PresShell::FlushPendingNotifications doesn't early-return in the case
3159 // where the PresShell hasn't yet been initialized (and therefore we haven't
3160 // yet done the initial style traversal of the DOM tree). We should arguably
3161 // fix up the callers and assert against this case, but we just detect and
3162 // handle it for now.
3163 return;
3166 // It'd be bad!
3167 PresShell::AutoAssertNoFlush noReentrantFlush(*presShell);
3169 // Create a AnimationsWithDestroyedFrame during restyling process to
3170 // stop animations and transitions on elements that have no frame at the end
3171 // of the restyling process.
3172 AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
3174 ServoStyleSet* styleSet = StyleSet();
3175 Document* doc = presContext->Document();
3177 // Ensure the refresh driver is active during traversal to avoid mutating
3178 // mActiveTimer and mMostRecentRefresh time.
3179 presContext->RefreshDriver()->MostRecentRefresh();
3181 if (!doc->GetServoRestyleRoot()) {
3182 // This might post new restyles, so need to do it here. Don't do it if we're
3183 // already going to restyle tho, so that we don't potentially reflow with
3184 // dirty styling.
3185 presContext->UpdateContainerQueryStyles();
3186 presContext->FinishedContainerQueryUpdate();
3189 // Perform the Servo traversal, and the post-traversal if required. We do this
3190 // in a loop because certain rare paths in the frame constructor can trigger
3191 // additional style invalidations.
3193 // FIXME(emilio): Confirm whether that's still true now that XBL is gone.
3194 mInStyleRefresh = true;
3195 if (mHaveNonAnimationRestyles) {
3196 ++mAnimationGeneration;
3199 if (mRestyleForCSSRuleChanges) {
3200 aFlags |= ServoTraversalFlags::ForCSSRuleChanges;
3203 while (styleSet->StyleDocument(aFlags)) {
3204 ClearSnapshots();
3206 // Select scroll anchors for frames that have been scrolled. Do this
3207 // before processing restyled frames so that anchor nodes are correctly
3208 // marked when directly moving frames with RecomputePosition.
3209 presContext->PresShell()->FlushPendingScrollAnchorSelections();
3211 nsStyleChangeList currentChanges;
3212 bool anyStyleChanged = false;
3214 // Recreate styles , and queue up change hints (which also handle lazy frame
3215 // construction).
3216 nsTArray<RefPtr<Element>> anchorsToSuppress;
3219 DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
3220 while (Element* root = iter.GetNextStyleRoot()) {
3221 nsTArray<nsIFrame*> wrappersToRestyle;
3222 ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle,
3223 anchorsToSuppress);
3224 ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty;
3225 anyStyleChanged |= ProcessPostTraversal(root, state, flags);
3228 // We want to suppress adjustments the current (before-change) scroll
3229 // anchor container now, and save a reference to the content node so that
3230 // we can suppress them in the after-change scroll anchor .
3231 for (Element* element : anchorsToSuppress) {
3232 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3233 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3234 container->SuppressAdjustments();
3240 doc->ClearServoRestyleRoot();
3241 ClearSnapshots();
3243 // Process the change hints.
3245 // Unfortunately, the frame constructor can generate new change hints while
3246 // processing existing ones. We redirect those into a secondary queue and
3247 // iterate until there's nothing left.
3249 ReentrantChangeList newChanges;
3250 mReentrantChanges = &newChanges;
3251 while (!currentChanges.IsEmpty()) {
3252 ProcessRestyledFrames(currentChanges);
3253 MOZ_ASSERT(currentChanges.IsEmpty());
3254 for (ReentrantChange& change : newChanges) {
3255 if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
3256 !change.mContent->GetPrimaryFrame()) {
3257 // SVG Elements post change hints without ensuring that the primary
3258 // frame will be there after that (see bug 1366142).
3260 // Just ignore those, since we can't really process them.
3261 continue;
3263 currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
3264 change.mContent, change.mHint);
3266 newChanges.Clear();
3268 mReentrantChanges = nullptr;
3271 // Suppress adjustments in the after-change scroll anchors if needed, now
3272 // that we're done reframing everything.
3273 for (Element* element : anchorsToSuppress) {
3274 if (nsIFrame* frame = element->GetPrimaryFrame()) {
3275 if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
3276 container->SuppressAdjustments();
3281 if (anyStyleChanged) {
3282 // Maybe no styles changed when:
3284 // * Only explicit change hints were posted in the first place.
3285 // * When an attribute or state change in the content happens not to need
3286 // a restyle after all.
3288 // In any case, we don't need to increment the restyle generation in that
3289 // case.
3290 IncrementRestyleGeneration();
3293 mInStyleRefresh = false;
3294 presContext->UpdateContainerQueryStyles();
3295 mInStyleRefresh = true;
3298 doc->ClearServoRestyleRoot();
3299 presContext->FinishedContainerQueryUpdate();
3300 ClearSnapshots();
3301 styleSet->AssertTreeIsClean();
3303 mHaveNonAnimationRestyles = false;
3304 mRestyleForCSSRuleChanges = false;
3305 mInStyleRefresh = false;
3307 // Now that everything has settled, see if we have enough free rule nodes in
3308 // the tree to warrant sweeping them.
3309 styleSet->MaybeGCRuleTree();
3311 // Note: We are in the scope of |animationsWithDestroyedFrame|, so
3312 // |mAnimationsWithDestroyedFrame| is still valid.
3313 MOZ_ASSERT(mAnimationsWithDestroyedFrame);
3314 mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
3317 #ifdef DEBUG
3318 static void VerifyFlatTree(const nsIContent& aContent) {
3319 StyleChildrenIterator iter(&aContent);
3321 for (auto* content = iter.GetNextChild(); content;
3322 content = iter.GetNextChild()) {
3323 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
3324 VerifyFlatTree(*content);
3327 #endif
3329 void RestyleManager::ProcessPendingRestyles() {
3330 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT);
3331 #ifdef DEBUG
3332 if (auto* root = mPresContext->Document()->GetRootElement()) {
3333 VerifyFlatTree(*root);
3335 #endif
3337 DoProcessPendingRestyles(ServoTraversalFlags::Empty);
3340 void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() {
3341 if (mSnapshots.IsEmpty()) {
3342 return;
3344 for (const auto& key : mSnapshots.Keys()) {
3345 // Servo data for the element might have been dropped. (e.g. by removing
3346 // from its document)
3347 if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3348 Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots);
3351 ClearSnapshots();
3354 void RestyleManager::UpdateOnlyAnimationStyles() {
3355 bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
3356 if (!doCSS) {
3357 return;
3360 DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations);
3363 void RestyleManager::ElementStateChanged(Element* aElement,
3364 ElementState aChangedBits) {
3365 #ifdef EARLY_BETA_OR_EARLIER
3366 if (MOZ_UNLIKELY(mInStyleRefresh)) {
3367 MOZ_CRASH_UNSAFE_PRINTF(
3368 "Element state change during style refresh (%" PRIu64 ")",
3369 aChangedBits.GetInternalValue());
3371 #endif
3373 const ElementState kVisitedAndUnvisited =
3374 ElementState::VISITED | ElementState::UNVISITED;
3376 // We'll restyle when the relevant visited query finishes, regardless of the
3377 // style (see Link::VisitedQueryFinished). So there's no need to do anything
3378 // as a result of this state change just yet.
3380 // Note that this check checks for _both_ bits: This is only true when visited
3381 // changes to unvisited or vice-versa, but not when we start or stop being a
3382 // link itself.
3383 if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) {
3384 aChangedBits &= ~kVisitedAndUnvisited;
3385 if (aChangedBits.IsEmpty()) {
3386 return;
3390 if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) {
3391 Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint);
3394 // Don't bother taking a snapshot if no rules depend on these state bits.
3396 // We always take a snapshot for the LTR/RTL event states, since Servo doesn't
3397 // track those bits in the same way, and we know that :dir() rules are always
3398 // present in UA style sheets.
3399 if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) &&
3400 !StyleSet()->HasStateDependency(*aElement, aChangedBits)) {
3401 return;
3404 // Assuming we need to invalidate cached style in getComputedStyle for
3405 // undisplayed elements, since we don't know if it is needed.
3406 IncrementUndisplayedRestyleGeneration();
3408 if (!aElement->HasServoData()) {
3409 return;
3412 ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
3413 ElementState previousState = aElement->StyleState() ^ aChangedBits;
3414 snapshot.AddState(previousState);
3416 ServoStyleSet& styleSet = *StyleSet();
3417 MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
3418 MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
3421 void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
3422 Element* aChild,
3423 ElementState aChangedBits) {
3424 const auto* parentNode = aChild->GetParentNode();
3425 MOZ_ASSERT(parentNode);
3426 const auto parentFlags = parentNode->GetSelectorFlags();
3427 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3428 return;
3431 if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
3432 RestyleSiblingsForNthOf(aChild, parentFlags);
3436 static inline bool AttributeInfluencesOtherPseudoClassState(
3437 const Element& aElement, const nsAtom* aAttribute) {
3438 // We must record some state for :-moz-browser-frame,
3439 // :-moz-table-border-nonzero, and :-moz-select-list-box.
3440 if (aAttribute == nsGkAtoms::mozbrowser) {
3441 return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame);
3444 if (aAttribute == nsGkAtoms::border) {
3445 return aElement.IsHTMLElement(nsGkAtoms::table);
3448 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
3449 return aElement.IsHTMLElement(nsGkAtoms::select);
3452 return false;
3455 static inline bool NeedToRecordAttrChange(
3456 const ServoStyleSet& aStyleSet, const Element& aElement,
3457 int32_t aNameSpaceID, nsAtom* aAttribute,
3458 bool* aInfluencesOtherPseudoClassState) {
3459 *aInfluencesOtherPseudoClassState =
3460 AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
3462 // If the attribute influences one of the pseudo-classes that are backed by
3463 // attributes, we just record it.
3464 if (*aInfluencesOtherPseudoClassState) {
3465 return true;
3468 // We assume that id and class attributes are used in class/id selectors, and
3469 // thus record them.
3471 // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
3472 // presumably we could try to filter the old and new id, but it's not clear
3473 // it's worth it.
3474 if (aNameSpaceID == kNameSpaceID_None &&
3475 (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
3476 return true;
3479 // We always record lang="", even though we force a subtree restyle when it
3480 // changes, since it can change how its siblings match :lang(..) due to
3481 // selectors like :lang(..) + div.
3482 if (aAttribute == nsGkAtoms::lang) {
3483 return true;
3486 // Otherwise, just record the attribute change if a selector in the page may
3487 // reference it from an attribute selector.
3488 return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
3491 void RestyleManager::AttributeWillChange(Element* aElement,
3492 int32_t aNameSpaceID,
3493 nsAtom* aAttribute, int32_t aModType) {
3494 TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute);
3497 void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) {
3498 TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None,
3499 nsGkAtoms::_class);
3502 void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement,
3503 int32_t aNameSpaceID,
3504 nsAtom* aAttribute) {
3505 MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
3507 bool influencesOtherPseudoClassState;
3508 if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute,
3509 &influencesOtherPseudoClassState)) {
3510 return;
3513 // We cannot tell if the attribute change will affect the styles of
3514 // undisplayed elements, because we don't actually restyle those elements
3515 // during the restyle traversal. So just assume that the attribute change can
3516 // cause the style to change.
3517 IncrementUndisplayedRestyleGeneration();
3519 // Relative selector invalidation travels ancestor and earlier sibling
3520 // direction, so it's very possible that it invalidates a styled element.
3521 if (!aElement.HasServoData() &&
3522 !(aElement.GetSelectorFlags() &
3523 NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
3524 return;
3527 // Some other random attribute changes may also affect the transitions,
3528 // so we also set this true here.
3529 mHaveNonAnimationRestyles = true;
3531 ServoElementSnapshot& snapshot = SnapshotFor(aElement);
3532 snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
3534 if (influencesOtherPseudoClassState) {
3535 snapshot.AddOtherPseudoClassState(aElement);
3539 // For some attribute changes we must restyle the whole subtree:
3541 // * lang="" and xml:lang="" can affect all descendants due to :lang()
3542 // * exportparts can affect all descendant parts. We could certainly integrate
3543 // it better in the invalidation machinery if it was necessary.
3544 static inline bool AttributeChangeRequiresSubtreeRestyle(
3545 const Element& aElement, nsAtom* aAttr) {
3546 if (aAttr == nsGkAtoms::exportparts) {
3547 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for
3548 // exportparts attribute changes?
3549 return !!aElement.GetShadowRoot();
3551 return aAttr == nsGkAtoms::lang;
3554 void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
3555 nsAtom* aAttribute, int32_t aModType,
3556 const nsAttrValue* aOldValue) {
3557 MOZ_ASSERT(!mInStyleRefresh);
3559 auto changeHint = nsChangeHint(0);
3560 auto restyleHint = RestyleHint{0};
3562 changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
3564 MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
3565 MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
3567 if (aAttribute == nsGkAtoms::style) {
3568 restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
3569 } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
3570 restyleHint |= RestyleHint::RestyleSubtree();
3571 } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) {
3572 // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part
3573 // attribute changes?
3574 restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS;
3577 if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
3578 // See if we have appearance information for a theme.
3579 StyleAppearance appearance =
3580 primaryFrame->StyleDisplay()->EffectiveAppearance();
3581 if (appearance != StyleAppearance::None) {
3582 nsITheme* theme = PresContext()->Theme();
3583 if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) {
3584 bool repaint = false;
3585 theme->WidgetStateChanged(primaryFrame, appearance, aAttribute,
3586 &repaint, aOldValue);
3587 if (repaint) {
3588 changeHint |= nsChangeHint_RepaintFrame;
3593 primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
3596 if (restyleHint || changeHint) {
3597 Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
3600 if (restyleHint) {
3601 // Assuming we need to invalidate cached style in getComputedStyle for
3602 // undisplayed elements, since we don't know if it is needed.
3603 IncrementUndisplayedRestyleGeneration();
3605 // If we change attributes, we have to mark this to be true, so we will
3606 // increase the animation generation for the new created transition if any.
3607 mHaveNonAnimationRestyles = true;
3611 void RestyleManager::RestyleSiblingsForNthOf(Element* aChild,
3612 NodeSelectorFlags aParentFlags) {
3613 StyleSet()->RestyleSiblingsForNthOf(*aChild,
3614 static_cast<uint32_t>(aParentFlags));
3617 void RestyleManager::MaybeRestyleForNthOfAttribute(
3618 Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3619 const auto* parentNode = aChild->GetParentNode();
3620 MOZ_ASSERT(parentNode);
3621 const auto parentFlags = parentNode->GetSelectorFlags();
3622 if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
3623 return;
3625 if (!aChild->HasServoData()) {
3626 return;
3629 bool mightHaveNthOfDependency;
3630 auto& styleSet = *StyleSet();
3631 if (aAttribute == nsGkAtoms::id) {
3632 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3633 ? aOldValue->GetAtomValue()
3634 : nullptr;
3635 mightHaveNthOfDependency =
3636 styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
3637 } else if (aAttribute == nsGkAtoms::_class) {
3638 mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
3639 } else {
3640 mightHaveNthOfDependency =
3641 styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
3644 if (mightHaveNthOfDependency) {
3645 RestyleSiblingsForNthOf(aChild, parentFlags);
3649 void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
3650 Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
3651 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3652 return;
3654 auto& styleSet = *StyleSet();
3655 if (aAttribute == nsGkAtoms::id) {
3656 auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
3657 ? aOldValue->GetAtomValue()
3658 : nullptr;
3659 styleSet.MaybeInvalidateRelativeSelectorIDDependency(
3660 *aElement, oldAtom, aElement->GetID(), Snapshots());
3661 } else if (aAttribute == nsGkAtoms::_class) {
3662 styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement,
3663 Snapshots());
3664 } else {
3665 styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(
3666 *aElement, aAttribute, Snapshots());
3670 void RestyleManager::MaybeRestyleForRelativeSelectorState(
3671 ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
3672 if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
3673 return;
3675 aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(
3676 *aElement, aChangedBits, Snapshots());
3679 void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
3680 // This is only called when moving frames in or out of the first-line
3681 // pseudo-element (or one of its descendants). We can't say much about
3682 // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into
3683 // something inside an inline-block on the first line the ancestors could be
3684 // totally arbitrary), but we will definitely find a line frame on the
3685 // ancestor chain. Note that the lineframe may not actually be the one that
3686 // corresponds to ::first-line; when we're moving _out_ of the ::first-line it
3687 // will be one of the continuations instead.
3688 #ifdef DEBUG
3690 nsIFrame* f = aFrame->GetParent();
3691 while (f && !f->IsLineFrame()) {
3692 f = f->GetParent();
3694 MOZ_ASSERT(f, "Must have found a first-line frame");
3696 #endif
3698 DoReparentComputedStyleForFirstLine(aFrame, *StyleSet());
3701 static bool IsFrameAboutToGoAway(nsIFrame* aFrame) {
3702 auto* element = Element::FromNode(aFrame->GetContent());
3703 if (!element) {
3704 return false;
3706 return !element->HasServoData();
3709 void RestyleManager::DoReparentComputedStyleForFirstLine(
3710 nsIFrame* aFrame, ServoStyleSet& aStyleSet) {
3711 if (aFrame->IsBackdropFrame()) {
3712 // Style context of backdrop frame has no parent style, and thus we do not
3713 // need to reparent it.
3714 return;
3717 if (IsFrameAboutToGoAway(aFrame)) {
3718 // We're entering a display: none subtree, which we know it's going to get
3719 // rebuilt. Don't bother reparenting.
3720 return;
3723 if (aFrame->IsPlaceholderFrame()) {
3724 // Also reparent the out-of-flow and all its continuations. We're doing
3725 // this to match Gecko for now, but it's not clear that this behavior is
3726 // correct per spec. It's certainly pretty odd for out-of-flows whose
3727 // containing block is not within the first line.
3729 // Right now we're somewhat inconsistent in this testcase:
3731 // <style>
3732 // div { color: orange; clear: left; }
3733 // div::first-line { color: blue; }
3734 // </style>
3735 // <div>
3736 // <span style="float: left">What color is this text?</span>
3737 // </div>
3738 // <div>
3739 // <span><span style="float: left">What color is this text?</span></span>
3740 // </div>
3742 // We make the first float orange and the second float blue. On the other
3743 // hand, if the float were within an inline-block that was on the first
3744 // line, arguably it _should_ inherit from the ::first-line...
3745 nsIFrame* outOfFlow =
3746 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
3747 MOZ_ASSERT(outOfFlow, "no out-of-flow frame");
3748 for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) {
3749 DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet);
3753 // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try
3754 // to remove it?
3755 nsIFrame* providerFrame;
3756 ComputedStyle* newParentStyle =
3757 aFrame->GetParentComputedStyle(&providerFrame);
3758 // If our provider is our child, we want to reparent it first, because we
3759 // inherit style from it.
3760 bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
3761 nsIFrame* providerChild = nullptr;
3762 if (isChild) {
3763 DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet);
3764 // Get the style again after ReparentComputedStyle() which might have
3765 // changed it.
3766 newParentStyle = providerFrame->Style();
3767 providerChild = providerFrame;
3768 MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
3769 "Out of flow provider?");
3772 if (!newParentStyle) {
3773 // No need to do anything here for this frame, but we should still reparent
3774 // its descendants, because those may have styles that inherit from the
3775 // parent of this frame (e.g. non-anonymous columns in an anonymous
3776 // colgroup).
3777 MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(),
3778 "Why did this frame not end up with a parent context?");
3779 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3780 return;
3783 bool isElement = aFrame->GetContent()->IsElement();
3785 // We probably don't want to initiate transitions from ReparentComputedStyle,
3786 // since we call it during frame construction rather than in response to
3787 // dynamic changes.
3788 // Also see the comment at the start of
3789 // nsTransitionManager::ConsiderInitiatingTransition.
3791 // We don't try to do the fancy copying from previous continuations that
3792 // GeckoRestyleManager does here, because that relies on knowing the parents
3793 // of ComputedStyles, and we don't know those.
3794 ComputedStyle* oldStyle = aFrame->Style();
3795 Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr;
3796 ComputedStyle* newParent = newParentStyle;
3798 if (!providerFrame) {
3799 // No providerFrame means we inherited from a display:contents thing. Our
3800 // layout parent style is the style of our nearest ancestor frame. But we
3801 // have to be careful to do that with our placeholder, not with us, if we're
3802 // out of flow.
3803 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
3804 aFrame->FirstContinuation()
3805 ->GetPlaceholderFrame()
3806 ->GetLayoutParentStyleForOutOfFlow(&providerFrame);
3807 } else {
3808 providerFrame = nsIFrame::CorrectStyleParentFrame(
3809 aFrame->GetParent(), oldStyle->GetPseudoType());
3812 ComputedStyle* layoutParent = providerFrame->Style();
3814 RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle(
3815 oldStyle, newParent, layoutParent, ourElement);
3816 aFrame->SetComputedStyle(newStyle);
3818 // This logic somewhat mirrors the logic in
3819 // RestyleManager::ProcessPostTraversal.
3820 if (isElement) {
3821 // We can't use UpdateAdditionalComputedStyles as-is because it needs a
3822 // ServoRestyleState and maintaining one of those during a _frametree_
3823 // traversal is basically impossible.
3824 int32_t index = 0;
3825 while (auto* oldAdditionalStyle =
3826 aFrame->GetAdditionalComputedStyle(index)) {
3827 RefPtr<ComputedStyle> newAdditionalContext =
3828 aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle,
3829 newStyle, nullptr);
3830 aFrame->SetAdditionalComputedStyle(index, newAdditionalContext);
3831 ++index;
3835 // Generally, owned anon boxes are our descendants. The only exceptions are
3836 // tables (for the table wrapper) and inline frames (for the block part of the
3837 // block-in-inline split). We're going to update our descendants when looping
3838 // over kids, and we don't want to update the block part of a block-in-inline
3839 // split if the inline is on the first line but the block is not (and if the
3840 // block is, it's the child of something else on the first line and will get
3841 // updated as a child). And given how this method ends up getting called, if
3842 // we reach here for a table frame, we are already in the middle of
3843 // reparenting the table wrapper frame. So no need to
3844 // UpdateStyleOfOwnedAnonBoxes() here.
3846 ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
3848 // We do not need to do the equivalent of UpdateFramePseudoElementStyles,
3849 // because those are handled by our descendant walk.
3852 void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame,
3853 nsIFrame* aProviderChild,
3854 ServoStyleSet& aStyleSet) {
3855 for (const auto& childList : aFrame->ChildLists()) {
3856 for (nsIFrame* child : childList.mList) {
3857 // only do frames that are in flow
3858 if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
3859 child != aProviderChild) {
3860 DoReparentComputedStyleForFirstLine(child, aStyleSet);
3866 } // namespace mozilla