mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-17 15:59:43 +00:00
LibWeb/CSS: Make non-nested & selector have 0 specificity
If a & simple selector is on a style rule with no parent style rule, then it behaves like :scope - but notably, :scope provides 1 specificity in the class category, but & is supposed to provide 0. To solve this, we stop replacing it directly, and just handle the & like any other simple selector. We know that if the selector engine ever sees one, it's equivalent to :scope, because the nested ones will have been replaced with :is() before that point. This gets us one more subtest pass. :^)
This commit is contained in:
parent
219346011b
commit
7a104fef66
Notes:
github-actions[bot]
2024-11-09 13:30:26 +00:00
Author: https://github.com/AtkinsSJ
Commit: 7a104fef66
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2240
4 changed files with 19 additions and 17 deletions
|
@ -6,6 +6,6 @@ Rerun
|
||||||
|
|
||||||
Found 1 tests
|
Found 1 tests
|
||||||
|
|
||||||
1 Fail
|
1 Pass
|
||||||
Details
|
Details
|
||||||
Result Test Name MessageFail CSS Nesting: Specificity of top-level '&'
|
Result Test Name MessagePass CSS Nesting: Specificity of top-level '&'
|
|
@ -177,28 +177,25 @@ SelectorList const& CSSStyleRule::absolutized_selectors() const
|
||||||
// "When used in the selector of a nested style rule, the nesting selector represents the elements matched by the parent rule.
|
// "When used in the selector of a nested style rule, the nesting selector represents the elements matched by the parent rule.
|
||||||
// When used in any other context, it represents the same elements as :scope in that context (unless otherwise defined)."
|
// When used in any other context, it represents the same elements as :scope in that context (unless otherwise defined)."
|
||||||
// https://drafts.csswg.org/css-nesting-1/#nest-selector
|
// https://drafts.csswg.org/css-nesting-1/#nest-selector
|
||||||
Selector::SimpleSelector parent_selector;
|
|
||||||
if (auto const* parent_style_rule = this->parent_style_rule()) {
|
if (auto const* parent_style_rule = this->parent_style_rule()) {
|
||||||
// TODO: If there's only 1, we don't have to use `:is()` for it
|
// TODO: If there's only 1, we don't have to use `:is()` for it
|
||||||
parent_selector = {
|
Selector::SimpleSelector parent_selector = {
|
||||||
.type = Selector::SimpleSelector::Type::PseudoClass,
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
.value = Selector::SimpleSelector::PseudoClassSelector {
|
.value = Selector::SimpleSelector::PseudoClassSelector {
|
||||||
.type = PseudoClass::Is,
|
.type = PseudoClass::Is,
|
||||||
.argument_selector_list = parent_style_rule->absolutized_selectors(),
|
.argument_selector_list = parent_style_rule->absolutized_selectors(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
parent_selector = {
|
|
||||||
.type = Selector::SimpleSelector::Type::PseudoClass,
|
|
||||||
.value = Selector::SimpleSelector::PseudoClassSelector { .type = PseudoClass::Scope },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectorList absolutized_selectors;
|
SelectorList absolutized_selectors;
|
||||||
for (auto const& selector : selectors())
|
for (auto const& selector : selectors())
|
||||||
absolutized_selectors.append(selector->absolutized(parent_selector));
|
absolutized_selectors.append(selector->absolutized(parent_selector));
|
||||||
|
|
||||||
m_cached_absolutized_selectors = move(absolutized_selectors);
|
m_cached_absolutized_selectors = move(absolutized_selectors);
|
||||||
|
} else {
|
||||||
|
// NOTE: We can't actually replace & with :scope, because & has to have 0 specificity.
|
||||||
|
// So we leave it, and treat & like :scope during matching.
|
||||||
|
m_cached_absolutized_selectors = m_selectors;
|
||||||
|
}
|
||||||
|
|
||||||
return m_cached_absolutized_selectors.value();
|
return m_cached_absolutized_selectors.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,8 +179,11 @@ u32 Selector::specificity() const
|
||||||
// ignore the universal selector
|
// ignore the universal selector
|
||||||
break;
|
break;
|
||||||
case SimpleSelector::Type::Nesting:
|
case SimpleSelector::Type::Nesting:
|
||||||
// We should have replaced this already
|
// "The specificity of the nesting selector is equal to the largest specificity among the complex selectors in the parent style rule’s selector list (identical to the behavior of :is()), or zero if no such selector list exists."
|
||||||
VERIFY_NOT_REACHED();
|
// - https://drafts.csswg.org/css-nesting/#ref-for-specificity
|
||||||
|
// The parented case is handled by replacing & with :is().
|
||||||
|
// So if we got here, the specificity is 0.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -760,8 +760,10 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
|
||||||
// Pseudo-element matching/not-matching is handled in the top level matches().
|
// Pseudo-element matching/not-matching is handled in the top level matches().
|
||||||
return true;
|
return true;
|
||||||
case CSS::Selector::SimpleSelector::Type::Nesting:
|
case CSS::Selector::SimpleSelector::Type::Nesting:
|
||||||
// We should only try to match selectors that have been absolutized!
|
// Nesting either behaves like :is(), or like :scope.
|
||||||
VERIFY_NOT_REACHED();
|
// :is() is handled already, by us replacing it with :is() directly, so if we
|
||||||
|
// got here, it's :scope.
|
||||||
|
return matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector { .type = CSS::PseudoClass::Scope }, style_sheet_for_rule, element, shadow_host, scope, selector_kind);
|
||||||
}
|
}
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue