/* -*- 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/DocumentStyleRootIterator.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoStyleSet.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)) { return aFrame.GetParent()->IsViewportFrame() ? nullptr : aFrame.GetParent(); } if (aFrame.IsBulletFrame()) { 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)); MOZ_ASSERT(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. } // 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) { StyleSet()->ClearDataAndMarkDeviceDirty(); 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()); } } } Unused << Servo_TakeChangeHint(aElement); 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(nsStyleContext& 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; } nsStyleContext* oldContext = aTextFrame->StyleContext(); 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: nsStyleContext& mParentContext; ServoRestyleState& mParentRestyleState; RefPtr mStyle; bool mShouldPostHints; bool mShouldComputeHints; nsChangeHint mComputedHint; }; // 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) { 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)->GetFirstLetter(); } 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(), /* 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 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()); } bool ServoRestyleManager::ProcessPostTraversal(Element* aElement, nsStyleContext* aParentContext, ServoRestyleState& aRestyleState) { 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. nsChangeHint changeHint = Servo_TakeChangeHint(aElement); // 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 // ServoComputedValues 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(); } } RefPtr computedValues = aRestyleState.StyleSet().ResolveServoStyle(aElement); // Note that we rely in the fact that we don't cascade pseudo-element styles // separately right now (that is, if a pseudo style changes, the normal style // changes too). // // Otherwise we should probably encode that information somehow to avoid // expensive checks in the common case. // // Also, we're going to need to check for pseudos of display: contents // elements, though that is buggy right now even in non-stylo mode, see // bug 1251799. const bool recreateContext = oldStyleContext && oldStyleContext->ComputedValues() != computedValues; 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 (recreateContext) { MOZ_ASSERT(styleFrame || displayContentsNode); auto pseudo = aElement->GetPseudoElementType(); nsIAtom* pseudoTag = pseudo == CSSPseudoElementType::NotPseudo ? nullptr : nsCSSPseudoElements::GetPseudoAtom(pseudo); newContext = aRestyleState.StyleSet().GetContext( computedValues.forget(), aParentContext, pseudoTag, pseudo, aElement); newContext->ResolveSameStructsAs(PresContext(), 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()) { f->SetStyleContext(newContext); } if (MOZ_UNLIKELY(displayContentsNode)) { MOZ_ASSERT(!styleFrame); displayContentsNode->mStyle = newContext; } if (styleFrame) { 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 descendantsNeedFrames = aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES); const bool traverseElementChildren = aElement->HasDirtyDescendantsForServo() || aElement->HasAnimationOnlyDirtyDescendantsForServo() || descendantsNeedFrames; const bool traverseTextChildren = recreateContext || descendantsNeedFrames; bool recreatedAnyContext = recreateContext; if (traverseElementChildren || traverseTextChildren) { nsStyleContext* upToDateContext = recreateContext ? newContext : oldStyleContext; StyleChildrenIterator it(aElement); TextPostTraversalState textState(*upToDateContext, displayContentsNode && recreateContext, childrenRestyleState); for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { if (traverseElementChildren && n->IsElement()) { recreatedAnyContext |= ProcessPostTraversal( n->AsElement(), upToDateContext, childrenRestyleState); } 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 (recreateContext && styleFrame) { UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState); } aElement->UnsetHasDirtyDescendantsForServo(); aElement->UnsetHasAnimationOnlyDirtyDescendantsForServo(); aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES); 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) { // TODO(emilio, bz): Figure out the best way to diff these styles. return nullptr; } MOZ_CRASH("Unkown pseudo-element given to " "ServoRestyleManager::FrameForPseudoElement"); return nullptr; } void ServoRestyleManager::DoProcessPendingRestyles(TraversalRestyleBehavior aRestyleBehavior) { 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 animationOnly = aRestyleBehavior == TraversalRestyleBehavior::ForAnimationOnly; // 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 && !animationOnly) { ++mAnimationGeneration; } TraversalRestyleBehavior restyleBehavior = mRestyleForCSSRuleChanges ? TraversalRestyleBehavior::ForCSSRuleChanges : TraversalRestyleBehavior::Normal; while (animationOnly ? styleSet->StyleDocumentForAnimationOnly() : styleSet->StyleDocument(restyleBehavior)) { if (!animationOnly) { ClearSnapshots(); } // Recreate style contexts, and queue up change hints (which also handle // lazy frame construction). nsStyleChangeList currentChanges(StyleBackendType::Servo); DocumentStyleRootIterator iter(doc); bool anyStyleChanged = false; while (Element* root = iter.GetNextStyleRoot()) { ServoRestyleState state(*styleSet, currentChanges); anyStyleChanged |= ProcessPostTraversal(root, nullptr, state); } // 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. 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 (!animationOnly) { 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(TraversalRestyleBehavior::Normal); } void ServoRestyleManager::UpdateOnlyAnimationStyles() { // Bug 1365855: We also need to implement this for SMIL. bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates(); if (!doCSS) { return; } DoProcessPendingRestyles(TraversalRestyleBehavior::ForAnimationOnly); } void ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild) { // // XXXbholley: We need the Gecko logic here to correctly restyle for things // like :empty and positional selectors (though we may not need to post // restyle events as agressively as the Gecko path does). // // Bug 1297899 tracks this work. // } void ServoRestyleManager::RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent) { // // XXXbholley: We need the Gecko logic here to correctly restyle for things // like :empty and positional selectors (though we may not need to post // restyle events as agressively as the Gecko path does). // // Bug 1297899 tracks this work. // } void ServoRestyleManager::ContentRemoved(nsINode* aContainer, nsIContent* aOldChild, nsIContent* aFollowingSibling) { NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented"); } 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(); } PostRestyleEvent(aElement, restyleHint, changeHint); } static inline bool AttributeInfluencesOtherPseudoClassState(Element* aElement, nsIAtom* aAttribute) { // We must record some state for :-moz-browser-frame and // :-moz-table-border-nonzero. return (aAttribute == nsGkAtoms::mozbrowser && aElement->IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame)) || (aAttribute == nsGkAtoms::border && aElement->IsHTMLElement(nsGkAtoms::table)); } void ServoRestyleManager::AttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue) { MOZ_ASSERT(!mInStyleRefresh); if (!aElement->HasServoData()) { return; } bool influencesOtherPseudoClassState = AttributeInfluencesOtherPseudoClassState(aElement, aAttribute); if (!influencesOtherPseudoClassState && !((aNameSpaceID == kNameSpaceID_None && (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) || aAttribute == nsGkAtoms::lang || StyleSet()->MightHaveAttributeDependency(*aElement, aAttribute))) { return; } ServoElementSnapshot& snapshot = SnapshotFor(aElement); snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute); if (influencesOtherPseudoClassState) { snapshot.AddOtherPseudoClassState(aElement); } if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) { parent->NoteDirtyDescendantsForServo(); } } void ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { MOZ_ASSERT(!mInStyleRefresh); nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); if (primaryFrame) { primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType); } nsChangeHint hint = aElement->GetAttributeChangeHint(aAttribute, aModType); if (hint) { PostRestyleEvent(aElement, nsRestyleHint(0), hint); } if (aAttribute == nsGkAtoms::style) { PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0)); } // 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() if ((aAttribute == nsGkAtoms::cellpadding && aElement->IsHTMLElement(nsGkAtoms::table)) || aAttribute == nsGkAtoms::lang) { PostRestyleEvent(aElement, eRestyle_Subtree, nsChangeHint(0)); } else if (aElement->IsAttributeMapped(aAttribute)) { Servo_NoteExplicitHints(aElement, eRestyle_Self, nsChangeHint(0)); } } nsresult ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame) { NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented"); return NS_OK; } } // namespace mozilla