LibWeb/CSS: Keep invalid parts of <forgiving-selector-list>s around

Attempt 2! Reverts 2a5dbedad4

This time, set up a different combinator when producing a relative
invalid selector rather than a standalone one. This fixes the crash.

Original description below for simplicity because it still applies.

---

Selectors like `:is(.valid, &!?!?!invalid)` need to keep the invalid
part around, even though it will never match, for a couple of reasons:

- Serialization needs to include them
- For nesting, we care if a `&` appeared anywhere in the selector, even
  in an invalid part.

So this patch introduces an `Invalid` simple selector type, which simply
holds its original ComponentValues. We search through these looking for
`&`, and we dump them out directly when asked to serialize.
This commit is contained in:
Sam Atkins 2024-11-13 15:49:43 +00:00 committed by Andreas Kling
commit 5a1eb9e220
Notes: github-actions[bot] 2024-11-14 12:20:58 +00:00
8 changed files with 111 additions and 3 deletions

View file

@ -11,6 +11,28 @@
namespace Web::CSS {
static bool component_value_contains_nesting_selector(Parser::ComponentValue const& component_value)
{
if (component_value.is_delim('&'))
return true;
if (component_value.is_block()) {
for (auto const& child_value : component_value.block().value) {
if (component_value_contains_nesting_selector(child_value))
return true;
}
}
if (component_value.is_function()) {
for (auto const& child_value : component_value.function().value) {
if (component_value_contains_nesting_selector(child_value))
return true;
}
}
return false;
}
Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
: m_compound_selectors(move(compound_selectors))
{
@ -44,6 +66,17 @@ Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
if (m_contains_the_nesting_selector)
break;
}
if (simple_selector.type == SimpleSelector::Type::Invalid) {
auto& invalid = simple_selector.value.get<SimpleSelector::Invalid>();
for (auto& item : invalid.component_values) {
if (component_value_contains_nesting_selector(item)) {
m_contains_the_nesting_selector = true;
break;
}
}
if (m_contains_the_nesting_selector)
break;
}
}
if (m_contains_the_nesting_selector)
break;
@ -184,6 +217,9 @@ u32 Selector::specificity() const
// The parented case is handled by replacing & with :is().
// So if we got here, the specificity is 0.
break;
case SimpleSelector::Type::Invalid:
// Ignore invalid selectors
break;
}
}
}
@ -360,6 +396,12 @@ String Selector::SimpleSelector::serialize() const
// AD-HOC: Not in spec yet.
s.append('&');
break;
case Type::Invalid:
// AD-HOC: We're not told how to do these. Just serialize their component values.
auto invalid = value.get<Invalid>();
for (auto const& component_value : invalid.component_values)
s.append(component_value.to_string());
break;
}
return MUST(s.to_string());
}
@ -602,6 +644,7 @@ Selector::SimpleSelector Selector::SimpleSelector::absolutized(Selector::SimpleS
case Type::Class:
case Type::Attribute:
case Type::PseudoElement:
case Type::Invalid:
// Everything else isn't affected
return *this;
}