From a852c0a86aed0d9493d735dc82cb2e4e8909fc2a Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Mon, 11 May 2026 19:33:30 -0400 Subject: [PATCH] Support CSS shadow parts --- dom/base/nsGkAtomList.h | 1 + dom/xbl/nsBindingManager.cpp | 35 +- layout/style/RuleCascadeData.cpp | 39 +- layout/style/StyleRule.cpp | 1 + layout/style/nsCSSParser.cpp | 32 +- layout/style/nsCSSPseudoClassList.h | 3 + layout/style/nsCSSPseudoClasses.cpp | 6 +- layout/style/nsCSSPseudoElementList.h | 7 +- layout/style/nsCSSPseudoElements.cpp | 3 +- layout/style/nsCSSRuleUtils.cpp | 1784 ++++++++++++++++++++++++- 10 files changed, 1882 insertions(+), 29 deletions(-) diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index be33db9a07..928dadd82d 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -1003,6 +1003,7 @@ GK_ATOM(parameter, "parameter") GK_ATOM(parent, "parent") GK_ATOM(parentfocused, "parentfocused") GK_ATOM(parsetype, "parsetype") +GK_ATOM(part, "part") GK_ATOM(password, "password") GK_ATOM(pattern, "pattern") GK_ATOM(patternSeparator, "pattern-separator") diff --git a/dom/xbl/nsBindingManager.cpp b/dom/xbl/nsBindingManager.cpp index 5245821776..e44d29fa83 100644 --- a/dom/xbl/nsBindingManager.cpp +++ b/dom/xbl/nsBindingManager.cpp @@ -823,16 +823,37 @@ nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc, } } -// The approach in WalkAllShadowRootHostRules seems reasonable on the surface and reminds me of what is done with mScopedRoot elsewhere. But something important is missing, either here or in the code that would normally use it. - void nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc, ElementDependentRuleProcessorData* aData) - { - aData->mTreeMatchContext.mOnlyMatchHostPseudo = true; - WalkAllRules(aFunc, aData, true); - aData->mTreeMatchContext.mOnlyMatchHostPseudo = false; - } +{ + if (!mBoundContentSet) { + return; + } + + bool oldOnlyMatchHostPseudo = aData->mTreeMatchContext.mOnlyMatchHostPseudo; + nsIContent* oldScopedRoot = aData->mTreeMatchContext.mScopedRoot; + aData->mTreeMatchContext.mOnlyMatchHostPseudo = true; + + for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) { + nsIContent* boundContent = iter.Get()->GetKey(); + ShadowRoot* shadowRoot = boundContent->GetShadowRoot(); + if (!shadowRoot) { + continue; + } + + nsXBLBinding* binding = shadowRoot->GetAssociatedBinding(); + if (!binding) { + continue; + } + + aData->mTreeMatchContext.mScopedRoot = boundContent; + binding->WalkRules(aFunc, aData); + } + + aData->mTreeMatchContext.mScopedRoot = oldScopedRoot; + aData->mTreeMatchContext.mOnlyMatchHostPseudo = oldOnlyMatchHostPseudo; +} nsresult nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext, diff --git a/layout/style/RuleCascadeData.cpp b/layout/style/RuleCascadeData.cpp index da6a4e70de..20856c69f0 100644 --- a/layout/style/RuleCascadeData.cpp +++ b/layout/style/RuleCascadeData.cpp @@ -404,6 +404,22 @@ RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo) RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); } +static bool +SelectorContainsPseudoClass(nsCSSSelector* aSelector, CSSPseudoClassType aType) +{ + for (nsCSSSelector* selector = aSelector; selector; selector = selector->mNext) { + for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == aType) { + return true; + } + } + } + + return false; +} + void RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) { @@ -411,7 +427,10 @@ RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) if (selector->IsPseudoElement()) { selector = selector->mNext; } - if (nullptr != selector->mIDList) { + if (SelectorContainsPseudoClass(selector, CSSPseudoClassType::part)) { + AppendUniversalRule(aRuleInfo); + RULE_HASH_STAT_INCREMENT(mUniversalSelectors); + } else if (nullptr != selector->mIDList) { AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); RULE_HASH_STAT_INCREMENT(mIdSelectors); } else if (nullptr != selector->mClassList) { @@ -452,6 +471,10 @@ LookForTargetPseudo(nsCSSSelector* aSelector, nsRestyleHint* possibleChange) { if (aMatchContext->mOnlyMatchHostPseudo) { + if (SelectorContainsPseudoClass(aSelector, CSSPseudoClassType::part)) { + return true; + } + while (aSelector && aSelector->mNext != nullptr) { aSelector = aSelector->mNext; } @@ -502,7 +525,8 @@ ContentEnumFunc(const RuleValue& value, data->mTreeMatchContext.SetHaveRelevantLink(); } // XXX: Ignore the ancestor filter if we're testing the assigned slot. - bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot); + bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot || + data->mTreeMatchContext.mOnlyMatchHostPseudo); if (useAncestorFilter && ancestorFilter && !ancestorFilter->MightHaveMatchingAncestor( value.mAncestorSelectorHashes)) { @@ -556,9 +580,18 @@ ContentEnumFunc(const RuleValue& value, nodeContext, data->mTreeMatchContext, selectorFlags)) { + Element* treeMatchStart = data->mElement; + if (SelectorContainsPseudoClass(selector, CSSPseudoClassType::part)) { + nsIContent* partHost = data->mElement->GetContainingShadowHost(); + if (!partHost || !partHost->IsElement()) { + return; + } + treeMatchStart = partHost->AsElement(); + } + nsCSSSelector* next = selector->mNext; if (!next || nsCSSRuleUtils::SelectorMatchesTree( - data->mElement, + treeMatchStart, next, data->mTreeMatchContext, nodeContext.mIsRelevantLink ? SelectorMatchesTreeFlags(0) diff --git a/layout/style/StyleRule.cpp b/layout/style/StyleRule.cpp index 42aa43be4a..9135eefce8 100644 --- a/layout/style/StyleRule.cpp +++ b/layout/style/StyleRule.cpp @@ -335,6 +335,7 @@ nsCSSSelector::Clone(bool aDeepNext, bool aDeepNegations) const result->mCasedTag = mCasedTag; result->mOperator = mOperator; result->mPseudoType = mPseudoType; + result->mHybridPseudoType = mHybridPseudoType; NS_IF_CLONE(mIDList); NS_IF_CLONE(mClassList); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index afb9f195a8..6f0ce498f3 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -6690,6 +6690,8 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, if (hybridPseudoElementType == CSSPseudoElementType::slotted) { pseudoClassType = CSSPseudoClassType::slotted; aFlags |= SelectorParsingFlags::eDisallowCombinators; + } else if (hybridPseudoElementType == CSSPseudoElementType::part) { + pseudoClassType = CSSPseudoClassType::part; } } @@ -6842,6 +6844,28 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, UngetToken(); return eSelectorParsingStatus_Error; } + else if (hybridPseudoElementType != CSSPseudoElementType::NotPseudo) { + aSelector.SetHybridPseudoType(hybridPseudoElementType); + // Ensure hybrid pseudo-elements are rejected if they're not allowed. + if (disallowPseudoElements) { + UngetToken(); + return eSelectorParsingStatus_Error; + } + + if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { + parsingStatus = + ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); + } + else if (nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType)) { + parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, + pseudoClassType, + flags); + } + else { + MOZ_ASSERT(false, "unexpected hybrid pseudo-element"); + parsingStatus = eSelectorParsingStatus_Error; + } + } else if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { parsingStatus = ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); @@ -6853,14 +6877,6 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, else { MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType), "unexpected pseudo with function token"); - if (hybridPseudoElementType != CSSPseudoElementType::NotPseudo) { - aSelector.SetHybridPseudoType(hybridPseudoElementType); - // Ensure hybrid pseudo-elements are rejected if they're not allowed. - if (disallowPseudoElements) { - UngetToken(); - return eSelectorParsingStatus_Error; - } - } parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, pseudoClassType, flags); diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index f1ab538357..ab70f39ce5 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -94,6 +94,9 @@ CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "") // Match slot nodes. CSS_PSEUDO_CLASS(slotted, ":slotted", 0, "layout.css.slotted-pseudo.enabled") +// Match elements exposed through a shadow host's part attribute. +CSS_PSEUDO_CLASS(part, ":part", 0, "dom.webcomponents.enabled") + // Match nodes that are HTML but not XHTML CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "") diff --git a/layout/style/nsCSSPseudoClasses.cpp b/layout/style/nsCSSPseudoClasses.cpp index 8e12a9648c..c3681fc518 100644 --- a/layout/style/nsCSSPseudoClasses.cpp +++ b/layout/style/nsCSSPseudoClasses.cpp @@ -125,7 +125,8 @@ nsCSSPseudoClasses::HasStringArg(Type aType) aType == Type::mozSystemMetric || aType == Type::mozLocaleDir || aType == Type::mozDir || - aType == Type::dir; + aType == Type::dir || + aType == Type::part; } bool @@ -210,5 +211,6 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType) /* static */ bool nsCSSPseudoClasses::IsHybridPseudoElement(Type aType) { - return aType == Type::slotted; + return aType == Type::slotted || + aType == Type::part; } diff --git a/layout/style/nsCSSPseudoElementList.h b/layout/style/nsCSSPseudoElementList.h index 6693d42e75..881c9bce37 100644 --- a/layout/style/nsCSSPseudoElementList.h +++ b/layout/style/nsCSSPseudoElementList.h @@ -28,10 +28,13 @@ CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2) CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2) -// XXX: ::slotted() is treated as if it were a pseudo-class, and -// is never parsed as a pseudo-element. +// XXX: ::slotted() and ::part() are treated as if they were pseudo-classes, +// and are never parsed as pseudo-elements. CSS_PSEUDO_ELEMENT(slotted, ":slotted", CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING) +CSS_PSEUDO_ELEMENT(part, ":part", + CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING | + CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0) diff --git a/layout/style/nsCSSPseudoElements.cpp b/layout/style/nsCSSPseudoElements.cpp index fb871ff858..6053bd39e4 100644 --- a/layout/style/nsCSSPseudoElements.cpp +++ b/layout/style/nsCSSPseudoElements.cpp @@ -78,7 +78,8 @@ nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom) /* static */ bool nsCSSPseudoElements::IsHybridPseudoElement(CSSPseudoElementType aType) { - return aType == CSSPseudoElementType::slotted; + return aType == CSSPseudoElementType::slotted || + aType == CSSPseudoElementType::part; } /* static */ bool diff --git a/layout/style/nsCSSRuleUtils.cpp b/layout/style/nsCSSRuleUtils.cpp index 1b987cbab8..b4b7139b9a 100644 --- a/layout/style/nsCSSRuleUtils.cpp +++ b/layout/style/nsCSSRuleUtils.cpp @@ -186,6 +186,211 @@ InitSystemMetrics() #endif return true; +======= +/* -*- 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/. */ + +#include "nsCSSRuleUtils.h" + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLSlotElement.h" +#include "mozilla/dom/ShadowRoot.h" +#include "nsIMozBrowserFrame.h" +#include "nsGkAtoms.h" +#include "nsRuleWalker.h" +#include "nsStyleUtil.h" +#include "StyleRule.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled" + +static bool gSupportVisitedPseudo = true; +static bool gLoadImportedSheetsInOrder = true; + +static nsTArray>* sSystemMetrics = 0; + +#ifdef XP_WIN +uint8_t nsCSSRuleUtils::sWinThemeId = LookAndFeel::eWindowsTheme_Generic; +#endif + +/* static */ void +nsCSSRuleUtils::Startup() +{ + Preferences::AddBoolVarCache( + &gSupportVisitedPseudo, VISITED_PSEUDO_PREF, true); + Preferences::AddBoolVarCache(&gLoadImportedSheetsInOrder, + "layout.css.load-imported-sheets-in-order", + true); +} + +static bool +InitSystemMetrics() +{ + NS_ASSERTION(!sSystemMetrics, "already initialized"); + + sSystemMetrics = new nsTArray>; + NS_ENSURE_TRUE(sSystemMetrics, false); + + /*************************************************************************** + * ANY METRICS ADDED HERE SHOULD ALSO BE ADDED AS MEDIA QUERIES IN * + * nsMediaFeatures.cpp * + ***************************************************************************/ + + int32_t metricResult = + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollArrowStyle); + if (metricResult & LookAndFeel::eScrollArrow_StartBackward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_backward); + } + if (metricResult & LookAndFeel::eScrollArrow_StartForward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_start_forward); + } + if (metricResult & LookAndFeel::eScrollArrow_EndBackward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_backward); + } + if (metricResult & LookAndFeel::eScrollArrow_EndForward) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_end_forward); + } + + metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollSliderStyle); + if (metricResult != LookAndFeel::eScrollThumbStyle_Normal) { + sSystemMetrics->AppendElement(nsGkAtoms::scrollbar_thumb_proportional); + } + + metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::overlay_scrollbars); + } + + metricResult = LookAndFeel::GetInt(LookAndFeel::eIntID_MenuBarDrag); + if (metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::menubar_drag); + } + + nsresult rv = + LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsDefaultTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_default_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacGraphiteTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::mac_graphite_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacLionTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::mac_lion_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_MacYosemiteTheme, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::mac_yosemite_theme); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorApplies, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_applies); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsAccentColorIsDark, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_accent_color_is_dark); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_DWMCompositor, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_compositor); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsGlass, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_glass); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ColorPickerAvailable, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::color_picker_available); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_WindowsClassic, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::windows_classic); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_TouchEnabled, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::touch_enabled); + } + + rv = LookAndFeel::GetInt(LookAndFeel::eIntID_SwipeAnimationEnabled, + &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::swipe_animation_enabled); + } + + rv = + LookAndFeel::GetInt(LookAndFeel::eIntID_PhysicalHomeButton, &metricResult); + if (NS_SUCCEEDED(rv) && metricResult) { + sSystemMetrics->AppendElement(nsGkAtoms::physical_home_button); + } + +#ifdef XP_WIN + if (NS_SUCCEEDED(LookAndFeel::GetInt( + LookAndFeel::eIntID_WindowsThemeIdentifier, &metricResult))) { + nsCSSRuleUtils::SetWindowsThemeIdentifier( + static_cast(metricResult)); + switch (metricResult) { + case LookAndFeel::eWindowsTheme_Aero: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero); + break; + case LookAndFeel::eWindowsTheme_AeroLite: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_aero_lite); + break; + case LookAndFeel::eWindowsTheme_Generic: + sSystemMetrics->AppendElement(nsGkAtoms::windows_theme_generic); + break; + } + } +#endif + + return true; +} + +static nsPseudoClassList* +FindPseudoClass(const nsCSSSelector* aSelector, CSSPseudoClassType aType) +{ + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == aType) { + return pseudoClass; + } + } + + return nullptr; +} + +static bool +ElementMatchesPart(Element* aElement, const nsPseudoClassList* aPseudoClass) +{ + MOZ_ASSERT(aPseudoClass->mType == CSSPseudoClassType::part); + MOZ_ASSERT(aPseudoClass->u.mString); + + nsAutoString partValue; + if (!aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::part, partValue)) { + return false; + } + + const nsDefaultStringComparator comparator; + return nsStyleUtil::ValueIncludes( + partValue, nsDependentString(aPseudoClass->u.mString), comparator); } /* static */ void @@ -641,16 +846,462 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, "Has(Attribute|State)DependentStyle"); if (aNodeMatchContext.mIsFeatureless && +======= + delete sSystemMetrics; + sSystemMetrics = nullptr; +} + +/* static */ void +nsCSSRuleUtils::Shutdown() +{ + FreeSystemMetrics(); +} + +/* static */ bool +nsCSSRuleUtils::HasSystemMetric(nsIAtom* aMetric) +{ + if (!sSystemMetrics && !InitSystemMetrics()) { + return false; + } + return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex; +} + +/* static */ bool +nsCSSRuleUtils::LoadImportedSheetsInOrderEnabled() +{ + return gLoadImportedSheetsInOrder; +} + +#ifdef XP_WIN +/* static */ uint8_t +nsCSSRuleUtils::GetWindowsThemeIdentifier() +{ + if (!sSystemMetrics) + InitSystemMetrics(); + return sWinThemeId; +} +#endif + +/* static */ bool +nsCSSRuleUtils::RestrictedSelectorListMatches( + Element* aElement, + TreeMatchContext& aTreeMatchContext, + nsCSSSelectorList* aSelectorList) +{ + MOZ_ASSERT(!aTreeMatchContext.mForScopedStyle, + "mCurrentStyleScope will need to be saved and restored after the " + "SelectorMatchesTree call"); + + NodeMatchContext nodeContext(EventStates(), false); + SelectorMatchesFlags flags = aElement->IsInShadowTree() + ? SelectorMatchesFlags::NONE + : SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE; + return SelectorListMatches( + aElement, aSelectorList, nodeContext, aTreeMatchContext, flags); +} + +/* static */ EventStates +nsCSSRuleUtils::GetContentState(Element* aElement, + const TreeMatchContext& aTreeMatchContext) +{ + EventStates state = aElement->StyleState(); + + // If we are not supposed to mark visited links as such, be sure to + // flip the bits appropriately. We want to do this here, rather + // than in GetContentStateForVisitedHandling, so that we don't + // expose that :visited support is disabled to the Web page. + if (state.HasState(NS_EVENT_STATE_VISITED) && + (!gSupportVisitedPseudo || aElement->OwnerDoc()->IsBeingUsedAsImage() || + aTreeMatchContext.mUsingPrivateBrowsing)) { + state &= ~NS_EVENT_STATE_VISITED; + state |= NS_EVENT_STATE_UNVISITED; + } + return state; +} + +/* static */ bool +nsCSSRuleUtils::IsLink(const Element* aElement) +{ + EventStates state = aElement->StyleState(); + return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | + NS_EVENT_STATE_UNVISITED); +} + +/* static */ EventStates +nsCSSRuleUtils::GetContentStateForVisitedHandling( + Element* aElement, + const TreeMatchContext& aTreeMatchContext, + nsRuleWalker::VisitedHandlingType aVisitedHandling, + bool aIsRelevantLink) +{ + EventStates contentState = GetContentState(aElement, aTreeMatchContext); + if (contentState.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | + NS_EVENT_STATE_UNVISITED)) { + MOZ_ASSERT(IsLink(aElement), "IsLink() should match state"); + contentState &= ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED); + if (aIsRelevantLink) { + switch (aVisitedHandling) { + case nsRuleWalker::eRelevantLinkUnvisited: + contentState |= NS_EVENT_STATE_UNVISITED; + break; + case nsRuleWalker::eRelevantLinkVisited: + contentState |= NS_EVENT_STATE_VISITED; + break; + case nsRuleWalker::eLinksVisitedOrUnvisited: + contentState |= NS_EVENT_STATE_UNVISITED | NS_EVENT_STATE_VISITED; + break; + } + } else { + contentState |= NS_EVENT_STATE_UNVISITED; + } + } + return contentState; +} + +/* static */ bool +nsCSSRuleUtils::RestrictedSelectorMatches( + Element* aElement, + nsCSSSelector* aSelector, + TreeMatchContext& aTreeMatchContext) +{ + MOZ_ASSERT(aSelector->IsRestrictedSelector(), + "aSelector must not have a pseudo-element"); + + NS_WARNING_ASSERTION( + !HasPseudoClassSelectorArgsWithCombinators(aSelector), + "processing eRestyle_SomeDescendants can be slow if pseudo-classes with " + "selector arguments can now have combinators in them"); + + // We match aSelector as if :visited and :link both match visited and + // unvisited links. + + NodeMatchContext nodeContext(EventStates(), + nsCSSRuleUtils::IsLink(aElement)); + if (nodeContext.mIsRelevantLink) { + aTreeMatchContext.SetHaveRelevantLink(); + } + aTreeMatchContext.ResetForUnvisitedMatching(); + bool matches = SelectorMatches(aElement, + aSelector, + nodeContext, + aTreeMatchContext, + SelectorMatchesFlags::NONE); + if (nodeContext.mIsRelevantLink) { + aTreeMatchContext.ResetForVisitedMatching(); + if (SelectorMatches(aElement, + aSelector, + nodeContext, + aTreeMatchContext, + SelectorMatchesFlags::NONE)) { + matches = true; + } + } + return matches; +} + +// Return whether the selector matches conditions for the :active and +// :hover quirk. +static inline bool +ActiveHoverQuirkMatches(nsCSSSelector* aSelector, + SelectorMatchesFlags aSelectorFlags) +{ + if (aSelector->HasTagSelector() || aSelector->mAttrList || + aSelector->mIDList || aSelector->mClassList || + aSelector->IsPseudoElement() || aSelector->IsHybridPseudoElement() || + // Having this quirk means that some selectors will no longer match, + // so it's better to return false when we aren't sure (i.e., the + // flags are unknown). + aSelectorFlags & (SelectorMatchesFlags::UNKNOWN | + SelectorMatchesFlags::HAS_PSEUDO_ELEMENT | + SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT | + SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE)) { + return false; + } + + // No pseudo-class other than :active and :hover. + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType != CSSPseudoClassType::hover && + pseudoClass->mType != CSSPseudoClassType::active) { + return false; + } + } + + return true; +} + +// This function is to be called once we have fetched a value for an attribute +// whose namespace and name match those of aAttrSelector. This function +// performs comparisons on the value only, based on aAttrSelector->mFunction. +static bool +AttrMatchesValue(const nsAttrSelector* aAttrSelector, + const nsString& aValue, + bool isHTML) +{ + NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); + + // http://lists.w3.org/Archives/Public/www-style/2008Apr/0038.html + // *= (CONTAINSMATCH) ~= (INCLUDES) ^= (BEGINSMATCH) $= (ENDSMATCH) + // all accept the empty string, but match nothing. + if (aAttrSelector->mValue.IsEmpty() && + (aAttrSelector->mFunction == NS_ATTR_FUNC_INCLUDES || + aAttrSelector->mFunction == NS_ATTR_FUNC_ENDSMATCH || + aAttrSelector->mFunction == NS_ATTR_FUNC_BEGINSMATCH || + aAttrSelector->mFunction == NS_ATTR_FUNC_CONTAINSMATCH)) + return false; + + const nsDefaultStringComparator defaultComparator; + const nsASCIICaseInsensitiveStringComparator ciComparator; + const nsStringComparator& comparator = + aAttrSelector->IsValueCaseSensitive(isHTML) + ? static_cast(defaultComparator) + : static_cast(ciComparator); + + switch (aAttrSelector->mFunction) { + case NS_ATTR_FUNC_EQUALS: + return aValue.Equals(aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_INCLUDES: + return nsStyleUtil::ValueIncludes( + aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_DASHMATCH: + return nsStyleUtil::DashMatchCompare( + aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_ENDSMATCH: + return StringEndsWith(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_BEGINSMATCH: + return StringBeginsWith(aValue, aAttrSelector->mValue, comparator); + case NS_ATTR_FUNC_CONTAINSMATCH: + return FindInReadable(aAttrSelector->mValue, aValue, comparator); + default: + NS_NOTREACHED("Shouldn't be ending up here"); + return false; + } +} + +static inline bool +edgeChildMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + bool checkFirst, + bool checkLast) +{ + nsIContent* parent = aElement->GetParent(); + if (parent && aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + return (!checkFirst || aTreeMatchContext.mNthIndexCache.GetNthIndex( + aElement, false, false, true) == 1) && + (!checkLast || aTreeMatchContext.mNthIndexCache.GetNthIndex( + aElement, false, true, true) == 1); +} + +static inline bool +nthChildGenericMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + nsPseudoClassList* pseudoClass, + bool isOfType, + bool isFromEnd) +{ + nsIContent* parent = aElement->GetParent(); + if (parent && aTreeMatchContext.mForStyling) { + if (isFromEnd) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR); + else + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + } + + const int32_t index = aTreeMatchContext.mNthIndexCache.GetNthIndex( + aElement, isOfType, isFromEnd, false); + if (index <= 0) { + // Node is anonymous content (not really a child of its parent). + return false; + } + + const int32_t a = pseudoClass->u.mNumbers[0]; + const int32_t b = pseudoClass->u.mNumbers[1]; + // result should be true if there exists n >= 0 such that + // a * n + b == index. + if (a == 0) { + return b == index; + } + + // Integer division in C does truncation (towards 0). So + // check that the result is nonnegative, and that there was no + // truncation. + const CheckedInt indexMinusB = CheckedInt(index) - b; + const CheckedInt n = indexMinusB / a; + return n.isValid() && n.value() >= 0 && a * n == indexMinusB; +} + +static inline bool +edgeOfTypeMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + bool checkFirst, + bool checkLast) +{ + nsIContent* parent = aElement->GetParent(); + if (parent && aTreeMatchContext.mForStyling) { + if (checkLast) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR); + else + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + } + + return (!checkFirst || aTreeMatchContext.mNthIndexCache.GetNthIndex( + aElement, true, false, true) == 1) && + (!checkLast || aTreeMatchContext.mNthIndexCache.GetNthIndex( + aElement, true, true, true) == 1); +} + +static inline bool +checkGenericEmptyMatches(Element* aElement, + TreeMatchContext& aTreeMatchContext, + bool isWhitespaceSignificant) +{ + nsIContent* child = nullptr; + int32_t index = -1; + + if (aTreeMatchContext.mForStyling) + aElement->SetFlags(NODE_HAS_EMPTY_SELECTOR); + + do { + child = aElement->GetChildAt(++index); + // stop at first non-comment (and non-whitespace for + // :-moz-only-whitespace) node + } while (child && !nsStyleUtil::IsSignificantChild( + child, true, isWhitespaceSignificant)); + return (child == nullptr); +} + +/* static */ bool +nsCSSRuleUtils::StateSelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + SelectorMatchesFlags aSelectorFlags, + bool* const aDependence, + EventStates aStatesToCheck) +{ + NS_PRECONDITION(!aStatesToCheck.IsEmpty(), + "should only need to call StateSelectorMatches if " + "aStatesToCheck is not empty"); + + // Bit-based pseudo-classes + if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER) && + aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks && + ActiveHoverQuirkMatches(aSelector, aSelectorFlags) && + aElement->IsHTMLElement() && !nsCSSRuleUtils::IsLink(aElement)) { + // In quirks mode, only make links sensitive to selectors ":active" + // and ":hover". + return false; + } + + if (aTreeMatchContext.mForStyling && + aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER)) { + // Mark the element as having :hover-dependent style + aElement->SetHasRelevantHoverRules(); + } + + if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(aStatesToCheck)) { + if (aDependence) { + *aDependence = true; + } + } else { + EventStates contentState = + nsCSSRuleUtils::GetContentStateForVisitedHandling( + aElement, + aTreeMatchContext, + aTreeMatchContext.VisitedHandling(), + aNodeMatchContext.mIsRelevantLink); + if (!contentState.HasAtLeastOneOfStates(aStatesToCheck)) { + return false; + } + } + + return true; +} + +/* static */ bool +nsCSSRuleUtils::StateSelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + SelectorMatchesFlags aSelectorFlags) +{ + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + auto idx = static_cast(pseudoClass->mType); + EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx]; + if (!statesToCheck.IsEmpty() && !StateSelectorMatches(aElement, + aSelector, + aNodeMatchContext, + aTreeMatchContext, + aSelectorFlags, + nullptr, + statesToCheck)) { + return false; + } + } + return true; +} + +// Returns whether aSelector can match featureless elements. +/* static */ bool +nsCSSRuleUtils::CanMatchFeaturelessElement(nsCSSSelector* aSelector) +{ + if (aSelector->HasFeatureSelectors()) { + return false; + } + + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + if (pseudoClass->mType == CSSPseudoClassType::host || + pseudoClass->mType == CSSPseudoClassType::hostContext) { + return true; + } + } + + return false; +} + +// |aDependence| has two functions: +// * when non-null, it indicates that we're processing a negation, +// which is done only when SelectorMatches calls itself recursively +// * what it points to should be set to true whenever a test is skipped +// because of aNodeMatchContent.mStateMask +/* static */ bool +nsCSSRuleUtils::SelectorMatches(Element* aElement, + nsCSSSelector* aSelector, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + SelectorMatchesFlags aSelectorFlags, + bool* const aDependence) +{ + NS_PRECONDITION(!aSelector->IsPseudoElement(), + "Pseudo-element snuck into SelectorMatches?"); + MOZ_ASSERT(aTreeMatchContext.mForStyling || + !aNodeMatchContext.mIsRelevantLink, + "mIsRelevantLink should be set to false when mForStyling " + "is false since we don't know how to set it correctly in " + "Has(Attribute|State)DependentStyle"); + + nsPseudoClassList* partPseudo = + FindPseudoClass(aSelector, CSSPseudoClassType::part); + + if (aNodeMatchContext.mIsFeatureless && !partPseudo && !CanMatchFeaturelessElement(aSelector)) { return false; } - Element* targetElement = aElement; - if (aTreeMatchContext.mForAssignedSlot) { - HTMLSlotElement* slot = aElement->GetAssignedSlot(); - // We're likely testing the slottable's ancestors and it might - // not have an assigned slot, so return early. - if (!slot) { + Element* targetElement = aElement; + if (aTreeMatchContext.mForAssignedSlot) { + HTMLSlotElement* slot = aElement->GetAssignedSlot(); + // We're likely testing the slottable's ancestors and it might + // not have an assigned slot, so return early. + if (!slot) { return false; } targetElement = slot->AsElement(); @@ -883,6 +1534,280 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, } break; case CSSPseudoClassType::mozAnyPrivate: { +======= + if (partPseudo) { + if (!ElementMatchesPart(aElement, partPseudo)) { + return false; + } + + nsIContent* partHostContent = aElement->GetContainingShadowHost(); + if (!partHostContent || !partHostContent->IsElement()) { + return false; + } + + Element* partHost = partHostContent->AsElement(); + if (aTreeMatchContext.mScopedRoot) { + ShadowRoot* scopedShadow = aTreeMatchContext.mScopedRoot->GetShadowRoot(); + if (!scopedShadow || partHost->GetContainingShadow() != scopedShadow) { + return false; + } + } + + targetElement = partHost; + } + + // namespace/tag match + // optimization : bail out early if we can + if ((kNameSpaceID_Unknown != aSelector->mNameSpace && + targetElement->GetNameSpaceID() != aSelector->mNameSpace)) + return false; + + if (aSelector->mLowercaseTag) { + nsIAtom* selectorTag = + (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()) + ? aSelector->mLowercaseTag + : aSelector->mCasedTag; + if (selectorTag != targetElement->NodeInfo()->NameAtom()) { + return false; + } + } + + nsAtomList* IDList = aSelector->mIDList; + if (IDList) { + nsIAtom* id = targetElement->GetID(); + if (id) { + // case sensitivity: bug 93371 + const bool isCaseSensitive = + aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; + + if (isCaseSensitive) { + do { + if (IDList->mAtom != id) { + return false; + } + IDList = IDList->mNext; + } while (IDList); + } else { + // Use EqualsIgnoreASCIICase instead of full on unicode case conversion + // in order to save on performance. This is only used in quirks mode + // anyway. + nsDependentAtomString id1Str(id); + do { + if (!nsContentUtils::EqualsIgnoreASCIICase( + id1Str, nsDependentAtomString(IDList->mAtom))) { + return false; + } + IDList = IDList->mNext; + } while (IDList); + } + } else { + // Element has no id but we have an id selector + return false; + } + } + + nsAtomList* classList = aSelector->mClassList; + if (classList) { + // test for class match + const nsAttrValue* elementClasses = targetElement->GetClasses(); + if (!elementClasses) { + // Element has no classes but we have a class selector + return false; + } + + // case sensitivity: bug 93371 + const bool isCaseSensitive = + aTreeMatchContext.mCompatMode != eCompatibility_NavQuirks; + + while (classList) { + if (!elementClasses->Contains( + classList->mAtom, isCaseSensitive ? eCaseMatters : eIgnoreCase)) { + return false; + } + classList = classList->mNext; + } + } + + const bool isOutsideShadowTree = + !!(aSelectorFlags & SelectorMatchesFlags::IS_OUTSIDE_SHADOW_TREE); + const bool isNegated = (aDependence != nullptr); + // The selectors for which we set node bits are, unfortunately, early + // in this function (because they're pseudo-classes, which are + // generally quick to test, and thus earlier). If they were later, + // we'd probably avoid setting those bits in more cases where setting + // them is unnecessary. + NS_ASSERTION(aNodeMatchContext.mStateMask.IsEmpty() || + !aTreeMatchContext.mForStyling, + "mForStyling must be false if we're just testing for " + "state-dependence"); + + // test for pseudo class match + for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; + pseudoClass; + pseudoClass = pseudoClass->mNext) { + auto idx = static_cast(pseudoClass->mType); + EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx]; + if (statesToCheck.IsEmpty()) { + // keep the cases here in the same order as the list in + // nsCSSPseudoClassList.h + switch (pseudoClass->mType) { + case CSSPseudoClassType::empty: + if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, true)) { + return false; + } + break; + + case CSSPseudoClassType::mozOnlyWhitespace: + if (!checkGenericEmptyMatches(aElement, aTreeMatchContext, false)) { + return false; + } + break; + + case CSSPseudoClassType::mozEmptyExceptChildrenWithLocalname: { + NS_ASSERTION(pseudoClass->u.mString, "Must have string!"); + nsIContent* child = nullptr; + int32_t index = -1; + + if (aTreeMatchContext.mForStyling) + // FIXME: This isn't sufficient to handle: + // :-moz-empty-except-children-with-localname() + E + // :-moz-empty-except-children-with-localname() ~ E + // because we don't know to restyle the grandparent of the + // inserted/removed element (as in bug 534804 for :empty). + aElement->SetFlags(NODE_HAS_SLOW_SELECTOR); + do { + child = aElement->GetChildAt(++index); + } while (child && + (!nsStyleUtil::IsSignificantChild(child, true, false) || + (child->GetNameSpaceID() == aElement->GetNameSpaceID() && + child->NodeInfo()->NameAtom()->Equals( + nsDependentString(pseudoClass->u.mString))))); + if (child != nullptr) { + return false; + } + } break; + + case CSSPseudoClassType::lang: { + NS_ASSERTION(nullptr != pseudoClass->u.mString, + "null lang parameter"); + if (!pseudoClass->u.mString || !*pseudoClass->u.mString) { + return false; + } + + // We have to determine the language of the current element. Since + // this is currently no property and since the language is inherited + // from the parent we have to be prepared to look at all parent + // nodes. The language itself is encoded in the LANG attribute. + nsAutoString language; + if (aElement->GetLang(language)) { + if (!nsStyleUtil::DashMatchCompare( + language, + nsDependentString(pseudoClass->u.mString), + nsASCIICaseInsensitiveStringComparator())) { + return false; + } + // This pseudo-class matched; move on to the next thing + break; + } + + nsIDocument* doc = aTreeMatchContext.mDocument; + if (doc) { + // Try to get the language from the HTTP header or if this + // is missing as well from the preferences. + // The content language can be a comma-separated list of + // language codes. + doc->GetContentLanguage(language); + + nsDependentString langString(pseudoClass->u.mString); + language.StripWhitespace(); + int32_t begin = 0; + int32_t len = language.Length(); + while (begin < len) { + int32_t end = language.FindChar(char16_t(','), begin); + if (end == kNotFound) { + end = len; + } + if (nsStyleUtil::DashMatchCompare( + Substring(language, begin, end - begin), + langString, + nsASCIICaseInsensitiveStringComparator())) { + break; + } + begin = end + 1; + } + if (begin < len) { + // This pseudo-class matched + break; + } + } + } + return false; + + case CSSPseudoClassType::mozBoundElement: + if (aTreeMatchContext.mScopedRoot != aElement) { + return false; + } + break; + + case CSSPseudoClassType::root: + if (aElement != aElement->OwnerDoc()->GetRootElement()) { + return false; + } + break; + + case CSSPseudoClassType::is: + case CSSPseudoClassType::matches: + case CSSPseudoClassType::any: + case CSSPseudoClassType::where: { + if (!SelectorListMatches(aElement, + pseudoClass, + aNodeMatchContext, + aTreeMatchContext, + true)) { + return false; + } + } break; + + case CSSPseudoClassType::mozAny: { + // XXX: For compatibility, we retain :-moz-any()'s original behavior, + // which is to be unforgiving and reject complex selectors in + // its selector list argument. + if (!SelectorListMatches(aElement, + pseudoClass, + aNodeMatchContext, + aTreeMatchContext, + false, + true)) { + return false; + } + } break; + + case CSSPseudoClassType::mozAnyPrivate: { + if (!SelectorListMatches( + aElement, pseudoClass, aNodeMatchContext, aTreeMatchContext)) { + return false; + } + } break; + + case CSSPseudoClassType::slotted: { + if (aTreeMatchContext.mForAssignedSlot) { + aTreeMatchContext.mForAssignedSlot = false; + } + + // Slottables cannot be matched from the outer tree. + if (isOutsideShadowTree) { + return false; + } + + // Slot elements cannot be matched. + if (aElement->IsHTMLElement(nsGkAtoms::slot)) { + return false; + } + + // The current element must have an assigned slot. + if (!aElement->GetAssignedSlot()) { + return false; + } if (!SelectorListMatches( aElement, pseudoClass, aNodeMatchContext, aTreeMatchContext)) { return false; @@ -1762,3 +2687,850 @@ TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const } } #endif +======= + case CSSPseudoClassType::part: + break; + + case CSSPseudoClassType::host: { + ShadowRoot* shadow = aElement->GetShadowRoot(); + // In order to match :host, the element must be a shadow root host, + // we must be matching only against host pseudo selectors, and the + // selector's context must be the shadow root (the selector must be + // featureless, the left-most selector, and be in a shadow root + // style). + if (!shadow || aSelector->HasFeatureSelectors() || + isOutsideShadowTree) { + return false; + } + + // Check if the element has the same shadow root. + if (aTreeMatchContext.mScopedRoot) { + if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) { + return false; + } + } + + // Reject if the next selector is an explicit universal selector. + if (aSelector->mNext && aSelector->mNext->mExplicitUniversal) { + return false; + } + + // The :host selector may also be be functional, with a compound + // selector. If this is the case, then also ensure that the host + // element matches against the compound selector. + if (!pseudoClass->u.mSelectorList) { + break; + } + + // Match if any selector in the argument list matches. + // FIXME: What this effectively does is bypass the "featureless" + // selector check under SelectorMatches. + NodeMatchContext nodeContext(aNodeMatchContext.mStateMask, + aNodeMatchContext.mIsRelevantLink); + if (!SelectorListMatches( + aElement, pseudoClass, nodeContext, aTreeMatchContext)) { + return false; + } + } break; + + case CSSPseudoClassType::hostContext: { + // In order to match host-context, the element must be a + // shadow root host and the selector's context must be the + // shadow root (aTreeMatchContext.mScopedRoot is set to the + // host of the shadow root where the style is contained, + // thus the element must be mScopedRoot). If the UNKNOWN + // selector flag is set, relax the shadow root host + // requirement because this pseudo class walks through + // ancestors looking for a match, thus the selector can be + // dependant on aElement even though it is not the host. The + // dependency would otherwise be missed because when UNKNOWN + // is set, selector matching may not have started from the top. + if (!((aElement->GetShadowRoot() && + aElement == aTreeMatchContext.mScopedRoot) || + aSelectorFlags & SelectorMatchesFlags::UNKNOWN)) { + return false; + } + + Element* currentElement = aElement; + while (currentElement) { + NodeMatchContext nodeContext( + EventStates(), nsCSSRuleUtils::IsLink(currentElement)); + if (SelectorListMatches(currentElement, + pseudoClass, + nodeContext, + aTreeMatchContext)) { + break; + } + + nsIContent* flattenedParent = + currentElement->GetFlattenedTreeParent(); + currentElement = flattenedParent && flattenedParent->IsElement() + ? flattenedParent->AsElement() + : nullptr; + } + if (!currentElement) { + return false; + } + } break; + + case CSSPseudoClassType::firstChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, true, false)) { + return false; + } + break; + + case CSSPseudoClassType::firstNode: { + nsIContent* firstNode = nullptr; + nsIContent* parent = aElement->GetParent(); + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + int32_t index = -1; + do { + firstNode = parent->GetChildAt(++index); + // stop at first non-comment and non-whitespace node + } while (firstNode && + !nsStyleUtil::IsSignificantChild(firstNode, true, false)); + } + if (aElement != firstNode) { + return false; + } + } break; + + case CSSPseudoClassType::lastChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, false, true)) { + return false; + } + break; + + case CSSPseudoClassType::lastNode: { + nsIContent* lastNode = nullptr; + nsIContent* parent = aElement->GetParent(); + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_EDGE_CHILD_SELECTOR); + + uint32_t index = parent->GetChildCount(); + do { + lastNode = parent->GetChildAt(--index); + // stop at first non-comment and non-whitespace node + } while (lastNode && + !nsStyleUtil::IsSignificantChild(lastNode, true, false)); + } + if (aElement != lastNode) { + return false; + } + } break; + + case CSSPseudoClassType::onlyChild: + if (!edgeChildMatches(aElement, aTreeMatchContext, true, true)) { + return false; + } + break; + + case CSSPseudoClassType::firstOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, false)) { + return false; + } + break; + + case CSSPseudoClassType::lastOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, false, true)) { + return false; + } + break; + + case CSSPseudoClassType::onlyOfType: + if (!edgeOfTypeMatches(aElement, aTreeMatchContext, true, true)) { + return false; + } + break; + + case CSSPseudoClassType::nthChild: + if (!nthChildGenericMatches( + aElement, aTreeMatchContext, pseudoClass, false, false)) { + return false; + } + break; + + case CSSPseudoClassType::nthLastChild: + if (!nthChildGenericMatches( + aElement, aTreeMatchContext, pseudoClass, false, true)) { + return false; + } + break; + + case CSSPseudoClassType::nthOfType: + if (!nthChildGenericMatches( + aElement, aTreeMatchContext, pseudoClass, true, false)) { + return false; + } + break; + + case CSSPseudoClassType::nthLastOfType: + if (!nthChildGenericMatches( + aElement, aTreeMatchContext, pseudoClass, true, true)) { + return false; + } + break; + + case CSSPseudoClassType::mozIsHTML: + if (!aTreeMatchContext.mIsHTMLDocument || + !aElement->IsHTMLElement()) { + return false; + } + break; + + case CSSPseudoClassType::mozNativeAnonymous: + if (!aElement->IsInNativeAnonymousSubtree()) { + return false; + } + break; + + case CSSPseudoClassType::mozSystemMetric: { + nsCOMPtr metric = NS_Atomize(pseudoClass->u.mString); + if (!nsCSSRuleUtils::HasSystemMetric(metric)) { + return false; + } + } break; + + case CSSPseudoClassType::mozLocaleDir: { + bool docIsRTL = + aTreeMatchContext.mDocument->GetDocumentState().HasState( + NS_DOCUMENT_STATE_RTL_LOCALE); + + nsDependentString dirString(pseudoClass->u.mString); + + if (dirString.EqualsLiteral("rtl")) { + if (!docIsRTL) { + return false; + } + } else if (dirString.EqualsLiteral("ltr")) { + if (docIsRTL) { + return false; + } + } else { + // Selectors specifying other directions never match. + return false; + } + } break; + + case CSSPseudoClassType::mozLWTheme: { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() <= + nsIDocument::Doc_Theme_None) { + return false; + } + } break; + + case CSSPseudoClassType::mozLWThemeBrightText: { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != + nsIDocument::Doc_Theme_Bright) { + return false; + } + } break; + + case CSSPseudoClassType::mozLWThemeDarkText: { + if (aTreeMatchContext.mDocument->GetDocumentLWTheme() != + nsIDocument::Doc_Theme_Dark) { + return false; + } + } break; + + case CSSPseudoClassType::mozWindowInactive: + if (!aTreeMatchContext.mDocument->GetDocumentState().HasState( + NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { + return false; + } + break; + + case CSSPseudoClassType::mozTableBorderNonzero: { + if (!aElement->IsHTMLElement(nsGkAtoms::table)) { + return false; + } + const nsAttrValue* val = aElement->GetParsedAttr(nsGkAtoms::border); + if (!val || (val->Type() == nsAttrValue::eInteger && + val->GetIntegerValue() == 0)) { + return false; + } + } break; + + case CSSPseudoClassType::mozBrowserFrame: { + nsCOMPtr browserFrame = + do_QueryInterface(aElement); + if (!browserFrame || !browserFrame->GetReallyIsBrowser()) { + return false; + } + } break; + + case CSSPseudoClassType::mozDir: + case CSSPseudoClassType::dir: { + if (aDependence) { + EventStates states = + nsCSSPseudoClasses::sPseudoClassStateDependences[static_cast( + pseudoClass->mType)]; + if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) { + *aDependence = true; + return false; + } + } + + // If we only had to consider HTML, directionality would be + // exclusively LTR or RTL. + // + // However, in markup languages where there is no direction attribute + // we have to consider the possibility that neither dir(rtl) nor + // dir(ltr) matches. + EventStates state = aElement->StyleState(); + nsDependentString dirString(pseudoClass->u.mString); + + if (dirString.EqualsLiteral("rtl")) { + if (!state.HasState(NS_EVENT_STATE_RTL)) { + return false; + } + } else if (dirString.EqualsLiteral("ltr")) { + if (!state.HasState(NS_EVENT_STATE_LTR)) { + return false; + } + } else { + // Selectors specifying other directions never match. + return false; + } + } break; + + case CSSPseudoClassType::scope: + if (aTreeMatchContext.mForScopedStyle) { + if (aTreeMatchContext.mCurrentStyleScope) { + // If mCurrentStyleScope is null, aElement must be the style + // scope root. This is because the PopStyleScopeForSelectorMatching + // call in SelectorMatchesTree sets mCurrentStyleScope to null + // as soon as we visit the style scope element, and we won't + // progress further up the tree after this call to + // SelectorMatches. Thus if mCurrentStyleScope is still set, + // we know the selector does not match. + return false; + } + } else if (aTreeMatchContext.HasSpecifiedScope()) { + if (!aTreeMatchContext.IsScopeElement(aElement)) { + return false; + } + } else { + if (aElement != aElement->OwnerDoc()->GetRootElement()) { + return false; + } + } + break; + + default: + MOZ_ASSERT(false, "How did that happen?"); + } + } else { + if (!StateSelectorMatches(aElement, + aSelector, + aNodeMatchContext, + aTreeMatchContext, + aSelectorFlags, + aDependence, + statesToCheck)) { + return false; + } + } + } + + bool result = true; + if (aSelector->mAttrList) { + // test for attribute match + if (!targetElement->HasAttrs()) { + // if no attributes on the content, no match + return false; + } else { + result = true; + nsAttrSelector* attr = aSelector->mAttrList; + nsIAtom* matchAttribute; + + do { + bool isHTML = + (aTreeMatchContext.mIsHTMLDocument && targetElement->IsHTMLElement()); + matchAttribute = isHTML ? attr->mLowercaseAttr : attr->mCasedAttr; + if (attr->mNameSpace == kNameSpaceID_Unknown) { + // Attr selector with a wildcard namespace. We have to examine all + // the attributes on our content node.... This sort of selector is + // essentially a boolean OR, over all namespaces, of equivalent attr + // selectors with those namespaces. So to evaluate whether it + // matches, evaluate for each namespace (the only namespaces that + // have a chance at matching, of course, are ones that the element + // actually has attributes in), short-circuiting if we ever match. + result = false; + const nsAttrName* attrName; + for (uint32_t i = 0; (attrName = targetElement->GetAttrNameAt(i)); + ++i) { + if (attrName->LocalName() != matchAttribute) { + continue; + } + if (attr->mFunction == NS_ATTR_FUNC_SET) { + result = true; + } else { + nsAutoString value; +#ifdef DEBUG + bool hasAttr = +#endif + targetElement->GetAttr( + attrName->NamespaceID(), attrName->LocalName(), value); + NS_ASSERTION(hasAttr, "GetAttrNameAt lied"); + result = AttrMatchesValue(attr, value, isHTML); + } + + // At this point |result| has been set by us + // explicitly in this loop. If it's false, we may still match + // -- the content may have another attribute with the same name but + // in a different namespace. But if it's true, we are done (we + // can short-circuit the boolean OR described above). + if (result) { + break; + } + } + } else if (attr->mFunction == NS_ATTR_FUNC_EQUALS) { + result = targetElement->AttrValueIs( + attr->mNameSpace, + matchAttribute, + attr->mValue, + attr->IsValueCaseSensitive(isHTML) ? eCaseMatters : eIgnoreCase); + } else if (!targetElement->HasAttr(attr->mNameSpace, matchAttribute)) { + result = false; + } else if (attr->mFunction != NS_ATTR_FUNC_SET) { + nsAutoString value; +#ifdef DEBUG + bool hasAttr = +#endif + targetElement->GetAttr(attr->mNameSpace, matchAttribute, value); + NS_ASSERTION(hasAttr, "HasAttr lied"); + result = AttrMatchesValue(attr, value, isHTML); + } + + attr = attr->mNext; + } while (attr && result); + } + } + + // apply SelectorMatches to the negated selectors in the chain + if (!isNegated) { + for (nsCSSSelector* negation = aSelector->mNegations; result && negation; + negation = negation->mNegations) { + bool dependence = false; + result = !SelectorMatches(targetElement, + negation, + aNodeMatchContext, + aTreeMatchContext, + SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT, + &dependence); + // If the selector does match due to the dependence on + // aNodeMatchContext.mStateMask, then we want to keep result true + // so that the final result of SelectorMatches is true. Doing so + // tells StateEnumFunc that there is a dependence on the state. + result = result || dependence; + } + } + return result; +} + +#undef STATE_CHECK + +#ifdef DEBUG +/* static */ bool +nsCSSRuleUtils::HasPseudoClassSelectorArgsWithCombinators(nsCSSSelector* aSelector) +{ + for (nsPseudoClassList* p = aSelector->mPseudoClassList; p; p = p->mNext) { + if (nsCSSPseudoClasses::HasSelectorListArg(p->mType)) { + for (nsCSSSelectorList* l = p->u.mSelectorList; l; l = l->mNext) { + if (l->mSelectors->mNext) { + return true; + } + } + } + } + for (nsCSSSelector* n = aSelector->mNegations; n; n = n->mNegations) { + if (n->mNext) { + return true; + } + } + return false; +} +#endif + +/* static */ bool +nsCSSRuleUtils::SelectorMatchesTree(Element* aPrevElement, + nsCSSSelector* aSelector, + TreeMatchContext& aTreeMatchContext, + SelectorMatchesTreeFlags aFlags) +{ + MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement()); + nsCSSSelector* selector = aSelector; + Element* prevElement = aPrevElement; + bool crossedShadowRootBoundary = false; + while (selector) { // check compound selectors + bool contentIsFeatureless = false; + NS_ASSERTION(!selector->mNext || selector->mNext->mOperator != char16_t(0), + "compound selector without combinator"); + + // If after the previous selector match we are now outside the + // current style scope, we don't need to match any further. + if (aTreeMatchContext.mForScopedStyle && + !aTreeMatchContext.IsWithinStyleScopeForSelectorMatching()) { + return false; + } + + // for adjacent sibling combinators, the content to test against the + // selector is the previous sibling *element* + Element* element = nullptr; + if (char16_t('+') == selector->mOperator || + char16_t('~') == selector->mOperator) { + // The relevant link must be an ancestor of the node being matched. + aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink); + nsIContent* parent = prevElement->GetParent(); + // Operate on the flattened element tree when matching the + // ::slotted() pseudo-element. + if (aTreeMatchContext.mRestrictToSlottedPseudo) { + parent = prevElement->GetFlattenedTreeParent(); + } + if (parent) { + if (aTreeMatchContext.mForStyling) + parent->SetFlags(NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); + + element = prevElement->GetPreviousElementSibling(); + } + } + // for descendant combinators and child combinators, the element + // to test against is the parent + else { + nsIContent* content = prevElement->GetParent(); + // Operate on the flattened element tree when matching the + // ::slotted() pseudo-element. + if (aTreeMatchContext.mRestrictToSlottedPseudo) { + content = prevElement->GetFlattenedTreeParent(); + } + + // In the shadow tree, the shadow host behaves as if it + // is a featureless parent of top-level elements of the shadow + // tree. Only cross shadow root boundary when the selector is the + // left most selector because ancestors of the host are not in + // the selector match list. + ShadowRoot* shadowRoot = + content ? ShadowRoot::FromNode(content) : nullptr; + if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) { + content = shadowRoot->GetHost(); + crossedShadowRootBoundary = true; + contentIsFeatureless = true; + } + + // GetParent could return a document fragment; we only want + // element parents. + if (content && content->IsElement()) { + element = content->AsElement(); + if (aTreeMatchContext.mForScopedStyle) { + // We are moving up to the parent element; tell the + // TreeMatchContext, so that in case this element is the + // style scope element, selector matching stops before we + // traverse further up the tree. + aTreeMatchContext.PopStyleScopeForSelectorMatching(element); + } + + // Compatibility hack: First try matching this selector as though the + // element wasn't in the tree to allow old selectors + // were written before participated in CSS selector + // matching to work. + if (selector->mOperator == '>' && element->IsActiveChildrenElement()) { + Element* styleScope = aTreeMatchContext.mCurrentStyleScope; + if (SelectorMatchesTree( + element, selector, aTreeMatchContext, aFlags)) { + // It matched, don't try matching on the element at + // all. + return true; + } + // We want to reset mCurrentStyleScope on aTreeMatchContext + // back to its state before the SelectorMatchesTree call, in + // case that call happens to traverse past the style scope element + // and sets it to null. + aTreeMatchContext.mCurrentStyleScope = styleScope; + } + } + } + if (!element) { + return false; + } + if ((aFlags & eMatchOnConditionalRestyleAncestor) && + element->HasFlag(ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)) { + // If we're looking at an element that we already generated an + // eRestyle_SomeDescendants restyle hint for, then we should pretend + // that we matched here, because we don't know what the values of + // attributes on |element| were at the time we generated the + // eRestyle_SomeDescendants. This causes AttributeEnumFunc and + // HasStateDependentStyle below to generate a restyle hint for the + // change we're currently looking at, as we don't know whether the LHS + // of the selector we looked up matches or not. (We only pass in aFlags + // to cause us to look for eRestyle_SomeDescendants here under + // AttributeEnumFunc and HasStateDependentStyle.) + return true; + } + const bool isRelevantLink = + (aFlags & eLookForRelevantLink) && nsCSSRuleUtils::IsLink(element); + + NodeMatchContext nodeContext( + EventStates(), isRelevantLink, contentIsFeatureless); + if (isRelevantLink) { + // If we find an ancestor of the matched node that is a link + // during the matching process, then it's the relevant link (see + // constructor call above). + // Since we are still matching against selectors that contain + // :visited (they'll just fail), we will always find such a node + // during the selector matching process if there is a relevant + // link that can influence selector matching. + aFlags = SelectorMatchesTreeFlags(aFlags & ~eLookForRelevantLink); + aTreeMatchContext.SetHaveRelevantLink(); + } + if (SelectorMatches(element, + selector, + nodeContext, + aTreeMatchContext, + SelectorMatchesFlags::NONE)) { + // to avoid greedy matching, we need to recur if this is a + // descendant or general sibling combinator and the next + // combinator is different, but we can make an exception for + // sibling, then parent, since a sibling's parent is always the + // same. + if (NS_IS_GREEDY_OPERATOR(selector->mOperator) && selector->mNext && + selector->mNext->mOperator != selector->mOperator && + !(selector->mOperator == '~' && + NS_IS_ANCESTOR_OPERATOR(selector->mNext->mOperator))) { + + // pretend the selector didn't match, and step through content + // while testing the same selector + + // This approach is slightly strange in that when it recurs + // it tests from the top of the content tree, down. This + // doesn't matter much for performance since most selectors + // don't match. (If most did, it might be faster...) + Element* styleScope = aTreeMatchContext.mCurrentStyleScope; + if (SelectorMatchesTree(element, selector, aTreeMatchContext, aFlags)) { + return true; + } + // We want to reset mCurrentStyleScope on aTreeMatchContext + // back to its state before the SelectorMatchesTree call, in + // case that call happens to traverse past the style scope element + // and sets it to null. + aTreeMatchContext.mCurrentStyleScope = styleScope; + } + selector = selector->mNext; + if (!selector && !aTreeMatchContext.mIsTopmostScope && + aTreeMatchContext.mRestrictToSlottedPseudo && + aTreeMatchContext.mScopedRoot != element) { + return false; + } + } else { + // for adjacent sibling and child combinators, if we didn't find + // a match, we're done + if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) { + return false; // parent was required to match + } + } + prevElement = element; + } + return true; // all the selectors matched. +} + +/* static */ bool +nsCSSRuleUtils::SelectorListMatches(Element* aElement, + nsCSSSelectorList* aList, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + SelectorMatchesFlags aSelectorFlags, + bool aIsForgiving, + bool aPreventComplexSelectors) +{ + while (aList) { + nsCSSSelector* selector = aList->mSelectors; + // Forgiving selector lists are allowed to be empty, but they + // don't match anything. + if (!selector && aIsForgiving) { + return false; + } + NS_ASSERTION(selector, "Should have *some* selectors"); + NS_ASSERTION(!selector->IsPseudoElement(), "Shouldn't have been called"); + if (aPreventComplexSelectors) { + NS_ASSERTION(!selector->mNext, "Shouldn't have complex selectors"); + } + if (SelectorMatches(aElement, + selector, + aNodeMatchContext, + aTreeMatchContext, + aSelectorFlags)) { + nsCSSSelector* next = selector->mNext; + SelectorMatchesTreeFlags selectorTreeFlags = SelectorMatchesTreeFlags(0); + // Try to look for the closest ancestor link element if we're processing + // the selector list argument of a pseudo-class, but only if for a new style context (see SelectorMatches). + if (!aNodeMatchContext.mIsRelevantLink && aTreeMatchContext.mForStyling && + (aSelectorFlags & SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) { + selectorTreeFlags = eLookForRelevantLink; + } + + if (!next || SelectorMatchesTree( + aElement, next, aTreeMatchContext, selectorTreeFlags)) { + return true; + } + } + + aList = aList->mNext; + } + + return false; +} + +/* static */ bool +nsCSSRuleUtils::SelectorListMatches(Element* aElement, + nsPseudoClassList* aList, + NodeMatchContext& aNodeMatchContext, + TreeMatchContext& aTreeMatchContext, + bool aIsForgiving, + bool aPreventComplexSelectors) +{ + return SelectorListMatches(aElement, + aList->u.mSelectorList, + aNodeMatchContext, + aTreeMatchContext, + SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT, + aIsForgiving, + aPreventComplexSelectors); +} + +// TreeMatchContext and AncestorFilter out of line methods +void +TreeMatchContext::InitAncestors(Element* aElement) +{ + MOZ_ASSERT(!mAncestorFilter.mFilter); + MOZ_ASSERT(mAncestorFilter.mHashes.IsEmpty()); + MOZ_ASSERT(mStyleScopes.IsEmpty()); + + mAncestorFilter.mFilter = new AncestorFilter::Filter(); + + if (MOZ_LIKELY(aElement)) { + MOZ_ASSERT(aElement->GetUncomposedDoc() || + aElement->HasFlag(NODE_IS_IN_SHADOW_TREE), + "aElement must be in the document or in shadow tree " + "for the assumption that GetParentNode() is non-null " + "on all element ancestors of aElement to be true"); + // Collect up the ancestors + AutoTArray ancestors; + Element* cur = aElement; + do { + ancestors.AppendElement(cur); + cur = cur->GetParentElementCrossingShadowRoot(); + } while (cur); + + // Now push them in reverse order. + for (uint32_t i = ancestors.Length(); i-- != 0;) { + mAncestorFilter.PushAncestor(ancestors[i]); + PushStyleScope(ancestors[i]); + PushShadowHost(ancestors[i]); + } + } +} + +void +TreeMatchContext::InitStyleScopes(Element* aElement) +{ + MOZ_ASSERT(mStyleScopes.IsEmpty()); + + if (MOZ_LIKELY(aElement)) { + // Collect up the ancestors + AutoTArray ancestors; + Element* cur = aElement; + do { + ancestors.AppendElement(cur); + cur = cur->GetParentElementCrossingShadowRoot(); + } while (cur); + + // Now push them in reverse order. + for (uint32_t i = ancestors.Length(); i-- != 0;) { + PushStyleScope(ancestors[i]); + } + } +} + +void +AncestorFilter::PushAncestor(Element* aElement) +{ + MOZ_ASSERT(mFilter); + + uint32_t oldLength = mHashes.Length(); + + mPopTargets.AppendElement(oldLength); +#ifdef DEBUG + mElements.AppendElement(aElement); +#endif + mHashes.AppendElement(aElement->NodeInfo()->NameAtom()->hash()); + nsIAtom* id = aElement->GetID(); + if (id) { + mHashes.AppendElement(id->hash()); + } + const nsAttrValue* classes = aElement->GetClasses(); + if (classes) { + uint32_t classCount = classes->GetAtomCount(); + for (uint32_t i = 0; i < classCount; ++i) { + mHashes.AppendElement(classes->AtomAt(i)->hash()); + } + } + + uint32_t newLength = mHashes.Length(); + for (uint32_t i = oldLength; i < newLength; ++i) { + mFilter->add(mHashes[i]); + } +} + +void +AncestorFilter::PopAncestor() +{ + MOZ_ASSERT(!mPopTargets.IsEmpty()); + MOZ_ASSERT(mPopTargets.Length() == mElements.Length()); + + uint32_t popTargetLength = mPopTargets.Length(); + uint32_t newLength = mPopTargets[popTargetLength - 1]; + + mPopTargets.TruncateLength(popTargetLength - 1); +#ifdef DEBUG + mElements.TruncateLength(popTargetLength - 1); +#endif + + uint32_t oldLength = mHashes.Length(); + for (uint32_t i = newLength; i < oldLength; ++i) { + mFilter->remove(mHashes[i]); + } + mHashes.TruncateLength(newLength); +} + +#ifdef DEBUG +void +AncestorFilter::AssertHasAllAncestors(Element* aElement) const +{ + Element* cur = aElement->GetParentElementCrossingShadowRoot(); + while (cur) { + MOZ_ASSERT(mElements.Contains(cur)); + cur = cur->GetParentElementCrossingShadowRoot(); + } +} + +void +TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const +{ + if (aElement->IsInNativeAnonymousSubtree()) { + // Document style sheets are never applied to native anonymous content, + // so it's not possible for them to be in a