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