/* -*- 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 "nsIMemoryReporter.h" #include "nsSize.h" namespace mozilla { enum class MediaFeatureChangeReason : uint8_t; enum class StylePageSizeOrientation : uint8_t; enum class StyleRuleChangeKind : uint32_t; template struct StyleTimingFunction; struct StylePiecewiseLinearFunction; using StyleComputedTimingFunction = StyleTimingFunction; namespace css { class Rule; } // namespace css namespace dom { class CSSImportRule; class Element; class ShadowRoot; } // 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, ComputedStyle* aParentStyle, IsProbe = IsProbe::No); already_AddRefed ProbePseudoElementStyle( const dom::Element& aOriginatingElement, PseudoStyleType aType, ComputedStyle* aParentStyle) { return ResolvePseudoElementStyle(aOriginatingElement, aType, aParentStyle, IsProbe::Yes); } /** * @brief Get a style for a highlight pseudo element. * * The highlight is identified by its name `aHighlightName`. * * Returns null if there are no rules matching for the highlight pseudo * element. */ already_AddRefed ProbeHighlightPseudoElementStyle( const dom::Element& aOriginatingElement, const nsAtom* aHighlightName, ComputedStyle* aParentStyle); // 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, 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 gets the global page style. already_AddRefed ResolvePageContentStyle( const nsAtom* aPageName); 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 FirstPageSizeAndOrientation { Maybe orientation; Maybe size; }; // Gets the specified orientation and size used when the first page printed // has the name |aFirstPageName|, based on the page-size property. // // 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. FirstPageSizeAndOrientation GetFirstPageSizeAndOrientation( const nsAtom* aFirstPageName); 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); // Get a ComputedStyle that represents |aStyle|, but as though it additionally // matched the rules of the newly added |aAnimaitonaValue|. // // We use this function to temporarily generate a ComputedStyle for // calculating the cumulative change hints. // // This must hold: // The additional rules must be appropriate for the transition // level of the cascade, which is the highest level of the cascade. // (This is the case for one current caller, the cover rule used // for CSS transitions.) // Note: |aElement| should be the generated element if it is pseudo. already_AddRefed ResolveServoStyleByAddingAnimation( dom::Element* aElement, const ComputedStyle* aStyle, StyleAnimationValue* aAnimationValue); /** * 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; /** * 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; /** * 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* aNewParentIgnoringFirstLine, 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(); /** * Resolve all DeclarationBlocks attached to mapped * presentation attributes cached on the document. * * Call this before jumping into Servo's style system. */ void ResolveMappedAttrDeclarationBlocks(); /** * 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> 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); } 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