Support CSS shadow parts

This commit is contained in:
Basilisk-Dev 2026-05-11 19:33:30 -04:00 • committed by EAZYBLACK
commit b2d422ae49
10 changed files with 167 additions and 32 deletions

View file

@ -1003,6 +1003,7 @@ GK_ATOM(parameter, "parameter")
GK_ATOM(parent, "parent") GK_ATOM(parent, "parent")
GK_ATOM(parentfocused, "parentfocused") GK_ATOM(parentfocused, "parentfocused")
GK_ATOM(parsetype, "parsetype") GK_ATOM(parsetype, "parsetype")
GK_ATOM(part, "part")
GK_ATOM(password, "password") GK_ATOM(password, "password")
GK_ATOM(pattern, "pattern") GK_ATOM(pattern, "pattern")
GK_ATOM(patternSeparator, "pattern-separator") GK_ATOM(patternSeparator, "pattern-separator")

View file

@ -823,15 +823,36 @@ 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 void
nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc, nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc,
ElementDependentRuleProcessorData* aData) ElementDependentRuleProcessorData* aData)
{ {
if (!mBoundContentSet) {
return;
}
bool oldOnlyMatchHostPseudo = aData->mTreeMatchContext.mOnlyMatchHostPseudo;
nsIContent* oldScopedRoot = aData->mTreeMatchContext.mScopedRoot;
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true; aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
WalkAllRules(aFunc, aData, true);
aData->mTreeMatchContext.mOnlyMatchHostPseudo = false; 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 nsresult

View file

@ -404,6 +404,22 @@ RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo)
RuleValue(aRuleInfo, mRuleCount++, mQuirksMode)); 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 void
RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo) RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo)
{ {
@ -411,7 +427,10 @@ RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo)
if (selector->IsPseudoElement()) { if (selector->IsPseudoElement()) {
selector = selector->mNext; 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); AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo);
RULE_HASH_STAT_INCREMENT(mIdSelectors); RULE_HASH_STAT_INCREMENT(mIdSelectors);
} else if (nullptr != selector->mClassList) { } else if (nullptr != selector->mClassList) {
@ -452,6 +471,10 @@ LookForTargetPseudo(nsCSSSelector* aSelector,
nsRestyleHint* possibleChange) nsRestyleHint* possibleChange)
{ {
if (aMatchContext->mOnlyMatchHostPseudo) { if (aMatchContext->mOnlyMatchHostPseudo) {
if (SelectorContainsPseudoClass(aSelector, CSSPseudoClassType::part)) {
return true;
}
while (aSelector && aSelector->mNext != nullptr) { while (aSelector && aSelector->mNext != nullptr) {
aSelector = aSelector->mNext; aSelector = aSelector->mNext;
} }
@ -502,7 +525,8 @@ ContentEnumFunc(const RuleValue& value,
data->mTreeMatchContext.SetHaveRelevantLink(); data->mTreeMatchContext.SetHaveRelevantLink();
} }
// XXX: Ignore the ancestor filter if we're testing the assigned slot. // 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 && if (useAncestorFilter && ancestorFilter &&
!ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>( !ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>(
value.mAncestorSelectorHashes)) { value.mAncestorSelectorHashes)) {
@ -556,9 +580,18 @@ ContentEnumFunc(const RuleValue& value,
nodeContext, nodeContext,
data->mTreeMatchContext, data->mTreeMatchContext,
selectorFlags)) { 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; nsCSSSelector* next = selector->mNext;
if (!next || nsCSSRuleUtils::SelectorMatchesTree( if (!next || nsCSSRuleUtils::SelectorMatchesTree(
data->mElement, treeMatchStart,
next, next,
data->mTreeMatchContext, data->mTreeMatchContext,
nodeContext.mIsRelevantLink ? SelectorMatchesTreeFlags(0) nodeContext.mIsRelevantLink ? SelectorMatchesTreeFlags(0)

View file

@ -335,6 +335,7 @@ nsCSSSelector::Clone(bool aDeepNext, bool aDeepNegations) const
result->mCasedTag = mCasedTag; result->mCasedTag = mCasedTag;
result->mOperator = mOperator; result->mOperator = mOperator;
result->mPseudoType = mPseudoType; result->mPseudoType = mPseudoType;
result->mHybridPseudoType = mHybridPseudoType;
NS_IF_CLONE(mIDList); NS_IF_CLONE(mIDList);
NS_IF_CLONE(mClassList); NS_IF_CLONE(mClassList);

View file

@ -6959,6 +6959,8 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
if (hybridPseudoElementType == CSSPseudoElementType::slotted) { if (hybridPseudoElementType == CSSPseudoElementType::slotted) {
pseudoClassType = CSSPseudoClassType::slotted; pseudoClassType = CSSPseudoClassType::slotted;
aFlags |= SelectorParsingFlags::eDisallowCombinators; aFlags |= SelectorParsingFlags::eDisallowCombinators;
} else if (hybridPseudoElementType == CSSPseudoElementType::part) {
pseudoClassType = CSSPseudoClassType::part;
} }
} }
@ -7111,6 +7113,28 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
UngetToken(); UngetToken();
return eSelectorParsingStatus_Error; 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)) { else if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
parsingStatus = parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
@ -7122,14 +7146,6 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
else { else {
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType), MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
"unexpected pseudo with function token"); "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, parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
pseudoClassType, pseudoClassType,
flags); flags);

View file

@ -94,6 +94,9 @@ CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "")
// Match slot nodes. // Match slot nodes.
CSS_PSEUDO_CLASS(slotted, ":slotted", 0, "layout.css.slotted-pseudo.enabled") 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 // Match nodes that are HTML but not XHTML
CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "") CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "")

