ported from UXP: Support CSS shadow parts (51767db3)

This commit is contained in:
2026-05-19 22:52:52 +08:00
parent d7e253b9ae
commit c84685d78a
10 changed files with 167 additions and 32 deletions
+1
View File
@@ -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")
+28 -7
View File
@@ -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,
+36 -3
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)
+1
View File
@@ -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);
+24 -8
View File
@@ -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);
+3
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, "")
+4 -2
View File
@@ -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<bool>
+5 -2
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)
+2 -1
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
+63 -9
View File
@@ -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.