diff options
author | Sam Atkins <atkinssj@gmail.com> | 2021-06-30 16:27:37 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-07-11 23:19:56 +0200 |
commit | a558916e1f77ed21c49e2e59cd83f09ce2dfa5ac (patch) | |
tree | d5a769ec7e76312d53d7f429acf2443a5de53751 | |
parent | f7c79de0c504c54ae482e4b57921b5c69172e905 (diff) | |
download | serenity-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
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 {}; }; |