View file

@ -125,7 +125,8 @@ nsCSSPseudoClasses::HasStringArg(Type aType)
aType == Type::mozSystemMetric || aType == Type::mozSystemMetric ||
aType == Type::mozLocaleDir || aType == Type::mozLocaleDir ||
aType == Type::mozDir || aType == Type::mozDir ||
aType == Type::dir; aType == Type::dir ||
aType == Type::part;
} }
bool bool
@ -210,5 +211,6 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
/* static */ bool /* static */ bool
nsCSSPseudoClasses::IsHybridPseudoElement(Type aType) nsCSSPseudoClasses::IsHybridPseudoElement(Type aType)
{ {
return aType == Type::slotted; return aType == Type::slotted ||
aType == Type::part;
} }

View file

@ -28,10 +28,13 @@
CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2) CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2)
CSS_PSEUDO_ELEMENT(before, ":before", 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 // XXX: ::slotted() and ::part() are treated as if they were pseudo-classes,
// is never parsed as a pseudo-element. // and are never parsed as pseudo-elements.
CSS_PSEUDO_ELEMENT(slotted, ":slotted", CSS_PSEUDO_ELEMENT(slotted, ":slotted",
CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING) 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) CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)

View file

@ -78,7 +78,8 @@ nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom)
/* static */ bool /* static */ bool
nsCSSPseudoElements::IsHybridPseudoElement(CSSPseudoElementType aType) nsCSSPseudoElements::IsHybridPseudoElement(CSSPseudoElementType aType)
{ {
return aType == CSSPseudoElementType::slotted; return aType == CSSPseudoElementType::slotted ||
aType == CSSPseudoElementType::part;
} }
/* static */ bool /* static */ bool

View file

@ -9,6 +9,7 @@
#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/ShadowRoot.h"
#include "nsIMozBrowserFrame.h" #include "nsIMozBrowserFrame.h"
#include "nsGkAtoms.h"
#include "nsRuleWalker.h" #include "nsRuleWalker.h"
#include "nsStyleUtil.h" #include "nsStyleUtil.h"
#include "StyleRule.h" #include "StyleRule.h"
@ -188,6 +189,36 @@ InitSystemMetrics()
return true; 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 /* static */ void
nsCSSRuleUtils::FreeSystemMetrics() nsCSSRuleUtils::FreeSystemMetrics()
{ {
@ -640,7 +671,10 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
"is false since we don't know how to set it correctly in " "is false since we don't know how to set it correctly in "
"Has(Attribute|State)DependentStyle"); "Has(Attribute|State)DependentStyle");
if (aNodeMatchContext.mIsFeatureless && nsPseudoClassList* partPseudo =
FindPseudoClass(aSelector, CSSPseudoClassType::part);
if (aNodeMatchContext.mIsFeatureless && !partPseudo &&
!CanMatchFeaturelessElement(aSelector)) { !CanMatchFeaturelessElement(aSelector)) {
return false; return false;
} }
@ -656,6 +690,27 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
targetElement = slot->AsElement(); 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 // namespace/tag match
// optimization : bail out early if we can // optimization : bail out early if we can
if ((kNameSpaceID_Unknown != aSelector->mNameSpace && if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
@ -915,6 +970,9 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
} }
} break; } break;
case CSSPseudoClassType::part:
break;
case CSSPseudoClassType::host: { case CSSPseudoClassType::host: {
ShadowRoot* shadow = aElement->GetShadowRoot(); ShadowRoot* shadow = aElement->GetShadowRoot();
// In order to match :host, the element must be a shadow root host, // In order to match :host, the element must be a shadow root host,
@ -927,16 +985,12 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
return false; return false;
} }
// We're matching :host from inside the shadow root.
if (!aTreeMatchContext.mOnlyMatchHostPseudo) {
// Check if the element has the same shadow root. // Check if the element has the same shadow root.
if (aTreeMatchContext.mScopedRoot) { if (aTreeMatchContext.mScopedRoot) {
if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) { if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) {
return false; return false;
} }
} }
// We were called elsewhere.
}
// Reject if the next selector is an explicit universal selector. // Reject if the next selector is an explicit universal selector.
if (aSelector->mNext && aSelector->mNext->mExplicitUniversal) { if (aSelector->mNext && aSelector->mNext->mExplicitUniversal) {