Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/DocumentStyleRootIterator.h"
#include "mozilla/AttributeStyles.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Keyframe.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleRuleMap.h"
#include "mozilla/ServoTypes.h"
#include "mozilla/SMILAnimationController.h"
#include "mozilla/MediaFeatureChange.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/CSSBinding.h"
#include "mozilla/dom/CSSCounterStyleRule.h"
#include "mozilla/dom/CSSFontFaceRule.h"
#include "mozilla/dom/CSSFontFeatureValuesRule.h"
#include "mozilla/dom/CSSFontPaletteValuesRule.h"
#include "mozilla/dom/CSSImportRule.h"
#include "mozilla/dom/CSSContainerRule.h"
#include "mozilla/dom/CSSLayerBlockRule.h"
#include "mozilla/dom/CSSLayerStatementRule.h"
#include "mozilla/dom/CSSMarginRule.h"
#include "mozilla/dom/CSSMediaRule.h"
#include "mozilla/dom/CSSMozDocumentRule.h"
#include "mozilla/dom/CSSKeyframesRule.h"
#include "mozilla/dom/CSSKeyframeRule.h"
#include "mozilla/dom/CSSNamespaceRule.h"
#include "mozilla/dom/CSSNestedDeclarations.h"
#include "mozilla/dom/CSSPageRule.h"
#include "mozilla/dom/CSSPropertyRule.h"
#include "mozilla/dom/CSSPositionTryRule.h"
#include "mozilla/dom/CSSScopeRule.h"
#include "mozilla/dom/CSSSupportsRule.h"
#include "mozilla/dom/CSSStartingStyleRule.h"
#include "mozilla/dom/CSSStyleRule.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 "nsIAnonymousContentCreator.h"
#include "nsLayoutUtils.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsPrintfCString.h"
#include "gfxUserFontSet.h"
#include "nsWindowSizes.h"
namespace mozilla {
using namespace dom;
#ifdef DEBUG
bool ServoStyleSet::IsCurrentThreadInServoTraversal() {
return sInServoTraversal && (NS_IsMainThread() || Servo_IsWorkerThread());
}
#endif
// The definition of kOrigins relies on this.
static_assert(static_cast<uint8_t>(StyleOrigin::UserAgent) ==
static_cast<uint8_t>(OriginFlags::UserAgent));
static_assert(static_cast<uint8_t>(StyleOrigin::User) ==
static_cast<uint8_t>(OriginFlags::User));
static_assert(static_cast<uint8_t>(StyleOrigin::Author) ==
static_cast<uint8_t>(OriginFlags::Author));
constexpr const StyleOrigin ServoStyleSet::kOrigins[];
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)
: mSetInServoTraversal(aSet) {
MOZ_ASSERT(!aSet->StylistNeedsUpdate());
}
private:
AutoSetInServoTraversal mSetInServoTraversal;
};
ServoStyleSet::ServoStyleSet(Document& aDocument) : mDocument(&aDocument) {
PreferenceSheet::EnsureInitialized();
PodArrayZero(mCachedAnonymousContentStyleIndexes);
mRawData.reset(Servo_StyleSet_Init(&aDocument));
}
ServoStyleSet::~ServoStyleSet() {
MOZ_ASSERT(!IsInServoTraversal());
EnumerateStyleSheets([&](StyleSheet& aSheet) { aSheet.DropStyleSet(this); });
}
nsPresContext* ServoStyleSet::GetPresContext() {
return mDocument->GetPresContext();
}
template <typename Functor>
static void EnumerateShadowRoots(const Document& aDoc, const Functor& aCb) {
const Document::ShadowRootSet& shadowRoots = aDoc.ComposedShadowRoots();
for (ShadowRoot* root : shadowRoots) {
MOZ_ASSERT(root);
MOZ_DIAGNOSTIC_ASSERT(root->IsInComposedDoc());
aCb(*root);
}
}
void ServoStyleSet::ShellDetachedFromDocument() {
ClearNonInheritingComputedStyles();
mCachedAnonymousContentStyles.Clear();
PodArrayZero(mCachedAnonymousContentStyleIndexes);
mStyleRuleMap = nullptr;
// Remove all our stylesheets...
for (auto origin : kOrigins) {
for (size_t count = SheetCount(origin); count--;) {
RemoveStyleSheet(*SheetAt(origin, count));
}
}
// And remove all the CascadeDatas from memory.
UpdateStylistIfNeeded();
// Also GC the ruletree if it got big now that the DOM no longer has
// references to styles around anymore.
MaybeGCRuleTree();
}
void ServoStyleSet::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
// TODO(emilio): We could keep track of the actual shadow roots that need
// their styles recomputed.
SetStylistShadowDOMStyleSheetsDirty();
// FIXME(emilio): This should be done using stylesheet invalidation instead.
if (nsPresContext* pc = GetPresContext()) {
pc->RestyleManager()->PostRestyleEvent(
aShadowRoot.Host(), RestyleHint::RestyleSubtree(), nsChangeHint(0));
}
}
void ServoStyleSet::InvalidateStyleForDocumentStateChanges(
DocumentState 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 Shadow DOM. Consider just enumerating shadow roots instead and run
// invalidation individually, passing mRawData for the UA / User sheets.
AutoTArray<const StyleAuthorStyles*, 20> nonDocumentStyles;
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
});
Servo_InvalidateStyleForDocStateChanges(root, mRawData.get(),
&nonDocumentStyles,
aStatesChanged.GetInternalValue());
}
static const MediaFeatureChangeReason kMediaFeaturesAffectingDefaultStyle =
// Zoom changes change the meaning of em units.
MediaFeatureChangeReason::ZoomChange |
// 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
MediaFeatureChangeReason::ResolutionChange;
RestyleHint ServoStyleSet::MediumFeaturesChanged(
MediaFeatureChangeReason aReason) {
AutoTArray<StyleAuthorStyles*, 20> nonDocumentStyles;
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
nonDocumentStyles.AppendElement(authorStyles);
}
});
const bool mayAffectDefaultStyle =
bool(aReason & kMediaFeaturesAffectingDefaultStyle);
const MediumFeaturesChangedResult result =
Servo_StyleSet_MediumFeaturesChanged(mRawData.get(), &nonDocumentStyles,
mayAffectDefaultStyle);
const bool viewportChanged =
bool(aReason & MediaFeatureChangeReason::ViewportChange);
if (viewportChanged) {
InvalidateForViewportUnits(OnlyDynamic::No);
}
const bool rulesChanged =
result.mAffectsDocumentRules || result.mAffectsNonDocumentRules;
if (result.mAffectsDocumentRules) {
SetStylistStyleSheetsDirty();
}
if (result.mAffectsNonDocumentRules) {
SetStylistShadowDOMStyleSheetsDirty();
}
if (rulesChanged) {
// TODO(emilio): This could be more granular.
return RestyleHint::RestyleSubtree();
}
return RestyleHint{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 (mRawData) {
aSizes.mLayoutStyleSetsOther += mallocSizeOf(mRawData.get());
ServoStyleSetSizes sizes;
// Measure mRawData. We use ServoStyleSetMallocSizeOf rather than
// aMallocSizeOf to distinguish in DMD's output the memory measured within
// Servo code.
Servo_StyleSet_AddSizeOfExcludingThis(ServoStyleSetMallocSizeOf,
ServoStyleSetMallocEnclosingSizeOf,
&sizes, mRawData.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, RestyleHint::RestyleSubtree(), nsChangeHint(0));
}
}
Servo_StyleSet_SetAuthorStyleDisabled(mRawData.get(), mAuthorStyleDisabled);
SetStylistStyleSheetsDirty();
}
const ServoElementSnapshotTable& ServoStyleSet::Snapshots() {
MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
return GetPresContext()->RestyleManager()->Snapshots();
}
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.
Unused << mDocument->GetRootElement();
// FIXME(emilio): These two shouldn't be needed in theory, the call to the
// same function in PresShell should do the work, but as it turns out we
// ProcessPendingRestyles() twice, and runnables from frames just constructed
// can end up doing editing stuff, which adds stylesheets etc...
mDocument->FlushUserFontSet();
UpdateStylistIfNeeded();
mDocument->ResolveScheduledPresAttrs();
LookAndFeel::NativeInit();
mDocument->CacheAllKnownLangPrefs();
if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) {
nsPresContext* presContext = GetPresContext();
MOZ_ASSERT(presContext,
"For now, we don't call into here without a pres context");
// Ensure that the @font-face data is not stale
uint64_t generation = userFontSet->GetGeneration();
if (generation != mUserFontSetUpdateGeneration) {
mDocument->GetFonts()->CacheFontLoadability();
presContext->UpdateFontCacheUserFonts(userFontSet);
mUserFontSetUpdateGeneration = generation;
}
}
MOZ_ASSERT(!StylistNeedsUpdate());
}
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(
const StylePerDocumentStyleData* aRawData, ComputedStyle& aParent,
PseudoStyleType aType) {
MOZ_ASSERT(aType == PseudoStyleType::mozText ||
aType == PseudoStyleType::firstLetterContinuation);
auto inheritTarget = aType == PseudoStyleType::mozText
? InheritTarget::Text
: InheritTarget::FirstLetterContinuation;
RefPtr<ComputedStyle> style = aParent.GetCachedInheritingAnonBoxStyle(aType);
if (!style) {
style =
Servo_ComputedValues_Inherit(aRawData, aType, &aParent, inheritTarget)
.Consume();
MOZ_ASSERT(style);
aParent.SetCachedInheritedAnonBoxStyle(style);
}
return style.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStyleForText(
nsIContent* aTextNode, ComputedStyle* aParentStyle) {
MOZ_ASSERT(aTextNode && aTextNode->IsText());
MOZ_ASSERT(aTextNode->GetParent());
MOZ_ASSERT(aParentStyle);
return ResolveStyleForTextOrFirstLetterContinuation(
mRawData.get(), *aParentStyle, PseudoStyleType::mozText);
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveStyleForFirstLetterContinuation(
ComputedStyle* aParentStyle) {
MOZ_ASSERT(aParentStyle);
return ResolveStyleForTextOrFirstLetterContinuation(
mRawData.get(), *aParentStyle, PseudoStyleType::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(mRawData.get(),
PseudoStyleType::oofPlaceholder, nullptr,
InheritTarget::PlaceholderFrame)
.Consume();
MOZ_ASSERT(computedValues);
cache = computedValues;
return computedValues.forget();
}
static inline bool LazyPseudoIsCacheable(PseudoStyleType aType,
const Element& aOriginatingElement,
ComputedStyle* aParentStyle) {
return aParentStyle &&
!nsCSSPseudoElements::IsEagerlyCascadedInServo(aType) &&
aOriginatingElement.HasServoData() &&
!Servo_Element_IsPrimaryStyleReusedViaRuleNode(&aOriginatingElement);
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePseudoElementStyle(
const Element& aOriginatingElement, PseudoStyleType aType,
nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle,
IsProbe aIsProbe) {
// Runs from frame construction, this should have clean styles already, except
// with non-lazy FC...
UpdateStylistIfNeeded();
MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType));
// caching is done using `aType` only, therefore results would be wrong for
// pseudos with functional parameters (e.g. `::highlight(foo)`).
const bool cacheable =
!aFunctionalPseudoParameter &&
LazyPseudoIsCacheable(aType, aOriginatingElement, aParentStyle);
RefPtr<ComputedStyle> style =
cacheable ? aParentStyle->GetCachedLazyPseudoStyle(aType) : nullptr;
const bool isProbe = aIsProbe == IsProbe::Yes;
if (!style) {
// FIXME(emilio): Why passing null for probing as the parent style?
//
// There are callers which do pass the wrong parent style and it would
// assert (like ComputeSelectionStyle()). That's messy!
style = Servo_ResolvePseudoStyle(
&aOriginatingElement, aType, aFunctionalPseudoParameter,
isProbe, isProbe ? nullptr : aParentStyle, mRawData.get())
.Consume();
if (!style) {
MOZ_ASSERT(isProbe);
return nullptr;
}
if (cacheable) {
aParentStyle->SetCachedLazyPseudoStyle(style);
}
}
MOZ_ASSERT(style);
if (isProbe && !GeneratedContentPseudoExists(*aParentStyle, *style)) {
return nullptr;
}
return style.forget();
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveInheritingAnonymousBoxStyle(PseudoStyleType aType,
ComputedStyle* aParentStyle) {
MOZ_ASSERT(PseudoStyle::IsInheritingAnonBox(aType));
MOZ_ASSERT_IF(aParentStyle, !StylistNeedsUpdate());
UpdateStylistIfNeeded();
RefPtr<ComputedStyle> style = nullptr;
if (aParentStyle) {
style = aParentStyle->GetCachedInheritingAnonBoxStyle(aType);
}
if (!style) {
style = Servo_ComputedValues_GetForAnonymousBox(aParentStyle, aType,
mRawData.get())
.Consume();
MOZ_ASSERT(style);
if (aParentStyle) {
aParentStyle->SetCachedInheritedAnonBoxStyle(style);
}
}
return style.forget();
}
already_AddRefed<ComputedStyle>
ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(PseudoStyleType aType) {
MOZ_ASSERT(aType != PseudoStyleType::pageContent,
"Use ResolvePageContentStyle for page content");
MOZ_ASSERT(PseudoStyle::IsNonInheritingAnonBox(aType));
nsCSSAnonBoxes::NonInheriting type =
nsCSSAnonBoxes::NonInheritingTypeForPseudoType(aType);
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(!PseudoStyle::IsNonInheritingAnonBox(PseudoStyleType::viewport),
"viewport needs fixup to handle blockifying it");
RefPtr<ComputedStyle> computedValues =
Servo_ComputedValues_GetForAnonymousBox(nullptr, aType, mRawData.get())
.Consume();
MOZ_ASSERT(computedValues);
cache = computedValues;
return computedValues.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePageContentStyle(
const nsAtom* aPageName, const StylePagePseudoClassFlags& aPseudo) {
// The empty atom is used to indicate no specified page name, and is not
// usable as a page-rule selector. Changing this to null is a slight
// optimization to avoid the Servo code from doing an unnecessary hashtable
// lookup, and still use the style cache in this case.
if (aPageName == nsGkAtoms::_empty) {
aPageName = nullptr;
}
// Only use the cache when we are doing a lookup for page styles without a
// page-name or any pseudo classes.
const bool useCache = !aPageName && !aPseudo;
RefPtr<ComputedStyle>& cache =
mNonInheritingComputedStyles[nsCSSAnonBoxes::NonInheriting::pageContent];
if (useCache && cache) {
RefPtr<ComputedStyle> retval = cache;
return retval.forget();
}
UpdateStylistIfNeeded();
RefPtr<ComputedStyle> computedValues =
Servo_ComputedValues_GetForPageContent(mRawData.get(), aPageName, aPseudo)
.Consume();
MOZ_ASSERT(computedValues);
if (useCache) {
cache = computedValues;
}
return computedValues.forget();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveXULTreePseudoStyle(
dom::Element* aParentElement, nsCSSAnonBoxPseudoStaticAtom* aPseudoTag,
ComputedStyle* aParentStyle, const AtomArray& aInputWord) {
MOZ_ASSERT(nsCSSAnonBoxes::IsTreePseudoElement(aPseudoTag));
MOZ_ASSERT(aParentStyle);
NS_ASSERTION(!StylistNeedsUpdate(),
"Stylesheets modified when resolving XUL tree pseudo");
return Servo_ComputedValues_ResolveXULTreePseudoStyle(
aParentElement, aPseudoTag, aParentStyle, &aInputWord,
mRawData.get())
.Consume();
}
already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStartingStyle(
dom::Element& aElement) {
nsPresContext* pc = GetPresContext();
if (!pc) {
return nullptr;
}
return Servo_ResolveStartingStyle(
&aElement, &pc->RestyleManager()->Snapshots(), mRawData.get())
.Consume();
}
// manage the set of style sheets in the style set
void ServoStyleSet::AppendStyleSheet(StyleSheet& aSheet) {
MOZ_ASSERT(aSheet.IsApplicable());
MOZ_ASSERT(aSheet.RawContents(),
"Raw sheet should be in place before insertion.");
aSheet.AddStyleSet(this);
// 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(mRawData.get(), &aSheet);
SetStylistStyleSheetsDirty();
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(aSheet);
}
}
void ServoStyleSet::RemoveStyleSheet(StyleSheet& aSheet) {
aSheet.DropStyleSet(this);
// Maintain a mirrored list of sheets on the servo side.
Servo_StyleSet_RemoveStyleSheet(mRawData.get(), &aSheet);
SetStylistStyleSheetsDirty();
if (mStyleRuleMap) {
mStyleRuleMap->SheetRemoved(aSheet);
}
}
void ServoStyleSet::InsertStyleSheetBefore(StyleSheet& aNewSheet,
StyleSheet& aReferenceSheet) {
MOZ_ASSERT(aNewSheet.IsApplicable());
MOZ_ASSERT(aReferenceSheet.IsApplicable());
MOZ_ASSERT(&aNewSheet != &aReferenceSheet,
"Can't place sheet before itself.");
MOZ_ASSERT(aNewSheet.GetOrigin() == aReferenceSheet.GetOrigin(),
"Sheets should be in the same origin");
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.
aNewSheet.AddStyleSet(this);
// Maintain a mirrored list of sheets on the servo side.
Servo_StyleSet_InsertStyleSheetBefore(mRawData.get(), &aNewSheet,
&aReferenceSheet);
SetStylistStyleSheetsDirty();
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(aNewSheet);
}
}
size_t ServoStyleSet::SheetCount(Origin aOrigin) const {
return Servo_StyleSet_GetSheetCount(mRawData.get(), aOrigin);
}
StyleSheet* ServoStyleSet::SheetAt(Origin aOrigin, size_t aIndex) const {
return const_cast<StyleSheet*>(
Servo_StyleSet_GetSheetAt(mRawData.get(), aOrigin, aIndex));
}
ServoStyleSet::PageSizeAndOrientation
ServoStyleSet::GetDefaultPageSizeAndOrientation() {
PageSizeAndOrientation retval;
const RefPtr<ComputedStyle> style =
ResolvePageContentStyle(nullptr, StylePagePseudoClassFlags::NONE);
const StylePageSize& pageSize = style->StylePage()->mSize;
if (pageSize.IsSize()) {
const nscoord w = pageSize.AsSize().width.ToAppUnits();
const nscoord h = pageSize.AsSize().height.ToAppUnits();
// Ignoring sizes that include a zero width or height.
// These are also ignored in nsPageFrame::ComputePageSize()
// when calculating the scaling for a page size.
// combinations that produce a zero-sized page-content box.
if (w > 0 && h > 0) {
retval.size.emplace(w, h);
if (w > h) {
retval.orientation.emplace(StylePageSizeOrientation::Landscape);
} else if (w < h) {
retval.orientation.emplace(StylePageSizeOrientation::Portrait);
}
}
} else if (pageSize.IsOrientation()) {
retval.orientation.emplace(pageSize.AsOrientation());
} else {
MOZ_ASSERT(pageSize.IsAuto(), "Impossible page size");
}
return retval;
}
void ServoStyleSet::AppendAllNonDocumentAuthorSheets(
nsTArray<StyleSheet*>& aArray) const {
EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
aArray.AppendElement(aShadowRoot.SheetAt(index));
}
aArray.AppendElements(aShadowRoot.AdoptedStyleSheets());
});
}
void ServoStyleSet::AddDocStyleSheet(StyleSheet& aSheet) {
MOZ_ASSERT(aSheet.IsApplicable());
MOZ_ASSERT(aSheet.RawContents(),
"Raw sheet should be in place by this point.");
size_t index = mDocument->FindDocStyleSheetInsertionPoint(aSheet);
aSheet.AddStyleSet(this);
if (index < SheetCount(Origin::Author)) {
// This case is insert before.
StyleSheet* beforeSheet = SheetAt(Origin::Author, index);
// Maintain a mirrored list of sheets on the servo side.
Servo_StyleSet_InsertStyleSheetBefore(mRawData.get(), &aSheet, beforeSheet);
SetStylistStyleSheetsDirty();
} else {
// Maintain a mirrored list of sheets on the servo side.
Servo_StyleSet_AppendStyleSheet(mRawData.get(), &aSheet);
SetStylistStyleSheetsDirty();
}
if (mStyleRuleMap) {
mStyleRuleMap->SheetAdded(aSheet);
}
}
bool ServoStyleSet::GeneratedContentPseudoExists(
const ComputedStyle& aParentStyle, const ComputedStyle& aPseudoStyle) {
auto type = aPseudoStyle.GetPseudoType();
MOZ_ASSERT(type != PseudoStyleType::NotPseudo);
if (type == PseudoStyleType::marker) {
// ::marker only exist for list items (for now).
if (!aParentStyle.StyleDisplay()->IsListItem()) {
return false;
}
const auto& content = aPseudoStyle.StyleContent()->mContent;
// ::marker does not exist if 'content' is 'none' (this trumps
// any 'list-style-type' or 'list-style-image' values).
if (content.IsNone()) {
return false;
}
// ::marker only exist if we have 'content' or at least one of
// 'list-style-type' or 'list-style-image'.
if (aPseudoStyle.StyleList()->mListStyleType.IsNone() &&
aPseudoStyle.StyleList()->mListStyleImage.IsNone() &&
content.IsNormal()) {
return false;
}
// display:none is equivalent to not having a pseudo at all.
if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
return false;
}
}
// For ::before and ::after pseudo-elements, no 'content' items is
// equivalent to not having the pseudo-element at all.
if (type == PseudoStyleType::before || type == PseudoStyleType::after) {
if (!aPseudoStyle.StyleContent()->mContent.IsItems()) {
return false;
}
MOZ_ASSERT(!aPseudoStyle.StyleContent()->NonAltContentItems().IsEmpty(),
"IsItems() implies we have at least one item");
// display:none is equivalent to not having a pseudo at all.
if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
return false;
}
}
return true;
}
bool ServoStyleSet::StyleDocument(ServoTraversalFlags aFlags) {
AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_StyleComputation);
MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
if (!mDocument->GetServoRestyleRoot()) {
return false;
}
Element* rootElement = mDocument->GetRootElement();
if (rootElement && MOZ_UNLIKELY(!rootElement->HasServoData())) {
StyleNewSubtree(rootElement);
return true;
}
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;
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, mRawData.get(), &snapshots, aFlags) ||
root->HasAnyOfFlags(Element::kAllServoDescendantBits |
NODE_NEEDS_FRAME);
{
uint32_t existingBits = mDocument->GetServoRestyleRootDirtyBits();
Element* newRoot = nullptr;
while (parent && parent->HasDirtyDescendantsForServo()) {
MOZ_ASSERT(root == mDocument->GetServoRestyleRoot(),
"Restyle root shouldn't have magically changed");
// 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.
// We need to propagate the existing bits to the ancestor.
parent->SetFlags(existingBits);
newRoot = parent;
parent = parent->GetFlattenedTreeParentElementForStyle();
}
if (newRoot) {
mDocument->SetServoRestyleRoot(
newRoot, 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)) {
DocumentStyleRootIterator iter(mDocument->GetServoRestyleRoot());
while (Element* root = iter.GetNextStyleRoot()) {
postTraversalRequired |=
Servo_TraverseSubtree(root, mRawData.get(), &snapshots, aFlags) ||
root->HasAnyOfFlags(Element::kAllServoDescendantBits |
NODE_NEEDS_FRAME);
}
}
return postTraversalRequired;
}
void ServoStyleSet::StyleNewSubtree(Element* aRoot) {