gecko-dev/layout/style/ServoStyleSet.cpp

1487 строки
51 KiB
C++

/* -*- 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 "mozilla/ServoStyleSetInlines.h"
#include "gfxPlatformFontList.h"
#include "mozilla/AutoRestyleTimelineMarker.h"
#include "mozilla/DocumentStyleRootIterator.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleRuleMap.h"
#include "mozilla/ServoTypes.h"
#include "mozilla/SMILAnimationController.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSPseudoElements.h"
#include "nsDeviceContext.h"
#include "nsHTMLStyleSheet.h"
#include "nsIAnonymousContentCreator.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsMediaFeatures.h"
#include "nsPrintfCString.h"
#include "nsXBLPrototypeBinding.h"
#include "gfxUserFontSet.h"
#include "nsBindingManager.h"
#include "nsWindowSizes.h"
#include "GeckoProfiler.h"
using namespace mozilla;
using namespace mozilla::dom;
#ifdef DEBUG
bool ServoStyleSet::IsCurrentThreadInServoTraversal() {
return sInServoTraversal && (NS_IsMainThread() || Servo_IsWorkerThread());
}
#endif
namespace mozilla {
ServoStyleSet* sInServoTraversal = nullptr;
// On construction, sets sInServoTraversal to the given ServoStyleSet.
// On destruction, clears sInServoTraversal and calls RunPostTraversalTasks.
class MOZ_RAII AutoSetInServoTraversal {
public:
explicit AutoSetInServoTraversal(ServoStyleSet* aSet) : mSet(aSet) {
MOZ_ASSERT(!sInServoTraversal);
MOZ_ASSERT(aSet);
sInServoTraversal = aSet;
}
~AutoSetInServoTraversal() {
MOZ_ASSERT(sInServoTraversal);
sInServoTraversal = nullptr;
mSet->RunPostTraversalTasks();
}
private:
ServoStyleSet* mSet;
};
// Sets up for one or more calls to Servo_TraverseSubtree.
class MOZ_RAII AutoPrepareTraversal {
public:
explicit AutoPrepareTraversal(ServoStyleSet* aSet)
// For markers for animations, we have already set the markers in
// RestyleManager::PostRestyleEventForAnimations so that we don't need
// to care about animation restyles here.
: mTimelineMarker(aSet->mDocument->GetDocShell(), false),
mSetInServoTraversal(aSet) {
MOZ_ASSERT(!aSet->StylistNeedsUpdate());
}
private:
AutoRestyleTimelineMarker mTimelineMarker;
AutoSetInServoTraversal mSetInServoTraversal;
};
} // namespace mozilla
ServoStyleSet::ServoStyleSet()
: mDocument(nullptr),
mAuthorStyleDisabled(false),
mStylistState(StylistState::NotDirty),
mUserFontSetUpdateGeneration(0),
mNeedsRestyleAfterEnsureUniqueInner(false) {}
ServoStyleSet::~ServoStyleSet() {
for (auto& sheetArray : mSheets) {
for (auto& sheet : sheetArray) {
sheet->DropStyleSet(this);
}
}
}
nsPresContext* ServoStyleSet::GetPresContext() {
if (!mDocument) {
return nullptr;
}
return mDocument->GetPresContext();
}
void ServoStyleSet::Init(nsPresContext* aPresContext) {
mDocument = aPresContext->Document();
MOZ_ASSERT(GetPresContext() == aPresContext);
mRawSet.reset(Servo_StyleSet_Init(aPresContext));
aPresContext->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->RawContents(),
"We should only append non-null raw sheets.");
Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet);
}
}
// We added prefilled stylesheets into mRawSet, so the stylist is dirty.
// The Stylist should be updated later when necessary.
SetStylistStyleSheetsDirty();
// We may have Shadow DOM style changes that we weren't notified about because
// the document didn't have a shell, if the ShadowRoot was created in a
// display: none iframe.
//
// Now that we got a shell, we may need to get them up-to-date.
//
// TODO(emilio, bug 1418159): This wouldn't be needed if the StyleSet was
// owned by the document.
SetStylistXBLStyleSheetsDirty();
}
template <typename Functor>
void EnumerateShadowRoots(const Document& aDoc, const Functor& aCb) {
const Document::ShadowRootSet& shadowRoots = aDoc.ComposedShadowRoots();
for (auto iter = shadowRoots.ConstIter(); !iter.Done(); iter.Next()) {
ShadowRoot* root = iter.Get()->GetKey();
MOZ_ASSERT(root);
MOZ_DIAGNOSTIC_ASSERT(root->IsInComposedDoc());
aCb(*root);
}
}
void ServoStyleSet::Shutdown() {
// Make sure we drop our cached styles before the presshell arena starts going
// away.
ClearNonInheritingComputedStyles();
mRawSet = nullptr;
mStyleRuleMap = nullptr;
}
void ServoStyleSet::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
// TODO(emilio): We could keep track of the actual shadow roots that need
// their styles recomputed.
SetStylistXBLStyleSheetsDirty();
// FIXME(emilio): This should be done using stylesheet invalidation instead.
if (nsPresContext* pc = GetPresContext()) {
pc->RestyleManager()->PostRestyleEvent(aShadowRoot.Host(), eRestyle_Subtree,
nsChangeHint(0));
}
}
void ServoStyleSet::InvalidateStyleForDocumentStateChanges(
EventStates aStatesChanged) {
MOZ_ASSERT(mDocument);
MOZ_ASSERT(!aStatesChanged.IsEmpty());
nsPresContext* pc = GetPresContext();
if (!pc) {
return;
}
Element* root = mDocument->GetRootElement();
if (!root) {
return;
}
// TODO(emilio): It may be nicer to just invalidate stuff in a given subtree
// for XBL sheets / Shadow DOM. Consider just enumerating bound content
// instead and run invalidation individually, passing mRawSet for the UA /
// User sheets.
AutoTArray<RawServoAuthorStylesBorrowed, 20> nonDocumentStyles;
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
});
mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
[&](nsXBLPrototypeBinding* aProto) {
if (auto* authorStyles = aProto->GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
return true;
});
Servo_InvalidateStyleForDocStateChanges(
root, mRawSet.get(), &nonDocumentStyles, aStatesChanged.ServoValue());
}
static const MediaFeatureChangeReason kMediaFeaturesAffectingDefaultStyle =
// Zoom changes change the meaning of em units.
MediaFeatureChangeReason::ZoomChange |
// Changes the meaning of em units, depending on which one is the actual
// min-font-size.
MediaFeatureChangeReason::MinFontSizeChange |
// A resolution change changes the app-units-per-dev-pixels ratio, which
// some structs (Border, Outline, Column) store for clamping. We should
// arguably not do that, maybe doing it on layout directly, to try to avoid
// relying on the pres context (bug 1418159).
MediaFeatureChangeReason::ResolutionChange;
nsRestyleHint ServoStyleSet::MediumFeaturesChanged(
MediaFeatureChangeReason aReason) {
AutoTArray<RawServoAuthorStylesBorrowedMut, 20> nonDocumentStyles;
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
});
// FIXME(emilio): This is broken for XBL. See bug 1406875.
mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
[&](nsXBLPrototypeBinding* aProto) {
if (auto* authorStyles = aProto->GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
return true;
});
bool mayAffectDefaultStyle =
bool(aReason & kMediaFeaturesAffectingDefaultStyle);
const MediumFeaturesChangedResult result =
Servo_StyleSet_MediumFeaturesChanged(mRawSet.get(), &nonDocumentStyles,
mayAffectDefaultStyle);
const bool rulesChanged =
result.mAffectsDocumentRules || result.mAffectsNonDocumentRules;
if (result.mAffectsDocumentRules) {
SetStylistStyleSheetsDirty();
}
if (result.mAffectsNonDocumentRules) {
SetStylistXBLStyleSheetsDirty();
}
if (rulesChanged) {
return eRestyle_Subtree;
}
const bool viewportChanged =
bool(aReason & MediaFeatureChangeReason::ViewportChange);
if (result.mUsesViewportUnits && viewportChanged) {
return eRestyle_ForceDescendants;
}
return nsRestyleHint(0);
}
MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleSetMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoStyleSetMallocEnclosingSizeOf)
void ServoStyleSet::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
aSizes.mLayoutStyleSetsOther += mallocSizeOf(this);
if (mRawSet) {
aSizes.mLayoutStyleSetsOther += mallocSizeOf(mRawSet.get());
ServoStyleSetSizes sizes;
// Measure mRawSet. We use ServoStyleSetMallocSizeOf rather than
// aMallocSizeOf to distinguish in DMD's output the memory measured within
// Servo code.
Servo_StyleSet_AddSizeOfExcludingThis(ServoStyleSetMallocSizeOf,
ServoStyleSetMallocEnclosingSizeOf,
&sizes, mRawSet.get());
// The StyleSet does not contain precomputed pseudos; they are in the UA
// cache.
MOZ_RELEASE_ASSERT(sizes.mPrecomputedPseudos == 0);
aSizes.mLayoutStyleSetsStylistRuleTree += sizes.mRuleTree;
aSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +=
sizes.mElementAndPseudosMaps;
aSizes.mLayoutStyleSetsStylistInvalidationMap += sizes.mInvalidationMap;
aSizes.mLayoutStyleSetsStylistRevalidationSelectors +=
sizes.mRevalidationSelectors;
aSizes.mLayoutStyleSetsStylistOther += sizes.mOther;
}
if (mStyleRuleMap) {
aSizes.mLayoutStyleSetsOther +=
mStyleRuleMap->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
}
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mSheets
// - mNonInheritingComputedStyles
//
// The following members are not measured:
// - mDocument, because it a non-owning pointer
}
void ServoStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled) {
if (mAuthorStyleDisabled == aStyleDisabled) {
return;
}
mAuthorStyleDisabled = aStyleDisabled;
if (Element* root = mDocument->GetRootElement()) {
if (nsPresContext* pc = GetPresContext()) {
pc->RestyleManager()->PostRestyleEvent(root, eRestyle_Subtree,
nsChangeHint(0));
}
}
Servo_StyleSet_SetAuthorStyleDisabled(mRawSet.get(), mAuthorStyleDisabled);
// XXX Workaround for the assertion in InvalidateStyleForDocumentStateChanges
// which is called by nsIPresShell::SetAuthorStyleDisabled via nsIPresShell::
// RestyleForCSSRuleChanges. It is not really necessary because we don't need
// to rebuild stylist for this change. But we have bug around this, and we
// may want to rethink how things should work. See bug 1437785.
SetStylistStyleSheetsDirty();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleFor(
Element* aElement, LazyComputeBehavior aMayCompute) {
if (aMayCompute == LazyComputeBehavior::Allow) {
PreTraverseSync();
return ResolveStyleLazilyInternal(aElement,
CSSPseudoElementType::NotPseudo);
}
return ResolveServoStyle(*aElement);
}
const ServoElementSnapshotTable& ServoStyleSet::Snapshots() {
MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
return GetPresContext()->RestyleManager()->Snapshots();
}
void ServoStyleSet::ResolveMappedAttrDeclarationBlocks() {
if (nsHTMLStyleSheet* sheet = mDocument->GetAttributeStyleSheet()) {
sheet->CalculateMappedServoDeclarations();
}
mDocument->ResolveScheduledSVGPresAttrs();
}
void ServoStyleSet::PreTraverseSync() {
// 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 << mDocument->GetRootElement();
ResolveMappedAttrDeclarationBlocks();
nsMediaFeatures::InitSystemMetrics();
LookAndFeel::NativeInit();
nsPresContext* presContext = GetPresContext();
MOZ_ASSERT(presContext,
"For now, we don't call into here without a pres context");
if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) {
// Ensure that the @font-face data is not stale
uint64_t generation = userFontSet->GetGeneration();
if (generation != mUserFontSetUpdateGeneration) {
mDocument->GetFonts()->CacheFontLoadability();
presContext->DeviceContext()->UpdateFontCacheUserFonts(userFontSet);
mUserFontSetUpdateGeneration = generation;
}
}
MOZ_ASSERT(!StylistNeedsUpdate());
presContext->CacheAllLangs();
}
void ServoStyleSet::PreTraverse(ServoTraversalFlags aFlags, Element* aRoot) {
PreTraverseSync();
// Process animation stuff that we should avoid doing during the parallel
// traversal.
SMILAnimationController* smilController =
mDocument->HasAnimationController() ? mDocument->GetAnimationController()
: nullptr;
MOZ_ASSERT(GetPresContext());
if (aRoot) {
GetPresContext()->EffectCompositor()->PreTraverseInSubtree(aFlags, aRoot);
if (smilController) {
smilController->PreTraverseInSubtree(aRoot);
}
} else {
GetPresContext()->EffectCompositor()->PreTraverse(aFlags);
if (smilController) {
smilController->PreTraverse();
}
}
}
static inline already_AddRefed<ComputedStyle>
ResolveStyleForTextOrFirstLetterContinuation(RawServoStyleSetBorrowed aStyleSet,
ComputedStyle& aParent,
nsAtom* aAnonBox) {
MOZ_ASSERT(aAnonBox == nsCSSAnonBoxes::mozText() ||
aAnonBox == nsCSSAnonBoxes::firstLetterContinuation());
auto inheritTarget = aAnonBox == nsCSSAnonBoxes::mozText()
? InheritTarget::Text
: InheritTarget::FirstLetterContinuation;
RefPtr<ComputedStyle> style =
aParent.GetCachedInheritingAnonBoxStyle(aAnonBox);
if (!style) {
style = Servo_ComputedValues_Inherit(aStyleSet, aAnonBox, &aParent,
inheritTarget)
.Consume();
MOZ_ASSERT(style);
aParent.SetCachedInheritedAnonBoxStyle(aAnonBox, style);
}
return style.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleForText(
nsIContent* aTextNode, ComputedStyle* aParentContext) {
MOZ_ASSERT(aTextNode && aTextNode->IsText());
MOZ_ASSERT(aTextNode->GetParent());
MOZ_ASSERT(aParentContext);
return ResolveStyleForTextOrFirstLetterContinuation(
mRawSet.get(), *aParentContext, nsCSSAnonBoxes::mozText());
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleForFirstLetterContinuation(
ComputedStyle* aParentContext) {
MOZ_ASSERT(aParentContext);
return ResolveStyleForTextOrFirstLetterContinuation(
mRawSet.get(), *aParentContext,
nsCSSAnonBoxes::firstLetterContinuation());
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleForPlaceholder() {
RefPtr<ComputedStyle>& cache = mNonInheritingComputedStyles
[nsCSSAnonBoxes::NonInheriting::oofPlaceholder];
if (cache) {
RefPtr<ComputedStyle> retval = cache;
return retval.forget();
}
RefPtr<ComputedStyle> computedValues =
Servo_ComputedValues_Inherit(mRawSet.get(),
nsCSSAnonBoxes::oofPlaceholder(), nullptr,
InheritTarget::PlaceholderFrame)
.Consume();
MOZ_ASSERT(computedValues);
cache = computedValues;
return computedValues.forget();
}
static inline bool LazyPseudoIsCacheable(CSSPseudoElementType aType,
const Element& aOriginatingElement,
ComputedStyle* aParentContext) {
return aParentContext &&
!nsCSSPseudoElements::IsEagerlyCascadedInServo(aType) &&
aOriginatingElement.HasServoData() &&
!Servo_Element_IsPrimaryStyleReusedViaRuleNode(&aOriginatingElement);
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePseudoElementStyle(
Element* aOriginatingElement, CSSPseudoElementType aType,
ComputedStyle* aParentContext, Element* aPseudoElement) {
// Runs from frame construction, this should have clean styles already, except
// with non-lazy FC...
UpdateStylistIfNeeded();
MOZ_ASSERT(aType < CSSPseudoElementType::Count);
RefPtr<ComputedStyle> computedValues;
if (aPseudoElement) {
MOZ_ASSERT(aType == aPseudoElement->GetPseudoElementType());
computedValues =
Servo_ResolveStyle(aPseudoElement, mRawSet.get()).Consume();
} else {
bool cacheable =
LazyPseudoIsCacheable(aType, *aOriginatingElement, aParentContext);
computedValues =
cacheable ? aParentContext->GetCachedLazyPseudoStyle(aType) : nullptr;
if (!computedValues) {
computedValues = Servo_ResolvePseudoStyle(aOriginatingElement, aType,
/* is_probe = */ false,
aParentContext, mRawSet.get())
.Consume();
if (cacheable) {
aParentContext->SetCachedLazyPseudoStyle(computedValues);
}
}
}
MOZ_ASSERT(computedValues);
return computedValues.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleLazily(
Element* aElement, CSSPseudoElementType aPseudoType,
StyleRuleInclusion aRuleInclusion) {
PreTraverseSync();
return ResolveStyleLazilyInternal(aElement, aPseudoType, aRuleInclusion);
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveInheritingAnonymousBoxStyle(
nsAtom* aPseudoTag, ComputedStyle* aParentContext) {
MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
!nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
MOZ_ASSERT_IF(aParentContext, !StylistNeedsUpdate());
UpdateStylistIfNeeded();
RefPtr<ComputedStyle> style = nullptr;
if (aParentContext) {
style = aParentContext->GetCachedInheritingAnonBoxStyle(aPseudoTag);
}
if (!style) {
style = Servo_ComputedValues_GetForAnonymousBox(aParentContext, aPseudoTag,
mRawSet.get())
.Consume();
MOZ_ASSERT(style);
if (aParentContext) {
aParentContext->SetCachedInheritedAnonBoxStyle(aPseudoTag, style);
}
}
return style.forget();
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsAtom* 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<ComputedStyle>& cache = mNonInheritingComputedStyles[type];
if (cache) {
RefPtr<ComputedStyle> 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<ComputedStyle> computedValues =
Servo_ComputedValues_GetForAnonymousBox(nullptr, aPseudoTag,
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
cache = computedValues;
return computedValues.forget();
}
#ifdef MOZ_XUL
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveXULTreePseudoStyle(
dom::Element* aParentElement, nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
ComputedStyle* aParentContext, const AtomArray& aInputWord) {
MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag));
MOZ_ASSERT(aParentContext);
MOZ_ASSERT(!StylistNeedsUpdate());
return Servo_ComputedValues_ResolveXULTreePseudoStyle(
aParentElement, aPseudoTag, aParentContext, &aInputWord,
mRawSet.get())
.Consume();
}
#endif
// manage the set of style sheets in the style set
nsresult ServoStyleSet::AppendStyleSheet(SheetType aType, StyleSheet* aSheet) {
MOZ_ASSERT(aSheet);
MOZ_ASSERT(aSheet->IsApplicable());
MOZ_ASSERT(IsCSSSheetType(aType));
MOZ_ASSERT(aSheet->RawContents(),
"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);
SetStylistStyleSheetsDirty();
}
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(*aSheet);
}
return NS_OK;
}
nsresult ServoStyleSet::RemoveStyleSheet(SheetType aType, StyleSheet* aSheet) {
MOZ_ASSERT(aSheet);
MOZ_ASSERT(IsCSSSheetType(aType));
RemoveSheetOfType(aType, aSheet);
if (mRawSet) {
// Maintain a mirrored list of sheets on the servo side.
Servo_StyleSet_RemoveStyleSheet(mRawSet.get(), aSheet);
SetStylistStyleSheetsDirty();
}
if (mStyleRuleMap) {
mStyleRuleMap->SheetRemoved(*aSheet);
}
return NS_OK;
}
nsresult ServoStyleSet::ReplaceSheets(
SheetType aType, const nsTArray<RefPtr<StyleSheet>>& 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(), sheet);
}
}
mSheets[aType].Clear();
// Add in all the new sheets.
for (auto& sheet : aNewSheets) {
AppendSheetOfType(aType, sheet);
if (mRawSet) {
MOZ_ASSERT(sheet->RawContents(),
"Raw sheet should be in place before replacement.");
Servo_StyleSet_AppendStyleSheet(mRawSet.get(), sheet);
}
}
// Just don't bother calling SheetRemoved / SheetAdded, and recreate the rule
// map when needed.
mStyleRuleMap = nullptr;
return NS_OK;
}
nsresult ServoStyleSet::InsertStyleSheetBefore(SheetType aType,
StyleSheet* aNewSheet,
StyleSheet* aReferenceSheet) {
MOZ_ASSERT(aNewSheet);
MOZ_ASSERT(aReferenceSheet);
MOZ_ASSERT(aNewSheet->IsApplicable());
MOZ_ASSERT(aNewSheet != aReferenceSheet, "Can't place sheet before itself.");
MOZ_ASSERT(aNewSheet->RawContents(),
"Raw sheet should be in place before insertion.");
MOZ_ASSERT(aReferenceSheet->RawContents(),
"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,
aReferenceSheet);
SetStylistStyleSheetsDirty();
}
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(*aNewSheet);
}
return NS_OK;
}
int32_t ServoStyleSet::SheetCount(SheetType aType) const {
MOZ_ASSERT(IsCSSSheetType(aType));
return mSheets[aType].Length();
}
StyleSheet* ServoStyleSet::StyleSheetAt(SheetType aType, int32_t aIndex) const {
MOZ_ASSERT(IsCSSSheetType(aType));
return mSheets[aType][aIndex];
}
void ServoStyleSet::AppendAllNonDocumentAuthorSheets(
nsTArray<StyleSheet*>& aArray) const {
if (mDocument) {
mDocument->BindingManager()->AppendAllSheets(aArray);
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
aArray.AppendElement(aShadowRoot.SheetAt(index));
}
});
}
}
nsresult ServoStyleSet::RemoveDocStyleSheet(StyleSheet* aSheet) {
return RemoveStyleSheet(SheetType::Doc, aSheet);
}
nsresult ServoStyleSet::AddDocStyleSheet(StyleSheet* aSheet,
Document* aDocument) {
MOZ_ASSERT(aSheet->IsApplicable());
MOZ_ASSERT(aSheet->RawContents(),
"Raw sheet should be in place by this point.");
RefPtr<StyleSheet> 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.
StyleSheet* 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, 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);
SetStylistStyleSheetsDirty();
}
}
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(*aSheet);
}
return NS_OK;
}
already_AddRefed<ComputedStyle> ServoStyleSet::ProbePseudoElementStyle(
const Element& aOriginatingElement, CSSPseudoElementType aType,
ComputedStyle* aParentContext) {
// Runs from frame construction, this should have clean styles already, except
// with non-lazy FC...
UpdateStylistIfNeeded();
// NB: We ignore aParentContext, because in some cases
// (first-line/first-letter on anonymous box blocks) Gecko passes something
// nonsensical there. In all other cases we want to inherit directly from
// aOriginatingElement's styles anyway.
MOZ_ASSERT(aType < CSSPseudoElementType::Count);
bool cacheable =
LazyPseudoIsCacheable(aType, aOriginatingElement, aParentContext);
RefPtr<ComputedStyle> computedValues =
cacheable ? aParentContext->GetCachedLazyPseudoStyle(aType) : nullptr;
if (!computedValues) {
computedValues =
Servo_ResolvePseudoStyle(&aOriginatingElement, aType,
/* is_probe = */ true, nullptr, mRawSet.get())
.Consume();
if (!computedValues) {
return nullptr;
}
if (cacheable) {
// NB: We don't need to worry about the before/after handling below
// because those are eager and thus not |cacheable| anyway.
aParentContext->SetCachedLazyPseudoStyle(computedValues);
}
}
// 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 =
computedValues->ComputedData()->GetStyleDisplay();
const nsStyleContent* content =
computedValues->ComputedData()->GetStyleContent();
if (display->mDisplay == StyleDisplay::None ||
content->ContentCount() == 0) {
return nullptr;
}
}
return computedValues.forget();
}
bool ServoStyleSet::StyleDocument(ServoTraversalFlags aFlags) {
MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
if (!mDocument->GetServoRestyleRoot()) {
return false;
}
PreTraverse(aFlags);
AutoPrepareTraversal guard(this);
const SnapshotTable& snapshots = Snapshots();
// Restyle the document from the root element and each of the document level
// NAC subtree roots.
bool postTraversalRequired = false;
Element* rootElement = mDocument->GetRootElement();
MOZ_ASSERT_IF(rootElement, rootElement->HasServoData());
if (ShouldTraverseInParallel()) {
aFlags |= ServoTraversalFlags::ParallelTraversal;
}
// Do the first traversal.
DocumentStyleRootIterator iter(mDocument->GetServoRestyleRoot());
while (Element* root = iter.GetNextStyleRoot()) {
MOZ_ASSERT(MayTraverseFrom(root));
Element* parent = root->GetFlattenedTreeParentElementForStyle();
MOZ_ASSERT_IF(parent,
!parent->HasAnyOfFlags(Element::kAllServoDescendantBits));
postTraversalRequired |=
Servo_TraverseSubtree(root, mRawSet.get(), &snapshots, aFlags);
postTraversalRequired |= root->HasAnyOfFlags(
Element::kAllServoDescendantBits | NODE_NEEDS_FRAME);
if (parent) {
MOZ_ASSERT(root == mDocument->GetServoRestyleRoot());
if (parent->HasDirtyDescendantsForServo()) {
// If any style invalidation was triggered in our siblings, then we may
// need to post-traverse them, even if the root wasn't restyled after
// all.
uint32_t existingBits = mDocument->GetServoRestyleRootDirtyBits();
// We need to propagate the existing bits to the parent.
parent->SetFlags(existingBits);
mDocument->SetServoRestyleRoot(
parent, existingBits | ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
postTraversalRequired = true;
}
}
}
// If there are still animation restyles needed, trigger a second traversal to
// update CSS animations or transitions' styles.
//
// Note that we need to check the style root again, because doing another
// PreTraverse on the EffectCompositor might alter the style root. But we
// don't need to worry about NAC, since document-level NAC shouldn't have
// animations.
//
// 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.
if (GetPresContext()->EffectCompositor()->PreTraverse(aFlags)) {
nsINode* styleRoot = mDocument->GetServoRestyleRoot();
Element* root =
styleRoot->IsElement() ? styleRoot->AsElement() : rootElement;
postTraversalRequired |=
Servo_TraverseSubtree(root, mRawSet.get(), &snapshots, aFlags);
postTraversalRequired |= root->HasAnyOfFlags(
Element::kAllServoDescendantBits | NODE_NEEDS_FRAME);
}
return postTraversalRequired;
}
void ServoStyleSet::StyleNewSubtree(Element* aRoot) {
MOZ_ASSERT(GetPresContext());
MOZ_ASSERT(!aRoot->HasServoData());
MOZ_ASSERT(aRoot->GetFlattenedTreeParentNodeForStyle(),
"Not in the flat tree? Fishy!");
PreTraverseSync();
AutoPrepareTraversal guard(this);
// Do the traversal. The snapshots will not be used.
const SnapshotTable& snapshots = Snapshots();
auto flags = ServoTraversalFlags::Empty;
if (ShouldTraverseInParallel()) {
flags |= ServoTraversalFlags::ParallelTraversal;
}
DebugOnly<bool> postTraversalRequired =
Servo_TraverseSubtree(aRoot, mRawSet.get(), &snapshots, flags);
MOZ_ASSERT(!postTraversalRequired);
// Annoyingly, the newly-styled content may have animations that need
// starting, which requires traversing them again. Mark the elements
// that need animation processing, then do a forgetful traversal to
// update the styles and clear the animation bits.
if (GetPresContext()->EffectCompositor()->PreTraverseInSubtree(flags,
aRoot)) {
postTraversalRequired = Servo_TraverseSubtree(
aRoot, mRawSet.get(), &snapshots,
ServoTraversalFlags::AnimationOnly | ServoTraversalFlags::Forgetful |
ServoTraversalFlags::ClearAnimationOnlyDirtyDescendants);
MOZ_ASSERT(!postTraversalRequired);
}
}
void ServoStyleSet::MarkOriginsDirty(OriginFlags aChangedOrigins) {
if (MOZ_UNLIKELY(!mRawSet)) {
return;
}
SetStylistStyleSheetsDirty();
Servo_StyleSet_NoteStyleSheetsChanged(mRawSet.get(), aChangedOrigins);
}
void ServoStyleSet::SetStylistStyleSheetsDirty() {
// Note that there's another hidden mutator of mStylistState for XBL style
// sets in MediumFeaturesChanged...
//
// We really need to stop using a full-blown StyleSet there...
mStylistState |= StylistState::StyleSheetsDirty;
// We need to invalidate cached style in getComputedStyle for undisplayed
// elements, since we don't know if any of the style sheet change that we
// do would affect undisplayed elements.
if (nsPresContext* presContext = GetPresContext()) {
// XBL sheets don't have a pres context, but invalidating the restyle
// generation in that case is handled by SetXBLStyleSheetsDirty in the
// "master" stylist.
presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
}
}
void ServoStyleSet::SetStylistXBLStyleSheetsDirty() {
mStylistState |= StylistState::XBLStyleSheetsDirty;
// We need to invalidate cached style in getComputedStyle for undisplayed
// elements, since we don't know if any of the style sheet change that we
// do would affect undisplayed elements.
MOZ_ASSERT(GetPresContext());
GetPresContext()->RestyleManager()->IncrementUndisplayedRestyleGeneration();
}
void ServoStyleSet::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
if (mStyleRuleMap) {
mStyleRuleMap->RuleAdded(aSheet, aRule);
}
// FIXME(emilio): Could be more granular based on aRule.
MarkOriginsDirty(aSheet.GetOrigin());
}
void ServoStyleSet::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
if (mStyleRuleMap) {
mStyleRuleMap->RuleRemoved(aSheet, aRule);
}
// FIXME(emilio): Could be more granular based on aRule.
MarkOriginsDirty(aSheet.GetOrigin());
}
void ServoStyleSet::RuleChanged(StyleSheet& aSheet, css::Rule* aRule) {
// FIXME(emilio): Could be more granular based on aRule.
MarkOriginsDirty(aSheet.GetOrigin());
}
#ifdef DEBUG
void ServoStyleSet::AssertTreeIsClean() {
DocumentStyleRootIterator iter(mDocument);
while (Element* root = iter.GetNextStyleRoot()) {
Servo_AssertTreeIsClean(root);
}
}
#endif
bool ServoStyleSet::GetKeyframesForName(const Element& aElement,
const ComputedStyle& aStyle,
nsAtom* aName,
const nsTimingFunction& aTimingFunction,
nsTArray<Keyframe>& aKeyframes) {
MOZ_ASSERT(!StylistNeedsUpdate());
return Servo_StyleSet_GetKeyframesForName(
mRawSet.get(), &aElement, &aStyle, aName, &aTimingFunction, &aKeyframes);
}
nsTArray<ComputedKeyframeValues> ServoStyleSet::GetComputedKeyframeValuesFor(
const nsTArray<Keyframe>& aKeyframes, Element* aElement,
const mozilla::ComputedStyle* aStyle) {
nsTArray<ComputedKeyframeValues> result(aKeyframes.Length());
// Construct each nsTArray<PropertyStyleAnimationValuePair> here.
result.AppendElements(aKeyframes.Length());
Servo_GetComputedKeyframeValues(&aKeyframes, aElement, aStyle, mRawSet.get(),
&result);
return result;
}
void ServoStyleSet::GetAnimationValues(
RawServoDeclarationBlock* aDeclarations, Element* aElement,
const ComputedStyle* aComputedStyle,
nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues) {
// Servo_GetAnimationValues below won't handle ignoring existing element
// data for bfcached documents. (See comment in ResolveStyleLazily
// about these bfcache issues.)
Servo_GetAnimationValues(aDeclarations, aElement, aComputedStyle,
mRawSet.get(), &aAnimationValues);
}
already_AddRefed<ComputedStyle> ServoStyleSet::GetBaseContextForElement(
Element* aElement, const ComputedStyle* aStyle) {
return Servo_StyleSet_GetBaseComputedValuesForElement(mRawSet.get(), aElement,
aStyle, &Snapshots())
.Consume();
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveServoStyleByAddingAnimation(
Element* aElement, const ComputedStyle* aStyle,
RawServoAnimationValue* aAnimationValue) {
return Servo_StyleSet_GetComputedValuesByAddingAnimation(
mRawSet.get(), aElement, aStyle, &Snapshots(), aAnimationValue)
.Consume();
}
already_AddRefed<RawServoAnimationValue> ServoStyleSet::ComputeAnimationValue(
Element* aElement, RawServoDeclarationBlock* aDeclarations,
const mozilla::ComputedStyle* aStyle) {
return Servo_AnimationValue_Compute(aElement, aDeclarations, aStyle,
mRawSet.get())
.Consume();
}
bool ServoStyleSet::EnsureUniqueInnerOnCSSSheets() {
using SheetOwner =
Variant<ServoStyleSet*, nsXBLPrototypeBinding*, ShadowRoot*>;
AutoTArray<Pair<StyleSheet*, SheetOwner>, 32> queue;
for (auto& entryArray : mSheets) {
for (auto& sheet : entryArray) {
StyleSheet* downcasted = sheet;
queue.AppendElement(MakePair(downcasted, SheetOwner{this}));
}
}
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
queue.AppendElement(
MakePair(aShadowRoot.SheetAt(index), SheetOwner{&aShadowRoot}));
}
});
mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
[&](nsXBLPrototypeBinding* aProto) {
AutoTArray<StyleSheet*, 3> sheets;
aProto->AppendStyleSheetsTo(sheets);
for (auto* sheet : sheets) {
queue.AppendElement(MakePair(sheet, SheetOwner{aProto}));
}
return true;
});
bool anyNonDocStyleChanged = false;
while (!queue.IsEmpty()) {
uint32_t idx = queue.Length() - 1;
auto* sheet = queue[idx].first();
SheetOwner owner = queue[idx].second();
queue.RemoveElementAt(idx);
if (!sheet->HasUniqueInner()) {
RawServoAuthorStyles* authorStyles = nullptr;
if (owner.is<ShadowRoot*>()) {
authorStyles = owner.as<ShadowRoot*>()->GetServoStyles();
} else if (owner.is<nsXBLPrototypeBinding*>()) {
authorStyles = owner.as<nsXBLPrototypeBinding*>()->GetServoStyles();
}
if (authorStyles) {
Servo_AuthorStyles_ForceDirty(authorStyles);
mNeedsRestyleAfterEnsureUniqueInner = true;
anyNonDocStyleChanged = true;
}
}
// Only call EnsureUniqueInner for complete sheets. If we do call it on
// incomplete sheets, we'll cause problems when the sheet is actually
// loaded. We don't care about incomplete sheets here anyway, because this
// method is only invoked by nsPresContext::EnsureSafeToHandOutCSSRules.
// The CSSRule objects we are handing out won't contain any rules derived
// from incomplete sheets (because they aren't yet applied in styling).
if (sheet->IsComplete()) {
sheet->EnsureUniqueInner();
}
// Enqueue all the sheet's children.
AutoTArray<StyleSheet*, 3> children;
sheet->AppendAllChildSheets(children);
for (auto* sheet : children) {
queue.AppendElement(MakePair(sheet, owner));
}
}
if (anyNonDocStyleChanged) {
SetStylistXBLStyleSheetsDirty();
}
if (mNeedsRestyleAfterEnsureUniqueInner) {
// TODO(emilio): We could make this faster if needed tracking the specific
// origins and all that, but the only caller of this doesn't seem to really
// care about perf.
MarkOriginsDirty(OriginFlags::All);
}
bool res = mNeedsRestyleAfterEnsureUniqueInner;
mNeedsRestyleAfterEnsureUniqueInner = false;
return res;
}
void ServoStyleSet::ClearCachedStyleData() {
ClearNonInheritingComputedStyles();
Servo_StyleSet_RebuildCachedData(mRawSet.get());
}
void ServoStyleSet::CompatibilityModeChanged() {
Servo_StyleSet_CompatModeChanged(mRawSet.get());
SetStylistStyleSheetsDirty();
}
void ServoStyleSet::ClearNonInheritingComputedStyles() {
for (RefPtr<ComputedStyle>& ptr : mNonInheritingComputedStyles) {
ptr = nullptr;
}
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleLazilyInternal(
Element* aElement, CSSPseudoElementType aPseudoType,
StyleRuleInclusion aRuleInclusion) {
MOZ_ASSERT(GetPresContext(),
"For now, no style resolution without a pres context");
GetPresContext()->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<ComputedStyle> computedValues =
Servo_ResolveStyleLazily(elementForStyleResolution,
pseudoTypeForStyleResolution, aRuleInclusion,
&Snapshots(), mRawSet.get())
.Consume();
if (GetPresContext()->EffectCompositor()->PreTraverse(aElement,
aPseudoType)) {
computedValues =
Servo_ResolveStyleLazily(elementForStyleResolution,
pseudoTypeForStyleResolution, aRuleInclusion,
&Snapshots(), mRawSet.get())
.Consume();
}
MOZ_DIAGNOSTIC_ASSERT(computedValues->PresContextForFrame() ==
GetPresContext() ||
aElement->OwnerDoc()->GetBFCacheEntry());
return computedValues.forget();
}
bool ServoStyleSet::AppendFontFaceRules(
nsTArray<nsFontFaceRuleContainer>& aArray) {
// TODO(emilio): Can we make this so this asserts instead?
UpdateStylistIfNeeded();
Servo_StyleSet_GetFontFaceRules(mRawSet.get(), &aArray);
return true;
}
const RawServoCounterStyleRule* ServoStyleSet::CounterStyleRuleForName(
nsAtom* aName) {
MOZ_ASSERT(!StylistNeedsUpdate());
return Servo_StyleSet_GetCounterStyleRule(mRawSet.get(), aName);
}
already_AddRefed<gfxFontFeatureValueSet>
ServoStyleSet::BuildFontFeatureValueSet() {
MOZ_ASSERT(!StylistNeedsUpdate());
RefPtr<gfxFontFeatureValueSet> set =
Servo_StyleSet_BuildFontFeatureValueSet(mRawSet.get());
return set.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveForDeclarations(
const ComputedStyle* aParentOrNull,
RawServoDeclarationBlockBorrowed aDeclarations) {
// No need to update the stylist, we're only cascading aDeclarations.
return Servo_StyleSet_ResolveForDeclarations(mRawSet.get(), aParentOrNull,
aDeclarations)
.Consume();
}
void ServoStyleSet::UpdateStylist() {
MOZ_ASSERT(StylistNeedsUpdate());
if (mStylistState & StylistState::StyleSheetsDirty) {
// There's no need to compute invalidations and such for an XBL styleset,
// since they are loaded and unloaded synchronously, and they don't have to
// deal with dynamic content changes.
Element* root = mDocument->GetRootElement();
const ServoElementSnapshotTable* snapshots = nullptr;
if (nsPresContext* pc = GetPresContext()) {
snapshots = &pc->RestyleManager()->Snapshots();
}
Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root, snapshots);
}
if (MOZ_UNLIKELY(mStylistState & StylistState::XBLStyleSheetsDirty)) {
MOZ_ASSERT(GetPresContext(), "How did they get dirty?");
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
Servo_AuthorStyles_Flush(authorStyles, mRawSet.get());
}
});
mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
[&](nsXBLPrototypeBinding* aProto) {
if (auto* authorStyles = aProto->GetServoStyles()) {
Servo_AuthorStyles_Flush(authorStyles, mRawSet.get());
}
return true;
});
}
mStylistState = StylistState::NotDirty;
}
void ServoStyleSet::MaybeGCRuleTree() {
MOZ_ASSERT(NS_IsMainThread());
Servo_MaybeGCRuleTree(mRawSet.get());
}
/* static */ bool ServoStyleSet::MayTraverseFrom(const Element* aElement) {
MOZ_ASSERT(aElement->IsInComposedDoc());
nsINode* parent = aElement->GetFlattenedTreeParentNodeForStyle();
if (!parent) {
return false;
}
if (!parent->IsElement()) {
MOZ_ASSERT(parent->IsDocument());
return true;
}
if (!parent->AsElement()->HasServoData()) {
return false;
}
return !Servo_Element_IsDisplayNone(parent->AsElement());
}
bool ServoStyleSet::ShouldTraverseInParallel() const {
MOZ_ASSERT(mDocument->GetShell(), "Styling a document without a shell?");
if (!mDocument->GetShell()->IsActive()) {
return false;
}
#ifdef MOZ_GECKO_PROFILER
if (profiler_feature_active(ProfilerFeature::SequentialStyle)) {
return false;
}
#endif
return true;
}
void ServoStyleSet::PrependSheetOfType(SheetType aType, StyleSheet* aSheet) {
aSheet->AddStyleSet(this);
mSheets[aType].InsertElementAt(0, aSheet);
}
void ServoStyleSet::AppendSheetOfType(SheetType aType, StyleSheet* aSheet) {
aSheet->AddStyleSet(this);
mSheets[aType].AppendElement(aSheet);
}
void ServoStyleSet::InsertSheetOfType(SheetType aType, StyleSheet* aSheet,
StyleSheet* 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, StyleSheet* 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<PostTraversalTask> tasks;
tasks.SwapElements(mPostTraversalTasks);
for (auto& task : tasks) {
task.Run();
}
}
ServoStyleRuleMap* ServoStyleSet::StyleRuleMap() {
if (!mStyleRuleMap) {
mStyleRuleMap = MakeUnique<ServoStyleRuleMap>();
}
mStyleRuleMap->EnsureTable(*this);
return mStyleRuleMap.get();
}
bool ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
nsAtom* aAttribute) const {
return Servo_StyleSet_MightHaveAttributeDependency(mRawSet.get(), &aElement,
aAttribute);
}
bool ServoStyleSet::HasStateDependency(const Element& aElement,
EventStates aState) const {
return Servo_StyleSet_HasStateDependency(mRawSet.get(), &aElement,
aState.ServoValue());
}
bool ServoStyleSet::HasDocumentStateDependency(EventStates aState) const {
return Servo_StyleSet_HasDocumentStateDependency(mRawSet.get(),
aState.ServoValue());
}
already_AddRefed<ComputedStyle> ServoStyleSet::ReparentComputedStyle(
ComputedStyle* aComputedStyle, ComputedStyle* aNewParent,
ComputedStyle* aNewParentIgnoringFirstLine, ComputedStyle* aNewLayoutParent,
Element* aElement) {
return Servo_ReparentStyle(aComputedStyle, aNewParent,
aNewParentIgnoringFirstLine, aNewLayoutParent,
aElement, mRawSet.get())
.Consume();
}
NS_IMPL_ISUPPORTS(UACacheReporter, nsIMemoryReporter)
MOZ_DEFINE_MALLOC_SIZE_OF(ServoUACacheMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoUACacheMallocEnclosingSizeOf)
NS_IMETHODIMP
UACacheReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
ServoStyleSetSizes sizes;
Servo_UACache_AddSizeOf(ServoUACacheMallocSizeOf,
ServoUACacheMallocEnclosingSizeOf, &sizes);
#define REPORT(_path, _amount, _desc) \
do { \
size_t __amount = _amount; /* evaluate _amount only once */ \
if (__amount > 0) { \
MOZ_COLLECT_REPORT(_path, KIND_HEAP, UNITS_BYTES, __amount, _desc); \
} \
} while (0)
// The UA cache does not contain the rule tree; that's in the StyleSet.
MOZ_RELEASE_ASSERT(sizes.mRuleTree == 0);
REPORT("explicit/layout/servo-ua-cache/precomputed-pseudos",
sizes.mPrecomputedPseudos,
"Memory used by precomputed pseudo-element declarations within the "
"UA cache.");
REPORT("explicit/layout/servo-ua-cache/element-and-pseudos-maps",
sizes.mElementAndPseudosMaps,
"Memory used by element and pseudos maps within the UA cache.");
REPORT("explicit/layout/servo-ua-cache/invalidation-map",
sizes.mInvalidationMap,
"Memory used by invalidation maps within the UA cache.");
REPORT("explicit/layout/servo-ua-cache/revalidation-selectors",
sizes.mRevalidationSelectors,
"Memory used by selectors for cache revalidation within the UA "
"cache.");
REPORT("explicit/layout/servo-ua-cache/other", sizes.mOther,
"Memory used by other data within the UA cache");
return NS_OK;
}