diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 5008a24c93e..a796400619d 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -28,6 +30,7 @@ #include #include #include +#include namespace Web::SelectorEngine { @@ -131,11 +134,17 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co auto const& attribute_name = attribute.qualified_name.name.name; + auto const* attr = element.namespace_uri() == Namespace::HTML ? element.attributes()->get_attribute_with_lowercase_qualified_name(attribute_name) + : element.attributes()->get_attribute(attribute_name); + if (attribute.match_type == CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute) { // Early way out in case of an attribute existence selector. - return element.has_attribute(attribute_name); + return attr != nullptr; } + if (!attr) + return false; + auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch); auto const case_sensitivity = case_insensitive_match ? CaseSensitivity::CaseInsensitive @@ -144,14 +153,14 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co switch (attribute.match_type) { case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: return case_insensitive_match - ? Infra::is_ascii_case_insensitive_match(element.attribute(attribute_name).value_or({}), attribute.value) - : element.attribute(attribute_name) == attribute.value; + ? Infra::is_ascii_case_insensitive_match(attr->value(), attribute.value) + : attr->value() == attribute.value; case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord: { if (attribute.value.is_empty()) { // This selector is always false is match value is empty. return false; } - auto attribute_value = element.attribute(attribute_name).value_or({}); + auto const& attribute_value = attr->value(); auto const view = attribute_value.bytes_as_string_view().split_view(' '); auto const size = view.size(); for (size_t i = 0; i < size; ++i) { @@ -166,9 +175,9 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co } case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString: return !attribute.value.is_empty() - && element.attribute(attribute_name).value_or({}).contains(attribute.value, case_sensitivity); + && attr->value().contains(attribute.value, case_sensitivity); case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: { - auto const element_attr_value = element.attribute(attribute_name).value_or({}); + auto const& element_attr_value = attr->value(); if (element_attr_value.is_empty()) { // If the attribute value on element is empty, the selector is true // if the match value is also empty and false otherwise. @@ -184,10 +193,10 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co } case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString: return !attribute.value.is_empty() - && element.attribute(attribute_name).value_or({}).bytes_as_string_view().starts_with(attribute.value, case_sensitivity); + && attr->value().bytes_as_string_view().starts_with(attribute.value, case_sensitivity); case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString: return !attribute.value.is_empty() - && element.attribute(attribute_name).value_or({}).bytes_as_string_view().ends_with(attribute.value, case_sensitivity); + && attr->value().bytes_as_string_view().ends_with(attribute.value, case_sensitivity); default: break; } diff --git a/Userland/Libraries/LibWeb/DOM/Attr.cpp b/Userland/Libraries/LibWeb/DOM/Attr.cpp index 3be89605ae9..0c07d9e8762 100644 --- a/Userland/Libraries/LibWeb/DOM/Attr.cpp +++ b/Userland/Libraries/LibWeb/DOM/Attr.cpp @@ -35,6 +35,7 @@ JS::NonnullGCPtr Attr::clone(Document& document) Attr::Attr(Document& document, QualifiedName qualified_name, String value, Element* owner_element) : Node(document, NodeType::ATTRIBUTE_NODE) , m_qualified_name(move(qualified_name)) + , m_lowercase_name(MUST(String(m_qualified_name.as_string()).to_lowercase())) , m_value(move(value)) , m_owner_element(owner_element) { diff --git a/Userland/Libraries/LibWeb/DOM/Attr.h b/Userland/Libraries/LibWeb/DOM/Attr.h index 458ff293466..4c30cc73d53 100644 --- a/Userland/Libraries/LibWeb/DOM/Attr.h +++ b/Userland/Libraries/LibWeb/DOM/Attr.h @@ -30,6 +30,7 @@ public: Optional const& prefix() const { return m_qualified_name.prefix(); } FlyString const& local_name() const { return m_qualified_name.local_name(); } FlyString const& name() const { return m_qualified_name.as_string(); } + FlyString const& lowercase_name() const { return m_lowercase_name; } String const& value() const { return m_value; } void set_value(String value); @@ -51,6 +52,7 @@ private: virtual void visit_edges(Cell::Visitor&) override; QualifiedName m_qualified_name; + FlyString m_lowercase_name; String m_value; JS::GCPtr m_owner_element; }; diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp index 9aea6b2228c..581427f23c3 100644 --- a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp +++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp @@ -174,6 +174,19 @@ Attr const* NamedNodeMap::get_attribute(FlyString const& qualified_name, size_t* return nullptr; } +Attr const* NamedNodeMap::get_attribute_with_lowercase_qualified_name(FlyString const& lowercase_qualified_name) const +{ + bool compare_as_lowercase = associated_element().namespace_uri() == Namespace::HTML; + VERIFY(compare_as_lowercase); + + for (auto const& attribute : m_attributes) { + if (attribute->lowercase_name() == lowercase_qualified_name) + return attribute; + } + + return nullptr; +} + // https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace Attr* NamedNodeMap::get_attribute_ns(Optional const& namespace_, FlyString const& local_name, size_t* item_index) { diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h index b266aac7ab1..34949c1e404 100644 --- a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h +++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h @@ -54,6 +54,8 @@ public: Attr const* remove_attribute(FlyString const& qualified_name); Attr const* remove_attribute_ns(Optional const& namespace_, FlyString const& local_name); + Attr const* get_attribute_with_lowercase_qualified_name(FlyString const&) const; + private: explicit NamedNodeMap(Element&);