diff options
Diffstat (limited to 'layout/base/ServoRestyleManager.cpp')
-rw-r--r-- | layout/base/ServoRestyleManager.cpp | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/layout/base/ServoRestyleManager.cpp b/layout/base/ServoRestyleManager.cpp new file mode 100644 index 000000000..42ca23bb1 --- /dev/null +++ b/layout/base/ServoRestyleManager.cpp @@ -0,0 +1,594 @@ +/* -*- 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/ServoBindings.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/dom/ChildIterator.h" +#include "nsContentUtils.h" +#include "nsPrintfCString.h" +#include "nsStyleChangeList.h" + +using namespace mozilla::dom; + +namespace mozilla { + +ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext) + : RestyleManagerBase(aPresContext) +{ +} + +void +ServoRestyleManager::PostRestyleEvent(Element* aElement, + nsRestyleHint aRestyleHint, + nsChangeHint aMinChangeHint) +{ + if (MOZ_UNLIKELY(IsDisconnected()) || + MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) { + return; + } + + if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) { + return; // Nothing to do. + } + + // XXX This is a temporary hack to make style attribute change works. + // In the future, we should be able to use this hint directly. + if (aRestyleHint & eRestyle_StyleAttribute) { + aRestyleHint &= ~eRestyle_StyleAttribute; + aRestyleHint |= eRestyle_Self | eRestyle_Subtree; + } + + // Note that unlike in Servo, we don't mark elements as dirty until we process + // the restyle hints in ProcessPendingRestyles. + if (aRestyleHint || aMinChangeHint) { + ServoElementSnapshot* snapshot = SnapshotForElement(aElement); + snapshot->AddExplicitRestyleHint(aRestyleHint); + snapshot->AddExplicitChangeHint(aMinChangeHint); + } + + PostRestyleEventInternal(false); +} + +void +ServoRestyleManager::PostRestyleEventForLazyConstruction() +{ + PostRestyleEventInternal(true); +} + +void +ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint, + nsRestyleHint aRestyleHint) +{ + NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented"); +} + +void +ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint, + nsRestyleHint aRestyleHint) +{ + NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented"); +} + +static void +MarkSelfAndDescendantsAsNotDirtyForServo(nsIContent* aContent) +{ + aContent->UnsetIsDirtyForServo(); + + if (aContent->HasDirtyDescendantsForServo()) { + aContent->UnsetHasDirtyDescendantsForServo(); + + StyleChildrenIterator it(aContent); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + MarkSelfAndDescendantsAsNotDirtyForServo(n); + } + } +} + +void +ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent, + nsStyleContext* aParentContext, + ServoStyleSet* aStyleSet, + nsStyleChangeList& aChangeListToProcess) +{ + MOZ_ASSERT(aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT)); + + nsIFrame* primaryFrame = aContent->GetPrimaryFrame(); + if (!primaryFrame && !aContent->IsDirtyForServo()) { + // This happens when, for example, a display: none child of a + // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal. + MarkSelfAndDescendantsAsNotDirtyForServo(aContent); + return; + } + + // Work on text before. + if (!aContent->IsElement()) { + if (primaryFrame) { + RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext(); + RefPtr<nsStyleContext> newContext = + aStyleSet->ResolveStyleForText(aContent, aParentContext); + + for (nsIFrame* f = primaryFrame; f; + f = GetNextContinuationWithSameStyle(f, oldStyleContext)) { + f->SetStyleContext(newContext); + } + } + + aContent->UnsetIsDirtyForServo(); + return; + } + + Element* element = aContent->AsElement(); + if (element->IsDirtyForServo()) { + RefPtr<ServoComputedValues> computedValues = + Servo_ComputedValues_Get(aContent).Consume(); + MOZ_ASSERT(computedValues); + + nsChangeHint changeHint = nsChangeHint(0); + + // Add an explicit change hint if appropriate. + ServoElementSnapshot* snapshot; + if (mModifiedElements.Get(element, &snapshot)) { + changeHint |= snapshot->ExplicitChangeHint(); + } + + // Add the stored change hint if there's a frame. If there isn't a frame, + // generate a ReconstructFrame change hint if the new display value + // (which we can get from the ComputedValues stored on the node) is not + // none. + if (primaryFrame) { + changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint(); + } else { + const nsStyleDisplay* currentDisplay = + Servo_GetStyleDisplay(computedValues); + if (currentDisplay->mDisplay != StyleDisplay::None) { + changeHint |= nsChangeHint_ReconstructFrame; + } + } + + // Add the new change hint to the list of elements to process if + // we need to do any work. + if (changeHint) { + aChangeListToProcess.AppendChange(primaryFrame, element, changeHint); + } + + // The frame reconstruction step (if needed) will ask for the descendants' + // style correctly. If not needed, we're done too. + // + // Note that we must leave the old style on an existing frame that is + // about to be reframed, since some frame constructor code wants to + // inspect the old style to work out what to do. + if (changeHint & nsChangeHint_ReconstructFrame) { + // Since we might still have some dirty bits set on descendants, + // inconsistent with the clearing of HasDirtyDescendants we will do as + // we return from these recursive RecreateStyleContexts calls, we + // explicitly clear them here. Otherwise we will trigger assertions + // when we soon process the frame reconstruction. + MarkSelfAndDescendantsAsNotDirtyForServo(element); + return; + } + + // If there is no frame, and we didn't generate a ReconstructFrame change + // hint, then we don't need to do any more work. + if (!primaryFrame) { + aContent->UnsetIsDirtyForServo(); + return; + } + + // Hold the old style context alive, because it could become a dangling + // pointer during the replacement. In practice it's not a huge deal (on + // GetNextContinuationWithSameStyle the pointer is not dereferenced, only + // compared), but better not playing with dangling pointers if not needed. + RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext(); + MOZ_ASSERT(oldStyleContext); + + RefPtr<nsStyleContext> newContext = + aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr, + CSSPseudoElementType::NotPseudo); + + // XXX This could not always work as expected: there are kinds of content + // with the first split and the last sharing style, but others not. We + // should handle those properly. + for (nsIFrame* f = primaryFrame; f; + f = GetNextContinuationWithSameStyle(f, oldStyleContext)) { + f->SetStyleContext(newContext); + } + + // Update pseudo-elements state if appropriate. + const static CSSPseudoElementType pseudosToRestyle[] = { + CSSPseudoElementType::before, + CSSPseudoElementType::after, + }; + + for (CSSPseudoElementType pseudoType : pseudosToRestyle) { + nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType); + + if (nsIFrame* pseudoFrame = FrameForPseudoElement(element, pseudoTag)) { + // TODO: we could maybe make this more performant via calling into + // Servo just once to know which pseudo-elements we've got to restyle? + RefPtr<nsStyleContext> pseudoContext = + aStyleSet->ProbePseudoElementStyle(element, pseudoType, newContext); + + // If pseudoContext is null here, it means the frame is going away, so + // our change hint computation should have already indicated we need + // to reframe. + MOZ_ASSERT_IF(!pseudoContext, + changeHint & nsChangeHint_ReconstructFrame); + if (pseudoContext) { + pseudoFrame->SetStyleContext(pseudoContext); + + // We only care restyling text nodes, since other type of nodes + // (images), are still not supported. If that eventually changes, we + // may have to write more code here... Or not, I don't think too + // many inherited properties can affect those other frames. + StyleChildrenIterator it(pseudoFrame->GetContent()); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + if (n->IsNodeOfType(nsINode::eTEXT)) { + RefPtr<nsStyleContext> childContext = + aStyleSet->ResolveStyleForText(n, pseudoContext); + MOZ_ASSERT(n->GetPrimaryFrame(), + "How? This node is created at FC time!"); + n->GetPrimaryFrame()->SetStyleContext(childContext); + } + } + } + } + } + + aContent->UnsetIsDirtyForServo(); + } + + if (aContent->HasDirtyDescendantsForServo()) { + MOZ_ASSERT(primaryFrame, + "Frame construction should be scheduled, and it takes the " + "correct style for the children, so no need to be here."); + StyleChildrenIterator it(aContent); + for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { + if (n->IsElement() || n->IsNodeOfType(nsINode::eTEXT)) { + RecreateStyleContexts(n, primaryFrame->StyleContext(), + aStyleSet, aChangeListToProcess); + } + } + aContent->UnsetHasDirtyDescendantsForServo(); + } +} + +static void +MarkChildrenAsDirtyForServo(nsIContent* aContent) +{ + StyleChildrenIterator it(aContent); + + nsIContent* n = it.GetNextChild(); + bool hadChildren = bool(n); + for (; n; n = it.GetNextChild()) { + n->SetIsDirtyForServo(); + } + + if (hadChildren) { + aContent->SetHasDirtyDescendantsForServo(); + } +} + +/* static */ nsIFrame* +ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent, + nsIAtom* aPseudoTagOrNull) +{ + MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement()); + nsIFrame* primaryFrame = aContent->GetPrimaryFrame(); + + if (!aPseudoTagOrNull) { + return primaryFrame; + } + + if (!primaryFrame) { + return nullptr; + } + + // NOTE: we probably need to special-case display: contents here. Gecko's + // RestyleManager passes the primary frame of the parent instead. + if (aPseudoTagOrNull == nsCSSPseudoElements::before) { + return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent); + } + + if (aPseudoTagOrNull == nsCSSPseudoElements::after) { + return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent); + } + + MOZ_CRASH("Unkown pseudo-element given to " + "ServoRestyleManager::FrameForPseudoElement"); + return nullptr; +} + +/* static */ void +ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint) +{ + const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self | + eRestyle_Subtree | + eRestyle_LaterSiblings | + eRestyle_SomeDescendants; + // NB: For Servo, at least for now, restyling and running selector-matching + // against the subtree is necessary as part of restyling the element, so + // processing eRestyle_Self will perform at least as much work as + // eRestyle_Subtree. + if (aHint & (eRestyle_Self | eRestyle_Subtree)) { + aElement->SetIsDirtyForServo(); + aElement->MarkAncestorsAsHavingDirtyDescendantsForServo(); + // NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run + // selector matching on all the descendants. There's a bug on Servo to align + // meanings here (#12710) to avoid this potential source of confusion. + } else if (aHint & eRestyle_SomeDescendants) { + MarkChildrenAsDirtyForServo(aElement); + aElement->MarkAncestorsAsHavingDirtyDescendantsForServo(); + } + + if (aHint & eRestyle_LaterSiblings) { + aElement->MarkAncestorsAsHavingDirtyDescendantsForServo(); + for (nsIContent* cur = aElement->GetNextSibling(); cur; + cur = cur->GetNextSibling()) { + cur->SetIsDirtyForServo(); + } + } + + // TODO: Handle all other nsRestyleHint values. + if (aHint & ~HANDLED_RESTYLE_HINTS) { + NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s", + RestyleManagerBase::RestyleHintToString(aHint).get()).get()); + } +} + +void +ServoRestyleManager::ProcessPendingRestyles() +{ + MOZ_ASSERT(PresContext()->Document(), "No document? Pshaw!"); + MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!"); + + 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; + } + + if (!HasPendingRestyles()) { + return; + } + + ServoStyleSet* styleSet = StyleSet(); + nsIDocument* doc = PresContext()->Document(); + Element* root = doc->GetRootElement(); + if (root) { + // ProcessPendingRestyles can generate new restyles (e.g. from the + // frame constructor if it decides that a ReconstructFrame change must + // apply to the parent of the element that generated that hint). So + // we loop while mModifiedElements still has some restyles in it, clearing + // it after each RecreateStyleContexts call below. + while (!mModifiedElements.IsEmpty()) { + for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) { + ServoElementSnapshot* snapshot = iter.UserData(); + Element* element = iter.Key(); + + // The element is no longer in the document, so don't bother computing + // a final restyle hint for it. + // + // XXXheycam RestyleTracker checks that the element's GetComposedDoc() + // matches the document we're restyling. Do we need to do that too? + if (!element->IsInComposedDoc()) { + continue; + } + + // TODO: avoid the ComputeRestyleHint call if we already have the highest + // explicit restyle hint? + nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot); + hint |= snapshot->ExplicitRestyleHint(); + + if (hint) { + NoteRestyleHint(element, hint); + } + } + + if (!root->IsDirtyForServo() && !root->HasDirtyDescendantsForServo()) { + mModifiedElements.Clear(); + break; + } + + mInStyleRefresh = true; + styleSet->StyleDocument(/* aLeaveDirtyBits = */ true); + + // First do any queued-up frame creation. (see bugs 827239 and 997506). + // + // XXXEmilio I'm calling this to avoid random behavior changes, since we + // delay frame construction after styling we should re-check once our + // model is more stable whether we can skip this call. + // + // Note this has to be *after* restyling, because otherwise frame + // construction will find unstyled nodes, and that's not funny. + PresContext()->FrameConstructor()->CreateNeededFrames(); + + nsStyleChangeList changeList; + RecreateStyleContexts(root, nullptr, styleSet, changeList); + + mModifiedElements.Clear(); + ProcessRestyledFrames(changeList); + + mInStyleRefresh = false; + } + } + + MOZ_ASSERT(!doc->IsDirtyForServo()); + doc->UnsetHasDirtyDescendantsForServo(); + + IncrementRestyleGeneration(); +} + +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::ContentInserted(nsINode* aContainer, nsIContent* aChild) +{ + if (aContainer == aContainer->OwnerDoc()) { + // If we're getting this notification for the insertion of a root element, + // that means either: + // (a) We initialized the PresShell before the root element existed, or + // (b) The root element was removed and it or another root is being + // inserted. + // + // Either way the whole tree is dirty, so we should style the document. + MOZ_ASSERT(aChild == aChild->OwnerDoc()->GetRootElement()); + MOZ_ASSERT(aChild->IsDirtyForServo()); + StyleSet()->StyleDocument(/* aLeaveDirtyBits = */ false); + return; + } + + if (!aContainer->HasServoData()) { + // This can happen with display:none. Bug 1297249 tracks more investigation + // and assertions here. + return; + } + + // Style the new subtree because we will most likely need it during subsequent + // frame construction. Bug 1298281 tracks deferring this work in the lazy + // frame construction case. + StyleSet()->StyleNewSubtree(aChild); + + RestyleForInsertOrChange(aContainer, aChild); +} + +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::ContentAppended(nsIContent* aContainer, + nsIContent* aFirstNewContent) +{ + if (!aContainer->HasServoData()) { + // This can happen with display:none. Bug 1297249 tracks more investigation + // and assertions here. + return; + } + + // Style the new subtree because we will most likely need it during subsequent + // frame construction. Bug 1298281 tracks deferring this work in the lazy + // frame construction case. + if (aFirstNewContent->GetNextSibling()) { + aContainer->SetHasDirtyDescendantsForServo(); + StyleSet()->StyleNewChildren(aContainer); + } else { + StyleSet()->StyleNewSubtree(aFirstNewContent); + } + + RestyleForAppend(aContainer, aFirstNewContent); +} + +void +ServoRestyleManager::ContentRemoved(nsINode* aContainer, + nsIContent* aOldChild, + nsIContent* aFollowingSibling) +{ + NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented"); +} + +nsresult +ServoRestyleManager::ContentStateChanged(nsIContent* aContent, + EventStates aChangedBits) +{ + if (!aContent->IsElement()) { + return NS_OK; + } + + Element* aElement = aContent->AsElement(); + nsChangeHint changeHint; + nsRestyleHint restyleHint; + + // 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); + + EventStates previousState = aElement->StyleState() ^ aChangedBits; + ServoElementSnapshot* snapshot = SnapshotForElement(aElement); + snapshot->AddState(previousState); + + PostRestyleEvent(aElement, restyleHint, changeHint); + return NS_OK; +} + +void +ServoRestyleManager::AttributeWillChange(Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, int32_t aModType, + const nsAttrValue* aNewValue) +{ + ServoElementSnapshot* snapshot = SnapshotForElement(aElement); + snapshot->AddAttrs(aElement); +} + +void +ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsIAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) +{ + MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs()); + if (aAttribute == nsGkAtoms::style) { + PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0)); + } +} + +nsresult +ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame) +{ + NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented"); + return NS_OK; +} + +ServoElementSnapshot* +ServoRestyleManager::SnapshotForElement(Element* aElement) +{ + // NB: aElement is the argument for the construction of the snapshot in the + // not found case. + return mModifiedElements.LookupOrAdd(aElement, aElement); +} + +} // namespace mozilla |