/* -*- 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/ServoStyleSet.h" #include "gfxPlatformFontList.h" #include "mozilla/DocumentStyleRootIterator.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoRestyleManager.h" #include "mozilla/ServoStyleRuleMap.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/AnonymousContent.h" #include "mozilla/dom/ChildIterator.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/RestyleManagerInlines.h" #include "nsCSSAnonBoxes.h" #include "nsCSSFrameConstructor.h" #include "nsCSSPseudoElements.h" #include "nsCSSRuleProcessor.h" #include "nsDeviceContext.h" #include "nsHTMLStyleSheet.h" #include "nsIAnonymousContentCreator.h" #include "nsIDocumentInlines.h" #include "nsPrintfCString.h" #include "nsSMILAnimationController.h" #include "nsStyleContext.h" #include "nsStyleSet.h" #include "gfxUserFontSet.h" using namespace mozilla; using namespace mozilla::dom; static inline uint64_t UniqueIDForSheet(ServoStyleSheet* aSheet) { // Servo tracks sheets by unique ID, and it's important that a given // ServoStyleSheet has the same unique ID throughout its lifetime. // Instead of tracking an arbitrary unique ID for each sheet, // we use the sheet address as a unique ID. return reinterpret_cast(aSheet); } ServoStyleSet::ServoStyleSet() : mPresContext(nullptr) , mAllowResolveStaleStyles(false) , mAuthorStyleDisabled(false) , mStylistState(StylistState::NotDirty) , mUserFontSetUpdateGeneration(0) , mNeedsRestyleAfterEnsureUniqueInner(false) { } ServoStyleSet::~ServoStyleSet() { for (auto& sheetArray : mSheets) { for (auto& sheet : sheetArray) { sheet->DropStyleSet(this); } } } void ServoStyleSet::Init(nsPresContext* aPresContext) { mPresContext = aPresContext; mRawSet.reset(Servo_StyleSet_Init(aPresContext)); mPresContext->DeviceContext()->InitFontCache(); // Now that we have an mRawSet, go ahead and notify about whatever stylesheets // we have so far. for (auto& sheetArray : mSheets) { for (auto& sheet : sheetArray) { // There's no guarantee this will create a list on the servo side whose // ordering matches the list that would have been created had all those // sheets been appended/prepended/etc after we had mRawSet. That's okay // because Servo only needs to maintain relative ordering within a sheet // type, which this preserves. MOZ_ASSERT(sheet->RawSheet(), "We should only append non-null raw sheets."); Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet->RawSheet(), UniqueIDForSheet(sheet)); } } // No need to Servo_StyleSet_FlushStyleSheets because we just created the // mRawSet, so there was nothing to flush. } // Traverses the given frame tree, calling ClearServoDataFromSubtree on // any NAC that is found. static void ClearServoDataFromNAC(nsIFrame* aFrame) { nsIAnonymousContentCreator* ac = do_QueryFrame(aFrame); if (ac) { nsTArray nodes; ac->AppendAnonymousContentTo(nodes, 0); for (nsIContent* node : nodes) { if (node->IsElement()) { ServoRestyleManager::ClearServoDataFromSubtree(node->AsElement()); } } } nsIFrame::ChildListIterator lists(aFrame); for (; !lists.IsDone(); lists.Next()) { for (nsIFrame* child : lists.CurrentList()) { ClearServoDataFromNAC(child); } } } void ServoStyleSet::BeginShutdown() { nsIDocument* doc = mPresContext->Document(); // Remove the style rule map from document's observer and drop it. if (mStyleRuleMap) { doc->RemoveObserver(mStyleRuleMap); doc->CSSLoader()->RemoveObserver(mStyleRuleMap); mStyleRuleMap = nullptr; } // It's important to do this before mRawSet is released, since that will cause // a RuleTree GC, which needs to happen after we have dropped all of the // document's strong references to RuleNodes. We also need to do it here, // in BeginShutdown, and not in Shutdown, since Shutdown happens after the // frame tree has been destroyed, but before the script runners that delete // native anonymous content (which also could be holding on the RuleNodes) // have run. By clearing style here, before the frame tree is destroyed, // the AllChildrenIterator will find the anonymous content. // // Note that this is pretty bad for performance; we should find a way to // get by with the ServoNodeDatas being dropped as part of the document // going away. DocumentStyleRootIterator iter(doc); while (Element* root = iter.GetNextStyleRoot()) { ServoRestyleManager::ClearServoDataFromSubtree(root); } // We can also have some cloned canvas custom content stored in the document // (as done in nsCanvasFrame::DestroyFrom), due to bug 1348480, when we create // the clone (wastefully) during PresShell destruction. Clear data from that // clone. for (RefPtr& ac : doc->GetAnonymousContents()) { ServoRestyleManager::ClearServoDataFromSubtree(ac->GetContentNode()); } // Also look for any NAC created by position:fixed replicated frames in a // print or print preview presentation. if (nsIPresShell* shell = doc->GetShell()) { if (nsIFrame* pageSeq = shell->FrameConstructor()->GetPageSequenceFrame()) { auto iter = pageSeq->PrincipalChildList().begin(); if (*iter) { ++iter; // skip past first page while (nsIFrame* page = *iter) { MOZ_ASSERT(page->IsPageFrame()); // The position:fixed replicated frames live on the PageContent frame. nsIFrame* pageContent = page->PrincipalChildList().FirstChild(); MOZ_ASSERT(pageContent && pageContent->IsPageContentFrame()); for (nsIFrame* f : pageContent->GetChildList(nsIFrame::kFixedList)) { ClearServoDataFromNAC(f); } ++iter; } } } } } void ServoStyleSet::Shutdown() { // Make sure we drop our cached style contexts before the presshell arena // starts going away. ClearNonInheritingStyleContexts(); mRawSet = nullptr; } void ServoStyleSet::InvalidateStyleForCSSRuleChanges() { MOZ_ASSERT(StylistNeedsUpdate()); mPresContext->RestyleManager()->AsServo()->PostRestyleEventForCSSRuleChanges(); } bool ServoStyleSet::MediumFeaturesChanged() const { return Servo_StyleSet_MediumFeaturesChanged(mRawSet.get()); } size_t ServoStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); if (mStyleRuleMap) { n += mStyleRuleMap->SizeOfIncludingThis(aMallocSizeOf); } // Measurement of the following members may be added later if DMD finds it is // worthwhile: // - mRawSet // - mSheets // - mNonInheritingStyleContexts // // The following members are not measured: // - mPresContext, because it a non-owning pointer return n; } bool ServoStyleSet::GetAuthorStyleDisabled() const { return mAuthorStyleDisabled; } nsresult ServoStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled) { if (mAuthorStyleDisabled == aStyleDisabled) { return NS_OK; } mAuthorStyleDisabled = aStyleDisabled; ForceAllStyleDirty(); return NS_OK; } void ServoStyleSet::BeginUpdate() { } nsresult ServoStyleSet::EndUpdate() { return NS_OK; } already_AddRefed ServoStyleSet::ResolveStyleFor(Element* aElement, nsStyleContext* aParentContext, LazyComputeBehavior aMayCompute) { return GetContext(aElement, aParentContext, nullptr, CSSPseudoElementType::NotPseudo, aMayCompute); } already_AddRefed ServoStyleSet::GetContext(nsIContent* aContent, nsStyleContext* aParentContext, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, LazyComputeBehavior aMayCompute) { MOZ_ASSERT(aContent->IsElement()); Element* element = aContent->AsElement(); RefPtr computedValues; if (aMayCompute == LazyComputeBehavior::Allow) { PreTraverseSync(); computedValues = ResolveStyleLazily(element, CSSPseudoElementType::NotPseudo); } else { computedValues = ResolveServoStyle(element); } MOZ_ASSERT(computedValues); return GetContext(computedValues.forget(), aParentContext, aPseudoTag, aPseudoType, element); } already_AddRefed ServoStyleSet::GetContext(already_AddRefed aComputedValues, nsStyleContext* aParentContext, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, Element* aElementForAnimation) { bool isLink = false; bool isVisitedLink = false; // If we need visited styles for callers where `aElementForAnimation` is null, // we can precompute these and pass them as flags, similar to nsStyleSet.cpp. if (aElementForAnimation) { isLink = nsCSSRuleProcessor::IsLink(aElementForAnimation); isVisitedLink = nsCSSRuleProcessor::GetContentState(aElementForAnimation) .HasState(NS_EVENT_STATE_VISITED); } RefPtr computedValues = Move(aComputedValues); RefPtr visitedComputedValues = Servo_ComputedValues_GetVisitedStyle(computedValues).Consume(); // If `visitedComputedValues` is non-null, then there was a relevant link and // visited styles were computed. This corresponds to the cases where Gecko's // style system produces `aVisitedRuleNode`. // Set up `parentIfVisited` depending on whether our parent context has a // a visited style. If it doesn't but we do have visited styles, use the // regular parent context for visited. nsStyleContext *parentIfVisited = aParentContext ? aParentContext->GetStyleIfVisited() : nullptr; if (!parentIfVisited) { if (visitedComputedValues) { parentIfVisited = aParentContext; } } // The true visited state of the relevant link is used to decided whether // visited styles should be consulted for all visited dependent properties. bool relevantLinkVisited = isLink ? isVisitedLink : (aParentContext && aParentContext->RelevantLinkVisited()); RefPtr result = NS_NewStyleContext(aParentContext, mPresContext, aPseudoTag, aPseudoType, computedValues.forget()); if (visitedComputedValues) { RefPtr resultIfVisited = NS_NewStyleContext(parentIfVisited, mPresContext, aPseudoTag, aPseudoType, visitedComputedValues.forget()); resultIfVisited->SetIsStyleIfVisited(); result->SetStyleIfVisited(resultIfVisited.forget()); if (relevantLinkVisited) { result->AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED); } } // Set the body color on the pres context. See nsStyleSet::GetContext if (aElementForAnimation && aElementForAnimation->IsHTMLElement(nsGkAtoms::body) && aPseudoType == CSSPseudoElementType::NotPseudo && mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) { nsIDocument* doc = aElementForAnimation->GetUncomposedDoc(); if (doc && doc->GetBodyElement() == aElementForAnimation) { // Update the prescontext's body color mPresContext->SetBodyTextColor(result->StyleColor()->mColor); } } return result.forget(); } const ServoElementSnapshotTable& ServoStyleSet::Snapshots() { return mPresContext->RestyleManager()->AsServo()->Snapshots(); } void ServoStyleSet::ResolveMappedAttrDeclarationBlocks() { if (nsHTMLStyleSheet* sheet = mPresContext->Document()->GetAttributeStyleSheet()) { sheet->CalculateMappedServoDeclarations(mPresContext); } mPresContext->Document()->ResolveScheduledSVGPresAttrs(); } void ServoStyleSet::PreTraverseSync() { ResolveMappedAttrDeclarationBlocks(); nsCSSRuleProcessor::InitSystemMetrics(); // This is lazily computed and pseudo matching needs to access // it so force computation early. mPresContext->Document()->GetDocumentState(); // Ensure that the @font-face data is not stale if (gfxUserFontSet* userFontSet = mPresContext->Document()->GetUserFontSet()) { uint64_t generation = userFontSet->GetGeneration(); if (generation != mUserFontSetUpdateGeneration) { mPresContext->DeviceContext()->UpdateFontCacheUserFonts(userFontSet); mUserFontSetUpdateGeneration = generation; } } UpdateStylistIfNeeded(); mPresContext->CacheAllLangs(); } void ServoStyleSet::PreTraverse(Element* aRoot, EffectCompositor::AnimationRestyleType aRestyleType) { PreTraverseSync(); // Process animation stuff that we should avoid doing during the parallel // traversal. nsSMILAnimationController* smilController = mPresContext->Document()->GetAnimationController(); if (aRoot) { mPresContext->EffectCompositor() ->PreTraverseInSubtree(aRoot, aRestyleType); if (smilController) { smilController->PreTraverseInSubtree(aRoot); } } else { mPresContext->EffectCompositor()->PreTraverse(aRestyleType); if (smilController) { smilController->PreTraverse(); } } } bool ServoStyleSet::PrepareAndTraverseSubtree( RawGeckoElementBorrowed aRoot, TraversalRootBehavior aRootBehavior, TraversalRestyleBehavior aRestyleBehavior) { // Get the Document's root element to ensure that the cache is valid before // calling into the (potentially-parallel) Servo traversal, where a cache hit // is necessary to avoid a data race when updating the cache. mozilla::Unused << aRoot->OwnerDoc()->GetRootElement(); MOZ_ASSERT(!StylistNeedsUpdate()); AutoSetInServoTraversal guard(this); const SnapshotTable& snapshots = Snapshots(); bool isInitial = !aRoot->HasServoData(); bool forReconstruct = aRestyleBehavior == TraversalRestyleBehavior::ForReconstruct; bool forAnimationOnly = aRestyleBehavior == TraversalRestyleBehavior::ForAnimationOnly; #ifdef DEBUG bool forNewlyBoundElement = aRestyleBehavior == TraversalRestyleBehavior::ForNewlyBoundElement; #endif bool postTraversalRequired = Servo_TraverseSubtree( aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior); MOZ_ASSERT(!(isInitial || forReconstruct || forNewlyBoundElement) || !postTraversalRequired); // Don't need to trigger a second traversal if this restyle only needs // animation-only restyle. if (forAnimationOnly) { return postTraversalRequired; } auto root = const_cast(aRoot); // If there are still animation restyles needed, trigger a second traversal to // update CSS animations or transitions' styles. // // We don't need to do this for SMIL since SMIL only updates its animation // values once at the begin of a tick. As a result, even if the previous // traversal caused, for example, the font-size to change, the SMIL style // won't be updated until the next tick anyway. EffectCompositor* compositor = mPresContext->EffectCompositor(); EffectCompositor::AnimationRestyleType restyleType = EffectCompositor::AnimationRestyleType::Throttled; if (forReconstruct ? compositor->PreTraverseInSubtree(root, restyleType) : compositor->PreTraverse(restyleType)) { if (Servo_TraverseSubtree( aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior)) { MOZ_ASSERT(!forReconstruct); if (isInitial) { // We're doing initial styling, and the additional animation // traversal changed the styles that were set by the first traversal. // This would normally require a post-traversal to update the style // contexts, and the DOM now has dirty descendant bits and RestyleData // in expectation of that post-traversal. But since this is actually // the initial styling, there are no style contexts to update and no // frames to apply the change hints to, so we don't need to do that // post-traversal. Instead, just drop this state and tell the caller // that no post-traversal is required. MOZ_ASSERT(!postTraversalRequired); ServoRestyleManager::ClearRestyleStateFromSubtree(root); } else { postTraversalRequired = true; } } } return postTraversalRequired; } already_AddRefed ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode, nsStyleContext* aParentContext) { MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT)); MOZ_ASSERT(aTextNode->GetParent()); MOZ_ASSERT(aParentContext); // Gecko expects text node style contexts to be like elements that match no // rules: inherit the inherit structs, reset the reset structs. This is cheap // enough to do on the main thread, which means that the parallel style system // can avoid worrying about text nodes. const ServoComputedValues* parentComputedValues = aParentContext->ComputedValues(); RefPtr computedValues = Servo_ComputedValues_Inherit(mRawSet.get(), parentComputedValues, InheritTarget::Text).Consume(); return GetContext(computedValues.forget(), aParentContext, nsCSSAnonBoxes::mozText, CSSPseudoElementType::InheritingAnonBox, nullptr); } already_AddRefed ServoStyleSet::ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext) { const ServoComputedValues* parent = aParentContext->ComputedValues(); RefPtr computedValues = Servo_ComputedValues_Inherit(mRawSet.get(), parent, InheritTarget::FirstLetterContinuation) .Consume(); MOZ_ASSERT(computedValues); return GetContext(computedValues.forget(), aParentContext, nsCSSAnonBoxes::firstLetterContinuation, CSSPseudoElementType::InheritingAnonBox, nullptr); } already_AddRefed ServoStyleSet::ResolveStyleForPlaceholder() { RefPtr& cache = mNonInheritingStyleContexts[nsCSSAnonBoxes::NonInheriting::oofPlaceholder]; if (cache) { RefPtr retval = cache; return retval.forget(); } RefPtr computedValues = Servo_ComputedValues_Inherit(mRawSet.get(), nullptr, InheritTarget::PlaceholderFrame) .Consume(); MOZ_ASSERT(computedValues); RefPtr retval = GetContext(computedValues.forget(), nullptr, nsCSSAnonBoxes::oofPlaceholder, CSSPseudoElementType::NonInheritingAnonBox, nullptr); cache = retval; return retval.forget(); } already_AddRefed ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement, CSSPseudoElementType aType, nsStyleContext* aParentContext, Element* aPseudoElement) { UpdateStylistIfNeeded(); // NB: We ignore aParentContext, on the assumption that pseudo element styles // should just inherit from aOriginatingElement's primary style, which Servo // already knows. MOZ_ASSERT(aType < CSSPseudoElementType::Count); RefPtr computedValues; if (aPseudoElement) { MOZ_ASSERT(aType == aPseudoElement->GetPseudoElementType()); computedValues = Servo_ResolveStyle(aPseudoElement, mRawSet.get(), mAllowResolveStaleStyles).Consume(); } else { computedValues = Servo_ResolvePseudoStyle(aOriginatingElement, aType, /* is_probe = */ false, mRawSet.get()).Consume(); } MOZ_ASSERT(computedValues); bool isBeforeOrAfter = aType == CSSPseudoElementType::before || aType == CSSPseudoElementType::after; nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType); return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType, isBeforeOrAfter ? aOriginatingElement : nullptr); } already_AddRefed ServoStyleSet::ResolveTransientStyle(Element* aElement, nsIAtom* aPseudoTag, CSSPseudoElementType aPseudoType, StyleRuleInclusion aRuleInclusion) { RefPtr computedValues = ResolveTransientServoStyle(aElement, aPseudoType, aRuleInclusion); return GetContext(computedValues.forget(), nullptr, aPseudoTag, aPseudoType, nullptr); } already_AddRefed ServoStyleSet::ResolveTransientServoStyle( Element* aElement, CSSPseudoElementType aPseudoType, StyleRuleInclusion aRuleInclusion) { PreTraverseSync(); return ResolveStyleLazily(aElement, aPseudoType, aRuleInclusion); } already_AddRefed ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag, nsStyleContext* aParentContext) { MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) && !nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag)); UpdateStylistIfNeeded(); bool skipFixup = nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(aPseudoTag); const ServoComputedValues* parentStyle = aParentContext ? aParentContext->ComputedValues() : nullptr; RefPtr computedValues = Servo_ComputedValues_GetForAnonymousBox(parentStyle, aPseudoTag, skipFixup, mRawSet.get()).Consume(); #ifdef DEBUG if (!computedValues) { nsString pseudo; aPseudoTag->ToString(pseudo); NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s", NS_ConvertUTF16toUTF8(pseudo).get()).get()); MOZ_CRASH(); } #endif return GetContext(computedValues.forget(), aParentContext, aPseudoTag, CSSPseudoElementType::InheritingAnonBox, nullptr); } already_AddRefed ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag) { MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) && nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag)); MOZ_ASSERT(aPseudoTag != nsCSSAnonBoxes::pageContent, "If nsCSSAnonBoxes::pageContent ends up non-inheriting, check " "whether we need to do anything to move the " "@page handling from ResolveInheritingAnonymousBoxStyle to " "ResolveNonInheritingAnonymousBoxStyle"); nsCSSAnonBoxes::NonInheriting type = nsCSSAnonBoxes::NonInheritingTypeForPseudoTag(aPseudoTag); RefPtr& cache = mNonInheritingStyleContexts[type]; if (cache) { RefPtr retval = cache; return retval.forget(); } UpdateStylistIfNeeded(); // We always want to skip parent-based display fixup here. It never makes // sense for non-inheriting anonymous boxes. (Static assertions in // nsCSSAnonBoxes.cpp ensure that all non-inheriting non-anonymous boxes // are indeed annotated as skipping this fixup.) MOZ_ASSERT(!nsCSSAnonBoxes::IsNonInheritingAnonBox(nsCSSAnonBoxes::viewport), "viewport needs fixup to handle blockifying it"); RefPtr computedValues = Servo_ComputedValues_GetForAnonymousBox(nullptr, aPseudoTag, true, mRawSet.get()).Consume(); #ifdef DEBUG if (!computedValues) { nsString pseudo; aPseudoTag->ToString(pseudo); NS_ERROR(nsPrintfCString("stylo: could not get anon-box: %s", NS_ConvertUTF16toUTF8(pseudo).get()).get()); MOZ_CRASH(); } #endif RefPtr retval = GetContext(computedValues.forget(), nullptr, aPseudoTag, CSSPseudoElementType::NonInheritingAnonBox, nullptr); cache = retval; return retval.forget(); } // manage the set of style sheets in the style set nsresult ServoStyleSet::AppendStyleSheet(SheetType aType, ServoStyleSheet* aSheet) { MOZ_ASSERT(aSheet); MOZ_ASSERT(aSheet->IsApplicable()); MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType)); MOZ_ASSERT(aSheet->RawSheet(), "Raw sheet should be in place before insertion."); RemoveSheetOfType(aType, aSheet); AppendSheetOfType(aType, aSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. // Servo will remove aSheet from its original position as part of the call // to Servo_StyleSet_AppendStyleSheet. Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet->RawSheet(), UniqueIDForSheet(aSheet)); SetStylistStyleSheetsDirty(); } return NS_OK; } nsresult ServoStyleSet::PrependStyleSheet(SheetType aType, ServoStyleSheet* aSheet) { MOZ_ASSERT(aSheet); MOZ_ASSERT(aSheet->IsApplicable()); MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType)); MOZ_ASSERT(aSheet->RawSheet(), "Raw sheet should be in place before insertion."); RemoveSheetOfType(aType, aSheet); PrependSheetOfType(aType, aSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. // Servo will remove aSheet from its original position as part of the call // to Servo_StyleSet_PrependStyleSheet. Servo_StyleSet_PrependStyleSheet(mRawSet.get(), aSheet->RawSheet(), UniqueIDForSheet(aSheet)); SetStylistStyleSheetsDirty(); } return NS_OK; } nsresult ServoStyleSet::RemoveStyleSheet(SheetType aType, ServoStyleSheet* aSheet) { MOZ_ASSERT(aSheet); MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType)); RemoveSheetOfType(aType, aSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), UniqueIDForSheet(aSheet)); SetStylistStyleSheetsDirty(); } return NS_OK; } nsresult ServoStyleSet::ReplaceSheets(SheetType aType, const nsTArray>& aNewSheets) { // Gecko uses a two-dimensional array keyed by sheet type, whereas Servo // stores a flattened list. This makes ReplaceSheets a pretty clunky thing // to express. If the need ever arises, we can easily make this more efficent, // probably by aligning the representations better between engines. SetStylistStyleSheetsDirty(); // Remove all the existing sheets first. for (const auto& sheet : mSheets[aType]) { sheet->DropStyleSet(this); if (mRawSet) { Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), UniqueIDForSheet(sheet)); } } mSheets[aType].Clear(); // Add in all the new sheets. for (auto& sheet : aNewSheets) { AppendSheetOfType(aType, sheet); if (mRawSet) { MOZ_ASSERT(sheet->RawSheet(), "Raw sheet should be in place before replacement."); Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet->RawSheet(), UniqueIDForSheet(sheet)); } } return NS_OK; } nsresult ServoStyleSet::InsertStyleSheetBefore(SheetType aType, ServoStyleSheet* aNewSheet, ServoStyleSheet* aReferenceSheet) { MOZ_ASSERT(aNewSheet); MOZ_ASSERT(aReferenceSheet); MOZ_ASSERT(aNewSheet->IsApplicable()); MOZ_ASSERT(aNewSheet != aReferenceSheet, "Can't place sheet before itself."); MOZ_ASSERT(aNewSheet->RawSheet(), "Raw sheet should be in place before insertion."); MOZ_ASSERT(aReferenceSheet->RawSheet(), "Reference sheet should have a raw sheet."); // Servo will remove aNewSheet from its original position as part of the // call to Servo_StyleSet_InsertStyleSheetBefore. RemoveSheetOfType(aType, aNewSheet); InsertSheetOfType(aType, aNewSheet, aReferenceSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. Servo_StyleSet_InsertStyleSheetBefore(mRawSet.get(), aNewSheet->RawSheet(), UniqueIDForSheet(aNewSheet), UniqueIDForSheet(aReferenceSheet)); SetStylistStyleSheetsDirty(); } return NS_OK; } int32_t ServoStyleSet::SheetCount(SheetType aType) const { MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType)); return mSheets[aType].Length(); } ServoStyleSheet* ServoStyleSet::StyleSheetAt(SheetType aType, int32_t aIndex) const { MOZ_ASSERT(nsStyleSet::IsCSSSheetType(aType)); return mSheets[aType][aIndex]; } nsresult ServoStyleSet::RemoveDocStyleSheet(ServoStyleSheet* aSheet) { return RemoveStyleSheet(SheetType::Doc, aSheet); } nsresult ServoStyleSet::AddDocStyleSheet(ServoStyleSheet* aSheet, nsIDocument* aDocument) { MOZ_ASSERT(aSheet->IsApplicable()); MOZ_ASSERT(aSheet->RawSheet(), "Raw sheet should be in place by this point."); RefPtr strong(aSheet); RemoveSheetOfType(SheetType::Doc, aSheet); size_t index = aDocument->FindDocStyleSheetInsertionPoint(mSheets[SheetType::Doc], aSheet); if (index < mSheets[SheetType::Doc].Length()) { // This case is insert before. ServoStyleSheet *beforeSheet = mSheets[SheetType::Doc][index]; InsertSheetOfType(SheetType::Doc, aSheet, beforeSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. Servo_StyleSet_InsertStyleSheetBefore(mRawSet.get(), aSheet->RawSheet(), UniqueIDForSheet(aSheet), UniqueIDForSheet(beforeSheet)); SetStylistStyleSheetsDirty(); } } else { // This case is append. AppendSheetOfType(SheetType::Doc, aSheet); if (mRawSet) { // Maintain a mirrored list of sheets on the servo side. Servo_StyleSet_AppendStyleSheet(mRawSet.get(), aSheet->RawSheet(), UniqueIDForSheet(aSheet)); SetStylistStyleSheetsDirty(); } } return NS_OK; } already_AddRefed ServoStyleSet::ProbePseudoElementStyle(Element* aOriginatingElement, CSSPseudoElementType aType, nsStyleContext* aParentContext, Element* aPseudoElement) { UpdateStylistIfNeeded(); if (aPseudoElement) { NS_ERROR("stylo: We don't support CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE yet"); } // NB: We ignore aParentContext, on the assumption that pseudo element styles // should just inherit from aOriginatingElement's primary style, which Servo // already knows. MOZ_ASSERT(aType < CSSPseudoElementType::Count); RefPtr computedValues = Servo_ResolvePseudoStyle(aOriginatingElement, aType, /* is_probe = */ true, mRawSet.get()).Consume(); if (!computedValues) { return nullptr; } // For :before and :after pseudo-elements, having display: none or no // 'content' property is equivalent to not having the pseudo-element // at all. bool isBeforeOrAfter = aType == CSSPseudoElementType::before || aType == CSSPseudoElementType::after; if (isBeforeOrAfter) { const nsStyleDisplay* display = Servo_GetStyleDisplay(computedValues); const nsStyleContent* content = Servo_GetStyleContent(computedValues); // XXXldb What is contentCount for |content: ""|? if (display->mDisplay == StyleDisplay::None || content->ContentCount() == 0) { return nullptr; } } nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType); return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType, isBeforeOrAfter ? aOriginatingElement : nullptr); } nsRestyleHint ServoStyleSet::HasStateDependentStyle(dom::Element* aElement, EventStates aStateMask) { NS_WARNING("stylo: HasStateDependentStyle always returns zero!"); return nsRestyleHint(0); } nsRestyleHint ServoStyleSet::HasStateDependentStyle(dom::Element* aElement, CSSPseudoElementType aPseudoType, dom::Element* aPseudoElement, EventStates aStateMask) { NS_WARNING("stylo: HasStateDependentStyle always returns zero!"); return nsRestyleHint(0); } bool ServoStyleSet::StyleDocument(TraversalRestyleBehavior aRestyleBehavior) { MOZ_ASSERT( aRestyleBehavior == TraversalRestyleBehavior::Normal || aRestyleBehavior == TraversalRestyleBehavior::ForCSSRuleChanges, "StyleDocument() should be only called for normal traversal or CSS rule " "changes"); PreTraverse(); // Restyle the document from the root element and each of the document level // NAC subtree roots. bool postTraversalRequired = false; DocumentStyleRootIterator iter(mPresContext->Document()); while (Element* root = iter.GetNextStyleRoot()) { if (PrepareAndTraverseSubtree(root, TraversalRootBehavior::Normal, aRestyleBehavior)) { postTraversalRequired = true; } } return postTraversalRequired; } bool ServoStyleSet::StyleDocumentForAnimationOnly() { PreTraverse(nullptr, EffectCompositor::AnimationRestyleType::Full); bool postTraversalRequired = false; DocumentStyleRootIterator iter(mPresContext->Document()); while (Element* root = iter.GetNextStyleRoot()) { if (PrepareAndTraverseSubtree(root, TraversalRootBehavior::Normal, TraversalRestyleBehavior::ForAnimationOnly)) { postTraversalRequired = true; } } return postTraversalRequired; } void ServoStyleSet::StyleNewSubtree(Element* aRoot) { MOZ_ASSERT(!aRoot->HasServoData()); PreTraverse(); DebugOnly postTraversalRequired = PrepareAndTraverseSubtree(aRoot, TraversalRootBehavior::Normal, TraversalRestyleBehavior::Normal); MOZ_ASSERT(!postTraversalRequired); } void ServoStyleSet::StyleNewChildren(Element* aParent) { PreTraverse(); PrepareAndTraverseSubtree(aParent, TraversalRootBehavior::UnstyledChildrenOnly, TraversalRestyleBehavior::Normal); // We can't assert that Servo_TraverseSubtree returns false, since aParent // or some of its other children might have pending restyles. } void ServoStyleSet::StyleNewlyBoundElement(Element* aElement) { PreTraverse(); // In general the element is always styled by the time we're applying XBL // bindings, because we need to style the element to know what the binding // URI is. However, programmatic consumers of the XBL service (like the // XML pretty printer) _can_ apply bindings without having styled the bound // element. We could assert against this and require the callers manually // resolve the style first, but it's easy enough to just handle here. // // Also, when applying XBL bindings to elements within a display:none or // unstyled subtree (for example, when elements are wrapped to be // exposed to JS), we need to tell the traversal that it is OK to // skip restyling, rather than panic when trying to unwrap the styles // it expects to have just computed. TraversalRootBehavior rootBehavior = MOZ_UNLIKELY(!aElement->HasServoData()) ? TraversalRootBehavior::Normal : TraversalRootBehavior::UnstyledChildrenOnly; PrepareAndTraverseSubtree(aElement, rootBehavior, TraversalRestyleBehavior::ForNewlyBoundElement); } void ServoStyleSet::StyleSubtreeForReconstruct(Element* aRoot) { PreTraverse(aRoot); DebugOnly postTraversalRequired = PrepareAndTraverseSubtree(aRoot, TraversalRootBehavior::Normal, TraversalRestyleBehavior::ForReconstruct); MOZ_ASSERT(!postTraversalRequired); } void ServoStyleSet::ForceAllStyleDirty() { SetStylistStyleSheetsDirty(); Servo_StyleSet_NoteStyleSheetsChanged(mRawSet.get(), mAuthorStyleDisabled); } void ServoStyleSet::RecordStyleSheetChange( ServoStyleSheet* aSheet, StyleSheet::ChangeType aChangeType) { SetStylistStyleSheetsDirty(); switch (aChangeType) { case StyleSheet::ChangeType::RuleAdded: case StyleSheet::ChangeType::RuleRemoved: case StyleSheet::ChangeType::RuleChanged: // FIXME(emilio): We can presumably do better in a bunch of these. return ForceAllStyleDirty(); case StyleSheet::ChangeType::ApplicableStateChanged: case StyleSheet::ChangeType::Added: case StyleSheet::ChangeType::Removed: // Do nothing, we've already recorded the change in the // Append/Remove/Replace methods, etc, and will act consequently. return; } } #ifdef DEBUG void ServoStyleSet::AssertTreeIsClean() { DocumentStyleRootIterator iter(mPresContext->Document()); while (Element* root = iter.GetNextStyleRoot()) { Servo_AssertTreeIsClean(root); } } #endif bool ServoStyleSet::GetKeyframesForName(const nsString& aName, const nsTimingFunction& aTimingFunction, const ServoComputedValues* aComputedValues, nsTArray& aKeyframes) { UpdateStylistIfNeeded(); NS_ConvertUTF16toUTF8 name(aName); return Servo_StyleSet_GetKeyframesForName(mRawSet.get(), &name, &aTimingFunction, aComputedValues, &aKeyframes); } nsTArray ServoStyleSet::GetComputedKeyframeValuesFor( const nsTArray& aKeyframes, Element* aElement, ServoComputedValuesBorrowed aComputedValues) { nsTArray result(aKeyframes.Length()); // Construct each nsTArray here. result.AppendElements(aKeyframes.Length()); Servo_GetComputedKeyframeValues(&aKeyframes, aElement, aComputedValues, mRawSet.get(), &result); return result; } void ServoStyleSet::GetAnimationValues( RawServoDeclarationBlock* aDeclarations, Element* aElement, ServoComputedValuesBorrowed aComputedValues, nsTArray>& aAnimationValues) { Servo_GetAnimationValues(aDeclarations, aElement, aComputedValues, mRawSet.get(), &aAnimationValues); } already_AddRefed ServoStyleSet::GetBaseComputedValuesForElement(Element* aElement, CSSPseudoElementType aPseudoType) { return Servo_StyleSet_GetBaseComputedValuesForElement(mRawSet.get(), aElement, &Snapshots(), aPseudoType).Consume(); } already_AddRefed ServoStyleSet::ComputeAnimationValue( Element* aElement, RawServoDeclarationBlock* aDeclarations, ServoComputedValuesBorrowed aComputedValues) { return Servo_AnimationValue_Compute(aElement, aDeclarations, aComputedValues, mRawSet.get()).Consume(); } bool ServoStyleSet::EnsureUniqueInnerOnCSSSheets() { AutoTArray queue; for (auto& entryArray : mSheets) { for (auto& sheet : entryArray) { queue.AppendElement(sheet); } } // This is a stub until more of the functionality of nsStyleSet is // replicated for Servo here. // Bug 1290276 will replicate the nsStyleSet work of checking // a nsBindingManager while (!queue.IsEmpty()) { uint32_t idx = queue.Length() - 1; StyleSheet* sheet = queue[idx]; queue.RemoveElementAt(idx); sheet->EnsureUniqueInner(); // Enqueue all the sheet's children. sheet->AppendAllChildSheets(queue); } bool res = mNeedsRestyleAfterEnsureUniqueInner; mNeedsRestyleAfterEnsureUniqueInner = false; return res; } void ServoStyleSet::RebuildData() { ClearNonInheritingStyleContexts(); Servo_StyleSet_RebuildData(mRawSet.get()); } void ServoStyleSet::ClearDataAndMarkDeviceDirty() { ClearNonInheritingStyleContexts(); Servo_StyleSet_Clear(mRawSet.get()); mStylistState |= StylistState::FullyDirty; } void ServoStyleSet::CompatibilityModeChanged() { Servo_StyleSet_CompatModeChanged(mRawSet.get()); } already_AddRefed ServoStyleSet::ResolveServoStyle(Element* aElement) { UpdateStylistIfNeeded(); return Servo_ResolveStyle(aElement, mRawSet.get(), mAllowResolveStaleStyles).Consume(); } void ServoStyleSet::ClearNonInheritingStyleContexts() { for (RefPtr& ptr : mNonInheritingStyleContexts) { ptr = nullptr; } } already_AddRefed ServoStyleSet::ResolveStyleLazily(Element* aElement, CSSPseudoElementType aPseudoType, StyleRuleInclusion aRuleInclusion) { mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoType); MOZ_ASSERT(!StylistNeedsUpdate()); AutoSetInServoTraversal guard(this); /** * NB: This is needed because we process animations and transitions on the * pseudo-elements themselves, not on the parent's EagerPseudoStyles. * * That means that that style doesn't account for animations, and we can't do * that easily from the traversal without doing wasted work. * * As such, we just lie here a bit, which is the entrypoint of * getComputedStyle, the only API where this can be observed, to look at the * style of the pseudo-element if it exists instead. */ Element* elementForStyleResolution = aElement; CSSPseudoElementType pseudoTypeForStyleResolution = aPseudoType; if (aPseudoType == CSSPseudoElementType::before) { if (Element* pseudo = nsLayoutUtils::GetBeforePseudo(aElement)) { elementForStyleResolution = pseudo; pseudoTypeForStyleResolution = CSSPseudoElementType::NotPseudo; } } else if (aPseudoType == CSSPseudoElementType::after) { if (Element* pseudo = nsLayoutUtils::GetAfterPseudo(aElement)) { elementForStyleResolution = pseudo; pseudoTypeForStyleResolution = CSSPseudoElementType::NotPseudo; } } RefPtr computedValues = Servo_ResolveStyleLazily(elementForStyleResolution, pseudoTypeForStyleResolution, aRuleInclusion, &Snapshots(), mRawSet.get()).Consume(); if (mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoType)) { computedValues = Servo_ResolveStyleLazily(elementForStyleResolution, pseudoTypeForStyleResolution, aRuleInclusion, &Snapshots(), mRawSet.get()).Consume(); } return computedValues.forget(); } bool ServoStyleSet::AppendFontFaceRules(nsTArray& aArray) { UpdateStylistIfNeeded(); Servo_StyleSet_GetFontFaceRules(mRawSet.get(), &aArray); return true; } nsCSSCounterStyleRule* ServoStyleSet::CounterStyleRuleForName(nsIAtom* aName) { return Servo_StyleSet_GetCounterStyleRule(mRawSet.get(), aName); } already_AddRefed ServoStyleSet::ResolveForDeclarations( ServoComputedValuesBorrowedOrNull aParentOrNull, RawServoDeclarationBlockBorrowed aDeclarations) { UpdateStylistIfNeeded(); return Servo_StyleSet_ResolveForDeclarations(mRawSet.get(), aParentOrNull, aDeclarations).Consume(); } void ServoStyleSet::UpdateStylist() { MOZ_ASSERT(StylistNeedsUpdate()); if (mStylistState & StylistState::FullyDirty) { RebuildData(); if (mStylistState & StylistState::StyleSheetsDirty) { // Normally, whoever was in charge of posting a RebuildAllStyleDataEvent, // would also be in charge of posting a restyle/change hint according to // it. // // However, other stylesheets may have been added to the document in the // same period, so when both bits are set, we need to do a full subtree // update, because we can no longer reason about the state of the style // data. // // We could not clear the invalidations when rebuilding the data and // process them here... But it's not clear if that complexity is worth // to handle this edge case more efficiently. if (Element* root = mPresContext->Document()->GetDocumentElement()) { Servo_NoteExplicitHints(root, eRestyle_Subtree, nsChangeHint(0)); } } } else { MOZ_ASSERT(mStylistState & StylistState::StyleSheetsDirty); Element* root = mPresContext->Document()->GetDocumentElement(); Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root); } mStylistState = StylistState::NotDirty; } void ServoStyleSet::MaybeGCRuleTree() { MOZ_ASSERT(NS_IsMainThread()); Servo_MaybeGCRuleTree(mRawSet.get()); } void ServoStyleSet::PrependSheetOfType(SheetType aType, ServoStyleSheet* aSheet) { aSheet->AddStyleSet(this); mSheets[aType].InsertElementAt(0, aSheet); } void ServoStyleSet::AppendSheetOfType(SheetType aType, ServoStyleSheet* aSheet) { aSheet->AddStyleSet(this); mSheets[aType].AppendElement(aSheet); } void ServoStyleSet::InsertSheetOfType(SheetType aType, ServoStyleSheet* aSheet, ServoStyleSheet* aBeforeSheet) { for (uint32_t i = 0; i < mSheets[aType].Length(); ++i) { if (mSheets[aType][i] == aBeforeSheet) { aSheet->AddStyleSet(this); mSheets[aType].InsertElementAt(i, aSheet); return; } } } void ServoStyleSet::RemoveSheetOfType(SheetType aType, ServoStyleSheet* aSheet) { for (uint32_t i = 0; i < mSheets[aType].Length(); ++i) { if (mSheets[aType][i] == aSheet) { aSheet->DropStyleSet(this); mSheets[aType].RemoveElementAt(i); } } } void ServoStyleSet::RunPostTraversalTasks() { MOZ_ASSERT(!IsInServoTraversal()); if (mPostTraversalTasks.IsEmpty()) { return; } nsTArray tasks; tasks.SwapElements(mPostTraversalTasks); for (auto& task : tasks) { task.Run(); } } ServoStyleRuleMap* ServoStyleSet::StyleRuleMap() { if (!mStyleRuleMap) { mStyleRuleMap = new ServoStyleRuleMap(this); nsIDocument* doc = mPresContext->Document(); doc->AddObserver(mStyleRuleMap); doc->CSSLoader()->AddObserver(mStyleRuleMap); } return mStyleRuleMap; } bool ServoStyleSet::MightHaveAttributeDependency(nsIAtom* aAttribute) { return Servo_StyleSet_MightHaveAttributeDependency(mRawSet.get(), aAttribute); } bool ServoStyleSet::HasStateDependency(EventStates aState) { return Servo_StyleSet_HasStateDependency(mRawSet.get(), aState.ServoValue()); } ServoStyleSet* ServoStyleSet::sInServoTraversal = nullptr;