mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 04:27:44 +00:00
LibWeb: Initialize PseudoClass/PseudoElement selectors in one go
There was no real benefit to creating the SimpleSelector early and then modifying it, and doing so made this code harder to follow than it needs to be.
This commit is contained in:
parent
c0db19f63c
commit
75ec960495
2 changed files with 120 additions and 111 deletions
|
@ -420,10 +420,6 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_pseudo_sim
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_pseudo) {
|
if (is_pseudo) {
|
||||||
Selector::SimpleSelector simple_selector {
|
|
||||||
.type = Selector::SimpleSelector::Type::PseudoElement
|
|
||||||
};
|
|
||||||
|
|
||||||
auto const& name_token = tokens.next_token();
|
auto const& name_token = tokens.next_token();
|
||||||
if (!name_token.is(Token::Type::Ident)) {
|
if (!name_token.is(Token::Type::Ident)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected an ident for pseudo-element, got: '{}'", name_token.to_debug_string());
|
dbgln_if(CSS_PARSER_DEBUG, "Expected an ident for pseudo-element, got: '{}'", name_token.to_debug_string());
|
||||||
|
@ -439,158 +435,171 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_pseudo_sim
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name);
|
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name);
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
}
|
}
|
||||||
simple_selector.value = pseudo_element.value();
|
|
||||||
|
|
||||||
return simple_selector;
|
return Selector::SimpleSelector {
|
||||||
|
.type = Selector::SimpleSelector::Type::PseudoElement,
|
||||||
|
.value = pseudo_element.value()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peek_token_ends_selector())
|
if (peek_token_ends_selector())
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
||||||
auto const& pseudo_class_token = tokens.next_token();
|
auto const& pseudo_class_token = tokens.next_token();
|
||||||
Selector::SimpleSelector simple_selector {
|
|
||||||
.type = Selector::SimpleSelector::Type::PseudoClass,
|
|
||||||
.value = Selector::SimpleSelector::PseudoClass {}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pseudo_class_token.is(Token::Type::Ident)) {
|
if (pseudo_class_token.is(Token::Type::Ident)) {
|
||||||
auto pseudo_name = pseudo_class_token.token().ident();
|
auto pseudo_name = pseudo_class_token.token().ident();
|
||||||
if (has_ignored_vendor_prefix(pseudo_name))
|
if (has_ignored_vendor_prefix(pseudo_name))
|
||||||
return ParsingResult::IncludesIgnoredVendorPrefix;
|
return ParsingResult::IncludesIgnoredVendorPrefix;
|
||||||
|
|
||||||
if (pseudo_name.equals_ignoring_case("active")) {
|
auto make_pseudo_class_selector = [](auto pseudo_class) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Active;
|
return Selector::SimpleSelector {
|
||||||
} else if (pseudo_name.equals_ignoring_case("checked")) {
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Checked;
|
.value = Selector::SimpleSelector::PseudoClass {
|
||||||
} else if (pseudo_name.equals_ignoring_case("disabled")) {
|
.type = pseudo_class }
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Disabled;
|
};
|
||||||
} else if (pseudo_name.equals_ignoring_case("empty")) {
|
};
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Empty;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("enabled")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Enabled;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("first-child")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FirstChild;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("first-of-type")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FirstOfType;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("focus")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Focus;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("focus-within")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::FocusWithin;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("hover")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Hover;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("last-child")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::LastChild;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("last-of-type")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::LastOfType;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("link")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Link;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("only-child")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::OnlyChild;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("only-of-type")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::OnlyOfType;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("root")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Root;
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("visited")) {
|
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Visited;
|
|
||||||
|
|
||||||
} else if (pseudo_name.equals_ignoring_case("after")) {
|
if (pseudo_name.equals_ignoring_case("active")) {
|
||||||
// Single-colon syntax allowed for compatibility. https://www.w3.org/TR/selectors/#pseudo-element-syntax
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Active);
|
||||||
simple_selector.type = Selector::SimpleSelector::Type::PseudoElement;
|
} else if (pseudo_name.equals_ignoring_case("checked")) {
|
||||||
simple_selector.value = Selector::PseudoElement::After;
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Checked);
|
||||||
} else if (pseudo_name.equals_ignoring_case("before")) {
|
} else if (pseudo_name.equals_ignoring_case("disabled")) {
|
||||||
// See :after
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Disabled);
|
||||||
simple_selector.type = Selector::SimpleSelector::Type::PseudoElement;
|
} else if (pseudo_name.equals_ignoring_case("empty")) {
|
||||||
simple_selector.value = Selector::PseudoElement::Before;
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Empty);
|
||||||
} else if (pseudo_name.equals_ignoring_case("first-letter")) {
|
} else if (pseudo_name.equals_ignoring_case("enabled")) {
|
||||||
// See :after
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Enabled);
|
||||||
simple_selector.type = Selector::SimpleSelector::Type::PseudoElement;
|
} else if (pseudo_name.equals_ignoring_case("first-child")) {
|
||||||
simple_selector.value = Selector::PseudoElement::FirstLetter;
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstChild);
|
||||||
} else if (pseudo_name.equals_ignoring_case("first-line")) {
|
} else if (pseudo_name.equals_ignoring_case("first-of-type")) {
|
||||||
// See :after
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FirstOfType);
|
||||||
simple_selector.type = Selector::SimpleSelector::Type::PseudoElement;
|
} else if (pseudo_name.equals_ignoring_case("focus")) {
|
||||||
simple_selector.value = Selector::PseudoElement::FirstLine;
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Focus);
|
||||||
} else {
|
} else if (pseudo_name.equals_ignoring_case("focus-within")) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name);
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::FocusWithin);
|
||||||
return ParsingResult::SyntaxError;
|
} else if (pseudo_name.equals_ignoring_case("hover")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Hover);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("last-child")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastChild);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("last-of-type")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::LastOfType);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("link")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Link);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("only-child")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyChild);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("only-of-type")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::OnlyOfType);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("root")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root);
|
||||||
|
} else if (pseudo_name.equals_ignoring_case("visited")) {
|
||||||
|
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
return simple_selector;
|
// Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
|
||||||
}
|
// https://www.w3.org/TR/selectors/#pseudo-element-syntax
|
||||||
if (pseudo_class_token.is_function()) {
|
if (auto pseudo_element = pseudo_element_from_string(pseudo_name); pseudo_element.has_value()) {
|
||||||
auto parse_nth_child_pattern = [this](Selector::SimpleSelector& simple_selector, StyleFunctionRule const& pseudo_function, bool allow_of = false) -> bool {
|
switch (pseudo_element.value()) {
|
||||||
auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values());
|
case Selector::PseudoElement::After:
|
||||||
auto nth_child_pattern = parse_a_n_plus_b_pattern(function_values, allow_of ? AllowTrailingTokens::Yes : AllowTrailingTokens::No);
|
case Selector::PseudoElement::Before:
|
||||||
if (nth_child_pattern.has_value()) {
|
case Selector::PseudoElement::FirstLetter:
|
||||||
simple_selector.pseudo_class().nth_child_pattern = nth_child_pattern.value();
|
case Selector::PseudoElement::FirstLine:
|
||||||
} else {
|
return Selector::SimpleSelector {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid format for {}", pseudo_function.name());
|
.type = Selector::SimpleSelector::Type::PseudoElement,
|
||||||
return false;
|
.value = pseudo_element.value()
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!allow_of)
|
}
|
||||||
return true;
|
|
||||||
|
|
||||||
function_values.skip_whitespace();
|
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name);
|
||||||
if (!function_values.has_next_token())
|
return ParsingResult::SyntaxError;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
if (pseudo_class_token.is_function()) {
|
||||||
|
auto parse_nth_child_selector = [this](auto pseudo_class, Vector<StyleComponentValueRule> const& function_values, bool allow_of = false) -> Result<Selector::SimpleSelector, Parser::ParsingResult> {
|
||||||
|
auto tokens = TokenStream<StyleComponentValueRule>(function_values);
|
||||||
|
auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens, allow_of ? AllowTrailingTokens::Yes : AllowTrailingTokens::No);
|
||||||
|
if (!nth_child_pattern.has_value()) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid An+B format for {}", pseudo_class_name(pseudo_class));
|
||||||
|
return ParsingResult::SyntaxError;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (!allow_of || !tokens.has_next_token()) {
|
||||||
|
return Selector::SimpleSelector {
|
||||||
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
|
.value = Selector::SimpleSelector::PseudoClass {
|
||||||
|
.type = pseudo_class,
|
||||||
|
.nth_child_pattern = nth_child_pattern.release_value() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the `of <selector-list>` syntax
|
// Parse the `of <selector-list>` syntax
|
||||||
auto const& maybe_of = function_values.next_token();
|
auto const& maybe_of = tokens.next_token();
|
||||||
if (!(maybe_of.is(Token::Type::Ident) && maybe_of.token().ident().equals_ignoring_case("of"sv)))
|
if (!(maybe_of.is(Token::Type::Ident) && maybe_of.token().ident().equals_ignoring_case("of"sv)))
|
||||||
return false;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
||||||
function_values.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
auto selector_list = parse_a_selector_list(function_values, SelectorType::Standalone);
|
auto selector_list = parse_a_selector_list(tokens, SelectorType::Standalone);
|
||||||
if (selector_list.is_error())
|
if (selector_list.is_error())
|
||||||
return false;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
||||||
function_values.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
if (function_values.has_next_token())
|
if (tokens.has_next_token())
|
||||||
return false;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
||||||
simple_selector.pseudo_class().argument_selector_list = selector_list.value();
|
return Selector::SimpleSelector {
|
||||||
return true;
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
|
.value = Selector::SimpleSelector::PseudoClass {
|
||||||
|
.type = pseudo_class,
|
||||||
|
.nth_child_pattern = nth_child_pattern.release_value(),
|
||||||
|
.argument_selector_list = selector_list.release_value() }
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto const& pseudo_function = pseudo_class_token.function();
|
auto const& pseudo_function = pseudo_class_token.function();
|
||||||
if (pseudo_function.name().equals_ignoring_case("not")) {
|
if (pseudo_function.name().equals_ignoring_case("not")) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Not;
|
|
||||||
auto function_token_stream = TokenStream(pseudo_function.values());
|
auto function_token_stream = TokenStream(pseudo_function.values());
|
||||||
auto not_selector = parse_a_selector_list(function_token_stream, SelectorType::Standalone);
|
auto not_selector = parse_a_selector_list(function_token_stream, SelectorType::Standalone);
|
||||||
if (not_selector.is_error()) {
|
if (not_selector.is_error()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid selector in :not() clause");
|
dbgln_if(CSS_PARSER_DEBUG, "Invalid selector in :not() clause");
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
}
|
}
|
||||||
simple_selector.pseudo_class().argument_selector_list = not_selector.release_value();
|
|
||||||
|
return Selector::SimpleSelector {
|
||||||
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
|
.value = Selector::SimpleSelector::PseudoClass {
|
||||||
|
.type = Selector::SimpleSelector::PseudoClass::Type::Not,
|
||||||
|
.argument_selector_list = not_selector.release_value() }
|
||||||
|
};
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("lang"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("lang"sv)) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::Lang;
|
|
||||||
if (pseudo_function.values().is_empty()) {
|
if (pseudo_function.values().is_empty()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector");
|
dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector");
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
}
|
}
|
||||||
// FIXME: Support multiple, comma-separated, language ranges.
|
// FIXME: Support multiple, comma-separated, language ranges.
|
||||||
simple_selector.pseudo_class().languages.append(pseudo_function.values().first().token().to_string());
|
Vector<FlyString> languages;
|
||||||
|
languages.append(pseudo_function.values().first().token().to_string());
|
||||||
|
return Selector::SimpleSelector {
|
||||||
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
||||||
|
.value = Selector::SimpleSelector::PseudoClass {
|
||||||
|
.type = Selector::SimpleSelector::PseudoClass::Type::Lang,
|
||||||
|
.languages = move(languages) }
|
||||||
|
};
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::NthChild;
|
return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthChild, pseudo_function.values(), true);
|
||||||
if (!parse_nth_child_pattern(simple_selector, pseudo_function, true))
|
|
||||||
return ParsingResult::SyntaxError;
|
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("nth-last-child"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("nth-last-child"sv)) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
|
return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastChild, pseudo_function.values(), true);
|
||||||
if (!parse_nth_child_pattern(simple_selector, pseudo_function, true))
|
|
||||||
return ParsingResult::SyntaxError;
|
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("nth-of-type"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("nth-of-type"sv)) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::NthOfType;
|
return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthOfType, pseudo_function.values(), false);
|
||||||
if (!parse_nth_child_pattern(simple_selector, pseudo_function))
|
|
||||||
return ParsingResult::SyntaxError;
|
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type"sv)) {
|
||||||
simple_selector.pseudo_class().type = Selector::SimpleSelector::PseudoClass::Type::NthLastOfType;
|
return parse_nth_child_selector(Selector::SimpleSelector::PseudoClass::Type::NthLastOfType, pseudo_function.values(), false);
|
||||||
if (!parse_nth_child_pattern(simple_selector, pseudo_function))
|
|
||||||
return ParsingResult::SyntaxError;
|
|
||||||
} else {
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name());
|
|
||||||
return ParsingResult::SyntaxError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return simple_selector;
|
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name());
|
||||||
|
return ParsingResult::SyntaxError;
|
||||||
}
|
}
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string());
|
dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string());
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
|
|
|
@ -85,12 +85,12 @@ public:
|
||||||
|
|
||||||
// FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere.
|
// 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".
|
// Only used when "pseudo_class" is "NthChild" or "NthLastChild".
|
||||||
ANPlusBPattern nth_child_pattern;
|
ANPlusBPattern nth_child_pattern {};
|
||||||
|
|
||||||
SelectorList argument_selector_list {};
|
SelectorList argument_selector_list {};
|
||||||
|
|
||||||
// Used for :lang(en-gb,dk)
|
// Used for :lang(en-gb,dk)
|
||||||
Vector<FlyString> languages;
|
Vector<FlyString> languages {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Attribute {
|
struct Attribute {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue