diff options
Diffstat (limited to 'layout/style/nsRuleProcessorData.h')
-rw-r--r-- | layout/style/nsRuleProcessorData.h | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/layout/style/nsRuleProcessorData.h b/layout/style/nsRuleProcessorData.h new file mode 100644 index 000000000..6f8cba895 --- /dev/null +++ b/layout/style/nsRuleProcessorData.h @@ -0,0 +1,595 @@ +/* -*- 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 "nsAutoPtr.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<size_t hashListLength> + 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<Filter> mFilter; + + // Stack of indices to pop to. These are indices into mHashes. + nsTArray<uint32_t> 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<uint32_t> mHashes; + + // A debug-only stack of Elements for use in assertions +#ifdef DEBUG + nsTArray<mozilla::dom::Element*> 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<bool> 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 + AutoTArray<mozilla::dom::Element*, 1> 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 + // <style scoped> child). + AutoTArray<mozilla::dom::Element*, 1> mStyleScopes; + + // The current style scope element for selector matching. + mozilla::dom::Element* mCurrentStyleScope; + + // Constructor to use when creating a tree match context for styling + TreeMatchContext(bool aForStyling, + nsRuleWalker::VisitedHandlingType aVisitedHandling, + nsIDocument* aDocument, + MatchVisited aMatchVisited = eMatchVisitedDefault) + : mForStyling(aForStyling) + , mHaveRelevantLink(false) + , mHaveSpecifiedScope(false) + , mVisitedHandling(aVisitedHandling) + , mDocument(aDocument) + , mScopedRoot(nullptr) + , mIsHTMLDocument(aDocument->IsHTMLDocument()) + , mCompatMode(aDocument->GetCompatibilityMode()) + , mUsingPrivateBrowsing(false) + , mSkippingParentDisplayBasedStyleFixup(false) + , mForScopedStyle(false) + , mCurrentStyleScope(nullptr) + { + if (aMatchVisited != eNeverMatchVisited) { + nsILoadContext* loadContext = mDocument->GetLoadContext(); + if (loadContext) { + mUsingPrivateBrowsing = loadContext->UsePrivateBrowsing(); + } + } + } +}; + +struct MOZ_STACK_CLASS RuleProcessorData { + RuleProcessorData(nsPresContext* aPresContext, + nsRuleWalker* aRuleWalker) + : mPresContext(aPresContext), + mRuleWalker(aRuleWalker), + mScope(nullptr) + { + NS_PRECONDITION(mPresContext, "Must have prescontext"); + } + + nsPresContext* const mPresContext; + nsRuleWalker* const mRuleWalker; // Used to add rules to our results. + mozilla::dom::Element* mScope; +}; + +struct MOZ_STACK_CLASS ElementDependentRuleProcessorData : + public RuleProcessorData { + ElementDependentRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aElement, + nsRuleWalker* aRuleWalker, + TreeMatchContext& aTreeMatchContext) + : RuleProcessorData(aPresContext, aRuleWalker) + , mElement(aElement) + , mTreeMatchContext(aTreeMatchContext) + { + NS_ASSERTION(aElement, "null element leaked into SelectorMatches"); + NS_ASSERTION(aElement->OwnerDoc(), "Document-less node here?"); + NS_PRECONDITION(aTreeMatchContext.mForStyling == !!aRuleWalker, + "Should be styling if and only if we have a rule walker"); + } + + mozilla::dom::Element* const mElement; // weak ref, must not be null + TreeMatchContext& mTreeMatchContext; +}; + +struct MOZ_STACK_CLASS ElementRuleProcessorData : + public ElementDependentRuleProcessorData { + ElementRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aElement, + nsRuleWalker* aRuleWalker, + TreeMatchContext& aTreeMatchContext) + : ElementDependentRuleProcessorData(aPresContext, aElement, aRuleWalker, + aTreeMatchContext) + { + NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!"); + NS_PRECONDITION(aRuleWalker, "Must have rule walker"); + } +}; + +struct MOZ_STACK_CLASS PseudoElementRuleProcessorData : + public ElementDependentRuleProcessorData { + PseudoElementRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aParentElement, + nsRuleWalker* aRuleWalker, + mozilla::CSSPseudoElementType aPseudoType, + TreeMatchContext& aTreeMatchContext, + mozilla::dom::Element* aPseudoElement) + : ElementDependentRuleProcessorData(aPresContext, aParentElement, aRuleWalker, + aTreeMatchContext), + mPseudoType(aPseudoType), + mPseudoElement(aPseudoElement) + { + NS_PRECONDITION(aPseudoType < mozilla::CSSPseudoElementType::Count, + "invalid aPseudoType value"); + NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!"); + NS_PRECONDITION(aRuleWalker, "Must have rule walker"); + } + + mozilla::CSSPseudoElementType mPseudoType; + mozilla::dom::Element* const mPseudoElement; // weak ref +}; + +struct MOZ_STACK_CLASS AnonBoxRuleProcessorData : public RuleProcessorData { + AnonBoxRuleProcessorData(nsPresContext* aPresContext, + nsIAtom* aPseudoTag, + nsRuleWalker* aRuleWalker) + : RuleProcessorData(aPresContext, aRuleWalker), + mPseudoTag(aPseudoTag) + { + NS_PRECONDITION(aPseudoTag, "Must have pseudo tag"); + NS_PRECONDITION(aRuleWalker, "Must have rule walker"); + } + + nsIAtom* mPseudoTag; +}; + +#ifdef MOZ_XUL +struct MOZ_STACK_CLASS XULTreeRuleProcessorData : + public ElementDependentRuleProcessorData { + XULTreeRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aParentElement, + nsRuleWalker* aRuleWalker, + nsIAtom* aPseudoTag, + nsICSSPseudoComparator* aComparator, + TreeMatchContext& aTreeMatchContext) + : ElementDependentRuleProcessorData(aPresContext, aParentElement, + aRuleWalker, aTreeMatchContext), + mPseudoTag(aPseudoTag), + mComparator(aComparator) + { + NS_PRECONDITION(aPseudoTag, "null pointer"); + NS_PRECONDITION(aRuleWalker, "Must have rule walker"); + NS_PRECONDITION(aComparator, "must have a comparator"); + NS_PRECONDITION(aTreeMatchContext.mForStyling, "Styling here!"); + } + + nsIAtom* mPseudoTag; + nsICSSPseudoComparator* mComparator; +}; +#endif + +struct MOZ_STACK_CLASS StateRuleProcessorData : + public ElementDependentRuleProcessorData { + StateRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aElement, + mozilla::EventStates aStateMask, + TreeMatchContext& aTreeMatchContext) + : ElementDependentRuleProcessorData(aPresContext, aElement, nullptr, + aTreeMatchContext), + mStateMask(aStateMask) + { + NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!"); + } + // |HasStateDependentStyle| for which state(s)? + // Constants defined in mozilla/EventStates.h . + const mozilla::EventStates mStateMask; +}; + +struct MOZ_STACK_CLASS PseudoElementStateRuleProcessorData : + public StateRuleProcessorData { + PseudoElementStateRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aElement, + mozilla::EventStates aStateMask, + mozilla::CSSPseudoElementType aPseudoType, + TreeMatchContext& aTreeMatchContext, + mozilla::dom::Element* aPseudoElement) + : StateRuleProcessorData(aPresContext, aElement, aStateMask, + aTreeMatchContext), + mPseudoType(aPseudoType), + mPseudoElement(aPseudoElement) + { + NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!"); + } + + // We kind of want to inherit from both StateRuleProcessorData and + // PseudoElementRuleProcessorData. Instead we've just copied those + // members from PseudoElementRuleProcessorData to this struct. + mozilla::CSSPseudoElementType mPseudoType; + mozilla::dom::Element* const mPseudoElement; // weak ref +}; + +struct MOZ_STACK_CLASS AttributeRuleProcessorData : + public ElementDependentRuleProcessorData { + AttributeRuleProcessorData(nsPresContext* aPresContext, + mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + bool aAttrHasChanged, + const nsAttrValue* aOtherValue, + TreeMatchContext& aTreeMatchContext) + : ElementDependentRuleProcessorData(aPresContext, aElement, nullptr, + aTreeMatchContext), + mNameSpaceID(aNameSpaceID), + mAttribute(aAttribute), + mOtherValue(aOtherValue), + mModType(aModType), + mAttrHasChanged(aAttrHasChanged) + { + NS_PRECONDITION(!aTreeMatchContext.mForStyling, "Not styling here!"); + } + int32_t mNameSpaceID; // Namespace of the attribute involved. + nsIAtom* mAttribute; // |HasAttributeDependentStyle| for which attribute? + // non-null if we have the value. + const nsAttrValue* mOtherValue; + int32_t mModType; // The type of modification (see nsIDOMMutationEvent). + bool mAttrHasChanged; // Whether the attribute has already changed. +}; + +#endif /* !defined(nsRuleProcessorData_h_) */ |