LibWeb: Salt ancestor filter hashes by selector component

Separate ancestor filter hashes for tag names, ids, classes, and
attributes. This avoids false positives where identical strings in
different selector component kinds satisfy each other, while keeping the
filter conservative.
This commit is contained in:
Andreas Kling 2026-06-18 21:49:22 +02:00 committed by Andreas Kling
parent bc5d153e60
commit 0b2a5ac143
3 changed files with 46 additions and 7 deletions

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Web::CSS {
// Separate otherwise identical string hashes by selector component kind,
// e.g. `.article` versus `<article>`.
static constexpr u32 ancestor_filter_hash_for_tag_name(u32 hash)
{
return hash * 13;
}
static constexpr u32 ancestor_filter_hash_for_id(u32 hash)
{
return hash * 17;
}
static constexpr u32 ancestor_filter_hash_for_class(u32 hash)
{
return hash * 19;
}
static constexpr u32 ancestor_filter_hash_for_attribute(u32 hash)
{
return hash * 23;
}
}

View file

@ -8,6 +8,7 @@
#include "Selector.h"
#include <AK/GenericShorthands.h>
#include <AK/NeverDestroyed.h>
#include <LibWeb/CSS/AncestorFilter.h>
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Serialize.h>
@ -201,14 +202,16 @@ void Selector::collect_ancestor_hashes()
Vector<u32> hashes;
switch (simple_selector.type) {
case SimpleSelector::Type::Id:
hashes.append(ancestor_filter_hash_for_id(simple_selector.name().hash()));
break;
case SimpleSelector::Type::Class:
hashes.append(simple_selector.name().hash());
hashes.append(ancestor_filter_hash_for_class(simple_selector.name().hash()));
break;
case SimpleSelector::Type::TagName:
hashes.append(simple_selector.qualified_name().name.lowercase_name.hash());
hashes.append(ancestor_filter_hash_for_tag_name(simple_selector.qualified_name().name.lowercase_name.hash()));
break;
case SimpleSelector::Type::Attribute:
hashes.append(simple_selector.attribute().qualified_name.name.lowercase_name.hash());
hashes.append(ancestor_filter_hash_for_attribute(simple_selector.attribute().qualified_name.name.lowercase_name.hash()));
break;
case SimpleSelector::Type::PseudoClass: {
auto const& pseudo_class = simple_selector.pseudo_class();

View file

@ -25,6 +25,7 @@
#include <LibWeb/Animations/AnimationEffect.h>
#include <LibWeb/Animations/DocumentTimeline.h>
#include <LibWeb/Bindings/PrincipalHostDefined.h>
#include <LibWeb/CSS/AncestorFilter.h>
#include <LibWeb/CSS/AnimationEvent.h>
#include <LibWeb/CSS/CSSAnimation.h>
#include <LibWeb/CSS/CSSContainerRule.h>
@ -3787,13 +3788,13 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_math_depth(NonnullRefPtr<
static void for_each_element_hash(DOM::Element const& element, auto callback)
{
callback(element.local_name().ascii_case_insensitive_hash());
callback(ancestor_filter_hash_for_tag_name(element.local_name().ascii_case_insensitive_hash()));
if (element.id().has_value())
callback(element.id().value().hash());
callback(ancestor_filter_hash_for_id(element.id().value().hash()));
for (auto const& class_ : element.class_names())
callback(class_.hash());
callback(ancestor_filter_hash_for_class(class_.hash()));
element.for_each_attribute([&](auto& attribute) {
callback(attribute.name().ascii_case_insensitive_hash());
callback(ancestor_filter_hash_for_attribute(attribute.name().ascii_case_insensitive_hash()));
});
}