summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/CSS
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibWeb/CSS')
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp40
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp76
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.h30
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp68
4 files changed, 102 insertions, 112 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
index a8e2143045..1670570061 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
@@ -424,29 +424,31 @@ public:
if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
return {};
- CSS::Selector::SimpleSelector::Type type;
+ CSS::Selector::SimpleSelector simple_selector;
if (peek() == '*') {
- type = CSS::Selector::SimpleSelector::Type::Universal;
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
consume_one();
- CSS::Selector::SimpleSelector result;
- result.type = type;
- return result;
+ return simple_selector;
}
if (peek() == '.') {
- type = CSS::Selector::SimpleSelector::Type::Class;
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::Class;
consume_one();
} else if (peek() == '#') {
- type = CSS::Selector::SimpleSelector::Type::Id;
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::Id;
consume_one();
} else if (isalpha(peek())) {
- type = CSS::Selector::SimpleSelector::Type::TagName;
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::TagName;
+ } else if (peek() == '[') {
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::Attribute;
} else {
- type = CSS::Selector::SimpleSelector::Type::Universal;
+ simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
}
- if (type != CSS::Selector::SimpleSelector::Type::Universal) {
+ if ((simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal)
+ && (simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute)) {
+
while (is_valid_selector_char(peek()))
buffer.append(consume_one());
PARSE_VERIFY(!buffer.is_empty());
@@ -454,18 +456,16 @@ public:
auto value = String::copy(buffer);
- if (type == CSS::Selector::SimpleSelector::Type::TagName) {
+ if (simple_selector.type == CSS::Selector::SimpleSelector::Type::TagName) {
// Some stylesheets use uppercase tag names, so here's a hack to just lowercase them internally.
value = value.to_lowercase();
}
- CSS::Selector::SimpleSelector simple_selector;
- simple_selector.type = type;
simple_selector.value = value;
buffer.clear();
- if (peek() == '[') {
- CSS::Selector::SimpleSelector::AttributeMatchType attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute;
+ if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
+ CSS::Selector::SimpleSelector::Attribute::MatchType attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute;
String attribute_name;
String attribute_value;
bool in_value = false;
@@ -475,10 +475,10 @@ public:
char ch = consume_one();
if (ch == '=' || (ch == '~' && peek() == '=')) {
if (ch == '=') {
- attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
+ attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
} else if (ch == '~') {
consume_one();
- attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ContainsWord;
+ attribute_match_type = CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
}
attribute_name = String::copy(buffer);
buffer.clear();
@@ -503,9 +503,9 @@ public:
else
attribute_name = String::copy(buffer);
buffer.clear();
- simple_selector.attribute_match_type = attribute_match_type;
- simple_selector.attribute_name = attribute_name;
- simple_selector.attribute_value = attribute_value;
+ simple_selector.attribute.match_type = attribute_match_type;
+ simple_selector.attribute.name = attribute_name;
+ simple_selector.attribute.value = attribute_value;
if (expected_end_of_attribute_selector != ']') {
if (!consume_specific(expected_end_of_attribute_selector))
return {};
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index a222aab909..1af0849898 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -243,26 +243,18 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
if (check_for_eof_or_whitespace(current_value))
return {};
- Selector::SimpleSelector::Type type;
- String value;
+ Selector::SimpleSelector simple_selector;
// FIXME: Handle namespace prefixes.
if (current_value.is(Token::Type::Delim) && ((Token)current_value).delim() == "*") {
-
- // FIXME: Handle selectors like `*.foo`.
- type = Selector::SimpleSelector::Type::Universal;
- Selector::SimpleSelector result;
- result.type = type;
- return result;
- }
-
- if (current_value.is(Token::Type::Hash)) {
+ simple_selector.type = Selector::SimpleSelector::Type::Universal;
+ } else if (current_value.is(Token::Type::Hash)) {
if (((Token)current_value).m_hash_type != Token::HashType::Id) {
dbgln("Selector contains hash token that is not an id: {}", current_value.to_debug_string());
return {};
}
- type = Selector::SimpleSelector::Type::Id;
- value = ((Token)current_value).m_value.to_string();
+ simple_selector.type = Selector::SimpleSelector::Type::Id;
+ simple_selector.value = ((Token)current_value).m_value.to_string();
} else if (current_value.is(Token::Type::Delim) && ((Token)current_value).delim() == ".") {
current_value = tokens.next_token();
if (check_for_eof_or_whitespace(current_value))
@@ -273,33 +265,19 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
return {};
}
- type = Selector::SimpleSelector::Type::Class;
- value = current_value.token().ident().to_lowercase_string();
- } else if (current_value.is(Token::Type::Delim) && current_value.token().delim() == "*") {
- type = Selector::SimpleSelector::Type::Universal;
+ simple_selector.type = Selector::SimpleSelector::Type::Class;
+ simple_selector.value = current_value.token().ident().to_lowercase_string();
} else if (current_value.is(Token::Type::Ident)) {
- type = Selector::SimpleSelector::Type::TagName;
- value = current_value.token().ident().to_lowercase_string();
- } else if ((current_value.is(Token::Type::Delim) && current_value.token().delim() == ":")
- || (current_value.is_block() && current_value.block().is_square())) {
+ simple_selector.type = Selector::SimpleSelector::Type::TagName;
+ simple_selector.value = current_value.token().ident().to_lowercase_string();
+ } else if ((current_value.is(Token::Type::Delim) && current_value.token().delim() == ":")) {
// FIXME: This is a temporary hack until we make the Selector::SimpleSelector::Type changes.
- type = Selector::SimpleSelector::Type::Universal;
+ simple_selector.type = Selector::SimpleSelector::Type::Universal;
tokens.reconsume_current_input_token();
- } else {
- dbgln("Invalid simple selector!");
- return {};
- }
-
- Selector::SimpleSelector simple_selector;
- simple_selector.type = type;
- simple_selector.value = value;
-
- current_value = tokens.next_token();
- if (check_for_eof_or_whitespace(current_value))
- return simple_selector;
+ } else if (current_value.is_block() && current_value.block().is_square()) {
+ simple_selector.type = Selector::SimpleSelector::Type::Attribute;
- // FIXME: Attribute selectors want to be their own Selector::SimpleSelector::Type according to the spec.
- if (current_value.is_block() && current_value.block().is_square()) {
+ auto& attribute = simple_selector.attribute;
Vector<StyleComponentValueRule> const& attribute_parts = current_value.block().values();
@@ -315,8 +293,8 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
return {};
}
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::HasAttribute;
- simple_selector.attribute_name = attribute_part.token().ident();
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::HasAttribute;
+ attribute.name = attribute_part.token().ident();
if (attribute_parts.size() == 1)
return simple_selector;
@@ -329,7 +307,7 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
}
if (delim_part.token().delim() == "=") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
attribute_index++;
} else {
attribute_index++;
@@ -345,19 +323,19 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
}
if (delim_part.token().delim() == "~") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ContainsWord;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
attribute_index++;
} else if (delim_part.token().delim() == "*") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ContainsString;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsString;
attribute_index++;
} else if (delim_part.token().delim() == "|") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::StartsWithSegment;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment;
attribute_index++;
} else if (delim_part.token().delim() == "^") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::StartsWithString;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithString;
attribute_index++;
} else if (delim_part.token().delim() == "$") {
- simple_selector.attribute_match_type = Selector::SimpleSelector::AttributeMatchType::EndsWithString;
+ attribute.match_type = Selector::SimpleSelector::Attribute::MatchType::EndsWithString;
attribute_index++;
}
}
@@ -372,12 +350,18 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
dbgln("Expected a string or ident for the value to match attribute against, got: '{}'", value_part.to_debug_string());
return {};
}
- simple_selector.attribute_value = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
+ attribute.value = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
// FIXME: Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
- return simple_selector;
+ } else {
+ dbgln("Invalid simple selector!");
+ return {};
}
+ current_value = tokens.next_token();
+ if (check_for_eof_or_whitespace(current_value))
+ return simple_selector;
+
// FIXME: Pseudo-class selectors want to be their own Selector::SimpleSelector::Type according to the spec.
if (current_value.is(Token::Type::Colon)) {
bool is_pseudo = false;
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
index 7bed123dd0..ab19ce156d 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Sam Atkins <atkinssj@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -21,6 +22,7 @@ public:
TagName,
Id,
Class,
+ Attribute,
};
Type type { Type::Invalid };
@@ -56,20 +58,22 @@ public:
FlyString value;
- enum class AttributeMatchType {
- None,
- HasAttribute,
- ExactValueMatch,
- ContainsWord, // [att~=val]
- ContainsString, // [att*=val]
- StartsWithSegment, // [att|=val]
- StartsWithString, // [att^=val]
- EndsWithString, // [att$=val]
+ struct Attribute {
+ enum class MatchType {
+ None,
+ HasAttribute,
+ ExactValueMatch,
+ ContainsWord, // [att~=val]
+ ContainsString, // [att*=val]
+ StartsWithSegment, // [att|=val]
+ StartsWithString, // [att^=val]
+ EndsWithString, // [att$=val]
+ };
+ MatchType match_type { MatchType::None };
+ FlyString name;
+ String value;
};
-
- AttributeMatchType attribute_match_type { AttributeMatchType::None };
- FlyString attribute_name;
- String attribute_value;
+ Attribute attribute;
struct NthChildPattern {
int step_size = 0;
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index 364a405959..2bfeb77b3d 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Sam Atkins <atkinssj@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -24,6 +25,38 @@ static bool matches_hover_pseudo_class(DOM::Element const& element)
return element.is_ancestor_of(*hovered_node);
}
+static bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, DOM::Element const& element)
+{
+ switch (attribute.match_type) {
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute:
+ return element.has_attribute(attribute.name);
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
+ return element.attribute(attribute.name) == attribute.value;
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
+ return element.attribute(attribute.name).split(' ').contains_slow(attribute.value);
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
+ return element.attribute(attribute.name).contains(attribute.value);
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment:
+ return element.attribute(attribute.name).split('-').first() == attribute.value;
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
+ return element.attribute(attribute.name).starts_with(attribute.value);
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
+ return element.attribute(attribute.name).ends_with(attribute.value);
+ break;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::None:
+ VERIFY_NOT_REACHED();
+ break;
+ }
+
+ return false;
+}
+
static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element)
{
switch (component.pseudo_element) {
@@ -171,39 +204,6 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
break;
}
- switch (component.attribute_match_type) {
- case CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute:
- if (!element.has_attribute(component.attribute_name))
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch:
- if (element.attribute(component.attribute_name) != component.attribute_value)
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::ContainsWord:
- if (!element.attribute(component.attribute_name).split(' ').contains_slow(component.attribute_value))
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::ContainsString:
- if (!element.attribute(component.attribute_name).contains(component.attribute_value))
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::StartsWithSegment:
- if (element.attribute(component.attribute_name).split('-').first() != component.attribute_value)
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::StartsWithString:
- if (!element.attribute(component.attribute_name).starts_with(component.attribute_value))
- return false;
- break;
- case CSS::Selector::SimpleSelector::AttributeMatchType::EndsWithString:
- if (!element.attribute(component.attribute_name).ends_with(component.attribute_value))
- return false;
- break;
- default:
- break;
- }
-
switch (component.type) {
case CSS::Selector::SimpleSelector::Type::Universal:
return true;
@@ -213,6 +213,8 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
return element.has_class(component.value);
case CSS::Selector::SimpleSelector::Type::TagName:
return component.value == element.local_name();
+ case CSS::Selector::SimpleSelector::Type::Attribute:
+ return matches_attribute(component.attribute, element);
default:
VERIFY_NOT_REACHED();
}