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