LibWeb: Add namespaces to Universal and TagName selectors

This commit is contained in:
Sam Atkins 2023-08-08 15:11:48 +01:00 committed by Sam Atkins
parent 6c2ed0f51b
commit 1858f06881
Notes: sideshowbarker 2024-07-17 05:09:48 +09:00
13 changed files with 245 additions and 49 deletions

View file

@ -0,0 +1,41 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x207.3125 children: not-inline
BlockContainer <(anonymous)> at (8,8) content-size 784x161.65625 children: inline
line 0 width: 413.453125, height: 161.65625, bottom: 161.65625, baseline: 152
frag 0 from SVGSVGBox start: 0, length: 0, rect: [9,9 300x150]
frag 1 from TextNode start: 0, length: 1, rect: [310,146 8x17.46875]
" "
frag 2 from TextNode start: 0, length: 5, rect: [320,126 99.453125x43.671875]
"Hello"
SVGSVGBox <svg> at (9,9) content-size 300x150 [SVG] children: inline
InlineNode <a>
SVGTextBox <text> at (9,9) content-size 0x0 children: inline
TextNode <#text>
TextNode <#text>
InlineNode <math>
InlineNode <a>
TextNode <#text>
TextNode <#text>
BlockContainer <div> at (9,170.65625) content-size 782x43.65625 children: inline
line 0 width: 101.453125, height: 43.65625, bottom: 43.65625, baseline: 33.828125
frag 0 from TextNode start: 0, length: 5, rect: [10,170.65625 99.453125x43.671875]
"Hello"
InlineNode <a>
TextNode <#text>
BlockContainer <(anonymous)> at (8,215.3125) content-size 784x0 children: inline
TextNode <#text>
PaintableWithLines (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x207.3125]
PaintableWithLines (BlockContainer(anonymous)) [8,8 784x161.65625] overflow: [8,8 784x161.671875]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 302x152]
TextPaintable (TextNode<#text>)
InlinePaintable (InlineNode<math>)
InlinePaintable (InlineNode<a>)
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<DIV>) [8,169.65625 784x45.65625] overflow: [9,170.65625 782x43.671875]
InlinePaintable (InlineNode<A>)
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,215.3125 784x0]

View file

@ -0,0 +1,22 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x324 children: not-inline
SVGSVGBox <svg> at (18,18) content-size 100x100 [SVG] children: not-inline
BlockContainer <(anonymous)> at (8,128) content-size 784x0 children: inline
TextNode <#text>
BlockContainer <math> at (9,129) content-size 100x100 children: not-inline
BlockContainer <(anonymous)> at (8,230) content-size 784x0 children: inline
TextNode <#text>
BlockContainer <div> at (9,231) content-size 100x100 children: not-inline
BlockContainer <(anonymous)> at (8,332) content-size 784x0 children: inline
TextNode <#text>
PaintableWithLines (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x324]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 120x120]
PaintableWithLines (BlockContainer(anonymous)) [8,128 784x0]
PaintableWithLines (BlockContainer<math>) [8,128 102x102]
PaintableWithLines (BlockContainer(anonymous)) [8,230 784x0]
PaintableWithLines (BlockContainer<DIV>) [8,230 102x102]
PaintableWithLines (BlockContainer(anonymous)) [8,332 784x0]

View file

@ -0,0 +1,23 @@
<style>
@namespace s "http://www.w3.org/2000/svg";
body * {
border: 1px solid black;
}
a {
width: 100px;
}
|a {
width: 200px !important;
}
*|a {
height: 100px;
font-size: 40px;
}
s|a {
font-size: 80px;
}
</style>
<svg><a><text x="20" y="80">Hello</text></a></svg>
<math><a>Hello</a></math>
<div><a>Hello</a></div>

View file

@ -0,0 +1,21 @@
<style>
@namespace s "http://www.w3.org/2000/svg";
body * {
display: block;
width: 100px;
border: 1px solid black;
}
body |* {
width: 200px !important;
}
body *|* {
height: 100px;
}
body s|* {
border-width: 10px;
}
</style>
<svg></svg>
<math></math>
<div></div>

View file

