LibWeb: Implement :host and :host(<compound-selector>) selector matching

The :host family of pseudo class selectors select the shadow host
element when matching against a rule from within the element's shadow
tree.

This is a bit convoluted due to the fact that the document-level
StyleComputer keeps track of *all* style rules, and not just the
document-level ones.

In the future, we should refactor style storage so that shadow roots
have their own style scope, and we can simplify a lot of this.
This commit is contained in:
Andreas Kling 2024-07-23 15:22:28 +02:00 committed by Andreas Kling
parent 274c46a3c9
commit 4c326fc5f6
Notes: github-actions[bot] 2024-07-23 16:04:40 +00:00
7 changed files with 127 additions and 44 deletions

View file

@ -0,0 +1,19 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x63 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x47 children: not-inline
BlockContainer <main> at (8,8) content-size 784x47 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [23,23 343.96875x17] baseline: 28.296875
BlockContainer <div> at (23,23) content-size 343.96875x17 inline-block [BFC] children: inline
InlineNode <span>
frag 0 from TextNode start: 0, length: 42, rect: [23,23 343.96875x17] baseline: 13.296875
"hello :host and :host(<compound-selector>)"
TextNode <#text>
TextNode <#text>
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x63]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x47]
PaintableWithLines (BlockContainer<MAIN>) [8,8 784x47]
PaintableWithLines (BlockContainer<DIV>) [8,8 373.96875x47]
InlinePaintable (InlineNode<SPAN>)
TextPaintable (TextNode<#text>)

View file

@ -0,0 +1,4 @@
<!doctype html><body><template shadowrootmode="open"><main><div><template shadowrootmode="open"><style>
:host { display: inline-block; border: 5px solid red; }
:host(div) { padding: 10px; }
</style><span>hello :host and :host(&lt;compound-selector&gt;)</span>

View file

@ -34,6 +34,24 @@
namespace Web::SelectorEngine {
// Upward traversal for descendant (' ') and immediate child combinator ('>')
// If we're starting inside a shadow tree, traversal stops at the nearest shadow host.
// This is an implementation detail of the :host selector. Otherwise we would just traverse up to the document root.
static inline JS::GCPtr<DOM::Node const> traverse_up(JS::GCPtr<DOM::Node const> node, JS::GCPtr<DOM::Element const> shadow_host)
{
if (!node)
return nullptr;
if (shadow_host) {
// NOTE: We only traverse up to the shadow host, not beyond.
if (node == shadow_host)
return nullptr;
return node->parent_or_shadow_host_element();
}
return node->parent();
}
// https://drafts.csswg.org/selectors-4/#the-lang-pseudo
static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector<FlyString> const& languages)
{
@ -64,7 +82,7 @@ static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector
}
// https://drafts.csswg.org/selectors-4/#relational
static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& anchor)
static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& anchor, JS::GCPtr<DOM::Element const> shadow_host)
{
switch (selector.compound_selectors()[0].combinator) {
// Shouldn't be possible because we've parsed relative selectors, which always have a combinator, implicitly or explicitly.
@ -76,7 +94,7 @@ static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optio
if (!descendant.is_element())
return TraversalDecision::Continue;
auto const& descendant_element = static_cast<DOM::Element const&>(descendant);
if (matches(selector, style_sheet_for_rule, descendant_element, {}, {}, SelectorKind::Relative)) {
if (matches(selector, style_sheet_for_rule, descendant_element, shadow_host, {}, {}, SelectorKind::Relative)) {
has = true;
return TraversalDecision::Break;
}
@ -90,7 +108,7 @@ static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optio
if (!child.is_element())
return IterationDecision::Continue;
auto const& child_element = static_cast<DOM::Element const&>(child);
if (matches(selector, style_sheet_for_rule, child_element, {}, {}, SelectorKind::Relative)) {
if (matches(selector, style_sheet_for_rule, child_element, shadow_host, {}, {}, SelectorKind::Relative)) {
has = true;
return IterationDecision::Break;
}
@ -99,10 +117,10 @@ static inline bool matches_has_pseudo_class(CSS::Selector const& selector, Optio
return has;
}
case CSS::Selector::Combinator::NextSibling:
return anchor.next_element_sibling() != nullptr && matches(selector, style_sheet_for_rule, *anchor.next_element_sibling(), {}, {}, SelectorKind::Relative);
return anchor.next_element_sibling() != nullptr && matches(selector, style_sheet_for_rule, *anchor.next_element_sibling(), shadow_host, {}, {}, SelectorKind::Relative);
case CSS::Selector::Combinator::SubsequentSibling: {
for (auto* sibling = anchor.next_element_sibling(); sibling; sibling = sibling->next_element_sibling()) {
if (matches(selector, style_sheet_for_rule, *sibling, {}, {}, SelectorKind::Relative))
if (matches(selector, style_sheet_for_rule, *sibling, shadow_host, {}, {}, SelectorKind::Relative))
return true;
}
return false;
@ -318,7 +336,22 @@ static bool matches_open_state_pseudo_class(DOM::Element const& element, bool op
return false;
}
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
// https://drafts.csswg.org/css-scoping/#host-selector
static inline bool matches_host_pseudo_class(JS::NonnullGCPtr<DOM::Element const> element, JS::GCPtr<DOM::Element const> shadow_host, CSS::SelectorList const& argument_selector_list, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule)
{
// When evaluated in the context of a shadow tree, it matches the shadow trees shadow host if the shadow host,
// in its normal context, matches the selector argument. In any other context, it matches nothing.
if (!shadow_host || element != shadow_host)
return false;
// NOTE: There's either 0 or 1 argument selector, since the syntax is :host or :host(<compound-selector>)
if (!argument_selector_list.is_empty())
return matches(argument_selector_list.first(), style_sheet_for_rule, element, nullptr);
return true;
}
static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClassSelector const& pseudo_class, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
{
switch (pseudo_class.type) {
case CSS::PseudoClass::Link:
@ -382,8 +415,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
case CSS::PseudoClass::Root:
return is<HTML::HTMLHtmlElement>(element);
case CSS::PseudoClass::Host:
// FIXME: Implement :host selector.
return false;
return matches_host_pseudo_class(element, shadow_host, pseudo_class.argument_selector_list, style_sheet_for_rule);
case CSS::PseudoClass::Scope:
return scope ? &element == scope : is<HTML::HTMLHtmlElement>(element);
case CSS::PseudoClass::FirstOfType:
@ -415,20 +447,20 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
return false;
// These selectors should be relative selectors (https://drafts.csswg.org/selectors-4/#relative-selector)
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches_has_pseudo_class(selector, style_sheet_for_rule, element))
if (matches_has_pseudo_class(selector, style_sheet_for_rule, element, shadow_host))
return true;
}
return false;
case CSS::PseudoClass::Is:
case CSS::PseudoClass::Where:
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, style_sheet_for_rule, element))
if (matches(selector, style_sheet_for_rule, element, shadow_host))
return true;
}
return false;
case CSS::PseudoClass::Not:
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, style_sheet_for_rule, element))
if (matches(selector, style_sheet_for_rule, element, shadow_host))
return false;
}
return true;
@ -445,11 +477,11 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
if (!parent)
return false;
auto matches_selector_list = [&style_sheet_for_rule](CSS::SelectorList const& list, DOM::Element const& element) {
auto matches_selector_list = [&style_sheet_for_rule, shadow_host](CSS::SelectorList const& list, DOM::Element const& element) {
if (list.is_empty())
return true;
for (auto const& child_selector : list) {
if (matches(child_selector, style_sheet_for_rule, element)) {
if (matches(child_selector, style_sheet_for_rule, element, shadow_host)) {
return true;
}
}
@ -648,7 +680,7 @@ static ALWAYS_INLINE bool matches_namespace(
VERIFY_NOT_REACHED();
}
static inline bool matches(CSS::Selector::SimpleSelector const& component, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
static inline bool matches(CSS::Selector::SimpleSelector const& component, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
{
switch (component.type) {
case CSS::Selector::SimpleSelector::Type::Universal:
@ -675,7 +707,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
case CSS::Selector::SimpleSelector::Type::Attribute:
return matches_attribute(component.attribute(), style_sheet_for_rule, element);
case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope, selector_kind);
return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, shadow_host, scope, selector_kind);
case CSS::Selector::SimpleSelector::Type::PseudoElement:
// Pseudo-element matching/not-matching is handled in the top level matches().
return true;
@ -684,12 +716,13 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
}
}
static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, int component_list_index, DOM::Element const& element, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, int component_list_index, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
{
auto& compound_selector = selector.compound_selectors()[component_list_index];
for (auto& simple_selector : compound_selector.simple_selectors) {
if (!matches(simple_selector, style_sheet_for_rule, element, scope, selector_kind))
if (!matches(simple_selector, style_sheet_for_rule, element, shadow_host, scope, selector_kind)) {
return false;
}
}
// Always matches because we assume that element is already relative to its anchor
if (selector_kind == SelectorKind::Relative && component_list_index == 0)
@ -700,27 +733,29 @@ static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyle
return true;
case CSS::Selector::Combinator::Descendant:
VERIFY(component_list_index != 0);
for (auto* ancestor = element.parent(); ancestor; ancestor = ancestor->parent()) {
for (auto ancestor = traverse_up(element, shadow_host); ancestor; ancestor = traverse_up(ancestor, shadow_host)) {
if (!is<DOM::Element>(*ancestor))
continue;
if (matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), scope, selector_kind))
if (matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*ancestor), shadow_host, scope, selector_kind))
return true;
}
return false;
case CSS::Selector::Combinator::ImmediateChild:
case CSS::Selector::Combinator::ImmediateChild: {
VERIFY(component_list_index != 0);
if (!element.parent() || !is<DOM::Element>(*element.parent()))
auto parent = traverse_up(element, shadow_host);
if (!parent || !parent->is_element())
return false;
return matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*element.parent()), scope, selector_kind);
return matches(selector, style_sheet_for_rule, component_list_index - 1, static_cast<DOM::Element const&>(*parent), shadow_host, scope, selector_kind);
}
case CSS::Selector::Combinator::NextSibling:
VERIFY(component_list_index != 0);
if (auto* sibling = element.previous_element_sibling())
return matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, scope, selector_kind);
return matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, shadow_host, scope, selector_kind);
return false;
case CSS::Selector::Combinator::SubsequentSibling:
VERIFY(component_list_index != 0);
for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
if (matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, scope, selector_kind))
if (matches(selector, style_sheet_for_rule, component_list_index - 1, *sibling, shadow_host, scope, selector_kind))
return true;
}
return false;
@ -730,17 +765,17 @@ static inline bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyle
VERIFY_NOT_REACHED();
}
bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
bool matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, JS::GCPtr<DOM::ParentNode const> scope, SelectorKind selector_kind)
{
VERIFY(!selector.compound_selectors().is_empty());
if (pseudo_element.has_value() && selector.pseudo_element().has_value() && selector.pseudo_element().value().type() != pseudo_element)
return false;
if (!pseudo_element.has_value() && selector.pseudo_element().has_value())
return false;
return matches(selector, style_sheet_for_rule, selector.compound_selectors().size() - 1, element, scope, selector_kind);
return matches(selector, style_sheet_for_rule, selector.compound_selectors().size() - 1, element, shadow_host, scope, selector_kind);
}
static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& simple_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element)
static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& simple_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host)
{
switch (simple_selector.type) {
case CSS::Selector::SimpleSelector::Type::Universal:
@ -760,28 +795,28 @@ static bool fast_matches_simple_selector(CSS::Selector::SimpleSelector const& si
case CSS::Selector::SimpleSelector::Type::Attribute:
return matches_attribute(simple_selector.attribute(), style_sheet_for_rule, element);
case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(simple_selector.pseudo_class(), style_sheet_for_rule, element, nullptr, SelectorKind::Normal);
return matches_pseudo_class(simple_selector.pseudo_class(), style_sheet_for_rule, element, shadow_host, nullptr, SelectorKind::Normal);
default:
VERIFY_NOT_REACHED();
}
}
static bool fast_matches_compound_selector(CSS::Selector::CompoundSelector const& compound_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element)
static bool fast_matches_compound_selector(CSS::Selector::CompoundSelector const& compound_selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element, JS::GCPtr<DOM::Element const> shadow_host)
{
for (auto const& simple_selector : compound_selector.simple_selectors) {
if (!fast_matches_simple_selector(simple_selector, style_sheet_for_rule, element))
if (!fast_matches_simple_selector(simple_selector, style_sheet_for_rule, element, shadow_host))
return false;
}
return true;
}
bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element_to_match)
bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element_to_match, JS::GCPtr<DOM::Element const> shadow_host)
{
DOM::Element const* current = &element_to_match;
ssize_t compound_selector_index = selector.compound_selectors().size() - 1;
if (!fast_matches_compound_selector(selector.compound_selectors().last(), style_sheet_for_rule, *current))
if (!fast_matches_compound_selector(selector.compound_selectors().last(), style_sheet_for_rule, *current, shadow_host))
return false;
// NOTE: If we fail after following a child combinator, we may need to backtrack
@ -804,7 +839,7 @@ bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet con
backtrack_state = { current->parent_element(), compound_selector_index };
compound_selector = &selector.compound_selectors()[--compound_selector_index];
for (current = current->parent_element(); current; current = current->parent_element()) {
if (fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current))
if (fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current, shadow_host))
break;
}
if (!current)
@ -815,7 +850,7 @@ bool fast_matches(CSS::Selector const& selector, Optional<CSS::CSSStyleSheet con
current = current->parent_element();
if (!current)
return false;
if (!fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current)) {
if (!fast_matches_compound_selector(*compound_selector, style_sheet_for_rule, *current, shadow_host)) {
if (backtrack_state.element) {
current = backtrack_state.element;
compound_selector_index = backtrack_state.compound_selector_index;

View file

@ -16,9 +16,9 @@ enum class SelectorKind {
Relative,
};
bool matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, Optional<CSS::Selector::PseudoElement::Type> = {}, JS::GCPtr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal);
bool matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, JS::GCPtr<DOM::Element const> shadow_host, Optional<CSS::Selector::PseudoElement::Type> = {}, JS::GCPtr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal);
[[nodiscard]] bool fast_matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&);
[[nodiscard]] bool fast_matches(CSS::Selector const&, Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const&, JS::GCPtr<DOM::Element const> shadow_host);
[[nodiscard]] bool can_use_fast_matches(CSS::Selector const&);
}

