Support CSS shadow parts

This commit is contained in:
Basilisk-Dev 2026-05-11 19:33:30 -04:00 committed by wuggy
commit a852c0a86a
10 changed files with 1882 additions and 29 deletions

View file

@ -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")

View file

@ -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,

View file

@ -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<RuleValue::eMaxAncestorHashes>(
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)

View file

@ -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);

View file

@ -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);

View file

@ -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, "")

View file

@ -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;
}

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff