From c84685d78a2e031b1bbb68984e55bb61311dd8ad Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 19 May 2026 22:52:52 +0800 Subject: [PATCH] ported from UXP: Support CSS shadow parts (51767db3) --- 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 | 72 +++++++++++++++++++++++---- 10 files changed, 167 insertions(+), 32 deletions(-) diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index f0d3624d5..819042c9c 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -1004,6 +1004,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 bc3b159c6..24fe599a3 100644 --- a/dom/xbl/nsBindingManager.cpp +++ b/dom/xbl/nsBindingManager.cpp @@ -824,16 +824,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 7925ef100..58071a343 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 af505c7e2..75a8116c1 100644 --- a/layout/style/StyleRule.cpp +++ b/layout/style/StyleRule.cpp @@ -334,6 +334,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 d9d26d73e..f4ff49ea4 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -6993,6 +6993,8 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, if (hybridPseudoElementType == CSSPseudoElementType::slotted) { pseudoClassType = CSSPseudoClassType::slotted; aFlags |= SelectorParsingFlags::eDisallowCombinators; + } else if (hybridPseudoElementType == CSSPseudoElementType::part) { + pseudoClassType = CSSPseudoClassType::part; } } @@ -7145,6 +7147,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); @@ -7156,14 +7180,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 752f0c12d..84218c36a 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 8c1be43a2..25b1595ee 100644 --- a/layout/style/nsCSSPseudoClasses.cpp +++ b/layout/style/nsCSSPseudoClasses.cpp @@ -126,7 +126,8 @@ nsCSSPseudoClasses::HasStringArg(Type aType) aType == Type::mozEmptyExceptChildrenWithLocalname || aType == Type::mozSystemMetric || aType == Type::mozLocaleDir || - aType == Type::dir; + aType == Type::dir || + aType == Type::part; } bool @@ -211,7 +212,8 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType) /* static */ bool nsCSSPseudoClasses::IsHybridPseudoElement(Type aType) { - return aType == Type::slotted; + return aType == Type::slotted || + aType == Type::part; } /* static */ Maybe diff --git a/layout/style/nsCSSPseudoElementList.h b/layout/style/nsCSSPseudoElementList.h index 6693d42e7..881c9bce3 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 fb871ff85..6053bd39e 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 78b02a5aa..58f3f78f3 100644 --- a/layout/style/nsCSSRuleUtils.cpp +++ b/layout/style/nsCSSRuleUtils.cpp @@ -9,6 +9,7 @@ #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" @@ -183,6 +184,36 @@ InitSystemMetrics() 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 nsCSSRuleUtils::FreeSystemMetrics() { @@ -635,7 +666,10 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, "is false since we don't know how to set it correctly in " "Has(Attribute|State)DependentStyle"); - if (aNodeMatchContext.mIsFeatureless && + nsPseudoClassList* partPseudo = + FindPseudoClass(aSelector, CSSPseudoClassType::part); + + if (aNodeMatchContext.mIsFeatureless && !partPseudo && !CanMatchFeaturelessElement(aSelector)) { return false; } @@ -651,6 +685,27 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, targetElement = slot->AsElement(); } + 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 && @@ -910,6 +965,9 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, } } break; + case CSSPseudoClassType::part: + break; + case CSSPseudoClassType::host: { ShadowRoot* shadow = aElement->GetShadowRoot(); // In order to match :host, the element must be a shadow root host, @@ -922,15 +980,11 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement, return false; } - // We're matching :host from inside the shadow root. - if (!aTreeMatchContext.mOnlyMatchHostPseudo) { - // Check if the element has the same shadow root. - if (aTreeMatchContext.mScopedRoot) { - if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) { - return false; - } + // Check if the element has the same shadow root. + if (aTreeMatchContext.mScopedRoot) { + if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) { + return false; } - // We were called elsewhere. } // Reject if the next selector is an explicit universal selector.