View file

@ -322,6 +322,12 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
auto const& root_node = element.root();
auto shadow_root = is<DOM::ShadowRoot>(root_node) ? static_cast<DOM::ShadowRoot const*>(&root_node) : nullptr;
JS::GCPtr<DOM::Element const> shadow_host;
if (element.is_shadow_host())
shadow_host = element;
else if (shadow_root)
shadow_host = shadow_root->host();
auto const& rule_cache = rule_cache_for_cascade_origin(cascade_origin);
Vector<MatchingRule, 512> rules_to_run;
@ -366,22 +372,40 @@ Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& e
Vector<MatchingRule> matching_rules;
matching_rules.ensure_capacity(rules_to_run.size());
for (auto const& rule_to_run : rules_to_run) {
// FIXME: This needs to be revised when adding support for the :host and ::shadow selectors, which transition shadow tree boundaries
// FIXME: This needs to be revised when adding support for the ::shadow selector, as it needs to cross shadow boundaries.
auto rule_root = rule_to_run.shadow_root;
auto from_user_agent_or_user_stylesheet = rule_to_run.cascade_origin == CascadeOrigin::UserAgent || rule_to_run.cascade_origin == CascadeOrigin::User;
if (rule_root != shadow_root && !from_user_agent_or_user_stylesheet)
// NOTE: Inside shadow trees, we only match rules that are defined in the shadow tree's style sheets.
// The key exception is the shadow tree's *shadow host*, which needs to match :host rules from inside the shadow root.
// Also note that UA or User style sheets don't have a scope, so they are always relevant.
// FIXME: We should reorganize the data so that the document-level StyleComputer doesn't cache *all* rules,
// but instead we'd have some kind of "style scope" at the document level, and also one for each shadow root.
// Then we could only evaluate rules from the current style scope.
bool rule_is_relevant_for_current_scope = rule_root == shadow_root
|| (element.is_shadow_host() && rule_root == element.shadow_root())
|| from_user_agent_or_user_stylesheet;
if (!rule_is_relevant_for_current_scope)
continue;
// NOTE: When matching an element against a rule from outside the shadow root's style scope,
// we have to pass in null for the shadow host, otherwise combinator traversal will
// be confined to the element itself (since it refuses to cross the shadow boundary).
auto shadow_host_to_use = shadow_host;
if (element.is_shadow_host() && rule_root != element.shadow_root())
shadow_host_to_use = nullptr;
auto const& selector = rule_to_run.rule->selectors()[rule_to_run.selector_index];
if (should_reject_with_ancestor_filter(*selector))
continue;
if (rule_to_run.can_use_fast_matches) {
if (!SelectorEngine::fast_matches(selector, *rule_to_run.sheet, element))
if (!SelectorEngine::fast_matches(selector, *rule_to_run.sheet, element, shadow_host_to_use))
continue;
} else {
if (!SelectorEngine::matches(selector, *rule_to_run.sheet, element, pseudo_element))
if (!SelectorEngine::matches(selector, *rule_to_run.sheet, element, shadow_host_to_use, pseudo_element))
continue;
}
matching_rules.append(rule_to_run);

View file

@ -720,7 +720,7 @@ WebIDL::ExceptionOr<bool> Element::matches(StringView selectors) const
// 3. If the result of match a selector against an element, using s, this, and scoping root this, returns success, then return true; otherwise, return false.
auto sel = maybe_selectors.value();
for (auto& s : sel) {
if (SelectorEngine::matches(s, {}, *this, {}, static_cast<ParentNode const*>(this)))
if (SelectorEngine::matches(s, {}, *this, nullptr, {}, static_cast<ParentNode const*>(this)))
return true;
}
return false;
@ -739,7 +739,7 @@ WebIDL::ExceptionOr<DOM::Element const*> Element::closest(StringView selectors)
auto matches_selectors = [this](CSS::SelectorList const& selector_list, Element const* element) {
// 4. For each element in elements, if match a selector against an element, using s, element, and scoping root this, returns success, return element.
for (auto const& selector : selector_list) {
if (SelectorEngine::matches(selector, {}, *element, {}, this))
if (SelectorEngine::matches(selector, {}, *element, nullptr, {}, this))
return true;
}
return false;

View file

@ -12,6 +12,7 @@
#include <LibWeb/DOM/HTMLCollection.h>
#include <LibWeb/DOM/NodeOperations.h>
#include <LibWeb/DOM/ParentNode.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/StaticNodeList.h>
#include <LibWeb/Dump.h>
#include <LibWeb/Infra/CharacterTypes.h>
@ -44,7 +45,7 @@ WebIDL::ExceptionOr<JS::GCPtr<Element>> ParentNode::query_selector(StringView se
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
for_each_in_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, {}, element, {}, this)) {
if (SelectorEngine::matches(selector, {}, element, nullptr, {}, this)) {
result = &element;
return TraversalDecision::Break;
}
@ -76,7 +77,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<NodeList>> ParentNode::query_selector_all(S
// FIXME: This should be shadow-including. https://drafts.csswg.org/selectors-4/#match-a-selector-against-a-tree
for_each_in_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, {}, element, {}, this)) {
if (SelectorEngine::matches(selector, {}, element, nullptr, {}, this)) {
elements.append(&element);
}
}