@ -740,15 +740,28 @@ Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_se
if (peek_token_ends_selector())
return Optional<Selector::SimpleSelector> {};
// Handle universal and tag-name types together, since both can be namespaced
if (auto qualified_name = parse_selector_qualified_name(tokens, AllowWildcardName::Yes); qualified_name.has_value()) {
if (qualified_name->name.name == "*"sv) {
return Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::Universal,
.value = qualified_name.release_value(),
};
}
return Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::TagName,
.value = qualified_name.release_value(),
};
}
auto const& first_value = tokens.next_token();
if (first_value.is(Token::Type::Delim)) {
u32 delim = first_value.token().delim();
switch (delim) {
case '*':
return Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::Universal
};
// Handled already
VERIFY_NOT_REACHED();
case '.': {
if (peek_token_ends_selector())
return ParseError::SyntaxError;
@ -787,13 +800,7 @@ Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_se
.value = Selector::SimpleSelector::Name { FlyString::from_utf8(first_value.token().hash_value()).release_value_but_fixme_should_propagate_errors() }
};
}
if (first_value.is(Token::Type::Ident)) {
return Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::TagName,
// FIXME: XML requires case-sensitivity for identifiers, while HTML does not. As such, this should be reworked if XML support is added.
.value = Selector::SimpleSelector::Name { FlyString::from_deprecated_fly_string(first_value.token().ident().to_lowercase_string()).release_value_but_fixme_should_propagate_errors() }
};
}
if (first_value.is_block() && first_value.block().is_square())
return TRY(parse_attribute_simple_selector(first_value));

View file

@ -124,17 +124,31 @@ ErrorOr<String> Selector::SimpleSelector::serialize() const
StringBuilder s;
switch (type) {
case Selector::SimpleSelector::Type::TagName:
case Selector::SimpleSelector::Type::Universal:
// FIXME: 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
// FIXME: 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace) append "|" (U+007C) to s.
// 3. If this is a type selector append the serialization of the element name as an identifier to s.
if (type == Selector::SimpleSelector::Type::TagName) {
TRY(serialize_an_identifier(s, name()));
case Selector::SimpleSelector::Type::Universal: {
auto qualified_name = this->qualified_name();
// 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null
// namespace (not in a namespace) append the serialization of the namespace prefix as an identifier,
// followed by a "|" (U+007C) to s.
if (qualified_name.namespace_type == QualifiedName::NamespaceType::Named) {
TRY(serialize_an_identifier(s, qualified_name.namespace_));
TRY(s.try_append('|'));
}
// 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace)
// append "|" (U+007C) to s.
if (qualified_name.namespace_type == QualifiedName::NamespaceType::None)
TRY(s.try_append('|'));
// 3. If this is a type selector append the serialization of the element name as an identifier to s.
if (type == Selector::SimpleSelector::Type::TagName)
TRY(serialize_an_identifier(s, qualified_name.name.name));
// 4. If this is a universal selector append "*" (U+002A) to s.
if (type == Selector::SimpleSelector::Type::Universal)
TRY(s.try_append('*'));
break;
}
case Selector::SimpleSelector::Type::Attribute: {
auto& attribute = this->attribute();
@ -300,11 +314,23 @@ ErrorOr<String> Selector::serialize() const
&& compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::Universal) {
TRY(s.try_append(TRY(compound_selector.simple_selectors.first().serialize())));
}
// 2. Otherwise, for each simple selector in the compound selectors...
// FIXME: ...that is not a universal selector of which the namespace prefix maps to a namespace that is not the default namespace...
// ...serialize the simple selector and append the result to s.
// 2. Otherwise, for each simple selector in the compound selectors that is not a universal selector
// of which the namespace prefix maps to a namespace that is not the default namespace
// serialize the simple selector and append the result to s.
else {
for (auto& simple_selector : compound_selector.simple_selectors) {
if (simple_selector.type == SimpleSelector::Type::Universal) {
auto qualified_name = simple_selector.qualified_name();
if (qualified_name.namespace_type == SimpleSelector::QualifiedName::NamespaceType::Default)
continue;
// FIXME: I *think* if we have a namespace prefix that happens to equal the same as the default namespace,
// we also should skip it. But we don't have access to that here. eg:
// <style>
// @namespace "http://example";
// @namespace foo "http://example";
// foo|*.bar { } /* This would skip the `foo|*` when serializing. */
// </style>
}
TRY(s.try_append(TRY(simple_selector.serialize())));
}
}

View file

@ -183,7 +183,7 @@ public:
};
Type type;
Variant<Empty, Attribute, PseudoClass, PseudoElement, Name> value {};
Variant<Empty, Attribute, PseudoClass, PseudoElement, Name, QualifiedName> value {};
Attribute const& attribute() const { return value.get<Attribute>(); }
Attribute& attribute() { return value.get<Attribute>(); }
@ -196,6 +196,8 @@ public:
FlyString& name() { return value.get<Name>().name; }
FlyString const& lowercase_name() const { return value.get<Name>().lowercase_name; }
FlyString& lowercase_name() { return value.get<Name>().lowercase_name; }
QualifiedName const& qualified_name() const { return value.get<QualifiedName>(); }
QualifiedName& qualified_name() { return value.get<QualifiedName>(); }
ErrorOr<String> serialize() const;
};

View file

