) [8,230 102x102]
+ PaintableWithLines (BlockContainer(anonymous)) [8,332 784x0]
diff --git a/Tests/LibWeb/Layout/input/css-namespace-tag-name-selector.html b/Tests/LibWeb/Layout/input/css-namespace-tag-name-selector.html
new file mode 100644
index 0000000000..3a5a1d17f8
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-namespace-tag-name-selector.html
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/Tests/LibWeb/Layout/input/css-namespace-universal-selector.html b/Tests/LibWeb/Layout/input/css-namespace-universal-selector.html
new file mode 100644
index 0000000000..3f1a5d352c
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/css-namespace-universal-selector.html
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index 341bec0672..bfe9df5fe4 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -740,15 +740,28 @@ Parser::ParseErrorOr
> Parser::parse_simple_se
if (peek_token_ends_selector())
return Optional {};
+ // 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> 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));
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp
index d82f422e7d..1daa3fcc5a 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp
@@ -124,17 +124,31 @@ ErrorOr 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 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:
+ //
+ }
TRY(s.try_append(TRY(simple_selector.serialize())));
}
}
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
index 0a4f92caa0..7460c0ec99 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -183,7 +183,7 @@ public:
};
Type type;
- Variant value {};
+ Variant value {};
Attribute const& attribute() const { return value.get(); }
Attribute& attribute() { return value.get(); }
@@ -196,6 +196,8 @@ public:
FlyString& name() { return value.get().name; }
FlyString const& lowercase_name() const { return value.get().lowercase_name; }
FlyString& lowercase_name() { return value.get().lowercase_name; }
+ QualifiedName const& qualified_name() const { return value.get(); }
+ QualifiedName& qualified_name() { return value.get(); }
ErrorOr serialize() const;
};
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index 48a8065dd9..2e84bb70b3 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -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 scope)
+static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, Optional style_sheet_for_rule, DOM::Element const& element, JS::GCPtr 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 scope)
+static inline bool matches(CSS::Selector::SimpleSelector const& component, Optional style_sheet_for_rule, DOM::Element const& element, JS::GCPtr 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 scope)
+static inline bool matches(CSS::Selector const& selector, Optional style_sheet_for_rule, int component_list_index, DOM::Element const& element, JS::GCPtr 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(*ancestor))
continue;
- if (matches(selector, component_list_index - 1, static_cast(*ancestor), scope))
+ if (matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast(*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(*element.parent()))
return false;
- return matches(selector, component_list_index - 1, static_cast(*element.parent()), scope);
+ return matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast(*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 pseudo_element, JS::GCPtr scope)
+bool matches(CSS::Selector const& selector, Optional style_sheet_for_rule, DOM::Element const& element, Optional pseudo_element, JS::GCPtr 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);
}
}
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.h b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h
index c92f06a684..7d94bb6e3f 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.h
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.h
@@ -11,6 +11,6 @@
namespace Web::SelectorEngine {
-bool matches(CSS::Selector const&, DOM::Element const&, Optional = {}, JS::GCPtr scope = {});
+bool matches(CSS::Selector const&, Optional style_sheet_for_rule, DOM::Element const&, Optional = {}, JS::GCPtr scope = {});
}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
index 8f12b15924..b3a4687792 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
@@ -267,7 +267,7 @@ Vector 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::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;
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index 7c6b908a24..b308b3b3ca 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -562,7 +562,7 @@ WebIDL::ExceptionOr 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(this)))
+ if (SelectorEngine::matches(s, {}, *this, {}, static_cast(this)))
return true;
}
return false;
@@ -581,7 +581,7 @@ WebIDL::ExceptionOr 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;
diff --git a/Userland/Libraries/LibWeb/DOM/ParentNode.cpp b/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
index de799726fc..ff9acd9936 100644
--- a/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
+++ b/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
@@ -39,7 +39,7 @@ WebIDL::ExceptionOr> 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([&](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> 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([&](auto& element) {
for (auto& selector : selectors) {
- if (SelectorEngine::matches(selector, element, {}, this)) {
+ if (SelectorEngine::matches(selector, {}, element, {}, this)) {
elements.append(&element);
}
}
diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp
index 2b17b67438..6486c017d9 100644
--- a/Userland/Libraries/LibWeb/Dump.cpp
+++ b/Userland/Libraries/LibWeb/Dump.cpp
@@ -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())
+ if (simple_selector.value.has()) {
builder.append(simple_selector.name());
+ } else if (simple_selector.value.has()) {
+ 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();