1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 19:27:44 +00:00

LibWeb: Add 'PseudoClass' as a CSS SimpleSelector::Type

Same reasoning as the previous commit.
This commit is contained in:
Sam Atkins 2021-07-12 16:18:00 +01:00 committed by Andreas Kling
parent 96b2356cbb
commit 4af7d41879
6 changed files with 230 additions and 234 deletions

View file

@ -442,12 +442,15 @@ public:
simple_selector.type = CSS::Selector::SimpleSelector::Type::TagName; simple_selector.type = CSS::Selector::SimpleSelector::Type::TagName;
} else if (peek() == '[') { } else if (peek() == '[') {
simple_selector.type = CSS::Selector::SimpleSelector::Type::Attribute; simple_selector.type = CSS::Selector::SimpleSelector::Type::Attribute;
} else if (peek() == ':') {
simple_selector.type = CSS::Selector::SimpleSelector::Type::PseudoClass;
} else { } else {
simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal; simple_selector.type = CSS::Selector::SimpleSelector::Type::Universal;
} }
if ((simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal) if ((simple_selector.type != CSS::Selector::SimpleSelector::Type::Universal)
&& (simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute)) { && (simple_selector.type != CSS::Selector::SimpleSelector::Type::Attribute)
&& (simple_selector.type != CSS::Selector::SimpleSelector::Type::PseudoClass)) {
while (is_valid_selector_char(peek())) while (is_valid_selector_char(peek()))
buffer.append(consume_one()); buffer.append(consume_one());
@ -515,7 +518,7 @@ public:
return {}; return {};
} }
if (peek() == ':') { if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
// FIXME: Implement pseudo elements. // FIXME: Implement pseudo elements.
[[maybe_unused]] bool is_pseudo_element = false; [[maybe_unused]] bool is_pseudo_element = false;
consume_one(); consume_one();
@ -559,49 +562,51 @@ public:
if (is_pseudo_element) if (is_pseudo_element)
return {}; return {};
auto& pseudo_class = simple_selector.pseudo_class;
if (pseudo_name.equals_ignoring_case("link")) { if (pseudo_name.equals_ignoring_case("link")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Link;
} else if (pseudo_name.equals_ignoring_case("visited")) { } else if (pseudo_name.equals_ignoring_case("visited")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Visited;
} else if (pseudo_name.equals_ignoring_case("active")) { } else if (pseudo_name.equals_ignoring_case("active")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Active; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Active;
} else if (pseudo_name.equals_ignoring_case("hover")) { } else if (pseudo_name.equals_ignoring_case("hover")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Hover;
} else if (pseudo_name.equals_ignoring_case("focus")) { } else if (pseudo_name.equals_ignoring_case("focus")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Focus;
} else if (pseudo_name.equals_ignoring_case("first-child")) { } else if (pseudo_name.equals_ignoring_case("first-child")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild;
} else if (pseudo_name.equals_ignoring_case("last-child")) { } else if (pseudo_name.equals_ignoring_case("last-child")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild;
} else if (pseudo_name.equals_ignoring_case("only-child")) { } else if (pseudo_name.equals_ignoring_case("only-child")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild;
} else if (pseudo_name.equals_ignoring_case("empty")) { } else if (pseudo_name.equals_ignoring_case("empty")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Empty;
} else if (pseudo_name.equals_ignoring_case("root")) { } else if (pseudo_name.equals_ignoring_case("root")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Root;
} else if (pseudo_name.equals_ignoring_case("first-of-type")) { } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstOfType; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType;
} else if (pseudo_name.equals_ignoring_case("last-of-type")) { } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastOfType; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType;
} else if (pseudo_name.starts_with("nth-child", CaseSensitivity::CaseInsensitive)) { } else if (pseudo_name.starts_with("nth-child", CaseSensitivity::CaseInsensitive)) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::NthChild; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild;
simple_selector.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name)); pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
} else if (pseudo_name.starts_with("nth-last-child", CaseSensitivity::CaseInsensitive)) { } else if (pseudo_name.starts_with("nth-last-child", CaseSensitivity::CaseInsensitive)) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::NthLastChild; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
simple_selector.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name)); pseudo_class.nth_child_pattern = CSS::Selector::SimpleSelector::NthChildPattern::parse(capture_selector_args(pseudo_name));
} else if (pseudo_name.equals_ignoring_case("before")) { } else if (pseudo_name.equals_ignoring_case("before")) {
simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before; simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
} else if (pseudo_name.equals_ignoring_case("after")) { } else if (pseudo_name.equals_ignoring_case("after")) {
simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After; simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
} else if (pseudo_name.equals_ignoring_case("disabled")) { } else if (pseudo_name.equals_ignoring_case("disabled")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Disabled; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled;
} else if (pseudo_name.equals_ignoring_case("enabled")) { } else if (pseudo_name.equals_ignoring_case("enabled")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Enabled; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled;
} else if (pseudo_name.equals_ignoring_case("checked")) { } else if (pseudo_name.equals_ignoring_case("checked")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Checked; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Checked;
} else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) { } else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Not; pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Not;
simple_selector.not_selector = capture_selector_args(pseudo_name); pseudo_class.not_selector = capture_selector_args(pseudo_name);
} else { } else {
dbgln("Unknown pseudo class: '{}'", pseudo_name); dbgln("Unknown pseudo class: '{}'", pseudo_name);
return {}; return {};

View file

@ -270,10 +270,6 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
} else if (current_value.is(Token::Type::Ident)) { } else if (current_value.is(Token::Type::Ident)) {
simple_selector.type = Selector::SimpleSelector::Type::TagName; simple_selector.type = Selector::SimpleSelector::Type::TagName;
simple_selector.value = current_value.token().ident().to_lowercase_string(); 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.
simple_selector.type = Selector::SimpleSelector::Type::Universal;
tokens.reconsume_current_input_token();
} else if (current_value.is_block() && current_value.block().is_square()) { } else if (current_value.is_block() && current_value.block().is_square()) {
simple_selector.type = Selector::SimpleSelector::Type::Attribute; simple_selector.type = Selector::SimpleSelector::Type::Attribute;
@ -353,17 +349,7 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
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 // FIXME: Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
} else { } else if (current_value.is(Token::Type::Colon)) {
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; bool is_pseudo = false;
current_value = tokens.next_token(); current_value = tokens.next_token();
@ -382,46 +368,49 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
if (is_pseudo) if (is_pseudo)
return {}; return {};
auto& pseudo_class = simple_selector.pseudo_class;
current_value = tokens.next_token(); current_value = tokens.next_token();
if (check_for_eof_or_whitespace(current_value)) if (check_for_eof_or_whitespace(current_value))
return simple_selector; return {};
simple_selector.type = Selector::SimpleSelector::Type::PseudoClass;
if (current_value.is(Token::Type::Ident)) { if (current_value.is(Token::Type::Ident)) {
auto pseudo_name = ((Token)current_value).ident(); auto pseudo_name = ((Token)current_value).ident();
if (pseudo_name.equals_ignoring_case("link")) { if (pseudo_name.equals_ignoring_case("link")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Link; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Link;
} else if (pseudo_name.equals_ignoring_case("visited")) { } else if (pseudo_name.equals_ignoring_case("visited")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Visited; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Visited;
} else if (pseudo_name.equals_ignoring_case("active")) { } else if (pseudo_name.equals_ignoring_case("active")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Active; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Active;
} else if (pseudo_name.equals_ignoring_case("hover")) { } else if (pseudo_name.equals_ignoring_case("hover")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Hover; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Hover;
} else if (pseudo_name.equals_ignoring_case("focus")) { } else if (pseudo_name.equals_ignoring_case("focus")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Focus; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Focus;
} else if (pseudo_name.equals_ignoring_case("first-child")) { } else if (pseudo_name.equals_ignoring_case("first-child")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstChild; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::FirstChild;
} else if (pseudo_name.equals_ignoring_case("last-child")) { } else if (pseudo_name.equals_ignoring_case("last-child")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastChild; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::LastChild;
} else if (pseudo_name.equals_ignoring_case("only-child")) { } else if (pseudo_name.equals_ignoring_case("only-child")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::OnlyChild; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::OnlyChild;
} else if (pseudo_name.equals_ignoring_case("empty")) { } else if (pseudo_name.equals_ignoring_case("empty")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Empty; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Empty;
} else if (pseudo_name.equals_ignoring_case("root")) { } else if (pseudo_name.equals_ignoring_case("root")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Root; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Root;
} else if (pseudo_name.equals_ignoring_case("first-of-type")) { } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstOfType; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::FirstOfType;
} else if (pseudo_name.equals_ignoring_case("last-of-type")) { } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastOfType; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::LastOfType;
} else if (pseudo_name.equals_ignoring_case("before")) { } else if (pseudo_name.equals_ignoring_case("before")) {
simple_selector.pseudo_element = Selector::SimpleSelector::PseudoElement::Before; simple_selector.pseudo_element = Selector::SimpleSelector::PseudoElement::Before;
} else if (pseudo_name.equals_ignoring_case("after")) { } else if (pseudo_name.equals_ignoring_case("after")) {
simple_selector.pseudo_element = Selector::SimpleSelector::PseudoElement::After; simple_selector.pseudo_element = Selector::SimpleSelector::PseudoElement::After;
} else if (pseudo_name.equals_ignoring_case("disabled")) { } else if (pseudo_name.equals_ignoring_case("disabled")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Disabled; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Disabled;
} else if (pseudo_name.equals_ignoring_case("enabled")) { } else if (pseudo_name.equals_ignoring_case("enabled")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Enabled; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Enabled;
} else if (pseudo_name.equals_ignoring_case("checked")) { } else if (pseudo_name.equals_ignoring_case("checked")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Checked; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Checked;
} else { } else {
dbgln("Unknown pseudo class: '{}'", pseudo_name); dbgln("Unknown pseudo class: '{}'", pseudo_name);
return simple_selector; return simple_selector;
@ -429,28 +418,28 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
} else if (current_value.is(Token::Type::Function)) { } else if (current_value.is(Token::Type::Function)) {
auto& pseudo_function = current_value.function(); auto& pseudo_function = current_value.function();
if (pseudo_function.name().equals_ignoring_case("nth-child")) { if (pseudo_function.name().equals_ignoring_case("nth-child")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::NthChild; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild;
auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values()); auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values());
auto nth_child_pattern = parse_nth_child_pattern(function_values); auto nth_child_pattern = parse_nth_child_pattern(function_values);
if (nth_child_pattern.has_value()) { if (nth_child_pattern.has_value()) {
simple_selector.nth_child_pattern = nth_child_pattern.value(); pseudo_class.nth_child_pattern = nth_child_pattern.value();
} else { } else {
dbgln("Invalid nth-child format"); dbgln("Invalid nth-child format");
return {}; return {};
} }
} else if (pseudo_function.name().equals_ignoring_case("nth-last-child")) { } else if (pseudo_function.name().equals_ignoring_case("nth-last-child")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::NthLastChild; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values()); auto function_values = TokenStream<StyleComponentValueRule>(pseudo_function.values());
auto nth_child_pattern = parse_nth_child_pattern(function_values); auto nth_child_pattern = parse_nth_child_pattern(function_values);
if (nth_child_pattern.has_value()) { if (nth_child_pattern.has_value()) {
simple_selector.nth_child_pattern = nth_child_pattern.value(); pseudo_class.nth_child_pattern = nth_child_pattern.value();
} else { } else {
dbgln("Invalid nth-child format"); dbgln("Invalid nth-child format");
return {}; return {};
} }
} else if (pseudo_function.name().equals_ignoring_case("not")) { } else if (pseudo_function.name().equals_ignoring_case("not")) {
simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Not; pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not;
simple_selector.not_selector = pseudo_function.values_as_string(); pseudo_class.not_selector = pseudo_function.values_as_string();
} else { } else {
dbgln("Unknown pseudo class: '{}'()", pseudo_function.name()); dbgln("Unknown pseudo class: '{}'()", pseudo_function.name());
return {}; return {};
@ -459,10 +448,11 @@ Optional<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is
dbgln("Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", current_value.to_debug_string()); dbgln("Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", current_value.to_debug_string());
return {}; return {};
} }
} else {
dbgln("Invalid simple selector!");
return {};
} }
tokens.reconsume_current_input_token();
return simple_selector; return simple_selector;
}; };

