summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@gmail.com>2021-06-30 16:27:37 +0100
committerAndreas Kling <kling@serenityos.org>2021-07-11 23:19:56 +0200
commita558916e1f77ed21c49e2e59cd83f09ce2dfa5ac (patch)
treed5a769ec7e76312d53d7f429acf2443a5de53751
parentf7c79de0c504c54ae482e4b57921b5c69172e905 (diff)
downloadserenity-a558916e1f77ed21c49e2e59cd83f09ce2dfa5ac.zip
LibWeb: Make CSS selector parsing use StyleComponentValueRules
Also added some pseudo-classes that were handled in the deprecated parser: - :disabled - :enabled - :checked - :nth-child - :nth-last-child - :not
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp237
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleBlockRule.h7
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleComponentValueRule.h14
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h12
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.h1
5 files changed, 165 insertions, 106 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index eee2356dfb..6e9f947e1e 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -90,60 +90,76 @@ Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<StyleCompo
Vector<CSS::Selector::ComplexSelector> selectors;
size_t index = 0;
+
auto parse_simple_selector = [&]() -> Optional<CSS::Selector::SimpleSelector> {
- if (index >= parts.size()) {
+ if (index >= parts.size())
return {};
- }
- auto currentToken = parts.at(index).to_string();
+ auto current_value = parts.at(index);
+ index++;
+
CSS::Selector::SimpleSelector::Type type;
- if (currentToken == "*") {
+ String value;
+ // FIXME: Handle namespace prefixes.
+
+ if (current_value.is(Token::TokenType::Delim) && current_value.token().delim() == "*") {
+
+ // FIXME: Handle selectors like `*.foo`.
type = CSS::Selector::SimpleSelector::Type::Universal;
- index++;
CSS::Selector::SimpleSelector result;
result.type = type;
return result;
}
- if (currentToken == ".") {
- type = CSS::Selector::SimpleSelector::Type::Class;
- } else if (currentToken == "#") {
+ if (current_value.is(Token::TokenType::Hash)) {
+ if (current_value.token().m_hash_type != Token::HashType::Id) {
+ dbgln("Selector contains hash token that is not an id: {}", current_value.to_string());
+ return {};
+ }
type = CSS::Selector::SimpleSelector::Type::Id;
- } else if (currentToken == "*") {
+ value = current_value.token().m_value.to_string();
+ } else if (current_value.is(Token::TokenType::Delim) && current_value.token().delim() == ".") {
+ if (index >= parts.size())
+ return {};
+
+ current_value = parts.at(index);
+ index++;
+
+ if (!current_value.is(Token::TokenType::Ident)) {
+ dbgln("Expected an ident after '.', got: {}", current_value.to_string());
+ return {};
+ }
+
+ type = CSS::Selector::SimpleSelector::Type::Class;
+ value = current_value.to_string();
+ } else if (current_value.is(Token::TokenType::Delim) && current_value.token().delim() == "*") {
type = CSS::Selector::SimpleSelector::Type::Universal;
} else {
type = CSS::Selector::SimpleSelector::Type::TagName;
- }
-
- index++;
- auto value = currentToken;
-
- if (type == CSS::Selector::SimpleSelector::Type::TagName) {
- value = value.to_lowercase();
+ value = current_value.to_string().to_lowercase();
}
CSS::Selector::SimpleSelector simple_selector;
simple_selector.type = type;
simple_selector.value = value;
- if (index >= parts.size()) {
+ if (index >= parts.size())
return simple_selector;
- }
- currentToken = parts.at(index).to_string();
- if (currentToken.starts_with('[')) {
- auto adjusted = currentToken.substring(1, currentToken.length() - 2);
+ current_value = parts.at(index);
+ index++;
- // TODO: split on String :^)
- Vector<String> attribute_parts = adjusted.split(',');
+ // 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()) {
+
+ Vector<String> attribute_parts = current_value.block().values();
simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute;
simple_selector.attribute_name = attribute_parts.first();
size_t attribute_index = 1;
- if (attribute_index >= attribute_parts.size()) {
+ if (attribute_index >= attribute_parts.size())
return simple_selector;
- }
if (attribute_parts.at(attribute_index) == " =") {
simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
@@ -164,64 +180,88 @@ Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<StyleCompo
return simple_selector;
}
- if (currentToken == ":") {
+ // FIXME: Pseudo-class selectors want to be their own Selector::SimpleSelector::Type according to the spec.
+ if (current_value.is(Token::TokenType::Colon)) {
bool is_pseudo = false;
- index++;
- if (index >= parts.size()) {
+ current_value = parts.at(index);
+ index++;
+ if (index >= parts.size())
return {};
- }
- currentToken = parts.at(index).to_string();
- if (currentToken == ":") {
+ if (current_value.is(Token::TokenType::Colon)) {
is_pseudo = true;
+ current_value = parts.at(index);
index++;
+ if (index >= parts.size())
+ return {};
}
- if (index >= parts.size()) {
- return {};
- }
-
- currentToken = parts.at(index).to_string();
- auto pseudo_name = currentToken;
- index++;
-
// Ignore for now, otherwise we produce a "false positive" selector
// and apply styles to the element itself, not its pseudo element
- if (is_pseudo) {
+ if (is_pseudo)
return {};
- }
- if (pseudo_name.equals_ignoring_case("link")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link;
- } else if (pseudo_name.equals_ignoring_case("visited")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited;
- } else if (pseudo_name.equals_ignoring_case("active")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Active;
- } else if (pseudo_name.equals_ignoring_case("hover")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover;
- } else if (pseudo_name.equals_ignoring_case("focus")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus;
- } else if (pseudo_name.equals_ignoring_case("first-child")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild;
- } else if (pseudo_name.equals_ignoring_case("last-child")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild;
- } else if (pseudo_name.equals_ignoring_case("only-child")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild;
- } else if (pseudo_name.equals_ignoring_case("empty")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
- } else if (pseudo_name.equals_ignoring_case("root")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
- } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstOfType;
- } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
- simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastOfType;
- } else if (pseudo_name.equals_ignoring_case("before")) {
- simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
- } else if (pseudo_name.equals_ignoring_case("after")) {
- simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
+ current_value = parts.at(index);
+ index++;
+
+ if (current_value.is(Token::TokenType::Ident)) {
+ auto pseudo_name = current_value.token().ident();
+ if (pseudo_name.equals_ignoring_case("link")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link;
+ } else if (pseudo_name.equals_ignoring_case("visited")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited;
+ } else if (pseudo_name.equals_ignoring_case("active")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Active;
+ } else if (pseudo_name.equals_ignoring_case("hover")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover;
+ } else if (pseudo_name.equals_ignoring_case("focus")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus;
+ } else if (pseudo_name.equals_ignoring_case("first-child")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild;
+ } else if (pseudo_name.equals_ignoring_case("last-child")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild;
+ } else if (pseudo_name.equals_ignoring_case("only-child")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild;
+ } else if (pseudo_name.equals_ignoring_case("empty")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
+ } else if (pseudo_name.equals_ignoring_case("root")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
+ } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstOfType;
+ } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastOfType;
+ } else if (pseudo_name.equals_ignoring_case("before")) {
+ simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
+ } else if (pseudo_name.equals_ignoring_case("after")) {
+ simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
+ } else if (pseudo_name.equals_ignoring_case("disabled")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Disabled;
+ } else if (pseudo_name.equals_ignoring_case("enabled")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Enabled;
+ } else if (pseudo_name.equals_ignoring_case("checked")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Checked;
+ } else {
+ dbgln("Unknown pseudo class: '{}'", pseudo_name);
+ return simple_selector;
+ }
+ } else if (current_value.is_function()) {
+ auto pseudo_function = current_value.function();
+ if (pseudo_function.name().equals_ignoring_case("nth-child")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::NthChild;
+ simple_selector.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(pseudo_function.values_as_string());
+ } else if (pseudo_function.name().equals_ignoring_case("nth-last-child")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::NthLastChild;
+ simple_selector.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(pseudo_function.values_as_string());
+ } else if (pseudo_function.name().equals_ignoring_case("not")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Not;
+ simple_selector.not_selector = pseudo_function.values_as_string();
+ } else {
+ dbgln("Unknown pseudo class: '{}'()", pseudo_function.name());
+ return simple_selector;
+ }
} else {
- dbgln("Unknown pseudo class: '{}'", pseudo_name);
+ dbgln("Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", current_value.to_string());
return simple_selector;
}
}
@@ -232,30 +272,30 @@ Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<StyleCompo
auto parse_complex_selector = [&]() -> Optional<CSS::Selector::ComplexSelector> {
auto relation = CSS::Selector::ComplexSelector::Relation::Descendant;
- auto currentToken = parts.at(index).to_string();
- if (is_combinator(currentToken)) {
- if (currentToken == ">") {
- relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
- }
- if (currentToken == "+") {
- relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
- }
- if (currentToken == "~") {
- relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
- }
- if (currentToken == "||") {
- relation = CSS::Selector::ComplexSelector::Relation::Column;
+ auto current_value = parts.at(index);
+ if (current_value.is(Token::TokenType::Delim)) {
+ auto delim = current_value.token().delim();
+ if (is_combinator(delim)) {
+ if (delim == ">") {
+ relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
+ } else if (delim == "+") {
+ relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
+ } else if (delim == "~") {
+ relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
+ } else if (delim == "||") {
+ relation = CSS::Selector::ComplexSelector::Relation::Column;
+ }
+ index++;
}
- index++;
}
Vector<CSS::Selector::SimpleSelector> simple_selectors;
for (;;) {
auto component = parse_simple_selector();
- if (!component.has_value()) {
+ if (!component.has_value())
break;
- }
+
simple_selectors.append(component.value());
}
@@ -267,25 +307,21 @@ Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<StyleCompo
for (;;) {
auto complex = parse_complex_selector();
- if (complex.has_value()) {
+ if (complex.has_value())
selectors.append(complex.value());
- }
- if (index >= parts.size()) {
+ if (index >= parts.size())
break;
- }
- auto currentToken = parts.at(index).to_string();
- if (currentToken != ",") {
+ auto current_value = parts.at(index);
+ if (current_value.is(Token::TokenType::Comma))
break;
- }
index++;
}
- if (selectors.is_empty()) {
+ if (selectors.is_empty())
return {};
- }
selectors.first().relation = CSS::Selector::ComplexSelector::Relation::None;
@@ -381,11 +417,6 @@ AtStyleRule Parser::consume_an_at_rule()
reconsume_current_input_token();
auto value = consume_a_component_value();
- if (value.m_type == StyleComponentValueRule::ComponentType::Token) {
- if (value.m_token.is_whitespace()) {
- continue;
- }
- }
rule.m_prelude.append(value);
}
}
@@ -411,11 +442,6 @@ Optional<QualifiedStyleRule> Parser::consume_a_qualified_rule()
reconsume_current_input_token();
auto value = consume_a_component_value();
- if (value.m_type == StyleComponentValueRule::ComponentType::Token) {
- if (value.m_token.is_whitespace()) {
- continue;
- }
- }
rule.m_prelude.append(value);
}
@@ -751,5 +777,4 @@ Vector<StyleComponentValueRule> Parser::parse_as_list_of_comma_separated_compone
return rules;
}
-
}
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleBlockRule.h b/Userland/Libraries/LibWeb/CSS/Parser/StyleBlockRule.h
index 2b5b081e5d..84ba7d3ee6 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/StyleBlockRule.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleBlockRule.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
+ * Copyright (c) 2021, Sam Atkins <atkinssj@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -18,6 +19,12 @@ public:
StyleBlockRule();
~StyleBlockRule();
+ bool is_curly() const { return m_token.is_open_curly(); }
+ bool is_paren() const { return m_token.is_open_paren(); }
+ bool is_square() const { return m_token.is_open_square(); }
+
+ Vector<String> const& values() const { return m_values; }
+
String to_string() const;
private:
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleComponentValueRule.h b/Userland/Libraries/LibWeb/CSS/Parser/StyleComponentValueRule.h
index 40a77826de..f03265dbef 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/StyleComponentValueRule.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleComponentValueRule.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
+ * Copyright (c) 2021, Sam Atkins <atkinssj@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -25,6 +26,19 @@ public:
explicit StyleComponentValueRule(ComponentType);
~StyleComponentValueRule();
+ bool is_block() const { return m_type == ComponentType::Block; }
+ StyleBlockRule const& block() const { return m_block; }
+
+ bool is_function() const { return m_type == ComponentType::Function; }
+ StyleFunctionRule const& function() const { return m_function; }
+
+ bool is(Token::TokenType type) const
+ {
+ return m_type == ComponentType::Token && m_token.is(type);
+ }
+ Token const& token() const { return m_token; }
+ operator Token() const { return m_token; }
+
String to_string() const;
private:
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h b/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h
index 8d0fd15de3..82de57c3e8 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
+ * Copyright (c) 2021, Sam Atkins <atkinssj@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -20,6 +21,17 @@ public:
StyleFunctionRule();
~StyleFunctionRule();
+ String const& name() const { return m_name; }
+ Vector<String> const& values() const { return m_values; }
+ // FIXME: This method is a temporary hack while much of the parser still expects a string, rather than tokens.
+ String values_as_string() const
+ {
+ StringBuilder builder;
+ for (auto& value : m_values)
+ builder.append(value);
+ return builder.to_string();
+ }
+
String to_string() const;
private:
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
index 2a4eb11be0..682a2fb18c 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -78,6 +78,7 @@ public:
// FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere.
// Only used when "pseudo_class" is "NthChild" or "NthLastChild".
NthChildPattern nth_child_pattern;
+ // FIXME: This wants to be a Selector, rather than parsing it each time it is used.
String not_selector {};
};