mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 05:55:13 +00:00
Previously, we optimized hover style invalidation to mark for style updates only those elements that were matched by :hover selectors in the last style calculation. This change takes it a step further by invalidating only the elements where the set of selectors that use :hover changes after hovered element is modified. The implementation is as follows: 1. Collect all elements whose styles might be affected by a change in the hovered element. 2. Retrieve a list of all selectors that use :hover. 3. Test each selector against each element and record which selectors match. 4. Update m_hovered_node to the newly hovered element. 5. Repeat step 3. 6. For each element, compare the previous and current sets of matched selectors. If they differ, mark the element for style recalculation.
303 lines
9.8 KiB
C++
303 lines
9.8 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/FlyString.h>
|
|
#include <AK/RefCounted.h>
|
|
#include <AK/String.h>
|
|
#include <AK/Vector.h>
|
|
#include <LibWeb/CSS/Keyword.h>
|
|
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
|
#include <LibWeb/CSS/PseudoClass.h>
|
|
|
|
namespace Web::CSS {
|
|
|
|
using SelectorList = Vector<NonnullRefPtr<class Selector>>;
|
|
|
|
// This is a <complex-selector> in the spec. https://www.w3.org/TR/selectors-4/#complex
|
|
class Selector : public RefCounted<Selector> {
|
|
public:
|
|
class PseudoElement {
|
|
public:
|
|
enum class Type : u8 {
|
|
Before,
|
|
After,
|
|
FirstLine,
|
|
FirstLetter,
|
|
Marker,
|
|
MeterBar,
|
|
MeterEvenLessGoodValue,
|
|
MeterOptimumValue,
|
|
MeterSuboptimumValue,
|
|
ProgressValue,
|
|
ProgressBar,
|
|
Placeholder,
|
|
Selection,
|
|
SliderRunnableTrack,
|
|
SliderThumb,
|
|
Backdrop,
|
|
FileSelectorButton,
|
|
DetailsContent,
|
|
|
|
// Keep this last.
|
|
KnownPseudoElementCount,
|
|
|
|
// https://www.w3.org/TR/selectors-4/#compat
|
|
// NOTE: This is not last as the 'unknown -webkit- pseudo-elements' are not stored as part of any Element.
|
|
UnknownWebKit,
|
|
};
|
|
|
|
explicit PseudoElement(Type type)
|
|
: m_type(type)
|
|
{
|
|
VERIFY(is_known_pseudo_element_type(type));
|
|
}
|
|
|
|
PseudoElement(Type type, String name)
|
|
: m_type(type)
|
|
, m_name(move(name))
|
|
{
|
|
}
|
|
|
|
bool operator==(PseudoElement const&) const = default;
|
|
|
|
static Optional<PseudoElement> from_string(FlyString const&);
|
|
|
|
[[nodiscard]] static bool is_known_pseudo_element_type(Type type)
|
|
{
|
|
return to_underlying(type) < to_underlying(CSS::Selector::PseudoElement::Type::KnownPseudoElementCount);
|
|
}
|
|
|
|
static StringView name(Selector::PseudoElement::Type pseudo_element);
|
|
|
|
StringView name() const
|
|
{
|
|
if (!m_name.is_empty())
|
|
return m_name;
|
|
|
|
return name(m_type);
|
|
}
|
|
|
|
Type type() const { return m_type; }
|
|
|
|
private:
|
|
Type m_type;
|
|
String m_name;
|
|
};
|
|
|
|
struct SimpleSelector {
|
|
enum class Type : u8 {
|
|
Universal,
|
|
TagName,
|
|
Id,
|
|
Class,
|
|
Attribute,
|
|
PseudoClass,
|
|
PseudoElement,
|
|
Nesting,
|
|
Invalid,
|
|
};
|
|
|
|
struct ANPlusBPattern {
|
|
int step_size { 0 }; // "A"
|
|
int offset = { 0 }; // "B"
|
|
|
|
// https://www.w3.org/TR/css-syntax-3/#serializing-anb
|
|
String serialize() const
|
|
{
|
|
// 1. If A is zero, return the serialization of B.
|
|
if (step_size == 0) {
|
|
return String::number(offset);
|
|
}
|
|
|
|
// 2. Otherwise, let result initially be an empty string.
|
|
StringBuilder result;
|
|
|
|
// 3.
|
|
// - A is 1: Append "n" to result.
|
|
if (step_size == 1)
|
|
result.append('n');
|
|
// - A is -1: Append "-n" to result.
|
|
else if (step_size == -1)
|
|
result.append("-n"sv);
|
|
// - A is non-zero: Serialize A and append it to result, then append "n" to result.
|
|
else if (step_size != 0)
|
|
result.appendff("{}n", step_size);
|
|
|
|
// 4.
|
|
// - B is greater than zero: Append "+" to result, then append the serialization of B to result.
|
|
if (offset > 0)
|
|
result.appendff("+{}", offset);
|
|
// - B is less than zero: Append the serialization of B to result.
|
|
if (offset < 0)
|
|
result.appendff("{}", offset);
|
|
|
|
// 5. Return result.
|
|
return MUST(result.to_string());
|
|
}
|
|
};
|
|
|
|
struct PseudoClassSelector {
|
|
PseudoClass type;
|
|
|
|
// FIXME: We don't need this field on every single SimpleSelector, but it's also annoying to malloc it somewhere.
|
|
// Only used when "pseudo_class" is "NthChild" or "NthLastChild".
|
|
ANPlusBPattern nth_child_pattern {};
|
|
|
|
// FIXME: This would make more sense as part of SelectorList but that's currently a `using`
|
|
bool is_forgiving { false };
|
|
SelectorList argument_selector_list {};
|
|
|
|
// Used for :lang(en-gb,dk)
|
|
Vector<FlyString> languages {};
|
|
|
|
// Used by :dir()
|
|
Optional<Keyword> keyword {};
|
|
};
|
|
|
|
struct Name {
|
|
Name(FlyString n)
|
|
: name(move(n))
|
|
, lowercase_name(name.to_string().to_lowercase().release_value_but_fixme_should_propagate_errors())
|
|
{
|
|
}
|
|
|
|
FlyString name;
|
|
FlyString lowercase_name;
|
|
};
|
|
|
|
// Equivalent to `<wq-name>`
|
|
// https://www.w3.org/TR/selectors-4/#typedef-wq-name
|
|
struct QualifiedName {
|
|
enum class NamespaceType {
|
|
Default, // `E`
|
|
None, // `|E`
|
|
Any, // `*|E`
|
|
Named, // `ns|E`
|
|
};
|
|
NamespaceType namespace_type { NamespaceType::Default };
|
|
FlyString namespace_ {};
|
|
Name name;
|
|
};
|
|
|
|
struct Attribute {
|
|
enum class MatchType {
|
|
HasAttribute,
|
|
ExactValueMatch,
|
|
ContainsWord, // [att~=val]
|
|
ContainsString, // [att*=val]
|
|
StartsWithSegment, // [att|=val]
|
|
StartsWithString, // [att^=val]
|
|
EndsWithString, // [att$=val]
|
|
};
|
|
enum class CaseType {
|
|
DefaultMatch,
|
|
CaseSensitiveMatch,
|
|
CaseInsensitiveMatch,
|
|
};
|
|
MatchType match_type;
|
|
QualifiedName qualified_name;
|
|
String value {};
|
|
CaseType case_type;
|
|
};
|
|
|
|
struct Invalid {
|
|
Vector<Parser::ComponentValue> component_values;
|
|
};
|
|
|
|
Type type;
|
|
Variant<Empty, Attribute, PseudoClassSelector, PseudoElement, Name, QualifiedName, Invalid> value {};
|
|
|
|
Attribute const& attribute() const { return value.get<Attribute>(); }
|
|
Attribute& attribute() { return value.get<Attribute>(); }
|
|
PseudoClassSelector const& pseudo_class() const { return value.get<PseudoClassSelector>(); }
|
|
PseudoClassSelector& pseudo_class() { return value.get<PseudoClassSelector>(); }
|
|
PseudoElement const& pseudo_element() const { return value.get<PseudoElement>(); }
|
|
PseudoElement& pseudo_element() { return value.get<PseudoElement>(); }
|
|
|
|
FlyString const& name() const { return value.get<Name>().name; }
|
|
FlyString& name() { return value.get<Name>().name; }
|
|
FlyString const& lowercase_name() const { return value.get<Name>().lowercase_name; }
|
|
FlyString& lowercase_name() { return value.get<Name>().lowercase_name; }
|
|
QualifiedName const& qualified_name() const { return value.get<QualifiedName>(); }
|
|
QualifiedName& qualified_name() { return value.get<QualifiedName>(); }
|
|
|
|
String serialize() const;
|
|
|
|
Optional<SimpleSelector> absolutized(SimpleSelector const& selector_for_nesting) const;
|
|
};
|
|
|
|
enum class Combinator {
|
|
None,
|
|
ImmediateChild, // >
|
|
Descendant, // <whitespace>
|
|
NextSibling, // +
|
|
SubsequentSibling, // ~
|
|
Column, // ||
|
|
};
|
|
|
|
struct CompoundSelector {
|
|
// Spec-wise, the <combinator> is not part of a <compound-selector>,
|
|
// but it is more understandable to put them together.
|
|
Combinator combinator { Combinator::None };
|
|
Vector<SimpleSelector> simple_selectors;
|
|
|
|
Optional<CompoundSelector> absolutized(SimpleSelector const& selector_for_nesting) const;
|
|
};
|
|
|
|
static NonnullRefPtr<Selector> create(Vector<CompoundSelector>&& compound_selectors)
|
|
{
|
|
return adopt_ref(*new Selector(move(compound_selectors)));
|
|
}
|
|
|
|
~Selector() = default;
|
|
|
|
Vector<CompoundSelector> const& compound_selectors() const { return m_compound_selectors; }
|
|
Optional<PseudoElement> const& pseudo_element() const { return m_pseudo_element; }
|
|
NonnullRefPtr<Selector> relative_to(SimpleSelector const&) const;
|
|
bool contains_the_nesting_selector() const { return m_contains_the_nesting_selector; }
|
|
bool contains_hover_pseudo_class() const { return m_contains_hover_pseudo_class; }
|
|
RefPtr<Selector> absolutized(SimpleSelector const& selector_for_nesting) const;
|
|
u32 specificity() const;
|
|
String serialize() const;
|
|
|
|
auto const& ancestor_hashes() const { return m_ancestor_hashes; }
|
|
|
|
private:
|
|
explicit Selector(Vector<CompoundSelector>&&);
|
|
|
|
Vector<CompoundSelector> m_compound_selectors;
|
|
mutable Optional<u32> m_specificity;
|
|
Optional<Selector::PseudoElement> m_pseudo_element;
|
|
bool m_contains_the_nesting_selector { false };
|
|
bool m_contains_hover_pseudo_class { false };
|
|
|
|
void collect_ancestor_hashes();
|
|
|
|
Array<u32, 8> m_ancestor_hashes;
|
|
};
|
|
|
|
String serialize_a_group_of_selectors(SelectorList const& selectors);
|
|
|
|
SelectorList adapt_nested_relative_selector_list(SelectorList const&);
|
|
|
|
bool is_has_allowed_pseudo_element(Selector::PseudoElement::Type);
|
|
|
|
}
|
|
|
|
namespace AK {
|
|
|
|
template<>
|
|
struct Formatter<Web::CSS::Selector> : Formatter<StringView> {
|
|
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Selector const& selector)
|
|
{
|
|
return Formatter<StringView>::format(builder, selector.serialize());
|
|
}
|
|
};
|
|
|
|
}
|