View file

@ -49,6 +49,8 @@ u32 Selector::specificity() const
Selector::SimpleSelector::NthChildPattern Selector::SimpleSelector::NthChildPattern::parse(StringView const& args) Selector::SimpleSelector::NthChildPattern Selector::SimpleSelector::NthChildPattern::parse(StringView const& args)
{ {
// FIXME: Remove this when the DeprecatedCSSParser is gone.
// The new Parser::parse_nth_child_pattern() does the same as this, using Tokens.
CSS::Selector::SimpleSelector::NthChildPattern pattern; CSS::Selector::SimpleSelector::NthChildPattern pattern;
if (args.equals_ignoring_case("odd")) { if (args.equals_ignoring_case("odd")) {
pattern.step_size = 2; pattern.step_size = 2;

View file

@ -23,31 +23,49 @@ public:
Id, Id,
Class, Class,
Attribute, Attribute,
PseudoClass,
}; };
Type type { Type::Invalid }; Type type { Type::Invalid };
enum class PseudoClass { struct NthChildPattern {
None, int step_size = 0;
Link, int offset = 0;
Visited,
Hover, static NthChildPattern parse(StringView const& args);
Focus,
FirstChild,
LastChild,
OnlyChild,
Empty,
Root,
FirstOfType,
LastOfType,
NthChild,
NthLastChild,
Disabled,
Enabled,
Checked,
Not,
Active,
}; };
PseudoClass pseudo_class { PseudoClass::None };
struct PseudoClass {
enum class Type {
None,
Link,
Visited,
Hover,
Focus,
FirstChild,
LastChild,
OnlyChild,
Empty,
Root,
FirstOfType,
LastOfType,
NthChild,
NthLastChild,
Disabled,
Enabled,
Checked,
Not,
Active,
};
Type type { Type::None };
// 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 {};
};
PseudoClass pseudo_class;
enum class PseudoElement { enum class PseudoElement {
None, None,
@ -74,19 +92,6 @@ public:
String value; String value;
}; };
Attribute attribute; Attribute attribute;
struct NthChildPattern {
int step_size = 0;
int offset = 0;
static NthChildPattern parse(StringView const& args);
};
// 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 {};
}; };
struct ComplexSelector { struct ComplexSelector {

View file

@ -57,103 +57,75 @@ static bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& at
return false; return false;
} }
static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element) static bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, DOM::Element const& element)
{ {
switch (component.pseudo_element) { switch (pseudo_class.type) {
case CSS::Selector::SimpleSelector::PseudoElement::None: case CSS::Selector::SimpleSelector::PseudoClass::Type::None:
break; break;
default: case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
// FIXME: Implement pseudo-elements. return element.is_link();
return false; case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited:
}
switch (component.pseudo_class) {
case CSS::Selector::SimpleSelector::PseudoClass::None:
break;
case CSS::Selector::SimpleSelector::PseudoClass::Link:
if (!element.is_link())
return false;
break;
case CSS::Selector::SimpleSelector::PseudoClass::Visited:
// FIXME: Maybe match this selector sometimes? // FIXME: Maybe match this selector sometimes?
return false; return false;
case CSS::Selector::SimpleSelector::PseudoClass::Active: case CSS::Selector::SimpleSelector::PseudoClass::Type::Active:
if (!element.is_active()) return element.is_active();
return false; case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover:
break; return matches_hover_pseudo_class(element);
case CSS::Selector::SimpleSelector::PseudoClass::Hover: case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus:
if (!matches_hover_pseudo_class(element)) return element.is_focused();
return false; case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild:
break; return !element.previous_element_sibling();
case CSS::Selector::SimpleSelector::PseudoClass::Focus: case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild:
if (!element.is_focused()) return !element.next_element_sibling();
return false; case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild:
break; return !(element.previous_element_sibling() || element.next_element_sibling());
case CSS::Selector::SimpleSelector::PseudoClass::FirstChild: case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty:
if (element.previous_element_sibling()) return !(element.first_child_of_type<DOM::Element>() || element.first_child_of_type<DOM::Text>());
return false; case CSS::Selector::SimpleSelector::PseudoClass::Type::Root:
break; return is<HTML::HTMLElement>(element);
case CSS::Selector::SimpleSelector::PseudoClass::LastChild: case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
if (element.next_element_sibling())
return false;
break;
case CSS::Selector::SimpleSelector::PseudoClass::OnlyChild:
if (element.previous_element_sibling() || element.next_element_sibling())
return false;
break;
case CSS::Selector::SimpleSelector::PseudoClass::Empty:
if (element.first_child_of_type<DOM::Element>() || element.first_child_of_type<DOM::Text>())
return false;
break;
case CSS::Selector::SimpleSelector::PseudoClass::Root:
if (!is<HTML::HTMLElement>(element))
return false;
break;
case CSS::Selector::SimpleSelector::PseudoClass::FirstOfType:
for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) { for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
if (sibling->tag_name() == element.tag_name()) if (sibling->tag_name() == element.tag_name())
return false; return false;
} }
break; return true;
case CSS::Selector::SimpleSelector::PseudoClass::LastOfType: case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType:
for (auto* sibling = element.next_element_sibling(); sibling; sibling = sibling->next_element_sibling()) { for (auto* sibling = element.next_element_sibling(); sibling; sibling = sibling->next_element_sibling()) {
if (sibling->tag_name() == element.tag_name()) if (sibling->tag_name() == element.tag_name())
return false; return false;
} }
break; return true;
case CSS::Selector::SimpleSelector::PseudoClass::Disabled: case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input)) if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input))
return false; return false;
if (!element.has_attribute("disabled")) if (!element.has_attribute("disabled"))
return false; return false;
break; return true;
case CSS::Selector::SimpleSelector::PseudoClass::Enabled: case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled:
if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input)) if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input))
return false; return false;
if (element.has_attribute("disabled")) if (element.has_attribute("disabled"))
return false; return false;
break; return true;
case CSS::Selector::SimpleSelector::PseudoClass::Checked: case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input)) if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input))
return false; return false;
if (!element.has_attribute("checked")) if (!element.has_attribute("checked"))
return false; return false;
break; return true;
case CSS::Selector::SimpleSelector::PseudoClass::Not: { case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: {
if (component.not_selector.is_empty()) if (pseudo_class.not_selector.is_empty())
return false; return false;
auto not_selector = Web::parse_selector(CSS::DeprecatedParsingContext(element), component.not_selector); auto not_selector = Web::parse_selector(CSS::DeprecatedParsingContext(element), pseudo_class.not_selector);
if (!not_selector.has_value()) if (!not_selector.has_value())
return false; return false;
auto not_matches = matches(not_selector.value(), element); auto not_matches = matches(not_selector.value(), element);
if (not_matches) return !not_matches;
return false;
break;
} }
case CSS::Selector::SimpleSelector::PseudoClass::NthChild: case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
case CSS::Selector::SimpleSelector::PseudoClass::NthLastChild: case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
auto const step_size = component.nth_child_pattern.step_size; auto const step_size = pseudo_class.nth_child_pattern.step_size;
auto const offset = component.nth_child_pattern.offset; auto const offset = pseudo_class.nth_child_pattern.offset;
if (step_size == 0 && offset == 0) if (step_size == 0 && offset == 0)
return false; // "If both a and b are equal to zero, the pseudo-class represents no element in the document tree." return false; // "If both a and b are equal to zero, the pseudo-class represents no element in the document tree."
@ -162,7 +134,7 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
return false; return false;
int index = 1; int index = 1;
if (component.pseudo_class == CSS::Selector::SimpleSelector::PseudoClass::NthChild) { if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild) {
for (auto* child = parent->first_child_of_type<DOM::Element>(); child && child != &element; child = child->next_element_sibling()) for (auto* child = parent->first_child_of_type<DOM::Element>(); child && child != &element; child = child->next_element_sibling())
++index; ++index;
} else { } else {
@ -172,16 +144,10 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
if (step_size < 0) { if (step_size < 0) {
// When "step_size" is negative, selector represents first "offset" elements in document tree. // When "step_size" is negative, selector represents first "offset" elements in document tree.
if (offset <= 0 || index > offset) return !(offset <= 0 || index > offset);
return false;
else
break;
} else if (step_size == 1) { } else if (step_size == 1) {
// When "step_size == 1", selector represents last "offset" elements in document tree. // When "step_size == 1", selector represents last "offset" elements in document tree.
if (offset < 0 || index < offset) return !(offset < 0 || index < offset);
return false;
else
break;
} }
// Like "a % b", but handles negative integers correctly. // Like "a % b", but handles negative integers correctly.
@ -201,7 +167,20 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
} else if (canonical_modulo(index - offset, step_size) != 0) { } else if (canonical_modulo(index - offset, step_size) != 0) {
return false; return false;
} }
return true;
}
return false;
}
static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element)
{
switch (component.pseudo_element) {
case CSS::Selector::SimpleSelector::PseudoElement::None:
break; break;
default:
// FIXME: Implement pseudo-elements.
return false;
} }
switch (component.type) { switch (component.type) {
@ -215,6 +194,8 @@ static bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element
return component.value == element.local_name(); return component.value == element.local_name();
case CSS::Selector::SimpleSelector::Type::Attribute: case CSS::Selector::SimpleSelector::Type::Attribute:
return matches_attribute(component.attribute, element); return matches_attribute(component.attribute, element);
case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(component.pseudo_class, element);
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -317,72 +317,85 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
case CSS::Selector::SimpleSelector::Type::Attribute: case CSS::Selector::SimpleSelector::Type::Attribute:
type_description = "Attribute"; type_description = "Attribute";
break; break;
} case CSS::Selector::SimpleSelector::Type::PseudoClass:
type_description = "PseudoClass";
char const* pseudo_class_description = "";
switch (simple_selector.pseudo_class) {
case CSS::Selector::SimpleSelector::PseudoClass::Link:
pseudo_class_description = "Link";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Visited:
pseudo_class_description = "Visited";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Active:
pseudo_class_description = "Active";
break;
case CSS::Selector::SimpleSelector::PseudoClass::None:
pseudo_class_description = "None";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Root:
pseudo_class_description = "Root";
break;
case CSS::Selector::SimpleSelector::PseudoClass::FirstOfType:
pseudo_class_description = "FirstOfType";
break;
case CSS::Selector::SimpleSelector::PseudoClass::LastOfType:
pseudo_class_description = "LastOfType";
break;
case CSS::Selector::SimpleSelector::PseudoClass::NthChild:
pseudo_class_description = "NthChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::NthLastChild:
pseudo_class_description = "NthLastChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Focus:
pseudo_class_description = "Focus";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Empty:
pseudo_class_description = "Empty";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Hover:
pseudo_class_description = "Hover";
break;
case CSS::Selector::SimpleSelector::PseudoClass::LastChild:
pseudo_class_description = "LastChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::FirstChild:
pseudo_class_description = "FirstChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::OnlyChild:
pseudo_class_description = "OnlyChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Disabled:
pseudo_class_description = "Disabled";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Enabled:
pseudo_class_description = "Enabled";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Checked:
pseudo_class_description = "Checked";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Not:
pseudo_class_description = "Not";
break; break;
} }
builder.appendff("{}:{}", type_description, simple_selector.value); builder.appendff("{}:{}", type_description, simple_selector.value);
if (simple_selector.pseudo_class != CSS::Selector::SimpleSelector::PseudoClass::None)
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
auto const& pseudo_class = simple_selector.pseudo_class;
char const* pseudo_class_description = "";
switch (pseudo_class.type) {
case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
pseudo_class_description = "Link";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Visited:
pseudo_class_description = "Visited";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Active:
pseudo_class_description = "Active";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::None:
pseudo_class_description = "None";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Root:
pseudo_class_description = "Root";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
pseudo_class_description = "FirstOfType";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType:
pseudo_class_description = "LastOfType";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
pseudo_class_description = "NthChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
pseudo_class_description = "NthLastChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Focus:
pseudo_class_description = "Focus";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Empty:
pseudo_class_description = "Empty";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Hover:
pseudo_class_description = "Hover";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::LastChild:
pseudo_class_description = "LastChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstChild:
pseudo_class_description = "FirstChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyChild:
pseudo_class_description = "OnlyChild";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
pseudo_class_description = "Disabled";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled:
pseudo_class_description = "Enabled";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
pseudo_class_description = "Checked";
break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
pseudo_class_description = "Not";
break;
}
builder.appendff(" pseudo_class={}", pseudo_class_description); builder.appendff(" pseudo_class={}", pseudo_class_description);
if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not) {
builder.appendff("({})", pseudo_class.not_selector);
} else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)
|| (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)) {
builder.appendff("(step={}, offset={})", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset);
}
}
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) { if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Attribute) {
char const* attribute_match_type_description = ""; char const* attribute_match_type_description = "";