diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /layout/base/RestyleManager.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | layout/base/RestyleManager.cpp | 3788 |
1 files changed, 3788 insertions, 0 deletions
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp new file mode 100644 index 0000000000..98932a64b8 --- /dev/null +++ b/layout/base/RestyleManager.cpp @@ -0,0 +1,3788 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/RestyleManager.h" + +#include "mozilla/AnimationUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/AutoRestyleTimelineMarker.h" +#include "mozilla/AutoTimelineMarker.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/ComputedStyleInlines.h" +#include "mozilla/DocumentStyleRootIterator.h" +#include "mozilla/EffectSet.h" +#include "mozilla/GeckoBindings.h" +#include "mozilla/LayerAnimationInfo.h" +#include "mozilla/layers/AnimationInfo.h" +#include "mozilla/layout/ScrollAnchorContainer.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/ServoStyleSetInlines.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/ViewportFrame.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/dom/ChildIterator.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/ElementInlines.h" +#include "mozilla/dom/HTMLBodyElement.h" + +#include "ScrollSnap.h" +#include "nsAnimationManager.h" +#include "nsBlockFrame.h" +#include "nsIScrollableFrame.h" +#include "nsContentUtils.h" +#include "nsCSSFrameConstructor.h" +#include "nsCSSRendering.h" +#include "nsDocShell.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsImageFrame.h" +#include "nsPlaceholderFrame.h" +#include "nsPrintfCString.h" +#include "nsRefreshDriver.h" +#include "nsStyleChangeList.h" +#include "nsStyleUtil.h" +#include "nsTransitionManager.h" +#include "StickyScrollContainer.h" +#include "ActiveLayerTracker.h" + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif + +using mozilla::layers::AnimationInfo; +using mozilla::layout::ScrollAnchorContainer; + +using namespace mozilla::dom; +using namespace mozilla::layers; + +namespace mozilla { + +RestyleManager::RestyleManager(nsPresContext* aPresContext) + : mPresContext(aPresContext), + mRestyleGeneration(1), + mUndisplayedRestyleGeneration(1), + mInStyleRefresh(false), + mAnimationGeneration(0) { + MOZ_ASSERT(mPresContext); +} + +void RestyleManager::ContentInserted(nsIContent* aChild) { + MOZ_ASSERT(aChild->GetParentNode()); + RestyleForInsertOrChange(aChild); +} + +void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) { + auto* container = aFirstNewContent->GetParentNode(); + MOZ_ASSERT(container); + +#ifdef DEBUG + { + for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { + NS_ASSERTION(!cur->IsRootOfNativeAnonymousSubtree(), + "anonymous nodes should not be in child lists"); + } + } +#endif + uint32_t selectorFlags = + container->GetFlags() & + (NODE_RESTYLE_SELECTOR_FLAGS & ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + if (selectorFlags == 0) { + return; + } + + // The container cannot be a document. + MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); + + if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { + // see whether we need to restyle the container + bool wasEmpty = true; // :empty or :-moz-only-whitespace + for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent; + cur = cur->GetNextSibling()) { + // We don't know whether we're testing :empty or :-moz-only-whitespace, + // so be conservative and assume :-moz-only-whitespace (i.e., make + // IsSignificantChild less likely to be true, and thus make us more + // likely to restyle). + if (nsStyleUtil::IsSignificantChild(cur, false)) { + wasEmpty = false; + break; + } + } + if (wasEmpty && container->IsElement()) { + RestyleForEmptyChange(container->AsElement()); + return; + } + } + + if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { + if (container->IsElement()) { + PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } else { + RestylePreviousSiblings(aFirstNewContent); + RestyleSiblingsStartingWith(aFirstNewContent); + } + // Restyling the container is the most we can do here, so we're done. + return; + } + + if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { + // restyle the last element child before this node + for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur; + cur = cur->GetPreviousSibling()) { + if (cur->IsElement()) { + PostRestyleEvent(cur->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + break; + } + } + } +} + +void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) { + for (nsIContent* sibling = aStartingSibling; sibling; + sibling = sibling->GetPreviousSibling()) { + if (auto* element = Element::FromNode(sibling)) { + PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0)); + } + } +} + +void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) { + for (nsIContent* sibling = aStartingSibling; sibling; + sibling = sibling->GetNextSibling()) { + if (auto* element = Element::FromNode(sibling)) { + PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0)); + } + } +} + +void RestyleManager::RestyleForEmptyChange(Element* aContainer) { + PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0)); + + // In some cases (:empty + E, :empty ~ E), a change in the content of + // an element requires restyling its parent's siblings. + nsIContent* grandparent = aContainer->GetParent(); + if (!grandparent || + !(grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { + return; + } + RestyleSiblingsStartingWith(aContainer->GetNextSibling()); +} + +void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer, + nsIContent* aChangedChild) { + MOZ_ASSERT(aContainer->GetFlags() & NODE_HAS_EDGE_CHILD_SELECTOR); + MOZ_ASSERT(aChangedChild->GetParent() == aContainer); + // restyle the previously-first element child if it is after this node + bool passedChild = false; + for (nsIContent* content = aContainer->GetFirstChild(); content; + content = content->GetNextSibling()) { + if (content == aChangedChild) { + passedChild = true; + continue; + } + if (content->IsElement()) { + if (passedChild) { + PostRestyleEvent(content->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } + break; + } + } + // restyle the previously-last element child if it is before this node + passedChild = false; + for (nsIContent* content = aContainer->GetLastChild(); content; + content = content->GetPreviousSibling()) { + if (content == aChangedChild) { + passedChild = true; + continue; + } + if (content->IsElement()) { + if (passedChild) { + PostRestyleEvent(content->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } + break; + } + } +} + +template <typename CharT> +bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) { + for (auto index : IntegerRange(aUpTo)) { + if (!dom::IsSpaceCharacter(aBuffer[index])) { + return false; + } + } + return true; +} + +template <typename CharT> +bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength, + size_t aNewLength) { + MOZ_ASSERT(aOldLength <= aNewLength); + if (!WhitespaceOnly(aBuffer, aOldLength)) { + // The old text was already not whitespace-only. + return false; + } + + return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength); +} + +static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) { + MOZ_ASSERT(aChild->GetParent() == aContainer); + for (nsIContent* child = aContainer->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child == aChild) { + continue; + } + // We don't know whether we're testing :empty or :-moz-only-whitespace, + // so be conservative and assume :-moz-only-whitespace (i.e., make + // IsSignificantChild less likely to be true, and thus make us more + // likely to restyle). + if (nsStyleUtil::IsSignificantChild(child, false)) { + return true; + } + } + + return false; +} + +void RestyleManager::CharacterDataChanged( + nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { + nsINode* parent = aContent->GetParentNode(); + MOZ_ASSERT(parent, "How were we notified of a stray node?"); + + uint32_t slowSelectorFlags = parent->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; + if (!(slowSelectorFlags & + (NODE_HAS_EMPTY_SELECTOR | NODE_HAS_EDGE_CHILD_SELECTOR))) { + // Nothing to do, no other slow selector can change as a result of this. + return; + } + + if (!aContent->IsText()) { + // Doesn't matter to styling (could be a processing instruction or a + // comment), it can't change whether any selectors match or don't. + return; + } + + if (MOZ_UNLIKELY(!parent->IsElement())) { + MOZ_ASSERT(parent->IsShadowRoot()); + return; + } + + if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) { + // This is an anonymous node and thus isn't in child lists, so isn't taken + // into account for selector matching the relevant selectors here. + return; + } + + // Handle appends specially since they're common and we can know both the old + // and the new text exactly. + // + // TODO(emilio): This could be made much more general if :-moz-only-whitespace + // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only + // need to know whether we went from empty to non-empty, and that's trivial to + // know, with CharacterDataChangeInfo... + if (!aInfo.mAppend) { + // FIXME(emilio): This restyles unnecessarily if the text node is the only + // child of the parent element. Fortunately, it's uncommon to have such + // nodes and this not being an append. + // + // See the testcase in bug 1427625 for a test-case that triggers this. + RestyleForInsertOrChange(aContent); + return; + } + + const nsTextFragment* text = &aContent->AsText()->TextFragment(); + + const size_t oldLength = aInfo.mChangeStart; + const size_t newLength = text->GetLength(); + + const bool emptyChanged = !oldLength && newLength; + + const bool whitespaceOnlyChanged = + text->Is2b() + ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength) + : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength); + + if (!emptyChanged && !whitespaceOnlyChanged) { + return; + } + + if (slowSelectorFlags & NODE_HAS_EMPTY_SELECTOR) { + if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { + // We used to be empty, restyle the parent. + RestyleForEmptyChange(parent->AsElement()); + return; + } + } + + if (slowSelectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { + MaybeRestyleForEdgeChildChange(parent, aContent); + } +} + +// Restyling for a ContentInserted or CharacterDataChanged notification. +// This could be used for ContentRemoved as well if we got the +// notification before the removal happened (and sometimes +// CharacterDataChanged is more like a removal than an addition). +// The comments are written and variables are named in terms of it being +// a ContentInserted notification. +void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) { + nsINode* container = aChild->GetParentNode(); + MOZ_ASSERT(container); + + uint32_t selectorFlags = container->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; + if (selectorFlags == 0) { + return; + } + + NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(), + "anonymous nodes should not be in child lists"); + + // The container cannot be a document. + MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); + + if (selectorFlags & NODE_HAS_EMPTY_SELECTOR && container->IsElement()) { + // See whether we need to restyle the container due to :empty / + // :-moz-only-whitespace. + const bool wasEmpty = + !HasAnySignificantSibling(container->AsElement(), aChild); + if (wasEmpty) { + // FIXME(emilio): When coming from CharacterDataChanged this can restyle + // unnecessarily. Also can restyle unnecessarily if aChild is not + // significant anyway, though that's more unlikely. + RestyleForEmptyChange(container->AsElement()); + return; + } + } + + if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { + if (container->IsElement()) { + PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } else { + RestylePreviousSiblings(aChild); + RestyleSiblingsStartingWith(aChild); + } + // Restyling the container is the most we can do here, so we're done. + return; + } + + if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { + // Restyle all later siblings. + RestyleSiblingsStartingWith(aChild->GetNextSibling()); + } + + if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { + MaybeRestyleForEdgeChildChange(container, aChild); + } +} + +void RestyleManager::ContentRemoved(nsIContent* aOldChild, + nsIContent* aFollowingSibling) { + auto* container = aOldChild->GetParentNode(); + MOZ_ASSERT(container); + + // Computed style data isn't useful for detached nodes, and we'll need to + // recompute it anyway if we ever insert the nodes back into a document. + if (auto* element = Element::FromNode(aOldChild)) { + RestyleManager::ClearServoDataFromSubtree(element); + // If this element is undisplayed or may have undisplayed descendants, we + // need to invalidate the cache, since there's the unlikely event of those + // elements getting destroyed and their addresses reused in a way that we + // look up the cache with their address for a different element before it's + // invalidated. + IncrementUndisplayedRestyleGeneration(); + } + + uint32_t selectorFlags = container->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; + if (selectorFlags == 0) { + return; + } + + if (aOldChild->IsRootOfNativeAnonymousSubtree()) { + // This should be an assert, but this is called incorrectly in + // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging + // up the logs. Make it an assert again when that's fixed. + MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode), + "anonymous nodes should not be in child lists (bug 439258)"); + } + + // The container cannot be a document. + MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); + + if (selectorFlags & NODE_HAS_EMPTY_SELECTOR && container->IsElement()) { + // see whether we need to restyle the container + bool isEmpty = true; // :empty or :-moz-only-whitespace + for (nsIContent* child = container->GetFirstChild(); child; + child = child->GetNextSibling()) { + // We don't know whether we're testing :empty or :-moz-only-whitespace, + // so be conservative and assume :-moz-only-whitespace (i.e., make + // IsSignificantChild less likely to be true, and thus make us more + // likely to restyle). + if (nsStyleUtil::IsSignificantChild(child, false)) { + isEmpty = false; + break; + } + } + if (isEmpty && container->IsElement()) { + RestyleForEmptyChange(container->AsElement()); + return; + } + } + + if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { + if (container->IsElement()) { + PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } else { + RestylePreviousSiblings(aOldChild); + RestyleSiblingsStartingWith(aOldChild); + } + // Restyling the container is the most we can do here, so we're done. + return; + } + + if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { + // Restyle all later siblings. + RestyleSiblingsStartingWith(aFollowingSibling); + } + + if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { + // restyle the now-first element child if it was after aOldChild + bool reachedFollowingSibling = false; + for (nsIContent* content = container->GetFirstChild(); content; + content = content->GetNextSibling()) { + if (content == aFollowingSibling) { + reachedFollowingSibling = true; + // do NOT continue here; we might want to restyle this node + } + if (content->IsElement()) { + if (reachedFollowingSibling) { + PostRestyleEvent(content->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } + break; + } + } + // restyle the now-last element child if it was before aOldChild + reachedFollowingSibling = (aFollowingSibling == nullptr); + for (nsIContent* content = container->GetLastChild(); content; + content = content->GetPreviousSibling()) { + if (content->IsElement()) { + if (reachedFollowingSibling) { + PostRestyleEvent(content->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + } + break; + } + if (content == aFollowingSibling) { + reachedFollowingSibling = true; + } + } + } +} + +static bool StateChangeMayAffectFrame(const Element& aElement, + const nsIFrame& aFrame, + ElementState aStates) { + const bool brokenChanged = aStates.HasState(ElementState::BROKEN); + if (aFrame.IsGeneratedContentFrame()) { + if (aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)) { + return brokenChanged; + } + // If it's other generated content, ignore LOADING/etc state changes on it. + return false; + } + + const bool loadingChanged = aStates.HasState(ElementState::LOADING); + if (!brokenChanged && !loadingChanged) { + return false; + } + + if (aElement.IsHTMLElement(nsGkAtoms::img)) { + if (!brokenChanged) { + // Loading state doesn't affect <img>, see + // `nsImageFrame::ImageFrameTypeForElement`. + return false; + } + const bool needsImageFrame = + nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) != + nsImageFrame::ImageFrameType::None; + return needsImageFrame != aFrame.IsImageFrameOrSubclass(); + } + + if (aElement.IsSVGElement(nsGkAtoms::image)) { + // <image> gets an SVGImageFrame all the time. + return false; + } + + return brokenChanged || loadingChanged; +} + +/** + * Calculates the change hint and the restyle hint for a given content state + * change. + */ +static nsChangeHint ChangeForContentStateChange(const Element& aElement, + ElementState aStateMask) { + auto changeHint = nsChangeHint(0); + + // Any change to a content state that affects which frames we construct + // must lead to a frame reconstruct here if we already have a frame. + // Note that we never decide through non-CSS means to not create frames + // based on content states, so if we already don't have a frame we don't + // need to force a reframe -- if it's needed, the HasStateDependentStyle + // call will handle things. + if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) { + if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) { + return nsChangeHint_ReconstructFrame; + } + + StyleAppearance appearance = + primaryFrame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsPresContext* pc = primaryFrame->PresContext(); + nsITheme* theme = pc->Theme(); + if (theme->ThemeSupportsWidget(pc, primaryFrame, appearance)) { + bool repaint = false; + theme->WidgetStateChanged(primaryFrame, appearance, nullptr, &repaint, + nullptr); + if (repaint) { + changeHint |= nsChangeHint_RepaintFrame; + } + } + } + primaryFrame->ElementStateChanged(aStateMask); + } + + if (aStateMask.HasState(ElementState::VISITED)) { + // Exposing information to the page about whether the link is + // visited or not isn't really something we can worry about here. + // FIXME: We could probably do this a bit better. + changeHint |= nsChangeHint_RepaintFrame; + } + + // This changes the applicable text-transform in the editor root. + if (aStateMask.HasState(ElementState::REVEALED)) { + // This is the same change hint as tweaking text-transform. + changeHint |= NS_STYLE_HINT_REFLOW; + } + + return changeHint; +} + +#ifdef DEBUG +/* static */ +nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) { + nsCString result; + bool any = false; + const char* names[] = {"RepaintFrame", + "NeedReflow", + "ClearAncestorIntrinsics", + "ClearDescendantIntrinsics", + "NeedDirtyReflow", + "UpdateCursor", + "UpdateEffects", + "UpdateOpacityLayer", + "UpdateTransformLayer", + "ReconstructFrame", + "UpdateOverflow", + "UpdateSubtreeOverflow", + "UpdatePostTransformOverflow", + "UpdateParentOverflow", + "ChildrenOnlyTransform", + "RecomputePosition", + "UpdateContainingBlock", + "BorderStyleNoneChange", + "SchedulePaint", + "NeutralChange", + "InvalidateRenderingObservers", + "ReflowChangesSizeOrPosition", + "UpdateComputedBSize", + "UpdateUsesOpacity", + "UpdateBackgroundPosition", + "AddOrRemoveTransform", + "ScrollbarChange", + "UpdateTableCellSpans", + "VisibilityChange"}; + static_assert(nsChangeHint_AllHints == + static_cast<uint32_t>((1ull << ArrayLength(names)) - 1), + "Name list doesn't match change hints."); + uint32_t hint = + aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1); + uint32_t rest = + aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1); + if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) { + result.AppendLiteral("NS_STYLE_HINT_REFLOW"); + hint = hint & ~NS_STYLE_HINT_REFLOW; + any = true; + } else if ((hint & nsChangeHint_AllReflowHints) == + nsChangeHint_AllReflowHints) { + result.AppendLiteral("nsChangeHint_AllReflowHints"); + hint = hint & ~nsChangeHint_AllReflowHints; + any = true; + } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) { + result.AppendLiteral("NS_STYLE_HINT_VISUAL"); + hint = hint & ~NS_STYLE_HINT_VISUAL; + any = true; + } + for (uint32_t i = 0; i < ArrayLength(names); i++) { + if (hint & (1u << i)) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("nsChangeHint_%s", names[i]); + any = true; + } + } + if (rest) { + if (any) { + result.AppendLiteral(" | "); + } + result.AppendPrintf("0x%0x", rest); + } else { + if (!any) { + result.AppendLiteral("nsChangeHint(0)"); + } + } + return result; +} +#endif + +/** + * Frame construction helpers follow. + */ +#ifdef DEBUG +static bool gInApplyRenderingChangeToTree = false; +#endif + +/** + * Sync views on the frame and all of it's descendants (following placeholders). + * The change hint should be some combination of nsChangeHint_RepaintFrame, + * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else. + */ +static void SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint); + +static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint); + +/** + * This helper function is used to find the correct SVG frame to target when we + * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end + * up handling that hint while processing hints for one of the SVG frame's + * ancestor frames. + * + * The reason that we sometimes end up trying to process the hint for an + * ancestor of the SVG frame that the hint is intended for is due to the way we + * process restyle events. ApplyRenderingChangeToTree adjusts the frame from + * the restyled element's principle frame to one of its ancestor frames based + * on what nsCSSRendering::FindBackground returns, since the background style + * may have been propagated up to an ancestor frame. Processing hints using an + * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is + * a special case since it is intended to update a specific frame. + */ +static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) { + if (aFrame->IsViewportFrame()) { + // This happens if the root-<svg> is fixed positioned, in which case we + // can't use aFrame->GetContent() to find the primary frame, since + // GetContent() returns nullptr for ViewportFrame. + aFrame = aFrame->PrincipalChildList().FirstChild(); + } + // For an nsHTMLScrollFrame, this will get the SVG frame that has the + // children-only transforms: + aFrame = aFrame->GetContent()->GetPrimaryFrame(); + if (aFrame->IsSVGOuterSVGFrame()) { + aFrame = aFrame->PrincipalChildList().FirstChild(); + MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(), + "Where is the SVGOuterSVGFrame's anon child??"); + } + MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer), + "Children-only transforms only expected on SVG frames"); + return aFrame; +} + +// This function tries to optimize a position style change by either +// moving aFrame or ignoring the style change when it's safe to do so. +// It returns true when that succeeds, otherwise it posts a reflow request +// and returns false. +static bool RecomputePosition(nsIFrame* aFrame) { + // It's pointless to move around frames that have never been reflowed or + // are dirty (i.e. they will be reflowed). + if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY)) { + return true; + } + + // Don't process position changes on table frames, since we already handle + // the dynamic position change on the table wrapper frame, and the + // reflow-based fallback code path also ignores positions on inner table + // frames. + if (aFrame->IsTableFrame()) { + return true; + } + + const nsStyleDisplay* display = aFrame->StyleDisplay(); + // Changes to the offsets of a non-positioned element can safely be ignored. + if (display->mPosition == StylePositionProperty::Static) { + return true; + } + + // Don't process position changes on frames which have views or the ones which + // have a view somewhere in their descendants, because the corresponding view + // needs to be repositioned properly as well. + if (aFrame->HasView() || + aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) { + return false; + } + + if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { + // If the frame has an intrinsic block-size, we resolve its 'auto' margins + // after doing layout, since we need to know the frame's block size. See + // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(). + // + // Since the size of the frame doesn't change, we could modify the below + // computation to compute the margin correctly without doing a full reflow, + // however we decided to try doing a full reflow for now. + if (aFrame->HasIntrinsicKeywordForBSize()) { + WritingMode wm = aFrame->GetWritingMode(); + const auto* styleMargin = aFrame->StyleMargin(); + if (styleMargin->HasBlockAxisAuto(wm)) { + return false; + } + } + // Flexbox and Grid layout supports CSS Align and the optimizations below + // don't support that yet. + nsIFrame* ph = aFrame->GetPlaceholderFrame(); + if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) { + return false; + } + } + + // If we need to reposition any descendant that depends on our static + // position, then we also can't take the optimized path. + // + // TODO(emilio): It may be worth trying to find them and try to call + // RecomputePosition on them too instead of disabling the optimization... + if (aFrame->DescendantMayDependOnItsStaticPosition()) { + return false; + } + + aFrame->SchedulePaint(); + + auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) { + if (frame->IsInScrollAnchorChain()) { + ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame); + frame->PresShell()->PostPendingScrollAnchorAdjustment(container); + } + + // We need to trigger re-snapping to this content if we snapped to the + // content on the last scroll operation. + ScrollSnapUtils::PostPendingResnapIfNeededFor(frame); + }; + + // For relative positioning, we can simply update the frame rect + if (display->IsRelativelyOrStickyPositionedStyle()) { + if (aFrame->IsGridItem()) { + // A grid item's CB is its grid area, not the parent frame content area + // as is assumed below. + return false; + } + // Move the frame + if (display->mPosition == StylePositionProperty::Sticky) { + // Update sticky positioning for an entire element at once, starting with + // the first continuation or ib-split sibling. + // It's rare that the frame we already have isn't already the first + // continuation or ib-split sibling, but it can happen when styles differ + // across continuations such as ::first-line or ::first-letter, and in + // those cases we will generally (but maybe not always) do the work twice. + nsIFrame* firstContinuation = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + + StickyScrollContainer::ComputeStickyOffsets(firstContinuation); + StickyScrollContainer* ssc = + StickyScrollContainer::GetStickyScrollContainerForFrame( + firstContinuation); + if (ssc) { + ssc->PositionContinuations(firstContinuation); + } + } else { + MOZ_ASSERT(display->IsRelativelyPositionedStyle(), + "Unexpected type of positioning"); + for (nsIFrame* cont = aFrame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + nsIFrame* cb = cont->GetContainingBlock(); + WritingMode wm = cb->GetWritingMode(); + const LogicalSize cbSize = cb->ContentSize(); + const LogicalMargin newLogicalOffsets = + ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize); + const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm); + + // ReflowInput::ApplyRelativePositioning would work here, but + // since we've already checked mPosition and aren't changing the frame's + // normal position, go ahead and add the offsets directly. + // First, we need to ensure that the normal position is stored though. + bool hasProperty; + nsPoint normalPosition = cont->GetNormalPosition(&hasProperty); + if (!hasProperty) { + cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition); + } + cont->SetPosition(normalPosition + + nsPoint(newOffsets.left, newOffsets.top)); + } + } + + postPendingScrollAnchorOrResnap(aFrame); + return true; + } + + // For the absolute positioning case, set up a fake HTML reflow input for + // the frame, and then get the offsets and size from it. If the frame's size + // doesn't need to change, we can simply update the frame position. Otherwise + // we fall back to a reflow. + UniquePtr<gfxContext> rc = + aFrame->PresShell()->CreateReferenceRenderingContext(); + + // Construct a bogus parent reflow input so that there's a usable reflow input + // for the containing block. + nsIFrame* parentFrame = aFrame->GetParent(); + WritingMode parentWM = parentFrame->GetWritingMode(); + WritingMode frameWM = aFrame->GetWritingMode(); + LogicalSize parentSize = parentFrame->GetLogicalSize(); + + nsFrameState savedState = parentFrame->GetStateBits(); + ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(), + parentSize); + parentFrame->RemoveStateBits(~nsFrameState(0)); + parentFrame->AddStateBits(savedState); + + // The bogus parent state here was created with no parent state of its own, + // and therefore it won't have an mCBReflowInput set up. + // But we may need one (for InitCBReflowInput in a child state), so let's + // try to create one here for the cases where it will be needed. + Maybe<ReflowInput> cbReflowInput; + nsIFrame* cbFrame = parentFrame->GetContainingBlock(); + if (cbFrame && (aFrame->GetContainingBlock() != parentFrame || + parentFrame->IsTableFrame())) { + const auto cbWM = cbFrame->GetWritingMode(); + LogicalSize cbSize = cbFrame->GetLogicalSize(); + cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize); + cbReflowInput->SetComputedLogicalMargin( + cbWM, cbFrame->GetLogicalUsedMargin(cbWM)); + cbReflowInput->SetComputedLogicalPadding( + cbWM, cbFrame->GetLogicalUsedPadding(cbWM)); + cbReflowInput->SetComputedLogicalBorderPadding( + cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM)); + parentReflowInput.mCBReflowInput = cbReflowInput.ptr(); + } + + NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE && + parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE, + "parentSize should be valid"); + parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0)); + parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0)); + parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM)); + + parentReflowInput.SetComputedLogicalPadding( + parentWM, parentFrame->GetLogicalUsedPadding(parentWM)); + parentReflowInput.SetComputedLogicalBorderPadding( + parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM)); + LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM); + availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE; + + ViewportFrame* viewport = do_QueryFrame(parentFrame); + nsSize cbSize = + viewport + ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput) + .Size() + : aFrame->GetContainingBlock()->GetSize(); + const nsMargin& parentBorder = + parentReflowInput.mStyleBorder->GetComputedBorder(); + cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom()); + LogicalSize lcbSize(frameWM, cbSize); + ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame, + availSize, Some(lcbSize)); + nscoord computedISize = reflowInput.ComputedISize(); + nscoord computedBSize = reflowInput.ComputedBSize(); + const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM); + computedISize += frameBP.IStartEnd(frameWM); + if (computedBSize != NS_UNCONSTRAINEDSIZE) { + computedBSize += frameBP.BStartEnd(frameWM); + } + LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM); + nsSize size = aFrame->GetSize(); + // The RecomputePosition hint is not used if any offset changed between auto + // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new + // element height will be its intrinsic height, and since 'top' and 'bottom''s + // auto-ness hasn't changed, the old height must also be its intrinsic + // height, which we can assume hasn't changed (or reflow would have + // been triggered). + if (computedISize == logicalSize.ISize(frameWM) && + (computedBSize == NS_UNCONSTRAINEDSIZE || + computedBSize == logicalSize.BSize(frameWM))) { + // If we're solving for 'left' or 'top', then compute it here, in order to + // match the reflow code path. + // + // TODO(emilio): It'd be nice if this did logical math instead, but it seems + // to me the math should work out on vertical writing modes as well. See Bug + // 1675861 for some hints. + const nsMargin offset = reflowInput.ComputedPhysicalOffsets(); + const nsMargin margin = reflowInput.ComputedPhysicalMargin(); + + nscoord left = offset.left; + if (left == NS_AUTOOFFSET) { + left = + cbSize.width - offset.right - margin.right - size.width - margin.left; + } + + nscoord top = offset.top; + if (top == NS_AUTOOFFSET) { + top = cbSize.height - offset.bottom - margin.bottom - size.height - + margin.top; + } + + // Move the frame + nsPoint pos(parentBorder.left + left + margin.left, + parentBorder.top + top + margin.top); + aFrame->SetPosition(pos); + + postPendingScrollAnchorOrResnap(aFrame); + return true; + } + + // Fall back to a reflow + return false; +} + +/** + * Return true if aFrame's subtree has placeholders for out-of-flow content + * that would be affected due to the change to + * `aPossiblyChangingContainingBlock` (and thus would need to get reframed). + * + * In particular, this function returns true if there are placeholders whose OOF + * frames may need to be reparented (via reframing) as a result of whatever + * change actually happened. + * + * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether + * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed + * pos stuff, respectively, for the _new_ style that the frame already has, not + * the old one. + */ +static bool ContainingBlockChangeAffectsDescendants( + nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame, + bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) { + // All fixed-pos containing blocks should also be abs-pos containing blocks. + MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock); + + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* f : childList.mList) { + if (f->IsPlaceholderFrame()) { + nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); + // If SVG text frames could appear here, they could confuse us since + // they ignore their position style ... but they can't. + NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(), + "SVG text frames can't be out of flow"); + // Top-layer frames don't change containing block based on direct + // ancestors. + auto* display = outOfFlow->StyleDisplay(); + if (display->IsAbsolutelyPositionedStyle() && + display->mTopLayer == StyleTopLayer::None) { + const bool isContainingBlock = + aIsFixedPosContainingBlock || + (aIsAbsPosContainingBlock && + display->mPosition == StylePositionProperty::Absolute); + // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be + // a first continuation, see the assertion in the caller. + nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation(); + if (isContainingBlock) { + // If we are becoming a containing block, we only need to reframe if + // this oof's current containing block is an ancestor of the new + // frame. + if (parent != aPossiblyChangingContainingBlock && + nsLayoutUtils::IsProperAncestorFrame( + parent, aPossiblyChangingContainingBlock)) { + return true; + } + } else { + // If we are not a containing block anymore, we only need to reframe + // if we are the current containing block of the oof frame. + if (parent == aPossiblyChangingContainingBlock) { + return true; + } + } + } + } + // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or + // f->IsFixedPosContainingBlock() here. However, that would only + // be testing the *new* style of the frame, which might exclude + // descendants that currently have this frame as an abs-pos + // containing block. Taking the codepath where we don't reframe + // could lead to an unsafe call to + // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed + // the descendant and taken it off the absolute list. + if (ContainingBlockChangeAffectsDescendants( + aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock, + aIsFixedPosContainingBlock)) { + return true; + } + } + } + return false; +} + +// Returns the frame that would serve as the containing block for aFrame's +// positioned descendants, if aFrame had styles to make it a CB for such +// descendants. (Typically this is just aFrame itself, or its insertion frame). +// +// Returns nullptr if this frame can't be easily determined. +static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) { + if (aFrame->IsFieldSetFrame()) { + // FIXME: This should be easily implementable. + return nullptr; + } + nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame(); + if (insertionFrame == aFrame) { + return insertionFrame; + } + // Generally frames with a different insertion frame are hard to deal with, + // but scrollframes are easy because the containing block is just the + // insertion frame. + if (aFrame->IsScrollFrame()) { + return insertionFrame; + } + // Combobox frames are easy as well because they can't have positioned + // children anyways. + // Button and table cell frames are also easy because the containing block is + // the frame itself. + if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() || + aFrame->IsTableCellFrame()) { + return aFrame; + } + return nullptr; +} + +static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame, + nsIFrame* aMaybeChangingCB) { + // NOTE: This looks at the new style. + const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock(); + MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock()); + + const bool isAbsPosContainingBlock = + isFixedContainingBlock || aFrame->IsAbsPosContainingBlock(); + + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { + if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f, + isAbsPosContainingBlock, + isFixedContainingBlock)) { + return true; + } + } + return false; +} + +static void DoApplyRenderingChangeToTree(nsIFrame* aFrame, + nsChangeHint aChange) { + MOZ_ASSERT(gInApplyRenderingChangeToTree, + "should only be called within ApplyRenderingChangeToTree"); + + for (; aFrame; + aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) { + // Invalidate and sync views on all descendant frames, following + // placeholders. We don't need to update transforms in + // SyncViewsAndInvalidateDescendants, because there can't be any + // out-of-flows or popups that need to be transformed; all out-of-flow + // descendants of the transformed element must also be descendants of the + // transformed frame. + SyncViewsAndInvalidateDescendants( + aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame | + nsChangeHint_UpdateOpacityLayer | + nsChangeHint_SchedulePaint))); + // This must be set to true if the rendering change needs to + // invalidate content. If it's false, a composite-only paint + // (empty transaction) will be scheduled. + bool needInvalidatingPaint = false; + + // if frame has view, will already be invalidated + if (aChange & nsChangeHint_RepaintFrame) { + // Note that this whole block will be skipped when painting is suppressed + // (due to our caller ApplyRendingChangeToTree() discarding the + // nsChangeHint_RepaintFrame hint). If you add handling for any other + // hints within this block, be sure that they too should be ignored when + // painting is suppressed. + needInvalidatingPaint = true; + aFrame->InvalidateFrameSubtree(); + if ((aChange & nsChangeHint_UpdateEffects) && + aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // Need to update our overflow rects: + SVGUtils::ScheduleReflowSVG(aFrame); + } + + ActiveLayerTracker::NotifyNeedsRepaint(aFrame); + } + if (aChange & nsChangeHint_UpdateOpacityLayer) { + // FIXME/bug 796697: we can get away with empty transactions for + // opacity updates in many cases. + needInvalidatingPaint = true; + + ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity); + if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) { + // SVG effects paints the opacity without using + // nsDisplayOpacity. We need to invalidate manually. + aFrame->InvalidateFrameSubtree(); + } + } + if ((aChange & nsChangeHint_UpdateTransformLayer) && + aFrame->IsTransformed()) { + // Note: All the transform-like properties should map to the same + // layer activity index, so does the restyle count. Therefore, using + // eCSSProperty_transform should be fine. + ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform); + needInvalidatingPaint = true; + } + if (aChange & nsChangeHint_ChildrenOnlyTransform) { + needInvalidatingPaint = true; + nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame) + ->PrincipalChildList() + .FirstChild(); + for (; childFrame; childFrame = childFrame->GetNextSibling()) { + // Note: All the transform-like properties should map to the same + // layer activity index, so does the restyle count. Therefore, using + // eCSSProperty_transform should be fine. + ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform); + } + } + if (aChange & nsChangeHint_SchedulePaint) { + needInvalidatingPaint = true; + } + aFrame->SchedulePaint(needInvalidatingPaint + ? nsIFrame::PAINT_DEFAULT + : nsIFrame::PAINT_COMPOSITE_ONLY); + } +} + +static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame, + nsChangeHint aChange) { + MOZ_ASSERT(gInApplyRenderingChangeToTree, + "should only be called within ApplyRenderingChangeToTree"); + + NS_ASSERTION(nsChangeHint_size_t(aChange) == + (aChange & (nsChangeHint_RepaintFrame | + nsChangeHint_UpdateOpacityLayer | + nsChangeHint_SchedulePaint)), + "Invalid change flag"); + + aFrame->SyncFrameViewProperties(); + + for (const auto& [list, listID] : aFrame->ChildLists()) { + for (nsIFrame* child : list) { + if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { + // only do frames that don't have placeholders + if (child->IsPlaceholderFrame()) { + // do the out-of-flow frame and its continuations + nsIFrame* outOfFlowFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(child); + DoApplyRenderingChangeToTree(outOfFlowFrame, aChange); + } else if (listID == FrameChildListID::Popup) { + DoApplyRenderingChangeToTree(child, aChange); + } else { // regular frame + SyncViewsAndInvalidateDescendants(child, aChange); + } + } + } + } +} + +static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame, + nsChangeHint aChange) { + // We check StyleDisplay()->HasTransformStyle() in addition to checking + // IsTransformed() since we can get here for some frames that don't support + // CSS transforms, and table frames, which are their own odd-ball, since the + // transform is handled by their wrapper, which _also_ gets a separate hint. + NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) || + aFrame->IsTransformed() || + aFrame->StyleDisplay()->HasTransformStyle(), + "Unexpected UpdateTransformLayer hint"); + + if (aPresShell->IsPaintingSuppressed()) { + // Don't allow synchronous rendering changes when painting is turned off. + aChange &= ~nsChangeHint_RepaintFrame; + if (!aChange) { + return; + } + } + +// Trigger rendering updates by damaging this frame and any +// continuations of this frame. +#ifdef DEBUG + gInApplyRenderingChangeToTree = true; +#endif + if (aChange & nsChangeHint_RepaintFrame) { + // If the frame is the primary frame of either the body element or + // the html element, we propagate the repaint change hint to the + // viewport. This is necessary for background and scrollbar colors + // propagation. + if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) { + nsIFrame* rootFrame = aPresShell->GetRootFrame(); + MOZ_ASSERT(rootFrame, "No root frame?"); + DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame); + aChange &= ~nsChangeHint_RepaintFrame; + if (!aChange) { + return; + } + } + } + DoApplyRenderingChangeToTree(aFrame, aChange); +#ifdef DEBUG + gInApplyRenderingChangeToTree = false; +#endif +} + +static void AddSubtreeToOverflowTracker( + nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) { + if (aFrame->FrameMaintainsOverflow()) { + aOverflowChangedTracker.AddFrame(aFrame, + OverflowChangedTracker::CHILDREN_CHANGED); + } + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + AddSubtreeToOverflowTracker(child, aOverflowChangedTracker); + } + } +} + +static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) { + IntrinsicDirty dirtyType; + if (aHint & nsChangeHint_ClearDescendantIntrinsics) { + NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics, + "Please read the comments in nsChangeHint.h"); + NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow, + "ClearDescendantIntrinsics requires NeedDirtyReflow"); + dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants; + } else if ((aHint & nsChangeHint_UpdateComputedBSize) && + aFrame->HasAnyStateBits( + NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { + dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants; + } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) { + dirtyType = IntrinsicDirty::FrameAndAncestors; + } else { + dirtyType = IntrinsicDirty::None; + } + + if (aHint & nsChangeHint_UpdateComputedBSize) { + aFrame->SetHasBSizeChange(true); + } + + nsFrameState dirtyBits; + if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + dirtyBits = nsFrameState(0); + } else if ((aHint & nsChangeHint_NeedDirtyReflow) || + dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) { + dirtyBits = NS_FRAME_IS_DIRTY; + } else { + dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN; + } + + // If we're not going to clear any intrinsic sizes on the frames, and + // there are no dirty bits to set, then there's nothing to do. + if (dirtyType == IntrinsicDirty::None && !dirtyBits) return; + + ReflowRootHandling rootHandling; + if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) { + rootHandling = ReflowRootHandling::PositionOrSizeChange; + } else { + rootHandling = ReflowRootHandling::NoPositionOrSizeChange; + } + + do { + aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits, + rootHandling); + aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); + } while (aFrame); +} + +// Get the next sibling which might have a frame. This only considers siblings +// that stylo post-traversal looks at, so only elements and text. In +// particular, it ignores comments. +static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) { + for (nsIContent* next = aContent->GetNextSibling(); next; + next = next->GetNextSibling()) { + if (next->IsElement() || next->IsText()) { + return next; + } + } + + return nullptr; +} + +// If |aFrame| is dirty or has dirty children, then we can skip updating +// overflows since that will happen when it's reflowed. +static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) { + return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint, + nsIContent* aContent, + nsIFrame* aFrame, + nsPresContext* aPc) { + if (!(aHint & nsChangeHint_ScrollbarChange)) { + return; + } + aHint &= ~nsChangeHint_ScrollbarChange; + if (aHint & nsChangeHint_ReconstructFrame) { + return; + } + + MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame"); + + const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent(); + + // Only bother with this if we're the root or the body element, since: + // (a) It'd be *expensive* to reframe these particular nodes. They're + // at the root, so reframing would mean rebuilding the world. + // (b) It's often *unnecessary* to reframe for "overflow" changes on + // these particular nodes. In general, the only reason we reframe + // for "overflow" changes is so we can construct (or destroy) a + // scrollframe & scrollbars -- and the html/body nodes often don't + // need their own scrollframe/scrollbars because they coopt the ones + // on the viewport (which always exist). So depending on whether + // that's happening, we can skip the reframe for these nodes. + if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) { + // If the restyled element provided/provides the scrollbar styles for + // the viewport before and/or after this restyle, AND it's not coopting + // that responsibility from some other element (which would need + // reconstruction to make its own scrollframe now), THEN: we don't need + // to reconstruct - we can just reflow, because no scrollframe is being + // added/removed. + Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement(); + Element* newOverride = aPc->UpdateViewportScrollStylesOverride(); + + const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) { + if (aOverride) { + return aOverride == aContent; + } + return isRoot; + }; + + if (ProvidesScrollbarStyles(prevOverride) || + ProvidesScrollbarStyles(newOverride)) { + // If we get here, the restyled element provided the scrollbar styles + // for viewport before this restyle, OR it will provide them after. + if (!prevOverride || !newOverride || prevOverride == newOverride) { + // If we get here, the restyled element is NOT replacing (or being + // replaced by) some other element as the viewport's + // scrollbar-styles provider. (If it were, we'd potentially need to + // reframe to create a dedicated scrollframe for whichever element + // is being booted from providing viewport scrollbar styles.) + // + // Under these conditions, we're OK to assume that this "overflow" + // change only impacts the root viewport's scrollframe, which + // already exists, so we can simply reflow instead of reframing. + if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) { + sf->MarkScrollbarsDirtyForReflow(); + } else if (nsIScrollableFrame* sf = + aPc->PresShell()->GetRootScrollFrameAsScrollable()) { + sf->MarkScrollbarsDirtyForReflow(); + } + aHint |= nsChangeHint_ReflowHintsForScrollbarChange; + } else { + // If we changed the override element, we need to reconstruct as the old + // override element might start / stop being scrollable. + aHint |= nsChangeHint_ReconstructFrame; + } + return; + } + } + + const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow(); + if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) { + if (scrollable && sf->HasAllNeededScrollbars()) { + sf->MarkScrollbarsDirtyForReflow(); + // Once we've created scrollbars for a frame, don't bother reconstructing + // it just to remove them if we still need a scroll frame. + aHint |= nsChangeHint_ReflowHintsForScrollbarChange; + return; + } + } else if (aFrame->IsTextInputFrame()) { + // input / textarea for the most part don't honor overflow themselves, the + // editor root will deal with the change if needed. + // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(), + // so we need to reflow the textarea itself, not just the inner control. + aHint |= nsChangeHint_ReflowHintsForScrollbarChange; + return; + } else if (!scrollable) { + // Something changed, but we don't have nor will have a scroll frame, + // there's nothing to do here. + return; + } + + // Oh well, we couldn't optimize it out, just reconstruct frames for the + // subtree. + aHint |= nsChangeHint_ReconstructFrame; +} + +static void TryToHandleContainingBlockChange(nsChangeHint& aHint, + nsIFrame* aFrame) { + if (!(aHint & nsChangeHint_UpdateContainingBlock)) { + return; + } + if (aHint & nsChangeHint_ReconstructFrame) { + return; + } + MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame"); + nsIFrame* containingBlock = ContainingBlockForFrame(aFrame); + if (!containingBlock || + NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) { + // The frame has positioned children that need to be reparented, or it can't + // easily be converted to/from being an abs-pos container correctly. + aHint |= nsChangeHint_ReconstructFrame; + return; + } + const bool isCb = aFrame->IsAbsPosContainingBlock(); + + // The absolute container should be containingBlock. + for (nsIFrame* cont = containingBlock; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + // Normally frame construction would set state bits as needed, + // but we're not going to reconstruct the frame so we need to set + // them. It's because we need to set this state on each affected frame + // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up + // to ancestors (i.e. it can't be an change hint that is handled for + // descendants). + if (isCb) { + if (!cont->IsAbsoluteContainer() && + cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + cont->MarkAsAbsoluteContainingBlock(); + } + } else if (cont->IsAbsoluteContainer()) { + if (cont->HasAbsolutelyPositionedChildren()) { + // If |cont| still has absolutely positioned children, + // we can't call MarkAsNotAbsoluteContainingBlock. This + // will remove a frame list that still has children in + // it that we need to keep track of. + // The optimization of removing it isn't particularly + // important, although it does mean we skip some tests. + NS_WARNING("skipping removal of absolute containing block"); + } else { + cont->MarkAsNotAbsoluteContainingBlock(); + } + } + } +} + +void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) { + NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), + "Someone forgot a script blocker"); + + // See bug 1378219 comment 9: + // Recursive calls here are a bit worrying, but apparently do happen in the + // wild (although not currently in any of our automated tests). Try to get a + // stack from Nightly/Dev channel to figure out what's going on and whether + // it's OK. + MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion"); + + if (aChangeList.IsEmpty()) { + return; + } + + // If mDestroyedFrames is null, we want to create a new hashtable here + // and destroy it on exit; but if it is already non-null (because we're in + // a recursive call), we will continue to use the existing table to + // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit. + // We use a MaybeClearDestroyedFrames helper to conditionally reset the + // mDestroyedFrames pointer when this method returns. + typedef decltype(mDestroyedFrames) DestroyedFramesT; + class MOZ_RAII MaybeClearDestroyedFrames { + private: + DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames + const bool mResetOnDestruction; + + public: + explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget) + : mDestroyedFramesRef(aTarget), + mResetOnDestruction(!aTarget) // reset only if target starts out null + {} + ~MaybeClearDestroyedFrames() { + if (mResetOnDestruction) { + mDestroyedFramesRef.reset(nullptr); + } + } + }; + + MaybeClearDestroyedFrames maybeClear(mDestroyedFrames); + if (!mDestroyedFrames) { + mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>(); + } + + AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT); + + nsPresContext* presContext = PresContext(); + nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); + + bool didUpdateCursor = false; + + for (size_t i = 0; i < aChangeList.Length(); ++i) { + // Collect and coalesce adjacent siblings for lazy frame construction. + // Eventually it would be even better to make RecreateFramesForContent + // accept a range and coalesce all adjacent reconstructs (bug 1344139). + size_t lazyRangeStart = i; + while (i < aChangeList.Length() && aChangeList[i].mContent && + aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) && + (i == lazyRangeStart || + NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) == + aChangeList[i].mContent)) { + MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame); + MOZ_ASSERT(!aChangeList[i].mFrame); + ++i; + } + if (i != lazyRangeStart) { + nsIContent* start = aChangeList[lazyRangeStart].mContent; + nsIContent* end = + NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent); + if (!end) { + frameConstructor->ContentAppended( + start, nsCSSFrameConstructor::InsertionKind::Sync); + } else { + frameConstructor->ContentRangeInserted( + start, end, nsCSSFrameConstructor::InsertionKind::Sync); + } + } + for (size_t j = lazyRangeStart; j < i; ++j) { + MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() || + !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME)); + } + if (i == aChangeList.Length()) { + break; + } + + const nsStyleChangeData& data = aChangeList[i]; + nsIFrame* frame = data.mFrame; + nsIContent* content = data.mContent; + nsChangeHint hint = data.mHint; + bool didReflowThisFrame = false; + + NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) || + (hint & nsChangeHint_NeedReflow), + "Reflow hint bits set without actually asking for a reflow"); + + // skip any frame that has been destroyed due to a ripple effect + if (frame && mDestroyedFrames->Contains(frame)) { + continue; + } + + if (frame && frame->GetContent() != content) { + // XXXbz this is due to image maps messing with the primary frame of + // <area>s. See bug 135040. Remove this block once that's fixed. + frame = nullptr; + if (!(hint & nsChangeHint_ReconstructFrame)) { + continue; + } + } + + TryToDealWithScrollbarChange(hint, content, frame, presContext); + TryToHandleContainingBlockChange(hint, frame); + + if (hint & nsChangeHint_ReconstructFrame) { + // If we ever start passing true here, be careful of restyles + // that involve a reframe and animations. In particular, if the + // restyle we're processing here is an animation restyle, but + // the style resolution we will do for the frame construction + // happens async when we're not in an animation restyle already, + // problems could arise. + // We could also have problems with triggering of CSS transitions + // on elements whose frames are reconstructed, since we depend on + // the reconstruction happening synchronously. + frameConstructor->RecreateFramesForContent( + content, nsCSSFrameConstructor::InsertionKind::Sync); + continue; + } + + MOZ_ASSERT(frame, "This shouldn't happen"); + if (hint & nsChangeHint_AddOrRemoveTransform) { + for (nsIFrame* cont = frame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + if (cont->StyleDisplay()->HasTransform(cont)) { + cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); + } + // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be + // transformed by other means. It's OK to have the bit even if it's + // not needed. + } + } + + if (!frame->FrameMaintainsOverflow()) { + // frame does not maintain overflow rects, so avoid calling + // FinishAndStoreOverflow on it: + hint &= + ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform | + nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateParentOverflow | + nsChangeHint_UpdateSubtreeOverflow); + } + + if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) { + // Frame can not be transformed, and thus a change in transform will + // have no effect and we should not use either + // nsChangeHint_UpdatePostTransformOverflow or + // nsChangeHint_UpdateTransformLayerhint. + hint &= ~(nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateTransformLayer); + } + + if (hint & nsChangeHint_AddOrRemoveTransform) { + // When dropping a running transform animation we will first add an + // nsChangeHint_UpdateTransformLayer hint as part of the animation-only + // restyle. During the subsequent regular restyle, if the animation was + // the only reason the element had any transform applied, we will add + // nsChangeHint_AddOrRemoveTransform as part of the regular restyle. + // + // With the Gecko backend, these two change hints are processed + // after each restyle but when using the Servo backend they accumulate + // and are processed together after we have already removed the + // transform as part of the regular restyle. Since we don't actually + // need the nsChangeHint_UpdateTransformLayer hint if we already have + // a nsChangeHint_AddOrRemoveTransform hint, and since we + // will fail an assertion in ApplyRenderingChangeToTree if we try + // specify nsChangeHint_UpdateTransformLayer but don't have any + // transform style, we just drop the unneeded hint here. + hint &= ~nsChangeHint_UpdateTransformLayer; + } + + if ((hint & nsChangeHint_UpdateEffects) && + frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) { + SVGObserverUtils::UpdateEffects(frame); + } + if ((hint & nsChangeHint_InvalidateRenderingObservers) || + ((hint & nsChangeHint_UpdateOpacityLayer) && + frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) { + SVGObserverUtils::InvalidateRenderingObservers(frame); + frame->SchedulePaint(); + } + if (hint & nsChangeHint_NeedReflow) { + StyleChangeReflow(frame, hint); + didReflowThisFrame = true; + } + + // Here we need to propagate repaint frame change hint instead of update + // opacity layer change hint when we do opacity optimization for SVG. + // We can't do it in nsStyleEffects::CalcDifference() just like we do + // for the optimization for 0.99 over opacity values since we have no way + // to call SVGUtils::CanOptimizeOpacity() there. + if ((hint & nsChangeHint_UpdateOpacityLayer) && + SVGUtils::CanOptimizeOpacity(frame)) { + hint &= ~nsChangeHint_UpdateOpacityLayer; + hint |= nsChangeHint_RepaintFrame; + } + + if ((hint & nsChangeHint_UpdateUsesOpacity) && + frame->IsFrameOfType(nsIFrame::eTablePart)) { + NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer, + "should only return UpdateUsesOpacity hint " + "when also returning UpdateOpacityLayer hint"); + // When an internal table part (including cells) changes between + // having opacity 1 and non-1, it changes whether its + // backgrounds (and those of table parts inside of it) are + // painted as part of the table's nsDisplayTableBorderBackground + // display item, or part of its own display item. That requires + // invalidation, so change UpdateOpacityLayer to RepaintFrame. + hint &= ~nsChangeHint_UpdateOpacityLayer; + hint |= nsChangeHint_RepaintFrame; + } + + // Opacity disables preserve-3d, so if we toggle it, then we also need + // to update the overflow areas of all potentially affected frames. + if ((hint & nsChangeHint_UpdateUsesOpacity) && + frame->StyleDisplay()->mTransformStyle == + StyleTransformStyle::Preserve3d) { + hint |= nsChangeHint_UpdateSubtreeOverflow; + } + + if (hint & nsChangeHint_UpdateBackgroundPosition) { + // For most frame types, DLBI can detect background position changes, + // so we only need to schedule a paint. + hint |= nsChangeHint_SchedulePaint; + if (frame->IsFrameOfType(nsIFrame::eTablePart) || + frame->IsFrameOfType(nsIFrame::eMathML)) { + // Table parts and MathML frames don't build display items for their + // backgrounds, so DLBI can't detect background-position changes for + // these frames. Repaint the whole frame. + hint |= nsChangeHint_RepaintFrame; + } + } + + if (hint & + (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer | + nsChangeHint_UpdateTransformLayer | + nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) { + ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint); + } + if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) { + // It is possible for this to fall back to a reflow + if (!RecomputePosition(frame)) { + StyleChangeReflow(frame, nsChangeHint_NeedReflow | + nsChangeHint_ReflowChangesSizeOrPosition); + didReflowThisFrame = true; + } + } + NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) || + (hint & nsChangeHint_UpdateOverflow), + "nsChangeHint_UpdateOverflow should be passed too"); + if (!didReflowThisFrame && + (hint & (nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateParentOverflow | + nsChangeHint_UpdateSubtreeOverflow))) { + if (hint & nsChangeHint_UpdateSubtreeOverflow) { + for (nsIFrame* cont = frame; cont; + cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker); + } + // The work we just did in AddSubtreeToOverflowTracker + // subsumes some of the other hints: + hint &= ~(nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow); + } + if (hint & nsChangeHint_ChildrenOnlyTransform) { + // We need to update overflows. The correct frame(s) to update depends + // on whether the ChangeHint came from an outer or an inner svg. + nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame); + NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame), + "SVG frames should not have continuations " + "or ib-split siblings"); + NS_ASSERTION( + !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame), + "SVG frames should not have continuations " + "or ib-split siblings"); + if (hintFrame->IsSVGOuterSVGAnonChildFrame()) { + // The children only transform of an outer svg frame is applied to + // the outer svg's anonymous child frame (instead of to the + // anonymous child's children). + + if (!CanSkipOverflowUpdates(hintFrame)) { + mOverflowChangedTracker.AddFrame( + hintFrame, OverflowChangedTracker::CHILDREN_CHANGED); + } + } else { + // The children only transform is applied to the child frames of an + // inner svg frame, so update the child overflows. + nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild(); + for (; childFrame; childFrame = childFrame->GetNextSibling()) { + MOZ_ASSERT(childFrame->IsFrameOfType(nsIFrame::eSVG), + "Not expecting non-SVG children"); + if (!CanSkipOverflowUpdates(childFrame)) { + mOverflowChangedTracker.AddFrame( + childFrame, OverflowChangedTracker::CHILDREN_CHANGED); + } + NS_ASSERTION( + !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame), + "SVG frames should not have continuations " + "or ib-split siblings"); + NS_ASSERTION( + childFrame->GetParent() == hintFrame, + "SVG child frame not expected to have different parent"); + } + } + } + if (!CanSkipOverflowUpdates(frame)) { + if (hint & (nsChangeHint_UpdateOverflow | + nsChangeHint_UpdatePostTransformOverflow)) { + OverflowChangedTracker::ChangeKind changeKind; + // If we have both nsChangeHint_UpdateOverflow and + // nsChangeHint_UpdatePostTransformOverflow, + // CHILDREN_CHANGED is selected as it is + // strictly stronger. + if (hint & nsChangeHint_UpdateOverflow) { + changeKind = OverflowChangedTracker::CHILDREN_CHANGED; + } else { + changeKind = OverflowChangedTracker::TRANSFORM_CHANGED; + } + for (nsIFrame* cont = frame; cont; + cont = + nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + mOverflowChangedTracker.AddFrame(cont, changeKind); + } + } + // UpdateParentOverflow hints need to be processed in addition + // to the above, since if the processing of the above hints + // yields no change, the update will not propagate to the + // parent. + if (hint & nsChangeHint_UpdateParentOverflow) { + MOZ_ASSERT(frame->GetParent(), + "shouldn't get style hints for the root frame"); + for (nsIFrame* cont = frame; cont; + cont = + nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) { + mOverflowChangedTracker.AddFrame( + cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED); + } + } + } + } + if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) { + presContext->PresShell()->SynthesizeMouseMove(false); + didUpdateCursor = true; + } + if (hint & nsChangeHint_UpdateTableCellSpans) { + frameConstructor->UpdateTableCellSpans(content); + } + if (hint & nsChangeHint_VisibilityChange) { + frame->UpdateVisibleDescendantsState(); + } + } + + aChangeList.Clear(); + FlushOverflowChangedTracker(); +} + +/* static */ +uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) { + EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame); + return effectSet ? effectSet->GetAnimationGeneration() : 0; +} + +void RestyleManager::IncrementAnimationGeneration() { + // We update the animation generation at start of each call to + // ProcessPendingRestyles so we should ignore any subsequent (redundant) + // calls that occur while we are still processing restyles. + if (!mInStyleRefresh) { + ++mAnimationGeneration; + } +} + +/* static */ +void RestyleManager::AddLayerChangesForAnimation( + nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement, + nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) { + MOZ_ASSERT(aElement); + MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame); + if (!aStyleFrame) { + return; + } + + uint64_t frameGeneration = + RestyleManager::GetAnimationGenerationForFrame(aStyleFrame); + + Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties; + + nsChangeHint hint = nsChangeHint(0); + auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration, + DisplayItemType aDisplayItemType) -> bool { + if (aGeneration && frameGeneration != *aGeneration) { + // If we have a transform layer but don't have any transform style, we + // probably just removed the transform but haven't destroyed the layer + // yet. In this case we will typically add the appropriate change hint + // (nsChangeHint_UpdateContainingBlock) when we compare styles so in + // theory we could skip adding any change hint here. + // + // However, sometimes when we compare styles we'll get no change. For + // example, if the transform style was 'none' when we sent the transform + // animation to the compositor and the current transform style is now + // 'none' we'll think nothing changed but actually we still need to + // trigger an update to clear whatever style the transform animation set + // on the compositor. To handle this case we simply set all the change + // hints relevant to removing transform style (since we don't know exactly + // what changes happened while the animation was running on the + // compositor). + // + // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we + // did, ApplyRenderingChangeToTree would complain that we're updating a + // transform layer without a transform. + if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM && + !aStyleFrame->StyleDisplay()->HasTransformStyle()) { + // Add all the hints for a removing a transform if they are not already + // set for this frame. + if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform, + aHintForThisFrame))) { + hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform; + } + return true; + } + hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType); + } + + // We consider it's the first paint for the frame if we have an animation + // for the property but have no layer, for the case of WebRender, no + // corresponding animation info. + // Note that in case of animations which has properties preventing running + // on the compositor, e.g., width or height, corresponding layer is not + // created at all, but even in such cases, we normally set valid change + // hint for such animations in each tick, i.e. restyles in each tick. As + // a result, we usually do restyles for such animations in every tick on + // the main-thread. The only animations which will be affected by this + // explicit change hint are animations that have opacity/transform but did + // not have those properies just before. e.g, setting transform by + // setKeyframes or changing target element from other target which prevents + // running on the compositor, etc. + if (!aGeneration) { + nsChangeHint hintForDisplayItem = + LayerAnimationInfo::GetChangeHintFor(aDisplayItemType); + // We don't need to apply the corresponding change hint if we already have + // it. + if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) { + return true; + } + + if (!effectiveAnimationProperties) { + effectiveAnimationProperties.emplace( + nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame)); + } + const nsCSSPropertyIDSet& propertiesForDisplayItem = + LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType); + if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) { + hint |= hintForDisplayItem; + } + } + return true; + }; + + AnimationInfo::EnumerateGenerationOnFrame( + aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes, + maybeApplyChangeHint); + + if (hint) { + // We apply the hint to the primary frame, not the style frame. Transform + // and opacity hints apply to the table wrapper box, not the table box. + aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint); + } +} + +RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame( + RestyleManager* aRestyleManager) + : mRestyleManager(aRestyleManager), + mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) { + MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame, + "shouldn't construct recursively"); + mRestyleManager->mAnimationsWithDestroyedFrame = this; +} + +void RestyleManager::AnimationsWithDestroyedFrame :: + StopAnimationsForElementsWithoutFrames() { + StopAnimationsWithoutFrame(mContents, PseudoStyleType::NotPseudo); + StopAnimationsWithoutFrame(mBeforeContents, PseudoStyleType::before); + StopAnimationsWithoutFrame(mAfterContents, PseudoStyleType::after); + StopAnimationsWithoutFrame(mMarkerContents, PseudoStyleType::marker); +} + +void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame( + nsTArray<RefPtr<nsIContent>>& aArray, PseudoStyleType aPseudoType) { + nsAnimationManager* animationManager = + mRestyleManager->PresContext()->AnimationManager(); + nsTransitionManager* transitionManager = + mRestyleManager->PresContext()->TransitionManager(); + for (nsIContent* content : aArray) { + if (aPseudoType == PseudoStyleType::NotPseudo) { + if (content->GetPrimaryFrame()) { + continue; + } + } else if (aPseudoType == PseudoStyleType::before) { + if (nsLayoutUtils::GetBeforeFrame(content)) { + continue; + } + } else if (aPseudoType == PseudoStyleType::after) { + if (nsLayoutUtils::GetAfterFrame(content)) { + continue; + } + } else if (aPseudoType == PseudoStyleType::marker) { + if (nsLayoutUtils::GetMarkerFrame(content)) { + continue; + } + } + dom::Element* element = content->AsElement(); + + animationManager->StopAnimationsForElement(element, aPseudoType); + transitionManager->StopAnimationsForElement(element, aPseudoType); + + // All other animations should keep running but not running on the + // *compositor* at this point. + if (EffectSet* effectSet = EffectSet::Get(element, aPseudoType)) { + for (KeyframeEffect* effect : *effectSet) { + effect->ResetIsRunningOnCompositor(); + } + } + } +} + +#ifdef DEBUG +static bool IsAnonBox(const nsIFrame* aFrame) { + return aFrame->Style()->IsAnonBox(); +} + +static const nsIFrame* FirstContinuationOrPartOfIBSplit( + const nsIFrame* aFrame) { + if (!aFrame) { + return nullptr; + } + + return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); +} + +static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) { + const nsIFrame* parent = aFrame->GetParent(); + if (aFrame->IsTableFrame()) { + MOZ_ASSERT(parent->IsTableWrapperFrame()); + parent = parent->GetParent(); + } + + if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) { + if (parent->IsLineFrame()) { + parent = parent->GetParent(); + } + return parent->IsViewportFrame() ? nullptr + : FirstContinuationOrPartOfIBSplit(parent); + } + + if (aFrame->IsLineFrame()) { + // A ::first-line always ends up here via its block, which is therefore the + // right expected owner. That block can be an + // anonymous box. For example, we could have a ::first-line on a columnated + // block; the blockframe is the column-content anonymous box in that case. + // So we don't want to end up in the code below, which steps out of anon + // boxes. Just return the parent of the line frame, which is the block. + return parent; + } + + if (aFrame->IsLetterFrame()) { + // Ditto for ::first-letter. A first-letter always arrives here via its + // direct parent, except when it's parented to a ::first-line. + if (parent->IsLineFrame()) { + parent = parent->GetParent(); + } + return FirstContinuationOrPartOfIBSplit(parent); + } + + if (parent->IsLetterFrame()) { + // Things never have ::first-letter as their expected parent. Go + // on up to the ::first-letter's parent. + parent = parent->GetParent(); + } + + parent = FirstContinuationOrPartOfIBSplit(parent); + + // We've handled already anon boxes, so now we're looking at + // a frame of a DOM element or pseudo. Hop through anon and line-boxes + // generated by our DOM parent, and go find the owner frame for it. + while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) { + auto pseudo = parent->Style()->GetPseudoType(); + if (pseudo == PseudoStyleType::tableWrapper) { + const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild(); + MOZ_ASSERT(tableFrame->IsTableFrame()); + // Handle :-moz-table and :-moz-inline-table. + parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame; + } else { + // We get the in-flow parent here so that we can handle the OOF anonymous + // boxed to get the correct parent. + parent = parent->GetInFlowParent(); + } + parent = FirstContinuationOrPartOfIBSplit(parent); + } + + return parent; +} + +// FIXME(emilio, bug 1633685): We should ideally figure out how to properly +// restyle replicated fixed pos frames... We seem to assume everywhere that they +// can't get restyled at the moment... +static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) { + if (!aFrame->PresContext()->IsPaginated()) { + return false; + } + + for (; aFrame; aFrame = aFrame->GetParent()) { + if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && + !aFrame->FirstContinuation()->IsPrimaryFrame() && + nsLayoutUtils::IsReallyFixedPos(aFrame)) { + return true; + } + } + + return true; +} + +void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const { + MOZ_ASSERT(mOwner); + MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); + MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree()); + // We allow aParent.mOwner to be null, for cases when we're not starting at + // the root of the tree. We also allow aParent.mOwner to be somewhere up our + // expected owner chain not our immediate owner, which allows us creating long + // chains of ServoRestyleStates in some cases where it's just not worth it. + if (aParent.mOwner) { + const nsIFrame* owner = ExpectedOwnerForChild(mOwner); + if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) { + MOZ_ASSERT(IsAnonBox(owner), + "Should only have expected owner weirdness when anon boxes " + "are involved"); + bool found = false; + for (; owner; owner = ExpectedOwnerForChild(owner)) { + if (owner == aParent.mOwner) { + found = true; + break; + } + } + MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain"); + } + } +} + +nsChangeHint ServoRestyleState::ChangesHandledFor( + const nsIFrame* aFrame) const { + if (!mOwner) { + MOZ_ASSERT(!mChangesHandled); + return mChangesHandled; + } + + MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) || + IsInReplicatedFixedPosTree(aFrame), + "Missed some frame in the hierarchy?"); + return mChangesHandled; +} +#endif + +void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) { + MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(), + "All our wrappers are anon boxes, and why would we restyle " + "non-inheriting ones?"); + MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(), + "All our wrappers are anon boxes, and why would we restyle " + "non-inheriting ones?"); + MOZ_ASSERT( + aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent, + "Someone should be using TableAwareParentFor"); + MOZ_ASSERT( + aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper, + "Someone should be using TableAwareParentFor"); + // Make sure we only add first continuations. + aWrapperFrame = aWrapperFrame->FirstContinuation(); + nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr); + if (last == aWrapperFrame) { + // Already queued up, nothing to do. + return; + } + + // Make sure to queue up parents before children. But don't queue up + // ancestors of non-anonymous boxes here; those are handled when we traverse + // their non-anonymous kids. + if (aWrapperFrame->ParentIsWrapperAnonBox()) { + AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame)); + } + + // If the append fails, we'll fail to restyle properly, but that's probably + // better than crashing. + if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) { + aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true); + } +} + +void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) { + size_t i = mPendingWrapperRestyleOffset; + while (i < mPendingWrapperRestyles.Length()) { + i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i); + } + + mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset); +} + +size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, + size_t aIndex) { + // The frame at index aIndex is something we should restyle ourselves, but + // following frames may need separate ServoRestyleStates to restyle. + MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length()); + + nsIFrame* cur = mPendingWrapperRestyles[aIndex]; + MOZ_ASSERT(cur->Style()->IsWrapperAnonBox()); + + // Where is cur supposed to inherit from? From its parent frame, except in + // the case when cur is a table, in which case it should be its grandparent. + // Also, not in the case when the resulting frame would be a first-line; in + // that case we should be inheriting from the block, and the first-line will + // do its fixup later if needed. + // + // Note that after we do all that fixup the parent we get might still not be + // aParent; for example aParent could be a scrollframe, in which case we + // should inherit from the scrollcontent frame. Or the parent might be some + // continuation of aParent. + // + // Try to assert as much as we can about the parent we actually end up using + // without triggering bogus asserts in all those various edge cases. + nsIFrame* parent = cur->GetParent(); + if (cur->IsTableFrame()) { + MOZ_ASSERT(parent->IsTableWrapperFrame()); + parent = parent->GetParent(); + } + if (parent->IsLineFrame()) { + parent = parent->GetParent(); + } + MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent || + (parent->Style()->IsInheritingAnonBox() && + parent->GetContent() == aParent->GetContent())); + + // Now "this" is a ServoRestyleState for aParent, so if parent is not a next + // continuation (possibly across ib splits) of aParent we need a new + // ServoRestyleState for the kid. + Maybe<ServoRestyleState> parentRestyleState; + nsIFrame* parentForRestyle = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent); + if (parentForRestyle != aParent) { + parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty, + Type::InFlow); + } + ServoRestyleState& curRestyleState = + parentRestyleState ? *parentRestyleState : *this; + + // This frame may already have been restyled. Even if it has, we can't just + // return, because the next frame may be a kid of it that does need restyling. + if (cur->IsWrapperAnonBoxNeedingRestyle()) { + parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState); + cur->SetIsWrapperAnonBoxNeedingRestyle(false); + } + + size_t numProcessed = 1; + + // Note: no overflow possible here, since aIndex < length. + if (aIndex + 1 < mPendingWrapperRestyles.Length()) { + nsIFrame* next = mPendingWrapperRestyles[aIndex + 1]; + if (TableAwareParentFor(next) == cur && + next->IsWrapperAnonBoxNeedingRestyle()) { + // It might be nice if we could do better than nsChangeHint_Empty. On + // the other hand, presumably our mChangesHandled already has the bits + // we really want here so in practice it doesn't matter. + ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty, + Type::InFlow, + /* aAssertWrapperRestyleLength = */ false); + numProcessed += + childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1); + } + } + + return numProcessed; +} + +nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) { + // We want to get the anon box parent for aChild. where aChild has + // ParentIsWrapperAnonBox(). + // + // For the most part this is pretty straightforward, but there are two + // wrinkles. First, if aChild is a table, then we really want the parent of + // its table wrapper. + if (aChild->IsTableFrame()) { + aChild = aChild->GetParent(); + MOZ_ASSERT(aChild->IsTableWrapperFrame()); + } + + nsIFrame* parent = aChild->GetParent(); + // Now if parent is a cell-content frame, we actually want the cellframe. + if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) { + parent = parent->GetParent(); + } else if (parent->IsTableWrapperFrame()) { + // Must be a caption. In that case we want the table here. + MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption); + parent = parent->PrincipalChildList().FirstChild(); + } + return parent; +} + +void RestyleManager::PostRestyleEvent(Element* aElement, + RestyleHint aRestyleHint, + nsChangeHint aMinChangeHint) { + MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange), + "Didn't expect explicit change hints to be neutral!"); + if (MOZ_UNLIKELY(IsDisconnected()) || + MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) { + return; + } + + // We allow posting restyles from within change hint handling, but not from + // within the restyle algorithm itself. + MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal()); + + if (!aRestyleHint && !aMinChangeHint) { + // FIXME(emilio): we should assert against this instead. + return; // Nothing to do. + } + + // Assuming the restyle hints will invalidate cached style for + // getComputedStyle, since we don't know if any of the restyling that we do + // would affect undisplayed elements. + if (aRestyleHint) { + if (!(aRestyleHint & RestyleHint::ForAnimations())) { + mHaveNonAnimationRestyles = true; + } + + IncrementUndisplayedRestyleGeneration(); + } + + // Processing change hints sometimes causes new change hints to be generated, + // and very occasionally, additional restyle hints. We collect the change + // hints manually to avoid re-traversing the DOM to find them. + if (mReentrantChanges && !aRestyleHint) { + mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint}); + return; + } + + if (aRestyleHint || aMinChangeHint) { + Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint); + } +} + +void RestyleManager::PostRestyleEventForAnimations(Element* aElement, + PseudoStyleType aPseudoType, + RestyleHint aRestyleHint) { + Element* elementToRestyle = + AnimationUtils::GetElementForRestyle(aElement, aPseudoType); + + if (!elementToRestyle) { + // FIXME: Bug 1371107: When reframing happens, + // EffectCompositor::mElementsToRestyle still has unbound old pseudo + // element. We should drop it. + return; + } + + AutoRestyleTimelineMarker marker(mPresContext->GetDocShell(), + true /* animation-only */); + Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0)); +} + +void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint, + RestyleHint aRestyleHint) { + // NOTE(emilio): The semantics of these methods are quite funny, in the sense + // that we're not supposed to need to rebuild the actual stylist data. + // + // That's handled as part of the MediumFeaturesChanged stuff, if needed. + // + // Clear the cached style data only if we are guaranteed to process the whole + // DOM tree again. + // + // FIXME(emilio): Decouple this, probably. This probably just wants to reset + // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon + // box styles and such... But it doesn't really always need to clear the + // initial style of the document and similar... + if (aRestyleHint.DefinitelyRecascadesAllSubtree()) { + StyleSet()->ClearCachedStyleData(); + } + + DocumentStyleRootIterator iter(mPresContext->Document()); + while (Element* root = iter.GetNextStyleRoot()) { + PostRestyleEvent(root, aRestyleHint, aExtraHint); + } + + // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect + // non-inheriting anon boxes. It's not clear if we want to support that, but + // if we do, we need to re-selector-match them here. +} + +/* static */ +void RestyleManager::ClearServoDataFromSubtree(Element* aElement, + IncludeRoot aIncludeRoot) { + if (aElement->HasServoData()) { + StyleChildrenIterator it(aElement); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + if (n->IsElement()) { + ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes); + } + } + } + + if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) { + aElement->ClearServoData(); + MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits | + NODE_NEEDS_FRAME)); + MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot()); + } +} + +/* static */ +void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) { + if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) { + StyleChildrenIterator it(aElement); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + if (n->IsElement()) { + ClearRestyleStateFromSubtree(n->AsElement()); + } + } + } + + bool wasRestyled; + Unused << Servo_TakeChangeHint(aElement, &wasRestyled); + aElement->UnsetFlags(Element::kAllServoDescendantBits); +} + +/** + * This struct takes care of encapsulating some common state that text nodes may + * need to track during the post-traversal. + * + * This is currently used to properly compute change hints when the parent + * element of this node is a display: contents node, and also to avoid computing + * the style for text children more than once per element. + */ +struct RestyleManager::TextPostTraversalState { + public: + TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext, + bool aDisplayContentsParentStyleChanged, + ServoRestyleState& aParentRestyleState) + : mParentElement(aParentElement), + mParentContext(aParentContext), + mParentRestyleState(aParentRestyleState), + mStyle(nullptr), + mShouldPostHints(aDisplayContentsParentStyleChanged), + mShouldComputeHints(aDisplayContentsParentStyleChanged), + mComputedHint(nsChangeHint_Empty) {} + + nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); } + + ComputedStyle& ComputeStyle(nsIContent* aTextNode) { + if (!mStyle) { + mStyle = mParentRestyleState.StyleSet().ResolveStyleForText( + aTextNode, &ParentStyle()); + } + MOZ_ASSERT(mStyle); + return *mStyle; + } + + void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame, + ComputedStyle& aNewStyle) { + MOZ_ASSERT(aTextFrame); + MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText); + + if (MOZ_LIKELY(!mShouldPostHints)) { + return; + } + + ComputedStyle* oldStyle = aTextFrame->Style(); + MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText); + + // We rely on the fact that all the text children for the same element share + // style to avoid recomputing style differences for all of them. + // + // TODO(emilio): The above may not be true for ::first-{line,letter}, but + // we'll cross that bridge when we support those in stylo. + if (mShouldComputeHints) { + mShouldComputeHints = false; + uint32_t equalStructs; + mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs); + mComputedHint = NS_RemoveSubsumedHints( + mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame)); + } + + if (mComputedHint) { + mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent, + mComputedHint); + } + } + + private: + ComputedStyle& ParentStyle() { + if (!mParentContext) { + mLazilyResolvedParentContext = + ServoStyleSet::ResolveServoStyle(mParentElement); + mParentContext = mLazilyResolvedParentContext; + } + return *mParentContext; + } + + Element& mParentElement; + ComputedStyle* mParentContext; + RefPtr<ComputedStyle> mLazilyResolvedParentContext; + ServoRestyleState& mParentRestyleState; + RefPtr<ComputedStyle> mStyle; + bool mShouldPostHints; + bool mShouldComputeHints; + nsChangeHint mComputedHint; +}; + +static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet, + nsStyleChangeList& aChangeList) { + const nsStyleDisplay* display = aFrame->Style()->StyleDisplay(); + if (display->mTopLayer != StyleTopLayer::Top) { + return; + } + + // Elements in the top layer are guaranteed to have absolute or fixed + // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer. + MOZ_ASSERT(display->IsAbsolutelyPositionedStyle()); + + nsIFrame* backdropPlaceholder = + aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild(); + if (!backdropPlaceholder) { + return; + } + + MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame()); + nsIFrame* backdropFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder); + MOZ_ASSERT(backdropFrame->IsBackdropFrame()); + MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() == + PseudoStyleType::backdrop); + + RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle( + *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, + aFrame->Style()); + + // NOTE(emilio): We can't use the changes handled for the owner of the + // backdrop frame, since it's out of flow, and parented to the viewport or + // canvas frame (depending on the `position` value). + MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() || + backdropFrame->GetParent()->IsCanvasFrame()); + nsTArray<nsIFrame*> wrappersToRestyle; + nsTArray<RefPtr<Element>> anchorsToSuppress; + ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle, + anchorsToSuppress); + nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state); + MOZ_ASSERT(anchorsToSuppress.IsEmpty()); +} + +static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame, + ServoRestyleState& aRestyleState) { + MOZ_ASSERT( + !aFrame->IsBlockFrameOrSubclass(), + "You're probably duplicating work with UpdatePseudoElementStyles!"); + if (!aFrame->HasFirstLetterChild()) { + return; + } + + // We need to find the block the first-letter is associated with so we can + // find the right element for the first-letter's style resolution. Might as + // well just delegate the whole thing to that block. + nsIFrame* block = aFrame->GetParent(); + while (!block->IsBlockFrameOrSubclass()) { + block = block->GetParent(); + } + + static_cast<nsBlockFrame*>(block->FirstContinuation()) + ->UpdateFirstLetterStyle(aRestyleState); +} + +static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex, + ComputedStyle& aOldContext, + ServoRestyleState& aRestyleState) { + auto pseudoType = aOldContext.GetPseudoType(); + MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo); + MOZ_ASSERT( + !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType)); + + RefPtr<ComputedStyle> newStyle = + aRestyleState.StyleSet().ResolvePseudoElementStyle( + *aFrame->GetContent()->AsElement(), pseudoType, aFrame->Style()); + + uint32_t equalStructs; // Not used, actually. + nsChangeHint childHint = + aOldContext.CalcStyleDifference(*newStyle, &equalStructs); + if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && + !aFrame->IsColumnSpanInMulticolSubtree()) { + childHint = NS_RemoveSubsumedHints(childHint, + aRestyleState.ChangesHandledFor(aFrame)); + } + + if (childHint) { + if (childHint & nsChangeHint_ReconstructFrame) { + // If we generate a reconstruct here, remove any non-reconstruct hints we + // may have already generated for this content. + aRestyleState.ChangeList().PopChangesForContent(aFrame->GetContent()); + } + aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(), + childHint); + } + + aFrame->SetAdditionalComputedStyle(aIndex, newStyle); +} + +static void UpdateAdditionalComputedStyles(nsIFrame* aFrame, + ServoRestyleState& aRestyleState) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement()); + + // FIXME(emilio): Consider adding a bit or something to avoid the initial + // virtual call? + uint32_t index = 0; + while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) { + UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState); + } +} + +static void UpdateFramePseudoElementStyles(nsIFrame* aFrame, + ServoRestyleState& aRestyleState) { + if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) { + blockFrame->UpdatePseudoElementStyles(aRestyleState); + } else { + UpdateFirstLetterIfNeeded(aFrame, aRestyleState); + } + + UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(), + aRestyleState.ChangeList()); +} + +enum class ServoPostTraversalFlags : uint32_t { + Empty = 0, + // Whether parent was restyled. + ParentWasRestyled = 1 << 0, + // Skip sending accessibility notifications for all descendants. + SkipA11yNotifications = 1 << 1, + // Always send accessibility notifications if the element is shown. + // The SkipA11yNotifications flag above overrides this flag. + SendA11yNotificationsIfShown = 1 << 2, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags) + +// Send proper accessibility notifications and return post traversal +// flags for kids. +static ServoPostTraversalFlags SendA11yNotifications( + nsPresContext* aPresContext, Element* aElement, + const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle, + ServoPostTraversalFlags aFlags) { + using Flags = ServoPostTraversalFlags; + MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) || + !(aFlags & Flags::SendA11yNotificationsIfShown), + "The two a11y flags should never be set together"); + +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = GetAccService(); + if (!accService) { + // If we don't have accessibility service, accessibility is not + // enabled. Just skip everything. + return Flags::Empty; + } + + if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually != + aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { + if (aElement->GetParent() && + aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) { + accService->NotifyOfTabPanelVisibilityChange( + aPresContext->PresShell(), aElement, + aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually); + } + } + + if (aFlags & Flags::SkipA11yNotifications) { + // Propagate the skipping flag to descendants. + return Flags::SkipA11yNotifications; + } + + bool needsNotify = false; + const bool isVisible = aNewStyle.StyleVisibility()->IsVisible() && + !aNewStyle.StyleUI()->IsInert(); + if (aFlags & Flags::SendA11yNotificationsIfShown) { + if (!isVisible) { + // Propagate the sending-if-shown flag to descendants. + return Flags::SendA11yNotificationsIfShown; + } + // We have asked accessibility service to remove the whole subtree + // of element which becomes invisible from the accessible tree, but + // this element is visible, so we need to add it back. + needsNotify = true; + } else { + // If we shouldn't skip in any case, we need to check whether our + // own visibility has changed. + const bool wasVisible = aOldStyle.StyleVisibility()->IsVisible() && + !aOldStyle.StyleUI()->IsInert(); + needsNotify = wasVisible != isVisible; + } + + if (needsNotify) { + PresShell* presShell = aPresContext->PresShell(); + if (isVisible) { + accService->ContentRangeInserted(presShell, aElement, + aElement->GetNextSibling()); + // We are adding the subtree. Accessibility service would handle + // descendants, so we should just skip them from notifying. + return Flags::SkipA11yNotifications; + } + // Remove the subtree of this invisible element, and ask any shown + // descendant to add themselves back. + accService->ContentRemoved(presShell, aElement); + return Flags::SendA11yNotificationsIfShown; + } +#endif + + return Flags::Empty; +} + +bool RestyleManager::ProcessPostTraversal(Element* aElement, + ServoRestyleState& aRestyleState, + ServoPostTraversalFlags aFlags) { + nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement); + nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); + + MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(), + "Element without Servo data on a post-traversal? How?"); + + // NOTE(emilio): This is needed because for table frames the bit is set on the + // table wrapper (which is the primary frame), not on the table itself. + const bool isOutOfFlow = + primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); + + // We need this because any column-spanner's parent frame is not its DOM + // parent's primary frame. We need some special check similar to out-of-flow + // frames. + const bool isColumnSpan = + primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree(); + + // Grab the change hint from Servo. + bool wasRestyled; + nsChangeHint changeHint = + static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled)); + + RefPtr<ComputedStyle> upToDateStyleIfRestyled = + wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr; + + // We should really fix the weird primary frame mapping for image maps + // (bug 135040)... + if (styleFrame && styleFrame->GetContent() != aElement) { + MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass()); + styleFrame = nullptr; + } + + // Handle lazy frame construction by posting a reconstruct for any lazily- + // constructed roots. + if (aElement->HasFlag(NODE_NEEDS_FRAME)) { + changeHint |= nsChangeHint_ReconstructFrame; + MOZ_ASSERT(!styleFrame); + } + + if (styleFrame) { + MOZ_ASSERT(primaryFrame); + + nsIFrame* maybeAnonBoxChild; + if (isOutOfFlow) { + maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame(); + } else { + maybeAnonBoxChild = primaryFrame; + // Do not subsume change hints for the column-spanner. + if (!isColumnSpan) { + changeHint = NS_RemoveSubsumedHints( + changeHint, aRestyleState.ChangesHandledFor(styleFrame)); + } + } + + // If the parent wasn't restyled, the styles of our anon box parents won't + // change either. + if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && + maybeAnonBoxChild->ParentIsWrapperAnonBox()) { + aRestyleState.AddPendingWrapperRestyle( + ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild)); + } + + // If we don't have a ::marker pseudo-element, but need it, then + // reconstruct the frame. (The opposite situation implies 'display' + // changes so doesn't need to be handled explicitly here.) + if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() && + styleFrame->IsBlockFrameOrSubclass() && + !nsLayoutUtils::GetMarkerPseudo(aElement)) { + RefPtr<ComputedStyle> pseudoStyle = + aRestyleState.StyleSet().ProbePseudoElementStyle( + *aElement, PseudoStyleType::marker, upToDateStyleIfRestyled); + if (pseudoStyle) { + changeHint |= nsChangeHint_ReconstructFrame; + } + } + } + + // Although we shouldn't generate non-ReconstructFrame hints for elements with + // no frames, we can still get them here if they were explicitly posted by + // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be + // :visited. Skip processing these hints if there is no frame. + if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) && + changeHint) { + aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint); + } + + // If our change hint is reconstruct, we delegate to the frame constructor, + // which consumes the new style and expects the old style to be on the frame. + // + // XXXbholley: We should teach the frame constructor how to clear the dirty + // descendants bit to avoid the traversal here. + if (changeHint & nsChangeHint_ReconstructFrame) { + if (wasRestyled && + StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) { + const bool wasAbsPos = + styleFrame && + styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle(); + auto* newDisp = upToDateStyleIfRestyled->StyleDisplay(); + // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers + // + // We need to do the position check here rather than in + // DidSetComputedStyle because changing position reframes. + // + // We suppress adjustments whenever we change from being display: none to + // be an abspos. + // + // Similarly, for other changes from abspos to non-abspos styles. + // + // TODO(emilio): I _think_ chrome won't suppress adjustments whenever + // `display` changes. But that causes some infinite loops in cases like + // bug 1568778. + if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) { + aRestyleState.AddPendingScrollAnchorSuppression(aElement); + } + } + ClearRestyleStateFromSubtree(aElement); + return true; + } + + // TODO(emilio): We could avoid some refcount traffic here, specially in the + // ComputedStyle case, which uses atomic refcounting. + // + // Hold the ComputedStyle alive, because it could become a dangling pointer + // during the replacement. In practice it's not a huge deal, but better not + // playing with dangling pointers if not needed. + // + // NOTE(emilio): We could keep around the old computed style for display: + // contents elements too, but we don't really need it right now. + RefPtr<ComputedStyle> oldOrDisplayContentsStyle = + styleFrame ? styleFrame->Style() : nullptr; + + MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)), + "display: contents node has a frame, yet we didn't reframe it" + " above?"); + const bool isDisplayContents = !styleFrame && aElement->HasServoData() && + Servo_Element_IsDisplayContents(aElement); + if (isDisplayContents) { + oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement); + } + + Maybe<ServoRestyleState> thisFrameRestyleState; + if (styleFrame) { + auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow + : ServoRestyleState::Type::InFlow; + + thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type); + } + + // We can't really assume as used changes from display: contents elements (or + // other elements without frames). + ServoRestyleState& childrenRestyleState = + thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState; + + ComputedStyle* upToDateStyle = + wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle; + + ServoPostTraversalFlags childrenFlags = + wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled + : ServoPostTraversalFlags::Empty; + + if (wasRestyled && oldOrDisplayContentsStyle) { + MOZ_ASSERT(styleFrame || isDisplayContents); + + // We want to walk all the continuations here, even the ones with different + // styles. In practice, the only reason we get continuations with different + // styles here is ::first-line (::first-letter never affects element + // styles). But in that case, newStyle is the right context for the + // _later_ continuations anyway (the ones not affected by ::first-line), not + // the earlier ones, so there is no point stopping right at the point when + // we'd actually be setting the right ComputedStyle. + // + // This does mean that we may be setting the wrong ComputedStyle on our + // initial continuations; ::first-line fixes that up after the fact. + for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) { + MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0)); + f->SetComputedStyle(upToDateStyle); + } + + if (styleFrame) { + UpdateAdditionalComputedStyles(styleFrame, aRestyleState); + } + + if (!aElement->GetParent()) { + // This is the root. Update styles on the viewport as needed. + ViewportFrame* viewport = + do_QueryFrame(mPresContext->PresShell()->GetRootFrame()); + if (viewport) { + // NB: The root restyle state, not the one for our children! + viewport->UpdateStyle(aRestyleState); + } + } + + // Some changes to animations don't affect the computed style and yet still + // require the layer to be updated. For example, pausing an animation via + // the Web Animations API won't affect an element's style but still + // requires to update the animation on the layer. + // + // We can sometimes reach this when the animated style is being removed. + // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform + // style or not, we need to call it *after* setting |newStyle| to + // |styleFrame| to ensure the animated transform has been removed first. + AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint, + aRestyleState.ChangeList()); + + childrenFlags |= SendA11yNotifications(mPresContext, aElement, + *oldOrDisplayContentsStyle, + *upToDateStyle, aFlags); + } + + const bool traverseElementChildren = + aElement->HasAnyOfFlags(Element::kAllServoDescendantBits); + const bool traverseTextChildren = + wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES); + bool recreatedAnyContext = wasRestyled; + if (traverseElementChildren || traverseTextChildren) { + StyleChildrenIterator it(aElement); + TextPostTraversalState textState(*aElement, upToDateStyle, + isDisplayContents && wasRestyled, + childrenRestyleState); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + if (traverseElementChildren && n->IsElement()) { + recreatedAnyContext |= ProcessPostTraversal( + n->AsElement(), childrenRestyleState, childrenFlags); + } else if (traverseTextChildren && n->IsText()) { + recreatedAnyContext |= ProcessPostTraversalForText( + n, textState, childrenRestyleState, childrenFlags); + } + } + } + + // We want to update frame pseudo-element styles after we've traversed our + // kids, because some of those updates (::first-line/::first-letter) need to + // modify the styles of the kids, and the child traversal above would just + // clobber those modifications. + if (styleFrame) { + if (wasRestyled) { + // Make sure to update anon boxes and pseudo bits after updating text, + // otherwise ProcessPostTraversalForText could clobber first-letter + // styles, for example. + styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState); + } + // Process anon box wrapper frames before ::first-line bits, but _after_ + // owned anon boxes, since the children wrapper anon boxes could be + // inheriting from our own owned anon boxes. + childrenRestyleState.ProcessWrapperRestyles(styleFrame); + if (wasRestyled) { + UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState); + } else if (traverseElementChildren && + styleFrame->IsBlockFrameOrSubclass()) { + // Even if we were not restyled, if we're a block with a first-line and + // one of our descendant elements which is on the first line was restyled, + // we need to update the styles of things on the first line, because + // they're wrong now. + // + // FIXME(bz) Could we do better here? For example, could we keep track of + // frames that are "block with a ::first-line so we could avoid + // IsFrameOfType() and digging about for the first-line frame if not? + // Could we keep track of whether the element children we actually restyle + // are affected by first-line? Something else? Bug 1385443 tracks making + // this better. + nsIFrame* firstLineFrame = + static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame(); + if (firstLineFrame) { + for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { + ReparentComputedStyleForFirstLine(kid); + } + } + } + } + + aElement->UnsetFlags(Element::kAllServoDescendantBits); + return recreatedAnyContext; +} + +bool RestyleManager::ProcessPostTraversalForText( + nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState, + ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) { + // Handle lazy frame construction. + if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) { + aPostTraversalState.ChangeList().AppendChange( + nullptr, aTextNode, nsChangeHint_ReconstructFrame); + return true; + } + + // Handle restyle. + nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame(); + if (!primaryFrame) { + return false; + } + + // If the parent wasn't restyled, the styles of our anon box parents won't + // change either. + if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) && + primaryFrame->ParentIsWrapperAnonBox()) { + aRestyleState.AddPendingWrapperRestyle( + ServoRestyleState::TableAwareParentFor(primaryFrame)); + } + + ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode); + aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle); + + // We want to walk all the continuations here, even the ones with different + // styles. In practice, the only reasons we get continuations with different + // styles are ::first-line and ::first-letter. But in those cases, + // newStyle is the right context for the _later_ continuations anyway (the + // ones not affected by ::first-line/::first-letter), not the earlier ones, + // so there is no point stopping right at the point when we'd actually be + // setting the right ComputedStyle. + // + // This does mean that we may be setting the wrong ComputedStyle on our + // initial continuations; ::first-line/::first-letter fix that up after the + // fact. + for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) { + f->SetComputedStyle(&newStyle); + } + + return true; +} + +void RestyleManager::ClearSnapshots() { + for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) { + iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT); + iter.Remove(); + } +} + +ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) { + MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh); + + // NOTE(emilio): We can handle snapshots from a one-off restyle of those that + // we do to restyle stuff for reconstruction, for example. + // + // It seems to be the case that we always flush in between that happens and + // the next attribute change, so we can assert that we haven't handled the + // snapshot here yet. If this assertion didn't hold, we'd need to unset that + // flag from here too. + // + // Can't wait to make ProcessPendingRestyles the only entry-point for styling, + // so this becomes much easier to reason about. Today is not that day though. + MOZ_ASSERT(aElement.HasServoData()); + MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT)); + + ServoElementSnapshot* snapshot = + mSnapshots.GetOrInsertNew(&aElement, aElement); + aElement.SetFlags(ELEMENT_HAS_SNAPSHOT); + + // Now that we have a snapshot, make sure a restyle is triggered. + aElement.NoteDirtyForServo(); + return *snapshot; +} + +void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) { + nsPresContext* presContext = PresContext(); + PresShell* presShell = presContext->PresShell(); + + MOZ_ASSERT(presContext->Document(), "No document? Pshaw!"); + // FIXME(emilio): In the "flush animations" case, ideally, we should only + // recascade animation styles running on the compositor, so we shouldn't care + // about other styles, or new rules that apply to the page... + // + // However, that's not true as of right now, see bug 1388031 and bug 1388692. + MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) || + !presContext->HasPendingMediaQueryUpdates(), + "Someone forgot to update media queries?"); + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!"); + MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?"); + + if (MOZ_UNLIKELY(!presShell->DidInitialize())) { + // PresShell::FlushPendingNotifications doesn't early-return in the case + // where the PresShell hasn't yet been initialized (and therefore we haven't + // yet done the initial style traversal of the DOM tree). We should arguably + // fix up the callers and assert against this case, but we just detect and + // handle it for now. + return; + } + + // It'd be bad! + PresShell::AutoAssertNoFlush noReentrantFlush(*presShell); + + // Create a AnimationsWithDestroyedFrame during restyling process to + // stop animations and transitions on elements that have no frame at the end + // of the restyling process. + AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this); + + ServoStyleSet* styleSet = StyleSet(); + Document* doc = presContext->Document(); + + // Ensure the refresh driver is active during traversal to avoid mutating + // mActiveTimer and mMostRecentRefresh time. + presContext->RefreshDriver()->MostRecentRefresh(); + + if (!doc->GetServoRestyleRoot()) { + // This might post new restyles, so need to do it here. Don't do it if we're + // already going to restyle tho, so that we don't potentially reflow with + // dirty styling. + presContext->UpdateContainerQueryStyles(); + presContext->FinishedContainerQueryUpdate(); + } + + // Perform the Servo traversal, and the post-traversal if required. We do this + // in a loop because certain rare paths in the frame constructor can trigger + // additional style invalidations. + // + // FIXME(emilio): Confirm whether that's still true now that XBL is gone. + mInStyleRefresh = true; + if (mHaveNonAnimationRestyles) { + ++mAnimationGeneration; + } + + if (mRestyleForCSSRuleChanges) { + aFlags |= ServoTraversalFlags::ForCSSRuleChanges; + } + + while (styleSet->StyleDocument(aFlags)) { + ClearSnapshots(); + + // Select scroll anchors for frames that have been scrolled. Do this + // before processing restyled frames so that anchor nodes are correctly + // marked when directly moving frames with RecomputePosition. + presContext->PresShell()->FlushPendingScrollAnchorSelections(); + + nsStyleChangeList currentChanges; + bool anyStyleChanged = false; + + // Recreate styles , and queue up change hints (which also handle lazy frame + // construction). + nsTArray<RefPtr<Element>> anchorsToSuppress; + + { + AutoRestyleTimelineMarker marker(presContext->GetDocShell(), false); + DocumentStyleRootIterator iter(doc->GetServoRestyleRoot()); + while (Element* root = iter.GetNextStyleRoot()) { + nsTArray<nsIFrame*> wrappersToRestyle; + ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle, + anchorsToSuppress); + ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty; + anyStyleChanged |= ProcessPostTraversal(root, state, flags); + } + + // We want to suppress adjustments the current (before-change) scroll + // anchor container now, and save a reference to the content node so that + // we can suppress them in the after-change scroll anchor . + for (Element* element : anchorsToSuppress) { + if (nsIFrame* frame = element->GetPrimaryFrame()) { + if (auto* container = ScrollAnchorContainer::FindFor(frame)) { + container->SuppressAdjustments(); + } + } + } + } + + doc->ClearServoRestyleRoot(); + ClearSnapshots(); + + // Process the change hints. + // + // Unfortunately, the frame constructor can generate new change hints while + // processing existing ones. We redirect those into a secondary queue and + // iterate until there's nothing left. + { + AutoTimelineMarker marker(presContext->GetDocShell(), + "StylesApplyChanges"); + ReentrantChangeList newChanges; + mReentrantChanges = &newChanges; + while (!currentChanges.IsEmpty()) { + ProcessRestyledFrames(currentChanges); + MOZ_ASSERT(currentChanges.IsEmpty()); + for (ReentrantChange& change : newChanges) { + if (!(change.mHint & nsChangeHint_ReconstructFrame) && + !change.mContent->GetPrimaryFrame()) { + // SVG Elements post change hints without ensuring that the primary + // frame will be there after that (see bug 1366142). + // + // Just ignore those, since we can't really process them. + continue; + } + currentChanges.AppendChange(change.mContent->GetPrimaryFrame(), + change.mContent, change.mHint); + } + newChanges.Clear(); + } + mReentrantChanges = nullptr; + } + + // Suppress adjustments in the after-change scroll anchors if needed, now + // that we're done reframing everything. + for (Element* element : anchorsToSuppress) { + if (nsIFrame* frame = element->GetPrimaryFrame()) { + if (auto* container = ScrollAnchorContainer::FindFor(frame)) { + container->SuppressAdjustments(); + } + } + } + + if (anyStyleChanged) { + // Maybe no styles changed when: + // + // * Only explicit change hints were posted in the first place. + // * When an attribute or state change in the content happens not to need + // a restyle after all. + // + // In any case, we don't need to increment the restyle generation in that + // case. + IncrementRestyleGeneration(); + } + + mInStyleRefresh = false; + presContext->UpdateContainerQueryStyles(); + mInStyleRefresh = true; + } + + doc->ClearServoRestyleRoot(); + presContext->FinishedContainerQueryUpdate(); + ClearSnapshots(); + styleSet->AssertTreeIsClean(); + + mHaveNonAnimationRestyles = false; + mRestyleForCSSRuleChanges = false; + mInStyleRefresh = false; + + // Now that everything has settled, see if we have enough free rule nodes in + // the tree to warrant sweeping them. + styleSet->MaybeGCRuleTree(); + + // Note: We are in the scope of |animationsWithDestroyedFrame|, so + // |mAnimationsWithDestroyedFrame| is still valid. + MOZ_ASSERT(mAnimationsWithDestroyedFrame); + mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames(); +} + +#ifdef DEBUG +static void VerifyFlatTree(const nsIContent& aContent) { + StyleChildrenIterator iter(&aContent); + + for (auto* content = iter.GetNextChild(); content; + content = iter.GetNextChild()) { + MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent); + VerifyFlatTree(*content); + } +} +#endif + +void RestyleManager::ProcessPendingRestyles() { + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT); +#ifdef DEBUG + if (auto* root = mPresContext->Document()->GetRootElement()) { + VerifyFlatTree(*root); + } +#endif + + DoProcessPendingRestyles(ServoTraversalFlags::Empty); +} + +void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() { + if (mSnapshots.IsEmpty()) { + return; + } + for (const auto& key : mSnapshots.Keys()) { + // Servo data for the element might have been dropped. (e.g. by removing + // from its document) + if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) { + Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots); + } + } + ClearSnapshots(); +} + +void RestyleManager::UpdateOnlyAnimationStyles() { + bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates(); + if (!doCSS) { + return; + } + + DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations); +} + +void RestyleManager::ElementStateChanged(Element* aElement, + ElementState aChangedBits) { +#ifdef EARLY_BETA_OR_EARLIER + if (MOZ_UNLIKELY(mInStyleRefresh)) { + MOZ_CRASH_UNSAFE_PRINTF( + "Element state change during style refresh (%" PRIu64 ")", + aChangedBits.GetInternalValue()); + } +#endif + + const ElementState kVisitedAndUnvisited = + ElementState::VISITED | ElementState::UNVISITED; + + // We'll restyle when the relevant visited query finishes, regardless of the + // style (see Link::VisitedQueryFinished). So there's no need to do anything + // as a result of this state change just yet. + // + // Note that this check checks for _both_ bits: This is only true when visited + // changes to unvisited or vice-versa, but not when we start or stop being a + // link itself. + if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) { + aChangedBits &= ~kVisitedAndUnvisited; + if (aChangedBits.IsEmpty()) { + return; + } + } + + if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) { + Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint); + } + + // Don't bother taking a snapshot if no rules depend on these state bits. + // + // We always take a snapshot for the LTR/RTL event states, since Servo doesn't + // track those bits in the same way, and we know that :dir() rules are always + // present in UA style sheets. + if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) && + !StyleSet()->HasStateDependency(*aElement, aChangedBits)) { + return; + } + + // Assuming we need to invalidate cached style in getComputedStyle for + // undisplayed elements, since we don't know if it is needed. + IncrementUndisplayedRestyleGeneration(); + + if (!aElement->HasServoData()) { + return; + } + + ServoElementSnapshot& snapshot = SnapshotFor(*aElement); + ElementState previousState = aElement->StyleState() ^ aChangedBits; + snapshot.AddState(previousState); + + MaybeRestyleForNthOfState(*StyleSet(), aElement, aChangedBits); +} + +void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet, + Element* aChild, + ElementState aChangedBits) { + const auto* parentNode = aChild->GetParentNode(); + MOZ_ASSERT(parentNode); + const auto parentFlags = parentNode->GetFlags(); + if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) { + return; + } + + if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) { + RestyleSiblings(aChild, parentFlags); + } +} + +static inline bool AttributeInfluencesOtherPseudoClassState( + const Element& aElement, const nsAtom* aAttribute) { + // We must record some state for :-moz-browser-frame, + // :-moz-table-border-nonzero, and :-moz-select-list-box. + if (aAttribute == nsGkAtoms::mozbrowser) { + return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame); + } + + if (aAttribute == nsGkAtoms::border) { + return aElement.IsHTMLElement(nsGkAtoms::table); + } + + if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) { + return aElement.IsHTMLElement(nsGkAtoms::select); + } + + return false; +} + +static inline bool NeedToRecordAttrChange( + const ServoStyleSet& aStyleSet, const Element& aElement, + int32_t aNameSpaceID, nsAtom* aAttribute, + bool* aInfluencesOtherPseudoClassState) { + *aInfluencesOtherPseudoClassState = + AttributeInfluencesOtherPseudoClassState(aElement, aAttribute); + + // If the attribute influences one of the pseudo-classes that are backed by + // attributes, we just record it. + if (*aInfluencesOtherPseudoClassState) { + return true; + } + + // We assume that id and class attributes are used in class/id selectors, and + // thus record them. + // + // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet, + // presumably we could try to filter the old and new id, but it's not clear + // it's worth it. + if (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) { + return true; + } + + // We always record lang="", even though we force a subtree restyle when it + // changes, since it can change how its siblings match :lang(..) due to + // selectors like :lang(..) + div. + if (aAttribute == nsGkAtoms::lang) { + return true; + } + + // Otherwise, just record the attribute change if a selector in the page may + // reference it from an attribute selector. + return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute); +} + +void RestyleManager::AttributeWillChange(Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType) { + TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute); +} + +void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) { + TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None, + nsGkAtoms::_class); +} + +void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute) { + MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh); + + bool influencesOtherPseudoClassState; + if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute, + &influencesOtherPseudoClassState)) { + return; + } + + // We cannot tell if the attribute change will affect the styles of + // undisplayed elements, because we don't actually restyle those elements + // during the restyle traversal. So just assume that the attribute change can + // cause the style to change. + IncrementUndisplayedRestyleGeneration(); + + if (!aElement.HasServoData()) { + return; + } + + // Some other random attribute changes may also affect the transitions, + // so we also set this true here. + mHaveNonAnimationRestyles = true; + + ServoElementSnapshot& snapshot = SnapshotFor(aElement); + snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute); + + if (influencesOtherPseudoClassState) { + snapshot.AddOtherPseudoClassState(aElement); + } +} + +// For some attribute changes we must restyle the whole subtree: +// +// * <td> is affected by the cellpadding on its ancestor table +// * lang="" and xml:lang="" can affect all descendants due to :lang() +// * exportparts can affect all descendant parts. We could certainly integrate +// it better in the invalidation machinery if it was necessary. +static inline bool AttributeChangeRequiresSubtreeRestyle( + const Element& aElement, nsAtom* aAttr) { + if (aAttr == nsGkAtoms::cellpadding) { + return aElement.IsHTMLElement(nsGkAtoms::table); + } + // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for exportparts + // attribute changes? + if (aAttr == nsGkAtoms::exportparts) { + return !!aElement.GetShadowRoot(); + } + return aAttr == nsGkAtoms::lang; +} + +void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + MOZ_ASSERT(!mInStyleRefresh); + + auto changeHint = nsChangeHint(0); + auto restyleHint = RestyleHint{0}; + + changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType); + + MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue); + + if (aAttribute == nsGkAtoms::style) { + restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE; + } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) { + restyleHint |= RestyleHint::RestyleSubtree(); + } else if (aElement->IsAttributeMapped(aAttribute)) { + // FIXME(emilio): Does this really need to re-selector-match? + restyleHint |= RestyleHint::RESTYLE_SELF; + } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) { + // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part + // attribute changes? + restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS; + } + + if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) { + // See if we have appearance information for a theme. + StyleAppearance appearance = + primaryFrame->StyleDisplay()->EffectiveAppearance(); + if (appearance != StyleAppearance::None) { + nsITheme* theme = PresContext()->Theme(); + if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) { + bool repaint = false; + theme->WidgetStateChanged(primaryFrame, appearance, aAttribute, + &repaint, aOldValue); + if (repaint) { + changeHint |= nsChangeHint_RepaintFrame; + } + } + } + + primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); + } + + if (restyleHint || changeHint) { + Servo_NoteExplicitHints(aElement, restyleHint, changeHint); + } + + if (restyleHint) { + // Assuming we need to invalidate cached style in getComputedStyle for + // undisplayed elements, since we don't know if it is needed. + IncrementUndisplayedRestyleGeneration(); + + // If we change attributes, we have to mark this to be true, so we will + // increase the animation generation for the new created transition if any. + mHaveNonAnimationRestyles = true; + } +} + +void RestyleManager::RestyleSiblings( + Element* aChild, nsBaseContentList::FlagsType aParentFlags) { + const DebugOnly<nsINode*> parentNode = aChild->GetParentNode(); + MOZ_ASSERT(parentNode->IsElement() || parentNode->IsShadowRoot()); + + DebugOnly<bool> restyledSiblings = false; + // NODE_HAS_SLOW_SELECTOR typically indicates restyling the parent, but since + // we know we're restyling for :nth-last-child(.. of <selector>), we can + // restyle only previous siblings without under-invalidating. + if (aParentFlags & NODE_HAS_SLOW_SELECTOR) { + RestylePreviousSiblings(aChild->GetPreviousSibling()); + restyledSiblings = true; + } + if (aParentFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { + RestyleSiblingsStartingWith(aChild->GetNextSibling()); + restyledSiblings = true; + } + MOZ_ASSERT(restyledSiblings, + "How can we restyle siblings without a slow selector flag?"); +} + +void RestyleManager::MaybeRestyleForNthOfAttribute( + Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) { + const auto* parentNode = aChild->GetParentNode(); + MOZ_ASSERT(parentNode); + const auto parentFlags = parentNode->GetFlags(); + if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) { + return; + } + if (!aChild->HasServoData()) { + return; + } + + bool mightHaveNthOfDependency; + auto& styleSet = *StyleSet(); + if (aAttribute == nsGkAtoms::id) { + auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom + ? aOldValue->GetAtomValue() + : nullptr; + mightHaveNthOfDependency = + styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID()); + } else if (aAttribute == nsGkAtoms::_class) { + mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild); + } else { + mightHaveNthOfDependency = + styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute); + } + + if (mightHaveNthOfDependency) { + RestyleSiblings(aChild, parentFlags); + } +} + +void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) { + // This is only called when moving frames in or out of the first-line + // pseudo-element (or one of its descendants). We can't say much about + // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into + // something inside an inline-block on the first line the ancestors could be + // totally arbitrary), but we will definitely find a line frame on the + // ancestor chain. Note that the lineframe may not actually be the one that + // corresponds to ::first-line; when we're moving _out_ of the ::first-line it + // will be one of the continuations instead. +#ifdef DEBUG + { + nsIFrame* f = aFrame->GetParent(); + while (f && !f->IsLineFrame()) { + f = f->GetParent(); + } + MOZ_ASSERT(f, "Must have found a first-line frame"); + } +#endif + + DoReparentComputedStyleForFirstLine(aFrame, *StyleSet()); +} + +void RestyleManager::DoReparentComputedStyleForFirstLine( + nsIFrame* aFrame, ServoStyleSet& aStyleSet) { + if (aFrame->IsBackdropFrame()) { + // Style context of backdrop frame has no parent style, and thus we do not + // need to reparent it. + return; + } + + if (aFrame->IsPlaceholderFrame()) { + // Also reparent the out-of-flow and all its continuations. We're doing + // this to match Gecko for now, but it's not clear that this behavior is + // correct per spec. It's certainly pretty odd for out-of-flows whose + // containing block is not within the first line. + // + // Right now we're somewhat inconsistent in this testcase: + // + // <style> + // div { color: orange; clear: left; } + // div::first-line { color: blue; } + // </style> + // <div> + // <span style="float: left">What color is this text?</span> + // </div> + // <div> + // <span><span style="float: left">What color is this text?</span></span> + // </div> + // + // We make the first float orange and the second float blue. On the other + // hand, if the float were within an inline-block that was on the first + // line, arguably it _should_ inherit from the ::first-line... + nsIFrame* outOfFlow = + nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); + MOZ_ASSERT(outOfFlow, "no out-of-flow frame"); + for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) { + DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet); + } + } + + // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try + // to remove it? + nsIFrame* providerFrame; + ComputedStyle* newParentStyle = + aFrame->GetParentComputedStyle(&providerFrame); + // If our provider is our child, we want to reparent it first, because we + // inherit style from it. + bool isChild = providerFrame && providerFrame->GetParent() == aFrame; + nsIFrame* providerChild = nullptr; + if (isChild) { + DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet); + // Get the style again after ReparentComputedStyle() which might have + // changed it. + newParentStyle = providerFrame->Style(); + providerChild = providerFrame; + MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), + "Out of flow provider?"); + } + + if (!newParentStyle) { + // No need to do anything here for this frame, but we should still reparent + // its descendants, because those may have styles that inherit from the + // parent of this frame (e.g. non-anonymous columns in an anonymous + // colgroup). + MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(), + "Why did this frame not end up with a parent context?"); + ReparentFrameDescendants(aFrame, providerChild, aStyleSet); + return; + } + + bool isElement = aFrame->GetContent()->IsElement(); + + // We probably don't want to initiate transitions from ReparentComputedStyle, + // since we call it during frame construction rather than in response to + // dynamic changes. + // Also see the comment at the start of + // nsTransitionManager::ConsiderInitiatingTransition. + // + // We don't try to do the fancy copying from previous continuations that + // GeckoRestyleManager does here, because that relies on knowing the parents + // of ComputedStyles, and we don't know those. + ComputedStyle* oldStyle = aFrame->Style(); + Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr; + ComputedStyle* newParent = newParentStyle; + + ComputedStyle* newParentIgnoringFirstLine; + if (newParent->GetPseudoType() == PseudoStyleType::firstLine) { + MOZ_ASSERT( + providerFrame && providerFrame->GetParent()->IsBlockFrameOrSubclass(), + "How could we get a ::first-line parent style without having " + "a ::first-line provider frame?"); + // If newParent is a ::first-line style, get the parent blockframe, and then + // correct it for our pseudo as needed (e.g. stepping out of anon boxes). + // Use the resulting style for the "parent style ignoring ::first-line". + nsIFrame* blockFrame = providerFrame->GetParent(); + nsIFrame* correctedFrame = nsIFrame::CorrectStyleParentFrame( + blockFrame, oldStyle->GetPseudoType()); + newParentIgnoringFirstLine = correctedFrame->Style(); + } else { + newParentIgnoringFirstLine = newParent; + } + + if (!providerFrame) { + // No providerFrame means we inherited from a display:contents thing. Our + // layout parent style is the style of our nearest ancestor frame. But we + // have to be careful to do that with our placeholder, not with us, if we're + // out of flow. + if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { + aFrame->FirstContinuation() + ->GetPlaceholderFrame() + ->GetLayoutParentStyleForOutOfFlow(&providerFrame); + } else { + providerFrame = nsIFrame::CorrectStyleParentFrame( + aFrame->GetParent(), oldStyle->GetPseudoType()); + } + } + ComputedStyle* layoutParent = providerFrame->Style(); + + RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle( + oldStyle, newParent, newParentIgnoringFirstLine, layoutParent, + ourElement); + aFrame->SetComputedStyle(newStyle); + + // This logic somewhat mirrors the logic in + // RestyleManager::ProcessPostTraversal. + if (isElement) { + // We can't use UpdateAdditionalComputedStyles as-is because it needs a + // ServoRestyleState and maintaining one of those during a _frametree_ + // traversal is basically impossible. + uint32_t index = 0; + while (auto* oldAdditionalStyle = + aFrame->GetAdditionalComputedStyle(index)) { + RefPtr<ComputedStyle> newAdditionalContext = + aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle, + newStyle, newStyle, nullptr); + aFrame->SetAdditionalComputedStyle(index, newAdditionalContext); + ++index; + } + } + + // Generally, owned anon boxes are our descendants. The only exceptions are + // tables (for the table wrapper) and inline frames (for the block part of the + // block-in-inline split). We're going to update our descendants when looping + // over kids, and we don't want to update the block part of a block-in-inline + // split if the inline is on the first line but the block is not (and if the + // block is, it's the child of something else on the first line and will get + // updated as a child). And given how this method ends up getting called, if + // we reach here for a table frame, we are already in the middle of + // reparenting the table wrapper frame. So no need to + // UpdateStyleOfOwnedAnonBoxes() here. + + ReparentFrameDescendants(aFrame, providerChild, aStyleSet); + + // We do not need to do the equivalent of UpdateFramePseudoElementStyles, + // because those are handled by our descendant walk. +} + +void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame, + nsIFrame* aProviderChild, + ServoStyleSet& aStyleSet) { + if (aFrame->GetContent()->IsElement() && + !aFrame->GetContent()->AsElement()->HasServoData()) { + // We're getting into a display: none subtree, avoid reparenting into stuff + // that is going to go away anyway in seconds. + return; + } + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + // only do frames that are in flow + if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && + child != aProviderChild) { + DoReparentComputedStyleForFirstLine(child, aStyleSet); + } + } + } +} + +} // namespace mozilla |