@ -203,7 +203,7 @@ static inline DOM::Element const* next_sibling_with_same_tag_name(DOM::Element c
return nullptr;
}
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{
switch (pseudo_class.type) {
case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
@ -280,13 +280,13 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:
case CSS::Selector::SimpleSelector::PseudoClass::Type::Where:
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, element))
if (matches(selector, style_sheet_for_rule, element))
return true;
}
return false;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, element))
if (matches(selector, style_sheet_for_rule, element))
return false;
}
return true;
@ -303,11 +303,11 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
if (!parent)
return false;
auto matches_selector_list = [](CSS::SelectorList const& list, DOM::Element const& element) {
auto matches_selector_list = [&style_sheet_for_rule](CSS::SelectorList const& list, DOM::Element const& element) {
if (list.is_empty())
return true;
for (auto const& child_selector : list) {
if (matches(child_selector, element)) {
if (matches(child_selector, style_sheet_for_rule, element)) {
return true;
}
}
@ -426,24 +426,59 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
return false;
}
static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
static inline bool matches(CSS::Selector::SimpleSelector const& component, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{
switch (component.type) {
case CSS::Selector::SimpleSelector::Type::Universal:
return true;
case CSS::Selector::SimpleSelector::Type::TagName: {
auto qualified_name = component.qualified_name();
// Reject if the tag name doesn't match
if (component.type == CSS::Selector::SimpleSelector::Type::TagName) {
// See https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
if (element.document().document_type() == DOM::Document::Type::HTML) {
if (qualified_name.name.lowercase_name != element.local_name().view())
return false;
} else if (!Infra::is_ascii_case_insensitive_match(qualified_name.name.name, element.local_name())) {
return false;
}
}
// Match the namespace
switch (qualified_name.namespace_type) {
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default:
// "if no default namespace has been declared for selectors, this is equivalent to *|E."
if (!style_sheet_for_rule.has_value() || !style_sheet_for_rule->default_namespace().has_value())
return true;
// "Otherwise it is equivalent to ns|E where ns is the default namespace."
return element.namespace_() == style_sheet_for_rule->default_namespace();
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None:
// "elements with name E without a namespace"
return element.namespace_().is_empty();
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any:
// "elements with name E in any namespace, including those without a namespace"
return true;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named:
// "elements with name E in namespace ns"
// Unrecognized namespace prefixes are invalid, so don't match.
// (We can't detect this at parse time, since a namespace rule may be inserted later.)
// So, if we don't have a context to look up namespaces from, we fail to match.
if (!style_sheet_for_rule.has_value())
return false;
auto selector_namespace = style_sheet_for_rule->namespace_uri(qualified_name.namespace_);
return selector_namespace.has_value() && selector_namespace.value() == element.namespace_();
}
VERIFY_NOT_REACHED();
}
case CSS::Selector::SimpleSelector::Type::Id:
return component.name() == element.attribute(HTML::AttributeNames::id).view();
case CSS::Selector::SimpleSelector::Type::Class:
return element.has_class(component.name());
case CSS::Selector::SimpleSelector::Type::TagName:
// See https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
if (element.document().document_type() == DOM::Document::Type::HTML)
return component.lowercase_name() == element.local_name().view();
return Infra::is_ascii_case_insensitive_match(component.name(), element.local_name());
case CSS::Selector::SimpleSelector::Type::Attribute:
return matches_attribute(component.attribute(), element);
case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(component.pseudo_class(), element, scope);
return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope);
case CSS::Selector::SimpleSelector::Type::PseudoElement:
// Pseudo-element matching/not-matching is handled in the top level matches().
return true;
@ -452,11 +487,11 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::
}
}
static inline bool matches(CSS::Selector const& selector, int component_list_index, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, int component_list_index, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{
auto& relative_selector = selector.compound_selectors()[component_list_index];
for (auto& simple_selector : relative_selector.simple_selectors) {
if (!matches(simple_selector, element, scope))
if (!matches(simple_selector, style_sheet_for_rule, element, scope))
return false;
}
switch (relative_selector.combinator) {
@ -467,7 +502,7 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
for (auto* ancestor = element.parent(); ancestor; ancestor = ancestor->parent()) {
if (!is<DOM::Element>(*ancestor))
continue;
if (matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), scope))
if (matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), scope))
return true;
}
return false;
@ -475,16 +510,16 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
VERIFY(component_list_index != 0);
if (!element.parent() || !is<DOM::Element>(*element.parent()))
return false;
return matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*element.parent()), scope);
return matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*element.parent()), scope);
case CSS::Selector::Combinator::NextSibling:
VERIFY(component_list_index != 0);
if (auto* sibling = element.previous_element_sibling())
return matches(selector, component_list_index - 1, *sibling, scope);
return matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, scope);
return false;
case CSS::Selector::Combinator::SubsequentSibling:
VERIFY(component_list_index != 0);
for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
if (matches(selector, component_list_index - 1, *sibling, scope))
if (matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, scope))
return true;
}
return false;
@ -494,14 +529,14 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
VERIFY_NOT_REACHED();
}
bool matches(CSS::Selector const& selector, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element, JS::GCPtr<DOM::ParentNode const> scope)
bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element, JS::GCPtr<DOM::ParentNode const> scope)
{
VERIFY(!selector.compound_selectors().is_empty());
if (pseudo_element.has_value() && selector.pseudo_element() != pseudo_element)
return false;
if (!pseudo_element.has_value() && selector.pseudo_element().has_value())
return false;
return matches(selector, selector.compound_selectors().size() - 1, element, scope);
return matches(selector, style_sheet_for_rule, selector.compound_selectors().size() - 1, element, scope);
}
}

