summaryrefslogtreecommitdiffstats
path: root/layout/base/RestyleManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/RestyleManager.cpp')
-rw-r--r--layout/base/RestyleManager.cpp3788
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