From 3a71b8cda36d310eaac713f82d42fbb00edbd0e9 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 14 Nov 2024 14:07:38 +0000 Subject: [PATCH] LibWeb/CSS: Reject invalid :has() contents after absolutizing nesting After we absolutize the contents of :has(), we check that those child selectors don't contain anything that :has() rejects. This is a separate path than the checks inside the parser, which is unfortunate. Fixes a WPT ref test. :^) --- Libraries/LibWeb/CSS/Selector.cpp | 38 +++++++++++++++++++++++++++++++ Tests/LibWeb/TestConfig.ini | 1 - 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index 3db36bd626e..45076833fef 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -624,6 +624,33 @@ Optional Selector::CompoundSelector::absolutized(Sel }; } +static bool contains_invalid_contents_for_has(Selector const& selector) +{ + // :has() has special validity rules: + // - It can't appear inside itself + // - It bans most pseudo-elements + // https://drafts.csswg.org/selectors/#relational + + for (auto const& compound_selector : selector.compound_selectors()) { + for (auto const& simple_selector : compound_selector.simple_selectors) { + if (simple_selector.type == Selector::SimpleSelector::Type::PseudoElement) { + if (!is_has_allowed_pseudo_element(simple_selector.pseudo_element().type())) + return true; + } + if (simple_selector.type == Selector::SimpleSelector::Type::PseudoClass) { + if (simple_selector.pseudo_class().type == PseudoClass::Has) + return true; + for (auto& child_selector : simple_selector.pseudo_class().argument_selector_list) { + if (contains_invalid_contents_for_has(*child_selector)) + return true; + } + } + } + } + + return false; +} + Optional Selector::SimpleSelector::absolutized(Selector::SimpleSelector const& selector_for_nesting) const { switch (type) { @@ -647,6 +674,17 @@ Optional Selector::SimpleSelector::absolutized(Selecto } pseudo_class.argument_selector_list = move(new_selector_list); } + + // :has() has special validity rules + if (pseudo_class.type == PseudoClass::Has) { + for (auto const& selector : pseudo_class.argument_selector_list) { + if (contains_invalid_contents_for_has(selector)) { + dbgln_if(CSS_PARSER_DEBUG, "After absolutizing, :has() would contain invalid contents; rejecting"); + return {}; + } + } + } + return SimpleSelector { .type = Type::PseudoClass, .value = move(pseudo_class), diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini index 66c72e0014d..ea789db014b 100644 --- a/Tests/LibWeb/TestConfig.ini +++ b/Tests/LibWeb/TestConfig.ini @@ -59,7 +59,6 @@ Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resour Text/input/wpt-import/css/css-flexbox/flex-item-compressible-001.html ; WPT ref-tests that currently fail -Ref/input/wpt-import/css/css-nesting/has-nesting.html Ref/input/wpt-import/css/css-nesting/host-nesting-003.html Ref/input/wpt-import/css/css-nesting/host-nesting-004.html Ref/input/wpt-import/css/CSS2/floats/floats-wrap-top-below-bfc-002r.xht