View file

@ -11,6 +11,6 @@
namespace Web::SelectorEngine {
bool matches(CSS::Selector const&, DOM::Element const&, Optional<CSS::Selector::PseudoElement> = {}, JS::GCPtr<DOM::ParentNode const> scope = {});
bool matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, Optional<CSS::Selector::PseudoElement> = {}, JS::GCPtr<DOM::ParentNode const> scope = {});
}

View file

@ -267,7 +267,7 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
matching_rules.ensure_capacity(rules_to_run.size());
for (auto const& rule_to_run : rules_to_run) {
auto const& selector = rule_to_run.rule->selectors()[rule_to_run.selector_index];
if (SelectorEngine::matches(selector, element, pseudo_element))
if (SelectorEngine::matches(selector, *rule_to_run.sheet, element, pseudo_element))
matching_rules.append(rule_to_run);
}
return matching_rules;
@ -2614,7 +2614,7 @@ NonnullOwnPtr<StyleComputer::RuleCache> StyleComputer::make_rule_cache_for_casca
break;
}
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) {
rule_cache->rules_by_tag_name.ensure(simple_selector.name()).append(move(matching_rule));
rule_cache->rules_by_tag_name.ensure(simple_selector.qualified_name().name.lowercase_name).append(move(matching_rule));
++num_tag_name_rules;
added_to_bucket = true;
break;

View file

@ -562,7 +562,7 @@ WebIDL::ExceptionOr<bool> Element::matches(StringView selectors) const
// 3. If the result of match a selector against an element, using s, this, and scoping root this, returns success, then return true; otherwise, return false.
auto sel = maybe_selectors.value();
for (auto& s : sel) {
if (SelectorEngine::matches(s, *this, {}, static_cast<ParentNode const*>(this)))
if (SelectorEngine::matches(s, {}, *this, {}, static_cast<ParentNode const*>(this)))
return true;
}
return false;
@ -581,7 +581,7 @@ WebIDL::ExceptionOr<DOM::Element const*> Element::closest(StringView selectors)
auto matches_selectors = [this](CSS::SelectorList const& selector_list, Element const* element) {
// 4. For each element in elements, if match a selector against an element, using s, element, and scoping root this, returns success, return element.
for (auto& selector : selector_list) {
if (!SelectorEngine::matches(selector, *element, {}, this))
if (!SelectorEngine::matches(selector, {}, *element, {}, this))
return false;
}
return true;

View file

@ -39,7 +39,7 @@ WebIDL::ExceptionOr<JS::GCPtr<Element>> ParentNode::query_selector(StringView se
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
for_each_in_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, element, {}, this)) {
if (SelectorEngine::matches(selector, {}, element, {}, this)) {
result = &element;
return IterationDecision::Break;
}
@ -71,7 +71,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<NodeList>> ParentNode::query_selector_all(S
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
for_each_in_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, element, {}, this)) {
if (SelectorEngine::matches(selector, {}, element, {}, this)) {
elements.append(&element);
}
}

View file

@ -447,8 +447,27 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
builder.appendff("{}:", type_description);
// FIXME: This is goofy
if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>())
if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>()) {
builder.append(simple_selector.name());
} else if (simple_selector.value.has<CSS::Selector::SimpleSelector::QualifiedName>()) {
auto qualified_name = simple_selector.qualified_name();
StringView namespace_type;
switch (qualified_name.namespace_type) {
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default:
namespace_type = "Default"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None:
namespace_type = "None"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any:
namespace_type = "Any"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named:
namespace_type = "Named"sv;
break;
}
builder.appendff(" [NamespaceType={}, Namespace='{}', Name='{}']", namespace_type, qualified_name.namespace_, qualified_name.name.name);
}
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
auto const& pseudo_class = simple_selector.pseudo_class();