1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 15:58:11 +00:00

LibWeb: Support the :scope pseudo class

This commit is contained in:
Simon Wanner 2023-03-20 23:42:17 +01:00 committed by Andreas Kling
parent a96ba912b3
commit c8ebacb1c9
6 changed files with 23 additions and 12 deletions

View file

@ -479,6 +479,8 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root); return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Root);
if (pseudo_name.equals_ignoring_ascii_case("visited"sv)) if (pseudo_name.equals_ignoring_ascii_case("visited"sv))
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited); return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Visited);
if (pseudo_name.equals_ignoring_ascii_case("scope"sv))
return make_pseudo_class_selector(Selector::SimpleSelector::PseudoClass::Type::Scope);
// Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility. // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
// https://www.w3.org/TR/selectors/#pseudo-element-syntax // https://www.w3.org/TR/selectors/#pseudo-element-syntax

View file

@ -227,6 +227,7 @@ ErrorOr<String> Selector::SimpleSelector::serialize() const
case Selector::SimpleSelector::PseudoClass::Type::Enabled: case Selector::SimpleSelector::PseudoClass::Type::Enabled:
case Selector::SimpleSelector::PseudoClass::Type::Checked: case Selector::SimpleSelector::PseudoClass::Type::Checked:
case Selector::SimpleSelector::PseudoClass::Type::Active: case Selector::SimpleSelector::PseudoClass::Type::Active:
case Selector::SimpleSelector::PseudoClass::Type::Scope:
// If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s.
TRY(s.try_append(':')); TRY(s.try_append(':'));
TRY(s.try_append(pseudo_class_name(pseudo_class.type))); TRY(s.try_append(pseudo_class_name(pseudo_class.type)));

View file

