/* -*- 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/ServoRestyleManager.h" #include "mozilla/AutoRestyleTimelineMarker.h" #include "mozilla/AutoTimelineMarker.h" #include "mozilla/DocumentStyleRootIterator.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/ServoStyleContext.h" #include "mozilla/ServoStyleContextInlines.h" #include "mozilla/Unused.h" #include "mozilla/ViewportFrame.h" #include "mozilla/dom/ChildIterator.h" #include "mozilla/dom/ElementInlines.h" #include "nsBlockFrame.h" #include "nsBulletFrame.h" #include "nsPlaceholderFrame.h" #include "nsContentUtils.h" #include "nsCSSFrameConstructor.h" #include "nsPrintfCString.h" #include "nsRefreshDriver.h" #include "nsStyleChangeList.h" using namespace mozilla::dom; namespace mozilla { #ifdef DEBUG static bool IsAnonBox(const nsIFrame& aFrame) { return aFrame.StyleContext()->IsAnonBox(); } static const nsIFrame* FirstContinuationOrPartOfIBSplit(const nsIFrame* aFrame) { if (!aFrame) { return nullptr; } return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); } static const nsIFrame* ExpectedOwnerForChild(const nsIFrame& aFrame) { if (IsAnonBox(aFrame) && !aFrame.IsTextFrame()) { return aFrame.GetParent()->IsViewportFrame() ? nullptr : aFrame.GetParent(); } if (aFrame.IsBulletFrame()) { return aFrame.GetParent(); } 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 aFrame.GetParent(); } const nsIFrame* parent = FirstContinuationOrPartOfIBSplit(aFrame.GetParent()); if (aFrame.IsTableFrame()) { MOZ_ASSERT(parent->IsTableWrapperFrame()); parent = FirstContinuationOrPartOfIBSplit(parent->GetParent()); } // We've handled already anon boxes and bullet frames, 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->StyleContext()->GetPseudo(); if (pseudo == nsCSSAnonBoxes::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 { parent = parent->GetParent(); } parent = FirstContinuationOrPartOfIBSplit(parent); } return parent; } void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const { MOZ_ASSERT(mOwner); MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); // We allow aParent.mOwner to be null, for cases when we're not starting at // the root of the tree. MOZ_ASSERT_IF(aParent.mOwner, ExpectedOwnerForChild(*mOwner) == aParent.mOwner); } nsChangeHint ServoRestyleState::ChangesHandledFor(const nsIFrame& aFrame) const { if (!mOwner) { MOZ_ASSERT(!mChangesHandled); return mChangesHandled; } MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame), "Missed some frame in the hierarchy?"); return mChangesHandled; } #endif ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext) : RestyleManager(StyleBackendType::Servo, aPresContext) , mReentrantChanges(nullptr) { } void ServoRestyleManager::PostRestyleEvent(Element* aElement, nsRestyleHint 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 == 0 && !aMinChangeHint) { 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) { 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 & ~eRestyle_AllHintsWithAnimations) { mHaveNonAnimationRestyles = true; } if (aRestyleHint & eRestyle_LaterSiblings) { aRestyleHint &= ~eRestyle_LaterSiblings; nsRestyleHint siblingHint = eRestyle_Subtree; Element* current = aElement->GetNextElementSibling(); while (current) { Servo_NoteExplicitHints(current, siblingHint, nsChangeHint(0)); current = current->GetNextElementSibling(); } } if (aRestyleHint || aMinChangeHint) { Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint); } } void ServoRestyleManager::PostRestyleEventForCSSRuleChanges() { mRestyleForCSSRuleChanges = true; mPresContext->PresShell()->EnsureStyleFlush(); } /* static */ void ServoRestyleManager::PostRestyleEventForAnimations( Element* aElement, CSSPseudoElementType aPseudoType, nsRestyleHint aRestyleHint) { Element* elementToRestyle = EffectCompositor::GetElementToRestyle(aElement, aPseudoType); if (!elementToRestyle) { // FIXME: Bug 1371107: When reframing happens, // EffectCompositor::mElementsToRestyle still has unbinded old pseudo // element. We should drop it. return; } Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0)); } void ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint) { // NOTE(emilio): GeckoRestlyeManager does a sync style flush, which seems not // to be needed in my testing. PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint); } void ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint, nsRestyleHint 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. 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 ServoRestyleManager::ClearServoDataFromSubtree(Element* aElement) { if (!aElement->HasServoData()) { MOZ_ASSERT(!aElement->HasDirtyDescendantsForServo()); MOZ_ASSERT(!aElement->HasAnimationOnlyDirtyDescendantsForServo()); return; } StyleChildrenIterator it(aElement); for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { if (n->IsElement()) { ClearServoDataFromSubtree(n->AsElement()); } } aElement->ClearServoData(); aElement->UnsetHasDirtyDescendantsForServo(); aElement->UnsetHasAnimationOnlyDirtyDescendantsForServo(); } /* static */ void ServoRestyleManager::ClearRestyleStateFromSubtree(Element* aElement) { if (aElement->HasDirtyDescendantsForServo() || aElement->HasAnimationOnlyDirtyDescendantsForServo()) { StyleChildrenIterator it(aElement); for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { if (n->IsElement()) { ClearRestyleStateFromSubtree(n->AsElement()); } } } bool wasRestyled; Unused << Servo_TakeChangeHint(aElement, ServoTraversalFlags::Empty, &wasRestyled); aElement->UnsetHasDirtyDescendantsForServo(); aElement->UnsetHasAnimationOnlyDirtyDescendantsForServo(); aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES); } /** * 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 ServoRestyleManager::TextPostTraversalState { public: TextPostTraversalState(ServoStyleContext& aParentContext, bool aDisplayContentsParentStyleChanged, ServoRestyleState& aParentRestyleState) : mParentContext(aParentContext) , mParentRestyleState(aParentRestyleState) , mStyle(nullptr) , mShouldPostHints(aDisplayContentsParentStyleChanged) , mShouldComputeHints(aDisplayContentsParentStyleChanged) , mComputedHint(nsChangeHint_Empty) {} nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); } nsStyleContext& ComputeStyle(nsIContent* aTextNode) { if (!mStyle) { mStyle = mParentRestyleState.StyleSet().ResolveStyleForText( aTextNode, &mParentContext); } MOZ_ASSERT(mStyle); return *mStyle; } void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame, nsStyleContext& aNewContext) { MOZ_ASSERT(aTextFrame); MOZ_ASSERT(aNewContext.GetPseudo() == nsCSSAnonBoxes::mozText); if (MOZ_LIKELY(!mShouldPostHints)) { return; } ServoStyleContext* oldContext = aTextFrame->StyleContext()->AsServo(); MOZ_ASSERT(oldContext->GetPseudo() == nsCSSAnonBoxes::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, samePointerStructs; mComputedHint = oldContext->CalcStyleDifference(&aNewContext, &equalStructs, &samePointerStructs); mComputedHint = NS_RemoveSubsumedHints( mComputedHint, mParentRestyleState.ChangesHandledFor(*aTextFrame)); } if (mComputedHint) { mParentRestyleState.ChangeList().AppendChange( aTextFrame, aContent, mComputedHint); } } private: ServoStyleContext& mParentContext; ServoRestyleState& mParentRestyleState; RefPtr mStyle; bool mShouldPostHints; bool mShouldComputeHints; nsChangeHint mComputedHint; }; // Get the nsBlockFrame which might contain ::first-letter/::first-line for the // given element. Will return null if there is no such blockframe. static nsBlockFrame* GetBlockForElement(const Element* aElement) { nsIFrame* frame = aElement->GetPrimaryFrame(); if (!frame) { return nullptr; } // The first-letter frame will always be inside the content insertion frame, // which will always be a block if we have a first-letter frame at all. frame = frame->GetContentInsertionFrame(); if (!frame) { // We're a leaf; certainly no first-letter frame. return nullptr; } if (!frame->IsFrameOfType(nsIFrame::eBlockFrame)) { return nullptr; } return static_cast(frame); } // Find the first-letter frame for the given element, if any. Returns null to // indicate there isn't one. static nsIFrame* FindFirstLetterFrameForElement(const Element* aElement) { nsBlockFrame* f = GetBlockForElement(aElement); return f ? f->GetFirstLetter() : nullptr; } // Find the first-line frame for the given element, if any. Returns null to // indicate there isn't one. static nsIFrame* FindFirstLineFrameForElement(const Element* aElement) { nsBlockFrame* f = GetBlockForElement(aElement); return f ? f->GetFirstLineFrame() : nullptr; } static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet, nsStyleChangeList& aChangeList) { const nsStyleDisplay* display = aFrame->StyleContext()->StyleDisplay(); if (display->mTopLayer != NS_STYLE_TOP_LAYER_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(nsIFrame::kBackdropList).FirstChild(); if (!backdropPlaceholder) { return; } MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame()); nsIFrame* backdropFrame = nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder); MOZ_ASSERT(backdropFrame->IsBackdropFrame()); MOZ_ASSERT(backdropFrame->StyleContext()->GetPseudoType() == CSSPseudoElementType::backdrop); RefPtr newContext = aStyleSet.ResolvePseudoElementStyle(aFrame->GetContent()->AsElement(), CSSPseudoElementType::backdrop, aFrame->StyleContext()->AsServo(), /* aPseudoElement = */ nullptr); // 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 frame. MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame()); ServoRestyleState state(aStyleSet, aChangeList); aFrame->UpdateStyleOfOwnedChildFrame(backdropFrame, newContext, state); } static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame, ServoRestyleState& aRestyleState) { 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; while (!block->IsFrameOfType(nsIFrame::eBlockFrame)) { block = block->GetParent(); } static_cast(block->FirstContinuation())-> UpdateFirstLetterStyle(aRestyleState); } static void UpdateOneAdditionalStyleContext(nsIFrame* aFrame, uint32_t aIndex, ServoStyleContext& aOldContext, ServoRestyleState& aRestyleState) { auto pseudoType = aOldContext.GetPseudoType(); MOZ_ASSERT(pseudoType != CSSPseudoElementType::NotPseudo); MOZ_ASSERT( !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType)); RefPtr newContext = aRestyleState.StyleSet().ResolvePseudoElementStyle( aFrame->GetContent()->AsElement(), pseudoType, aFrame->StyleContext()->AsServo(), /* aPseudoElement = */ nullptr); uint32_t equalStructs, samePointerStructs; // Not used, actually. nsChangeHint childHint = aOldContext.CalcStyleDifference( newContext, &equalStructs, &samePointerStructs); if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { childHint = NS_RemoveSubsumedHints( childHint, aRestyleState.ChangesHandledFor(*aFrame)); } if (childHint) { aRestyleState.ChangeList().AppendChange( aFrame, aFrame->GetContent(), childHint); } aFrame->SetAdditionalStyleContext(aIndex, newContext); } static void UpdateAdditionalStyleContexts(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* oldContext = aFrame->GetAdditionalStyleContext(index)) { UpdateOneAdditionalStyleContext( aFrame, index++, *oldContext->AsServo(), aRestyleState); } } static void UpdateFramePseudoElementStyles(nsIFrame* aFrame, ServoRestyleState& aRestyleState) { // first-letter needs to be updated before first-line, because first-line can // change the style of the first-letter. UpdateFirstLetterIfNeeded(aFrame, aRestyleState); if (aFrame->IsFrameOfType(nsIFrame::eBlockFrame)) { static_cast(aFrame)->UpdatePseudoElementStyles(aRestyleState); } UpdateBackdropIfNeeded( aFrame, aRestyleState.StyleSet(), aRestyleState.ChangeList()); } static inline bool NeedsToTraverseElementChildren(const Element& aParent, ServoTraversalFlags aFlags) { if (aParent.HasAnimationOnlyDirtyDescendantsForServo()) { return true; } if (!(aFlags & ServoTraversalFlags::AnimationOnly)) { return aParent.HasDirtyDescendantsForServo() || aParent.HasFlag(NODE_DESCENDANTS_NEED_FRAMES); } return false; } bool ServoRestyleManager::ProcessPostTraversal( Element* aElement, ServoStyleContext* aParentContext, ServoRestyleState& aRestyleState, ServoTraversalFlags aFlags) { nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement); // 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 = aElement->GetPrimaryFrame() && aElement->GetPrimaryFrame()->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW); // Grab the change hint from Servo. // In case of flushing throttled animations, any restyle hints other than // animations are preserved since they are the hints which will be processed // in normal restyle later. bool wasRestyled; nsChangeHint changeHint = Servo_TakeChangeHint(aElement, aFlags, &wasRestyled); // We should really fix the weird primary frame mapping for image maps // (bug 135040)... if (styleFrame && styleFrame->GetContent() != aElement) { MOZ_ASSERT(styleFrame->IsImageFrame()); 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 && !isOutOfFlow) { changeHint = NS_RemoveSubsumedHints( changeHint, aRestyleState.ChangesHandledFor(*styleFrame)); } // 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) { ClearRestyleStateFromSubtree(aElement); return true; } // TODO(emilio): We could avoid some refcount traffic here, specially in the // ServoStyleContext case, which uses atomic refcounting. // // Hold the old style context 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. RefPtr oldStyleContext = styleFrame ? styleFrame->StyleContext()->AsServo() : nullptr; UndisplayedNode* displayContentsNode = nullptr; // FIXME(emilio, bug 1303605): This can be simpler for Servo. // Note that we intentionally don't check for display: none content. if (!oldStyleContext) { displayContentsNode = PresContext()->FrameConstructor()->GetDisplayContentsNodeFor(aElement); if (displayContentsNode) { oldStyleContext = displayContentsNode->mStyle->AsServo(); } } Maybe thisFrameRestyleState; if (styleFrame) { auto type = isOutOfFlow ? 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; RefPtr newContext = nullptr; if (wasRestyled && oldStyleContext) { MOZ_ASSERT(styleFrame || displayContentsNode); newContext = aRestyleState.StyleSet().ResolveServoStyle(aElement, aFlags); MOZ_ASSERT(oldStyleContext->ComputedData() != newContext->ComputedData()); newContext->ResolveSameStructsAs(oldStyleContext); // 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, newContext 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 style context. // // This does mean that we may be setting the wrong style context 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->GetAdditionalStyleContext(0)); f->SetStyleContext(newContext); } if (MOZ_UNLIKELY(displayContentsNode)) { MOZ_ASSERT(!styleFrame); displayContentsNode->mStyle = newContext; } if (styleFrame) { UpdateAdditionalStyleContexts(styleFrame, aRestyleState); styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState); } 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 |newContext| to // |styleFrame| to ensure the animated transform has been removed first. AddLayerChangesForAnimation( styleFrame, aElement, aRestyleState.ChangeList()); } const bool traverseElementChildren = NeedsToTraverseElementChildren(*aElement, aFlags); const bool descendantsNeedFrames = aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES); const bool forThrottledAnimationFlush = !!(aFlags & ServoTraversalFlags::AnimationOnly); const bool traverseTextChildren = wasRestyled || (!forThrottledAnimationFlush && descendantsNeedFrames); bool recreatedAnyContext = wasRestyled; if (traverseElementChildren || traverseTextChildren) { ServoStyleContext* upToDateContext = wasRestyled ? newContext : oldStyleContext; StyleChildrenIterator it(aElement); TextPostTraversalState textState(*upToDateContext, displayContentsNode && wasRestyled, childrenRestyleState); for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { if (traverseElementChildren && n->IsElement()) { recreatedAnyContext |= ProcessPostTraversal(n->AsElement(), upToDateContext, childrenRestyleState, aFlags); } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) { recreatedAnyContext |= ProcessPostTraversalForText(n, textState); } } } // 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) { UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState); } else if (traverseElementChildren && styleFrame->IsFrameOfType(nsIFrame::eBlockFrame)) { // 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(styleFrame)->GetFirstLineFrame(); if (firstLineFrame) { for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) { ReparentStyleContext(kid); } } } } if (!forThrottledAnimationFlush) { aElement->UnsetHasDirtyDescendantsForServo(); aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES); } aElement->UnsetHasAnimationOnlyDirtyDescendantsForServo(); return recreatedAnyContext; } bool ServoRestyleManager::ProcessPostTraversalForText( nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState) { // 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; } nsStyleContext& newContext = aPostTraversalState.ComputeStyle(aTextNode); aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newContext); // 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, // newContext 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 style context. // // This does mean that we may be setting the wrong style context on our // initial continuations; ::first-line/::first-letter fix that up after the // fact. for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) { f->SetStyleContext(&newContext); } return true; } void ServoRestyleManager::ClearSnapshots() { for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) { iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT); iter.Remove(); } } ServoElementSnapshot& ServoRestyleManager::SnapshotFor(Element* aElement) { MOZ_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.LookupOrAdd(aElement, aElement); aElement->SetFlags(ELEMENT_HAS_SNAPSHOT); nsIPresShell* presShell = mPresContext->PresShell(); presShell->EnsureStyleFlush(); return *snapshot; } /* static */ nsIFrame* ServoRestyleManager::FrameForPseudoElement(const Element* aElement, nsIAtom* aPseudoTagOrNull) { if (!aPseudoTagOrNull) { return nsLayoutUtils::GetStyleFrame(aElement); } if (aPseudoTagOrNull == nsCSSPseudoElements::before) { Element* pseudoElement = nsLayoutUtils::GetBeforePseudo(aElement); return pseudoElement ? nsLayoutUtils::GetStyleFrame(pseudoElement) : nullptr; } if (aPseudoTagOrNull == nsCSSPseudoElements::after) { Element* pseudoElement = nsLayoutUtils::GetAfterPseudo(aElement); return pseudoElement ? nsLayoutUtils::GetStyleFrame(pseudoElement) : nullptr; } if (aPseudoTagOrNull == nsCSSPseudoElements::firstLetter) { return FindFirstLetterFrameForElement(aElement); } if (aPseudoTagOrNull == nsCSSPseudoElements::firstLine) { return FindFirstLineFrameForElement(aElement); } MOZ_CRASH("Unkown pseudo-element given to " "ServoRestyleManager::FrameForPseudoElement"); return nullptr; } void ServoRestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) { MOZ_ASSERT(PresContext()->Document(), "No document? Pshaw!"); MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!"); MOZ_ASSERT(!mInStyleRefresh, "Reentrant call?"); if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) { // PresShell::FlushPendingNotifications doesn't early-return in the case // where the PreShell 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; } // 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(); nsIDocument* doc = PresContext()->Document(); bool forThrottledAnimationFlush = !!(aFlags & ServoTraversalFlags::AnimationOnly); // Ensure the refresh driver is active during traversal to avoid mutating // mActiveTimer and mMostRecentRefresh time. PresContext()->RefreshDriver()->MostRecentRefresh(); // Perform the Servo traversal, and the post-traversal if required. We do this // in a loop because certain rare paths in the frame constructor (like // uninstalling XBL bindings) can trigger additional style validations. mInStyleRefresh = true; if (mHaveNonAnimationRestyles && !forThrottledAnimationFlush) { ++mAnimationGeneration; } if (mRestyleForCSSRuleChanges) { aFlags |= ServoTraversalFlags::ForCSSRuleChanges; } while (styleSet->StyleDocument(aFlags)) { if (!forThrottledAnimationFlush) { ClearSnapshots(); } nsStyleChangeList currentChanges(StyleBackendType::Servo); bool anyStyleChanged = false; // Recreate style contexts, and queue up change hints (which also handle // lazy frame construction). { AutoRestyleTimelineMarker marker(mPresContext->GetDocShell(), forThrottledAnimationFlush); DocumentStyleRootIterator iter(doc); while (Element* root = iter.GetNextStyleRoot()) { ServoRestyleState state(*styleSet, currentChanges); anyStyleChanged |= ProcessPostTraversal(root, nullptr, state, aFlags); } } // 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( mPresContext->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; } 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(); } } FlushOverflowChangedTracker(); if (!forThrottledAnimationFlush) { 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(); } void ServoRestyleManager::ProcessPendingRestyles() { DoProcessPendingRestyles(ServoTraversalFlags::Empty); } void ServoRestyleManager::UpdateOnlyAnimationStyles() { // Bug 1365855: We also need to implement this for SMIL. bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates(); if (!doCSS) { return; } DoProcessPendingRestyles(ServoTraversalFlags::AnimationOnly); } void ServoRestyleManager::ContentStateChanged(nsIContent* aContent, EventStates aChangedBits) { MOZ_ASSERT(!mInStyleRefresh); if (!aContent->IsElement()) { return; } Element* aElement = aContent->AsElement(); nsChangeHint changeHint; nsRestyleHint restyleHint; if (!aElement->HasServoData()) { return; } // NOTE: restyleHint here is effectively always 0, since that's what // ServoStyleSet::HasStateDependentStyle returns. Servo computes on // ProcessPendingRestyles using the ElementSnapshot, but in theory could // compute it sequentially easily. // // Determine what's the best way to do it, and how much work do we save // processing the restyle hint early (i.e., computing the style hint here // sequentially, potentially saving the snapshot), vs lazily (snapshot // approach). // // If we take the sequential approach we need to specialize Servo's restyle // hints system a bit more, and mesure whether we save something storing the // restyle hint in the table and deferring the dirtiness setting until // ProcessPendingRestyles (that's a requirement if we store snapshots though), // vs processing the restyle hint in-place, dirtying the nodes on // PostRestyleEvent. // // If we definitely take the snapshot approach, we should take rid of // HasStateDependentStyle, etc (though right now they're no-ops). ContentStateChangedInternal(aElement, aChangedBits, &changeHint, &restyleHint); // 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(DIRECTION_STATES) && !StyleSet()->HasStateDependency(*aElement, aChangedBits)) { return; } ServoElementSnapshot& snapshot = SnapshotFor(aElement); EventStates previousState = aElement->StyleState() ^ aChangedBits; snapshot.AddState(previousState); if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) { parent->NoteDirtyDescendantsForServo(); } if (restyleHint || changeHint) { Servo_NoteExplicitHints(aElement, restyleHint, changeHint); } // Assuming we need to invalidate cached style in getComputedStyle for // undisplayed elements, since we don't know if it is needed. IncrementUndisplayedRestyleGeneration(); } static inline bool AttributeInfluencesOtherPseudoClassState(const Element& aElement, const nsIAtom* aAttribute) { // We must record some state for :-moz-browser-frame and // :-moz-table-border-nonzero. if (aAttribute == nsGkAtoms::mozbrowser) { return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame); } if (aAttribute == nsGkAtoms::border) { return aElement.IsHTMLElement(nsGkAtoms::table); } return false; } static inline bool NeedToRecordAttrChange(const ServoStyleSet& aStyleSet, const Element& aElement, int32_t aNameSpaceID, nsIAtom* 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 ServoRestyleManager::AttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue) { TakeSnapshotForAttributeChange(aElement, aNameSpaceID, aAttribute); } void ServoRestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) { TakeSnapshotForAttributeChange(aElement, kNameSpaceID_None, nsGkAtoms::_class); } void ServoRestyleManager::TakeSnapshotForAttributeChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute) { MOZ_ASSERT(!mInStyleRefresh); if (!aElement->HasServoData()) { return; } 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(); // 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); } if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) { parent->NoteDirtyDescendantsForServo(); } } // For some attribute changes we must restyle the whole subtree: // // * is affected by the cellpadding on its ancestor table // * lang="" and xml:lang="" can affect all descendants due to :lang() // static inline bool AttributeChangeRequiresSubtreeRestyle(const Element& aElement, nsIAtom* aAttr) { if (aAttr == nsGkAtoms::cellpadding) { return aElement.IsHTMLElement(nsGkAtoms::table); } return aAttr == nsGkAtoms::lang; } void ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { MOZ_ASSERT(!mInStyleRefresh); if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) { primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); } auto changeHint = nsChangeHint(0); auto restyleHint = nsRestyleHint(0); changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType); if (aAttribute == nsGkAtoms::style) { restyleHint |= eRestyle_StyleAttribute; } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) { restyleHint |= eRestyle_Subtree; } else if (aElement->IsAttributeMapped(aAttribute)) { restyleHint |= eRestyle_Self; } 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; } } nsresult ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame) { // This is only called when moving frames in or out of the first-line // pseudo-element (or one of its inline descendants). So aFrame's ancestors // must all be inline frames up until we find a first-line frame. Note that // the first-line frame 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()) { MOZ_ASSERT(f->IsInlineFrame(), "Must only have inline frames between us and the first-line " "frame"); f = f->GetParent(); } MOZ_ASSERT(f, "Must have found a first-line frame"); } #endif DoReparentStyleContext(aFrame, *StyleSet()); return NS_OK; } void ServoRestyleManager::DoReparentStyleContext(nsIFrame* aFrame, ServoStyleSet& aStyleSet) { if (aFrame->IsBackdropFrame()) { // Style context of backdrop frame has no parent style context, 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: // // //
// What color is this text? //
//
// What color is this text? //
// // 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()) { DoReparentStyleContext(outOfFlow, aStyleSet); } } nsIFrame* providerFrame; nsStyleContext* newParentContext = aFrame->GetParentStyleContext(&providerFrame); if (!newParentContext) { // No need to do anything here. #ifdef DEBUG // Make sure we have no children, so we really know there is nothing to do. nsIFrame::ChildListIterator lists(aFrame); for (; !lists.IsDone(); lists.Next()) { MOZ_ASSERT(lists.CurrentList().IsEmpty(), "Failing to reparent style context for child of " "non-inheriting anon box"); } #endif // DEBUG return; } // 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) { DoReparentStyleContext(providerFrame, aStyleSet); // Get the style context again after ReparentStyleContext() which might have // changed it. newParentContext = providerFrame->StyleContext(); providerChild = providerFrame; MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "Out of flow provider?"); } bool isElement = aFrame->GetContent()->IsElement(); // We probably don't want to initiate transitions from // ReparentStyleContext, 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 style contexts, and we don't know those. ServoStyleContext* oldContext = aFrame->StyleContext()->AsServo(); Element* ourElement = oldContext->GetPseudoType() == CSSPseudoElementType::NotPseudo && isElement ? aFrame->GetContent()->AsElement() : nullptr; ServoStyleContext* newParent = newParentContext->AsServo(); ServoStyleContext* newParentIgnoringFirstLine; if (newParent->GetPseudoType() == CSSPseudoElementType::firstLine) { MOZ_ASSERT(providerFrame && providerFrame->GetParent()-> IsFrameOfType(nsIFrame::eBlockFrame), "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 = nsFrame::CorrectStyleParentFrame(blockFrame, oldContext->GetPseudo()); newParentIgnoringFirstLine = correctedFrame->StyleContext()->AsServo(); } 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. providerFrame = nsFrame::CorrectStyleParentFrame(aFrame->GetParent(), oldContext->GetPseudo()); } ServoStyleContext* layoutParent = providerFrame->StyleContext()->AsServo(); RefPtr newContext = aStyleSet.ReparentStyleContext(oldContext, newParent, newParentIgnoringFirstLine, layoutParent, ourElement); aFrame->SetStyleContext(newContext); // This logic somewhat mirrors the logic in // ServoRestyleManager::ProcessPostTraversal. if (isElement) { // We can't use UpdateAdditionalStyleContexts as-is because it needs a // ServoRestyleState and maintaining one of those during a _frametree_ // traversal is basically impossible. uint32_t index = 0; while (nsStyleContext* oldAdditionalContext = aFrame->GetAdditionalStyleContext(index)) { RefPtr newAdditionalContext = aStyleSet.ReparentStyleContext(oldAdditionalContext->AsServo(), newContext, newContext, newContext, nullptr); aFrame->SetAdditionalStyleContext(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. nsIFrame::ChildListIterator lists(aFrame); for (; !lists.IsDone(); lists.Next()) { for (nsIFrame* child : lists.CurrentList()) { // only do frames that are in flow if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && child != providerChild) { DoReparentStyleContext(child, aStyleSet); } } } // We do not need to do the equivalent of UpdateFramePseudoElementStyles, // because those are hadled by our descendant walk. } } // namespace mozilla