/* -*- 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/. */ #ifndef mozilla_ServoStyleSet_h #define mozilla_ServoStyleSet_h #include "mozilla/AlreadyAddRefed.h" #include "mozilla/AnonymousContentKey.h" #include "mozilla/AtomArray.h" #include "mozilla/EnumeratedArray.h" #include "mozilla/Maybe.h" #include "mozilla/PostTraversalTask.h" #include "mozilla/ServoBindingTypes.h" #include "mozilla/ServoUtils.h" #include "mozilla/dom/RustTypes.h" #include "mozilla/UniquePtr.h" #include "MainThreadUtils.h" #include "nsCSSPseudoElements.h" #include "nsCSSAnonBoxes.h" #include "nsChangeHint.h" #include "nsCoord.h" #include "nsAtom.h" #include "nsIMemoryReporter.h" #include "nsTArray.h" #include "nsSize.h" namespace mozilla { enum class MediaFeatureChangeReason : uint8_t; enum class StylePageSizeOrientation : uint8_t; enum class StyleRuleChangeKind : uint32_t; class ErrorResult; template struct StyleTimingFunction; struct StylePagePseudoClassFlags; struct StylePiecewiseLinearFunction; using StyleComputedTimingFunction = StyleTimingFunction; namespace css { class Rule; } // namespace css namespace dom { class CSSImportRule; class Element; class ShadowRoot; struct PropertyDefinition; } // namespace dom namespace gfx { class FontPaletteValueSet; } // namespace gfx class StyleSheet; struct Keyframe; class ServoElementSnapshotTable; class ComputedStyle; class ServoStyleRuleMap; class StyleSheet; } // namespace mozilla class gfxFontFeatureValueSet; class nsIContent; class nsPresContext; class nsWindowSizes; struct TreeMatchContext; namespace mozilla { // A few flags used to track which kind of stylist state we may need to // update. enum class StylistState : uint8_t { // The stylist is not dirty, we should do nothing. NotDirty = 0, // The style sheets have changed, so we need to update the style data. StyleSheetsDirty = 1 << 0, // Some of the style sheets of the shadow trees in the document have // changed. ShadowDOMStyleSheetsDirty = 1 << 1, }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState) enum class StyleOrigin : uint8_t; // Bitfield type to represent Servo stylesheet origins. enum class OriginFlags : uint8_t { UserAgent = 0x01, User = 0x02, Author = 0x04, All = 0x07, }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OriginFlags) /** * The set of style sheets that apply to a document, backed by a Servo * Stylist. A ServoStyleSet contains StyleSheets. */ class ServoStyleSet { friend class RestyleManager; using SnapshotTable = ServoElementSnapshotTable; using Origin = StyleOrigin; // We assert that these match the Servo ones in the definition of this array. static constexpr Origin kOrigins[] = { Origin(static_cast(OriginFlags::UserAgent)), Origin(static_cast(OriginFlags::User)), Origin(static_cast(OriginFlags::Author)), }; public: static bool IsInServoTraversal() { return mozilla::IsInServoTraversal(); } #ifdef DEBUG // Used for debug assertions. We make this debug-only to prevent callers from // accidentally using it instead of IsInServoTraversal, which is cheaper. We // can change this if a use-case arises. static bool IsCurrentThreadInServoTraversal(); #endif static ServoStyleSet* Current() { return sInServoTraversal; } explicit ServoStyleSet(dom::Document&); ~ServoStyleSet(); void ShellDetachedFromDocument(); // Called when a rules in a stylesheet in this set, or a child sheet of that, // are mutated from CSSOM. void RuleAdded(StyleSheet&, css::Rule&); void RuleRemoved(StyleSheet&, css::Rule&); void RuleChanged(StyleSheet&, css::Rule*, StyleRuleChangeKind); void SheetCloned(StyleSheet&); void ImportRuleLoaded(dom::CSSImportRule&, StyleSheet&); // Runs style invalidation due to document state changes. void InvalidateStyleForDocumentStateChanges( dom::DocumentState aStatesChanged); void RecordShadowStyleChange(dom::ShadowRoot&); bool StyleSheetsHaveChanged() const { return StylistNeedsUpdate(); } RestyleHint MediumFeaturesChanged(MediaFeatureChangeReason); // Evaluates a given SourceSizeList, returning the optimal viewport width in // app units. // // The SourceSizeList parameter can be null, in which case it will return // 100vw. inline nscoord EvaluateSourceSizeList( const StyleSourceSizeList* aSourceSizeList) const; void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const; const StylePerDocumentStyleData* RawData() const { return mRawData.get(); } bool GetAuthorStyleDisabled() const { return mAuthorStyleDisabled; } bool UsesFontMetrics() const; void SetAuthorStyleDisabled(bool aStyleDisabled); // Get a CopmutedStyle for a text node (which no rules will match). // // The returned ComputedStyle will have nsCSSAnonBoxes::mozText() as its // pseudo. // // (Perhaps mozText should go away and we shouldn't even create style // contexts for such content nodes, when text-combine-upright is not // present. However, not doing any rule matching for them is a first step.) already_AddRefed ResolveStyleForText( nsIContent* aTextNode, ComputedStyle* aParentStyle); // Get a ComputedStyle for a first-letter continuation (which no rules will // match). // // The returned ComputedStyle will have // nsCSSAnonBoxes::firstLetterContinuation() as its pseudo. // // (Perhaps nsCSSAnonBoxes::firstLetterContinuation() should go away and we // shouldn't even create ComputedStyles for such frames. However, not doing // any rule matching for them is a first step. And right now we do use this // ComputedStyle for some things) already_AddRefed ResolveStyleForFirstLetterContinuation( ComputedStyle* aParentStyle); // Get a ComputedStyle for a placeholder frame (which no rules will match). // // The returned ComputedStyle will have nsCSSAnonBoxes::oofPlaceholder() as // its pseudo. // // (Perhaps nsCSSAnonBoxes::oofPaceholder() should go away and we shouldn't // even create ComputedStyle for placeholders. However, not doing any rule // matching for them is a first step.) already_AddRefed ResolveStyleForPlaceholder(); // Returns whether a given pseudo-element should exist or not. static bool GeneratedContentPseudoExists(const ComputedStyle& aParentStyle, const ComputedStyle& aPseudoStyle); enum class IsProbe { No, Yes, }; // Get a style for a pseudo-element. // // If IsProbe is Yes, then no style is returned if there are no rules matching // for the pseudo-element, or GeneratedContentPseudoExists returns false. // // If IsProbe is No, then the style is guaranteed to be non-null. already_AddRefed ResolvePseudoElementStyle( const dom::Element& aOriginatingElement, PseudoStyleType, nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle, IsProbe = IsProbe::No); already_AddRefed ProbePseudoElementStyle( const dom::Element& aOriginatingElement, PseudoStyleType aType, nsAtom* aFunctionalPseudoParameter, ComputedStyle* aParentStyle) { return ResolvePseudoElementStyle(aOriginatingElement, aType, aFunctionalPseudoParameter, aParentStyle, IsProbe::Yes); } // Resolves style for a (possibly-pseudo) Element without assuming that the // style has been resolved. If the element was unstyled and a new style // was resolved, it is not stored in the DOM. (That is, the element remains // unstyled.) already_AddRefed ResolveStyleLazily( const dom::Element&, PseudoStyleType = PseudoStyleType::NotPseudo, nsAtom* aFunctionalPseudoParameter = nullptr, StyleRuleInclusion = StyleRuleInclusion::All); // Get a ComputedStyle for an anonymous box. The pseudo type must be an // inheriting anon box. already_AddRefed ResolveInheritingAnonymousBoxStyle( PseudoStyleType, ComputedStyle* aParentStyle); // Get a ComputedStyle for an anonymous box. The pseudo type must be // a non-inheriting anon box, and must not be page-content. // See ResolvePageContentStyle for resolving page-content style. already_AddRefed ResolveNonInheritingAnonymousBoxStyle( PseudoStyleType aType); // Get a ComputedStyle for a pageContent box with the specified page-name. // A page name that is null or the empty atom and has no pseudo classes gets // the global page style. already_AddRefed ResolvePageContentStyle( const nsAtom* aPageName, const StylePagePseudoClassFlags& aPseudo); already_AddRefed ResolveXULTreePseudoStyle( dom::Element* aParentElement, nsCSSAnonBoxPseudoStaticAtom* aPseudoTag, ComputedStyle* aParentStyle, const AtomArray& aInputWord); size_t SheetCount(Origin) const; StyleSheet* SheetAt(Origin, size_t aIndex) const; struct PageSizeAndOrientation { Maybe orientation; Maybe size; }; // Gets the default page size and orientation (the size/orientation specified // by @page rules without a selector list), if any. // // If the specified size is just an orientation, then the size will be set to // nothing and the orientation will be set accordingly. // If the specified size is auto or square, then the orientation will be set // to nothing. // Otherwise, the size will and orientation is determined by the specified // page size. PageSizeAndOrientation GetDefaultPageSizeAndOrientation(); void AppendAllNonDocumentAuthorSheets(nsTArray& aArray) const; // Manage the set of style sheets in the style set void AppendStyleSheet(StyleSheet&); void InsertStyleSheetBefore(StyleSheet&, StyleSheet& aReferenceSheet); void RemoveStyleSheet(StyleSheet&); void AddDocStyleSheet(StyleSheet&); /** * Performs a Servo traversal to compute style for all dirty nodes in the * document. * * This will traverse all of the document's style roots (that is, its document * element, and the roots of the document-level native anonymous content). * * We specify |ForCSSRuleChanges| to try to update all CSS animations * when we call this function due to CSS rule changes since @keyframes rules * may have changed. * * Returns true if a post-traversal is required. */ bool StyleDocument(ServoTraversalFlags aFlags); /** * Eagerly styles a subtree of unstyled nodes that was just appended to the * tree. This is used in situations where we need the style immediately and * cannot wait for a future batch restyle. */ void StyleNewSubtree(dom::Element* aRoot); /** * Helper for correctly calling UpdateStylist without paying the cost of an * extra function call in the common no-rebuild-needed case. */ void UpdateStylistIfNeeded() { if (StylistNeedsUpdate()) { UpdateStylist(); } } /** * Checks whether the rule tree has crossed its threshold for unused nodes, * and if so, frees them. */ void MaybeGCRuleTree(); /** * Returns true if the given element may be used as the root of a style * traversal. Reasons for false include having an unstyled parent, or having * a parent that is display:none. * * Most traversal callsites don't need to check this, but some do. */ static bool MayTraverseFrom(const dom::Element* aElement); #ifdef DEBUG void AssertTreeIsClean(); #else void AssertTreeIsClean() {} #endif /** * Clears any cached style data that may depend on all sorts of computed * values. * * Right now this clears the non-inheriting ComputedStyle cache, resets the * default computed values, and clears cached anonymous content style. * * This does _not_, however, clear the stylist. */ void ClearCachedStyleData(); /** * Notifies the Servo stylesheet that the document's compatibility mode has * changed. */ void CompatibilityModeChanged(); template void EnumerateStyleSheets(T aCb) { for (auto origin : kOrigins) { for (size_t i = 0, count = SheetCount(origin); i < count; ++i) { aCb(*SheetAt(origin, i)); } } } /** * Resolve style for the given element, and return it as a * ComputedStyle. * * FIXME(emilio): Is there a point in this after bug 1367904? */ static inline already_AddRefed ResolveServoStyle( const dom::Element&); bool GetKeyframesForName(const dom::Element&, const ComputedStyle&, nsAtom* aName, const StyleComputedTimingFunction& aTimingFunction, nsTArray& aKeyframes); nsTArray GetComputedKeyframeValuesFor( const nsTArray& aKeyframes, dom::Element* aElement, PseudoStyleType aPseudoType, const ComputedStyle* aStyle); void GetAnimationValues( StyleLockedDeclarationBlock* aDeclarations, dom::Element* aElement, const mozilla::ComputedStyle* aStyle, nsTArray>& aAnimationValues); void AppendFontFaceRules(nsTArray& aArray); const StyleLockedCounterStyleRule* CounterStyleRuleForName(nsAtom* aName); // Get all the currently-active font feature values set. already_AddRefed BuildFontFeatureValueSet(); // Get the set of all currently-active font-palette-values. already_AddRefed BuildFontPaletteValueSet(); already_AddRefed GetBaseContextForElement( dom::Element* aElement, const ComputedStyle* aStyle); /** * Resolve style for a given declaration block with/without the parent style. * If the parent style is not specified, the document default computed values * is used. */ already_AddRefed ResolveForDeclarations( const ComputedStyle* aParentOrNull, const StyleLockedDeclarationBlock* aDeclarations); already_AddRefed ComputeAnimationValue( dom::Element* aElement, StyleLockedDeclarationBlock* aDeclaration, const mozilla::ComputedStyle* aStyle); void AppendTask(PostTraversalTask aTask) { MOZ_ASSERT(IsInServoTraversal()); // We currently only use PostTraversalTasks while the Servo font metrics // mutex is locked. If we need to use them in other situations during // a traversal, we should assert that we've taken appropriate // synchronization measures. AssertIsMainThreadOrServoFontMetricsLocked(); mPostTraversalTasks.AppendElement(aTask); } // Returns true if a restyle of the document is needed due to cloning // sheet inners. bool EnsureUniqueInnerOnCSSSheets(); // Returns the style rule map. ServoStyleRuleMap* StyleRuleMap(); /** * Returns true if a modification to an an attribute with the specified * local name might require us to restyle the element. * * This function allows us to skip taking a an attribute snapshot when * the modified attribute doesn't appear in an attribute selector in * a style sheet. */ bool MightHaveAttributeDependency(const dom::Element&, nsAtom* aAttribute) const; /** * Returns true if a modification to an attribute with the specified local * name might require us to restyle the element's siblings. */ bool MightHaveNthOfAttributeDependency(const dom::Element&, nsAtom* aAttribute) const; /** * Returns true if a modification to a class might require us to restyle the * element's siblings. */ bool MightHaveNthOfClassDependency(const dom::Element&); /** * Returns true if a modification to an ID might require us to restyle the * element's siblings. */ bool MightHaveNthOfIDDependency(const dom::Element&, nsAtom* aOldID, nsAtom* aNewID) const; /** * Maybe invalidate if a modification to an ID might require us to restyle * the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorIDDependency( const dom::Element&, nsAtom* aOldID, nsAtom* aNewID, const ServoElementSnapshotTable& aSnapshots); /** * Maybe invalidate if a modification to an attribute with the specified local * name might require us to restyle the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorClassDependency( const dom::Element&, const ServoElementSnapshotTable& aSnapshots); /** * Maybe invalidate if a modification to an ID might require us to restyle * the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorAttributeDependency( const dom::Element&, nsAtom* aAttribute, const ServoElementSnapshotTable& aSnapshots); /** * Maybe invalidate if a change in event state on an element might require us * to restyle the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorStateDependency( const dom::Element&, dom::ElementState, const ServoElementSnapshotTable& aSnapshots); /** * Maybe invalidate if a change on an element that might be selected by :empty * that might require us to restyle the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorForEmptyDependency(const dom::Element&); /** * Maybe invalidate if a state change on an element that might be selected * by a selector that can only selector first/last child, that * might require us to restyle the relative selector it refers to. */ void MaybeInvalidateRelativeSelectorForNthEdgeDependency(const dom::Element&); /** * Maybe invalidate if a state change on an element that might be selected by * :nth-* (Or :nth-like) selectors that might require us to restyle the * relative selector it refers to. */ void MaybeInvalidateRelativeSelectorForNthDependencyFromSibling( const dom::Element*); /** * Maybe invalidate if a DOM element insertion might require us to restyle * the relative selector to ancestors/previous siblings. */ void MaybeInvalidateForElementInsertion(const dom::Element&); /** * Maybe invalidate if a series of nodes is appended, among which may * be element(s) that might require us to restyle the relative selector * to ancestors/previous siblings. */ void MaybeInvalidateForElementAppend(const nsIContent&); /** * Maybe invalidate if a DOM element removal might require us to restyle * the relative selector to ancestors/previous siblings. */ void MaybeInvalidateForElementRemove(const dom::Element& aElement, const nsIContent* aFollowingSibling); /** * Returns true if a change in event state on an element might require * us to restyle the element. * * This function allows us to skip taking a state snapshot when * the changed state isn't depended upon by any pseudo-class selectors * in a style sheet. */ bool HasStateDependency(const dom::Element&, dom::ElementState) const; /** * Returns true if a change in event state on an element might require * us to restyle the element's siblings. */ bool HasNthOfStateDependency(const dom::Element&, dom::ElementState) const; /** * Restyle this element's siblings in order to propagate any potential change * in :nth-child(of) styling. */ void RestyleSiblingsForNthOf(const dom::Element&, uint32_t) const; /** * Returns true if a change in document state might require us to restyle the * document. */ bool HasDocumentStateDependency(dom::DocumentState) const; /** * Get a new ComputedStyle that uses the same rules as the given ComputedStyle * but has a different parent. * * aElement is non-null if this is a ComputedStyle for a frame whose mContent * is an element and which has no pseudo on its ComputedStyle (so it's the * actual style for the element being passed). */ already_AddRefed ReparentComputedStyle( ComputedStyle* aComputedStyle, ComputedStyle* aNewParent, ComputedStyle* aNewLayoutParent, dom::Element* aElement); /** * Invalidate styles where there's any viewport units dependent style. */ enum class OnlyDynamic : bool { No, Yes }; void InvalidateForViewportUnits(OnlyDynamic); private: friend class AutoSetInServoTraversal; friend class AutoPrepareTraversal; friend class PostTraversalTask; bool ShouldTraverseInParallel() const; void RuleChangedInternal(StyleSheet&, css::Rule&, StyleRuleChangeKind); /** * Forces all the ShadowRoot styles to be dirty. * * Only to be used for: * * * Devtools (dealing with sheet cloning). * * Compatibility-mode changes. * * Try to do something more incremental for other callers that are exposed to * the web. */ void ForceDirtyAllShadowStyles(); /** * Gets the pending snapshots to handle from the restyle manager. */ const SnapshotTable& Snapshots(); /** * Clear our cached mNonInheritingComputedStyles. * * We do this when we want to make sure those ComputedStyles won't live too * long (e.g. when rebuilding all style data or when shutting down the style * set). */ void ClearNonInheritingComputedStyles(); /** * Perform processes that we should do before traversing. * * When aRoot is null, the entire document is pre-traversed. Otherwise, * only the subtree rooted at aRoot is pre-traversed. */ void PreTraverse(ServoTraversalFlags aFlags, dom::Element* aRoot = nullptr); // Subset of the pre-traverse steps that involve syncing up data void PreTraverseSync(); /** * Records that the contents of style sheets at the specified origin have * changed since the last. Calling this will ensure that the Stylist * rebuilds its selector maps. */ void MarkOriginsDirty(OriginFlags aChangedOrigins); /** * Note that the stylist needs a style flush due to style sheet changes. */ void SetStylistStyleSheetsDirty(); void SetStylistShadowDOMStyleSheetsDirty(); bool StylistNeedsUpdate() const { return mStylistState != StylistState::NotDirty; } /** * Update the stylist as needed to ensure style data is up-to-date. * * This should only be called if StylistNeedsUpdate returns true. */ void UpdateStylist(); void RunPostTraversalTasks(); void PrependSheetOfType(Origin, StyleSheet*); void AppendSheetOfType(Origin, StyleSheet*); void InsertSheetOfType(Origin, StyleSheet*, StyleSheet* aBeforeSheet); void RemoveSheetOfType(Origin, StyleSheet*); const nsPresContext* GetPresContext() const { return const_cast(this)->GetPresContext(); } /** * Return the associated pres context if we're the master style set and we * have an associated pres shell. */ nsPresContext* GetPresContext(); // The owner document of this style set. Never null, and always outlives the // StyleSet. dom::Document* mDocument; UniquePtr mRawData; // Map from raw Servo style rule to Gecko's wrapper object. // Constructed lazily when requested by devtools. UniquePtr mStyleRuleMap; uint64_t mUserFontSetUpdateGeneration = 0; // Tasks to perform after a traversal, back on the main thread. // // These are similar to Servo's SequentialTasks, except that they are // posted by C++ code running on style worker threads. nsTArray mPostTraversalTasks; // Stores pointers to our cached ComputedStyles for non-inheriting anonymous // boxes. EnumeratedArray, size_t(nsCSSAnonBoxes::NonInheriting::_Count)> mNonInheritingComputedStyles; public: void PutCachedAnonymousContentStyles( AnonymousContentKey aKey, nsTArray>&& aStyles) { auto index = static_cast(aKey); MOZ_ASSERT(mCachedAnonymousContentStyles.Length() + aStyles.Length() < 256, "(index, length) pairs must be bigger"); MOZ_ASSERT(mCachedAnonymousContentStyleIndexes[index].second == 0, "shouldn't need to overwrite existing cached styles"); MOZ_ASSERT(!aStyles.IsEmpty(), "should have some styles to cache"); mCachedAnonymousContentStyleIndexes[index] = std::make_pair( mCachedAnonymousContentStyles.Length(), aStyles.Length()); mCachedAnonymousContentStyles.AppendElements(std::move(aStyles)); } void GetCachedAnonymousContentStyles( AnonymousContentKey aKey, nsTArray>& aStyles) { auto index = static_cast(aKey); auto loc = mCachedAnonymousContentStyleIndexes[index]; aStyles.AppendElements(mCachedAnonymousContentStyles.Elements() + loc.first, loc.second); } void RegisterProperty(const dom::PropertyDefinition&, ErrorResult&); private: // Map of AnonymousContentKey values to an (index, length) pair pointing into // mCachedAnonymousContentStyles. // // We assert that the index and length values fit into uint8_ts. Array, 1 << sizeof(AnonymousContentKey) * 8> mCachedAnonymousContentStyleIndexes; // Stores cached ComputedStyles for certain native anonymous content. nsTArray> mCachedAnonymousContentStyles; StylistState mStylistState = StylistState::NotDirty; bool mAuthorStyleDisabled = false; bool mNeedsRestyleAfterEnsureUniqueInner = false; }; class UACacheReporter final : public nsIMemoryReporter { NS_DECL_ISUPPORTS NS_DECL_NSIMEMORYREPORTER private: ~UACacheReporter() = default; }; } // namespace mozilla #endif // mozilla_ServoStyleSet_h