@ -111,6 +111,7 @@ public:
Where, Where,
Active, Active,
Lang, Lang,
Scope,
}; };
Type type; Type type;
@ -292,6 +293,8 @@ constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Ty
return "where"sv; return "where"sv;
case Selector::SimpleSelector::PseudoClass::Type::Lang: case Selector::SimpleSelector::PseudoClass::Type::Lang:
return "lang"sv; return "lang"sv;
case Selector::SimpleSelector::PseudoClass::Type::Scope:
return "scope"sv;
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -202,7 +202,7 @@ static inline DOM::Element const* next_sibling_with_same_tag_name(DOM::Element c
return nullptr; return nullptr;
} }
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, DOM::Element const& element) static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass const& pseudo_class, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{ {
switch (pseudo_class.type) { switch (pseudo_class.type) {
case CSS::Selector::SimpleSelector::PseudoClass::Type::Link: case CSS::Selector::SimpleSelector::PseudoClass::Type::Link:
@ -245,6 +245,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
} }
case CSS::Selector::SimpleSelector::PseudoClass::Type::Root: case CSS::Selector::SimpleSelector::PseudoClass::Type::Root:
return is<HTML::HTMLHtmlElement>(element); return is<HTML::HTMLHtmlElement>(element);
case CSS::Selector::SimpleSelector::PseudoClass::Type::Scope:
return scope ? &element == scope : is<HTML::HTMLHtmlElement>(element);
case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType: case CSS::Selector::SimpleSelector::PseudoClass::Type::FirstOfType:
return !previous_sibling_with_same_tag_name(element); return !previous_sibling_with_same_tag_name(element);
case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType: case CSS::Selector::SimpleSelector::PseudoClass::Type::LastOfType:
@ -373,7 +375,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
return false; return false;
} }
static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element) static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{ {
switch (component.type) { switch (component.type) {
case CSS::Selector::SimpleSelector::Type::Universal: case CSS::Selector::SimpleSelector::Type::Universal:
@ -390,7 +392,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::
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: case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(component.pseudo_class(), element); return matches_pseudo_class(component.pseudo_class(), element, scope);
case CSS::Selector::SimpleSelector::Type::PseudoElement: case CSS::Selector::SimpleSelector::Type::PseudoElement:
// Pseudo-element matching/not-matching is handled in the top level matches(). // Pseudo-element matching/not-matching is handled in the top level matches().
return true; return true;
@ -399,11 +401,11 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::
} }
} }
static inline bool matches(CSS::Selector const& selector, int component_list_index, DOM::Element const& element) static inline bool matches(CSS::Selector const& selector, int component_list_index, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope)
{ {
auto& relative_selector = selector.compound_selectors()[component_list_index]; auto& relative_selector = selector.compound_selectors()[component_list_index];
for (auto& simple_selector : relative_selector.simple_selectors) { for (auto& simple_selector : relative_selector.simple_selectors) {
if (!matches(simple_selector, element)) if (!matches(simple_selector, element, scope))
return false; return false;
} }
switch (relative_selector.combinator) { switch (relative_selector.combinator) {
@ -414,7 +416,7 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
for (auto* ancestor = element.parent(); ancestor; ancestor = ancestor->parent()) { for (auto* ancestor = element.parent(); ancestor; ancestor = ancestor->parent()) {
if (!is<DOM::Element>(*ancestor)) if (!is<DOM::Element>(*ancestor))
continue; continue;
if (matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor))) if (matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), scope))
return true; return true;
} }
return false; return false;
@ -422,16 +424,16 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
VERIFY(component_list_index != 0); VERIFY(component_list_index != 0);
if (!element.parent() || !is<DOM::Element>(*element.parent())) if (!element.parent() || !is<DOM::Element>(*element.parent()))
return false; return false;
return matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*element.parent())); return matches(selector, component_list_index - 1, static_cast<DOM::Element const&>(*element.parent()), scope);
case CSS::Selector::Combinator::NextSibling: case CSS::Selector::Combinator::NextSibling:
VERIFY(component_list_index != 0); VERIFY(component_list_index != 0);
if (auto* sibling = element.previous_element_sibling()) if (auto* sibling = element.previous_element_sibling())
return matches(selector, component_list_index - 1, *sibling); return matches(selector, component_list_index - 1, *sibling, scope);
return false; return false;
case CSS::Selector::Combinator::SubsequentSibling: case CSS::Selector::Combinator::SubsequentSibling:
VERIFY(component_list_index != 0); VERIFY(component_list_index != 0);
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 (matches(selector, component_list_index - 1, *sibling)) if (matches(selector, component_list_index - 1, *sibling, scope))
return true; return true;
} }
return false; return false;
@ -441,14 +443,14 @@ static inline bool matches(CSS::Selector const& selector, int component_list_ind
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
bool matches(CSS::Selector const& selector, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element) bool matches(CSS::Selector const& selector, DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element, JS::GCPtr<DOM::ParentNode const> scope)
{ {
VERIFY(!selector.compound_selectors().is_empty()); VERIFY(!selector.compound_selectors().is_empty());
if (pseudo_element.has_value() && selector.pseudo_element() != pseudo_element) if (pseudo_element.has_value() && selector.pseudo_element() != pseudo_element)
return false; return false;
if (!pseudo_element.has_value() && selector.pseudo_element().has_value()) if (!pseudo_element.has_value() && selector.pseudo_element().has_value())
return false; return false;
return matches(selector, selector.compound_selectors().size() - 1, element); return matches(selector, selector.compound_selectors().size() - 1, element, scope);
} }
} }

View file

@ -11,6 +11,6 @@
namespace Web::SelectorEngine { namespace Web::SelectorEngine {
bool matches(CSS::Selector const&, DOM::Element const&, Optional<CSS::Selector::PseudoElement> = {}); bool matches(CSS::Selector const&, DOM::Element const&, Optional<CSS::Selector::PseudoElement> = {}, JS::GCPtr<DOM::ParentNode const> scope = {});
} }

View file

@ -456,6 +456,9 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang: case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
pseudo_class_description = "Lang"; pseudo_class_description = "Lang";
break; break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Scope:
pseudo_class_description = "Scope";
break;
} }
builder.appendff(" pseudo_class={}", pseudo_class_description); builder.appendff(" pseudo_class={}", pseudo_class_description);