/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsCSSFrameConstructor.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 oldStyleContext = primaryFrame->StyleContext(); RefPtr 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 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 oldStyleContext = primaryFrame->StyleContext(); MOZ_ASSERT(oldStyleContext); RefPtr 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 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 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()); if (!aPseudoTagOrNull) { return aContent->GetPrimaryFrame(); } if (aPseudoTagOrNull == nsCSSPseudoElements::before) { return nsLayoutUtils::GetBeforeFrame(aContent); } if (aPseudoTagOrNull == nsCSSPseudoElements::after) { return nsLayoutUtils::GetAfterFrame(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"); } void ServoRestyleManager::ContentStateChanged(nsIContent* aContent, EventStates aChangedBits) { if (!aContent->IsElement()) { return; } 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); } 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