/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /* * data structures passed to nsIStyleRuleProcessor methods (to pull loop * invariant computations out of the loop) */ #ifndef nsRuleProcessorData_h_ #define nsRuleProcessorData_h_ #include "nsChangeHint.h" #include "nsCompatibility.h" #include "nsCSSPseudoElements.h" #include "nsRuleWalker.h" #include "nsNthIndexCache.h" #include "nsILoadContext.h" #include "nsIDocument.h" #include "mozilla/AutoRestore.h" #include "mozilla/BloomFilter.h" #include "mozilla/EventStates.h" #include "mozilla/GuardObjects.h" #include "mozilla/dom/Element.h" class nsIAtom; class nsIContent; class nsICSSPseudoComparator; struct TreeMatchContext; /** * An AncestorFilter is used to keep track of ancestors so that we can * quickly tell that a particular selector is not relevant to a given * element. */ class MOZ_STACK_CLASS AncestorFilter { friend struct TreeMatchContext; public: /* Maintenance of our ancestor state */ void PushAncestor(mozilla::dom::Element *aElement); void PopAncestor(); /* Check whether we might have an ancestor matching one of the given atom hashes. |hashes| must have length hashListLength */ template bool MightHaveMatchingAncestor(const uint32_t* aHashes) const { MOZ_ASSERT(mFilter); for (size_t i = 0; i < hashListLength && aHashes[i]; ++i) { if (!mFilter->mightContain(aHashes[i])) { return false; } } return true; } bool HasFilter() const { return mFilter; } #ifdef DEBUG void AssertHasAllAncestors(mozilla::dom::Element *aElement) const; #endif private: // Using 2^12 slots makes the Bloom filter a nice round page in // size, so let's do that. We get a false positive rate of 1% or // less even with several hundred things in the filter. Note that // we allocate the filter lazily, because not all tree match // contexts can use one effectively. typedef mozilla::BloomFilter<12, nsIAtom> Filter; nsAutoPtr mFilter; // Stack of indices to pop to. These are indices into mHashes. nsTArray mPopTargets; // List of hashes; this is what we pop using mPopTargets. We store // hashes of our ancestor element tag names, ids, and classes in // here. nsTArray mHashes; // A debug-only stack of Elements for use in assertions #ifdef DEBUG nsTArray mElements; #endif }; /** * A |TreeMatchContext| has data about a matching operation. The * data are not node-specific but are invariants of the DOM tree the * nodes being matched against are in. * * Most of the members are in parameters to selector matching. The * one out parameter is mHaveRelevantLink. Consumers that use a * TreeMatchContext for more than one matching operation and care * about :visited and mHaveRelevantLink need to * ResetForVisitedMatching() and ResetForUnvisitedMatching() as * needed. */ struct MOZ_STACK_CLASS TreeMatchContext { // Reset this context for matching for the style-if-:visited. void ResetForVisitedMatching() { NS_PRECONDITION(mForStyling, "Why is this being called?"); mHaveRelevantLink = false; mVisitedHandling = nsRuleWalker::eRelevantLinkVisited; } void ResetForUnvisitedMatching() { NS_PRECONDITION(mForStyling, "Why is this being called?"); mHaveRelevantLink = false; mVisitedHandling = nsRuleWalker::eRelevantLinkUnvisited; } void SetHaveRelevantLink() { mHaveRelevantLink = true; } bool HaveRelevantLink() const { return mHaveRelevantLink; } nsRuleWalker::VisitedHandlingType VisitedHandling() const { return mVisitedHandling; } void AddScopeElement(mozilla::dom::Element* aElement) { NS_PRECONDITION(mHaveSpecifiedScope, "Should be set before calling AddScopeElement()"); mScopes.AppendElement(aElement); } bool IsScopeElement(mozilla::dom::Element* aElement) const { return mScopes.Contains(aElement); } void SetHasSpecifiedScope() { mHaveSpecifiedScope = true; } bool HasSpecifiedScope() const { return mHaveSpecifiedScope; } /** * Initialize the ancestor filter and list of style scopes. If aElement is * not null, it and all its ancestors will be passed to * mAncestorFilter.PushAncestor and PushStyleScope, starting from the root and * going down the tree. Must only be called for elements in a document. */ void InitAncestors(mozilla::dom::Element *aElement); /** * Like InitAncestors, but only initializes the style scope list, not the * ancestor filter. May be called for elements outside a document. */ void InitStyleScopes(mozilla::dom::Element* aElement); void PushStyleScope(mozilla::dom::Element* aElement) { NS_PRECONDITION(aElement, "aElement must not be null"); if (aElement->IsScopedStyleRoot()) { mStyleScopes.AppendElement(aElement); } } void PopStyleScope(mozilla::dom::Element* aElement) { NS_PRECONDITION(aElement, "aElement must not be null"); if (mStyleScopes.SafeLastElement(nullptr) == aElement) { mStyleScopes.TruncateLength(mStyleScopes.Length() - 1); } } bool PopStyleScopeForSelectorMatching(mozilla::dom::Element* aElement) { NS_ASSERTION(mForScopedStyle, "only call PopStyleScopeForSelectorMatching " "when mForScopedStyle is true"); if (!mCurrentStyleScope) { return false; } if (mCurrentStyleScope == aElement) { mCurrentStyleScope = nullptr; } return true; } #ifdef DEBUG void AssertHasAllStyleScopes(mozilla::dom::Element* aElement) const; #endif bool SetStyleScopeForSelectorMatching(mozilla::dom::Element* aSubject, mozilla::dom::Element* aScope) { #ifdef DEBUG AssertHasAllStyleScopes(aSubject); #endif mForScopedStyle = !!aScope; if (!aScope) { // This is not for a scoped style sheet; return true, as we want // selector matching to proceed. mCurrentStyleScope = nullptr; return true; } if (aScope == aSubject) { // Although the subject is the same element as the scope, as soon // as we continue with selector matching up the tree we don't want // to match any more elements. So we return true to indicate that // we want to do the initial selector matching, but set // mCurrentStyleScope to null so that no ancestor elements will match. mCurrentStyleScope = nullptr; return true; } if (mStyleScopes.Contains(aScope)) { // mStyleScopes contains all of the scope elements that are ancestors of // aSubject, so if aScope is in mStyleScopes, then we do want selector // matching to proceed. mCurrentStyleScope = aScope; return true; } // Otherwise, we're not in the scope, and we don't want to proceed // with selector matching. mCurrentStyleScope = nullptr; return false; } bool IsWithinStyleScopeForSelectorMatching() const { NS_ASSERTION(mForScopedStyle, "only call IsWithinScopeForSelectorMatching " "when mForScopedStyle is true"); return mCurrentStyleScope; } /* Helper class for maintaining the ancestor state */ class MOZ_RAII AutoAncestorPusher { public: explicit AutoAncestorPusher(TreeMatchContext& aTreeMatchContext MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mPushedAncestor(false) , mPushedStyleScope(false) , mTreeMatchContext(aTreeMatchContext) , mElement(nullptr) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } void PushAncestorAndStyleScope(mozilla::dom::Element* aElement) { MOZ_ASSERT(!mElement); if (aElement) { mElement = aElement; mPushedAncestor = true; mPushedStyleScope = true; mTreeMatchContext.mAncestorFilter.PushAncestor(aElement); mTreeMatchContext.PushStyleScope(aElement); } } void PushAncestorAndStyleScope(nsIContent* aContent) { if (aContent && aContent->IsElement()) { PushAncestorAndStyleScope(aContent->AsElement()); } } void PushStyleScope(mozilla::dom::Element* aElement) { MOZ_ASSERT(!mElement); if (aElement) { mElement = aElement; mPushedStyleScope = true; mTreeMatchContext.PushStyleScope(aElement); } } void PushStyleScope(nsIContent* aContent) { if (aContent && aContent->IsElement()) { PushStyleScope(aContent->AsElement()); } } ~AutoAncestorPusher() { if (mPushedAncestor) { mTreeMatchContext.mAncestorFilter.PopAncestor(); } if (mPushedStyleScope) { mTreeMatchContext.PopStyleScope(mElement); } } private: bool mPushedAncestor; bool mPushedStyleScope; TreeMatchContext& mTreeMatchContext; mozilla::dom::Element* mElement; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; /* Helper class for tracking whether we're skipping the ApplyStyleFixups * code for special cases where child element style is modified based on * parent display value. * * The optional second parameter aSkipParentDisplayBasedStyleFixup allows * this class to be instantiated but only conditionally activated (e.g. * in cases where we may or may not want to be skipping flex/grid-item * style fixup for a particular chunk of code). */ class MOZ_RAII AutoParentDisplayBasedStyleFixupSkipper { public: explicit AutoParentDisplayBasedStyleFixupSkipper(TreeMatchContext& aTreeMatchContext, bool aSkipParentDisplayBasedStyleFixup = true MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mAutoRestorer(aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (aSkipParentDisplayBasedStyleFixup) { aTreeMatchContext.mSkippingParentDisplayBasedStyleFixup = true; } } private: mozilla::AutoRestore mAutoRestorer; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; // Is this matching operation for the creation of a style context? // (If it is, we need to set slow selector bits on nodes indicating // that certain restyling needs to happen.) const bool mForStyling; private: // When mVisitedHandling is eRelevantLinkUnvisited, this is set to true if a // relevant link (see explanation in definition of VisitedHandling enum) was // encountered during the matching process, which means that matching needs // to be rerun with eRelevantLinkVisited. Otherwise, its behavior is // undefined (it might get set appropriately, or might not). bool mHaveRelevantLink; // If true, then our contextual reference element set is specified, // and is given by mScopes. bool mHaveSpecifiedScope; // How matching should be performed. See the documentation for // nsRuleWalker::VisitedHandlingType. nsRuleWalker::VisitedHandlingType mVisitedHandling; // For matching :scope nsAutoTArray mScopes; public: // The document we're working with. nsIDocument* const mDocument; // Root of scoped stylesheet (set and unset by the supplier of the // scoped stylesheet). nsIContent* mScopedRoot; // Whether our document is HTML (as opposed to XML of some sort, // including XHTML). // XXX XBL2 issue: Should we be caching this? What should it be for XBL2? const bool mIsHTMLDocument; // Possibly remove use of mCompatMode in SelectorMatches? // XXX XBL2 issue: Should we be caching this? What should it be for XBL2? const nsCompatibility mCompatMode; // The nth-index cache we should use nsNthIndexCache mNthIndexCache; // An ancestor filter AncestorFilter mAncestorFilter; // Whether this document is using PB mode bool mUsingPrivateBrowsing; // Whether we're currently skipping the part of ApplyStyleFixups that changes // style of child elements based on their parent's display value // (e.g. for children of elements that have a mandatory frame-type for which // we ignore "display:flex/grid"). bool mSkippingParentDisplayBasedStyleFixup; // Whether this TreeMatchContext is being used with an nsCSSRuleProcessor // for an HTML5 scoped style sheet. bool mForScopedStyle; enum MatchVisited { eNeverMatchVisited, eMatchVisitedDefault }; // List of ancestor elements that define a style scope (due to having a //