mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
53f99e51f8
For example, this: ```css .foo { color: red; &:hover { color: green; } } ``` now has the same effect as this: ```css .foo { color: red; } .foo:hover { color: green; } ``` CSSStyleRule now has "absolutized selectors", which are its selectors with any `&`s resolved. We use these instead of the "real" selectors when matching them, meaning the style computer doesn't have to know or care about where the selector appears in the CSS document.
609 lines
27 KiB
C++
609 lines
27 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
|
||
*/
|
||
|
||
#include "Selector.h"
|
||
#include <AK/GenericShorthands.h>
|
||
#include <LibWeb/CSS/Serialize.h>
|
||
|
||
namespace Web::CSS {
|
||
|
||
Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
|
||
: m_compound_selectors(move(compound_selectors))
|
||
{
|
||
// FIXME: This assumes that only one pseudo-element is allowed in a selector, and that it appears at the end.
|
||
// This is not true in Selectors-4!
|
||
if (!m_compound_selectors.is_empty()) {
|
||
for (auto const& simple_selector : m_compound_selectors.last().simple_selectors) {
|
||
if (simple_selector.type == SimpleSelector::Type::PseudoElement) {
|
||
m_pseudo_element = simple_selector.pseudo_element();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-nesting-1/#contain-the-nesting-selector
|
||
// "A selector is said to contain the nesting selector if, when it was parsed as any type of selector,
|
||
// a <delim-token> with the value "&" (U+0026 AMPERSAND) was encountered."
|
||
for (auto const& compound_selector : m_compound_selectors) {
|
||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||
if (simple_selector.type == SimpleSelector::Type::Nesting) {
|
||
m_contains_the_nesting_selector = true;
|
||
break;
|
||
}
|
||
if (simple_selector.type == SimpleSelector::Type::PseudoClass) {
|
||
for (auto const& child_selector : simple_selector.pseudo_class().argument_selector_list) {
|
||
if (child_selector->contains_the_nesting_selector()) {
|
||
m_contains_the_nesting_selector = true;
|
||
break;
|
||
}
|
||
}
|
||
if (m_contains_the_nesting_selector)
|
||
break;
|
||
}
|
||
}
|
||
if (m_contains_the_nesting_selector)
|
||
break;
|
||
}
|
||
|
||
collect_ancestor_hashes();
|
||
}
|
||
|
||
void Selector::collect_ancestor_hashes()
|
||
{
|
||
size_t next_hash_index = 0;
|
||
auto append_unique_hash = [&](u32 hash) -> bool {
|
||
if (next_hash_index >= m_ancestor_hashes.size())
|
||
return true;
|
||
for (size_t i = 0; i < next_hash_index; ++i) {
|
||
if (m_ancestor_hashes[i] == hash)
|
||
return false;
|
||
}
|
||
m_ancestor_hashes[next_hash_index++] = hash;
|
||
return false;
|
||
};
|
||
|
||
auto last_combinator = m_compound_selectors.last().combinator;
|
||
for (ssize_t compound_selector_index = static_cast<ssize_t>(m_compound_selectors.size()) - 2; compound_selector_index >= 0; --compound_selector_index) {
|
||
auto const& compound_selector = m_compound_selectors[compound_selector_index];
|
||
if (last_combinator == Combinator::Descendant || last_combinator == Combinator::ImmediateChild) {
|
||
for (auto const& simple_selector : compound_selector.simple_selectors) {
|
||
switch (simple_selector.type) {
|
||
case SimpleSelector::Type::Id:
|
||
case SimpleSelector::Type::Class:
|
||
if (append_unique_hash(simple_selector.name().hash()))
|
||
return;
|
||
break;
|
||
case SimpleSelector::Type::TagName:
|
||
if (append_unique_hash(simple_selector.qualified_name().name.name.hash()))
|
||
return;
|
||
break;
|
||
case SimpleSelector::Type::Attribute:
|
||
if (append_unique_hash(simple_selector.attribute().qualified_name.name.name.hash()))
|
||
return;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
last_combinator = compound_selector.combinator;
|
||
}
|
||
|
||
for (size_t i = next_hash_index; i < m_ancestor_hashes.size(); ++i)
|
||
m_ancestor_hashes[i] = 0;
|
||
}
|
||
|
||
// https://www.w3.org/TR/selectors-4/#specificity-rules
|
||
u32 Selector::specificity() const
|
||
{
|
||
if (m_specificity.has_value())
|
||
return *m_specificity;
|
||
|
||
constexpr u32 ids_shift = 16;
|
||
constexpr u32 classes_shift = 8;
|
||
constexpr u32 tag_names_shift = 0;
|
||
constexpr u32 ids_mask = 0xff << ids_shift;
|
||
constexpr u32 classes_mask = 0xff << classes_shift;
|
||
constexpr u32 tag_names_mask = 0xff << tag_names_shift;
|
||
|
||
u32 ids = 0;
|
||
u32 classes = 0;
|
||
u32 tag_names = 0;
|
||
|
||
auto count_specificity_of_most_complex_selector = [&](auto& selector_list) {
|
||
u32 max_selector_list_argument_specificity = 0;
|
||
for (auto const& complex_selector : selector_list) {
|
||
max_selector_list_argument_specificity = max(max_selector_list_argument_specificity, complex_selector->specificity());
|
||
}
|
||
|
||
u32 child_ids = (max_selector_list_argument_specificity & ids_mask) >> ids_shift;
|
||
u32 child_classes = (max_selector_list_argument_specificity & classes_mask) >> classes_shift;
|
||
u32 child_tag_names = (max_selector_list_argument_specificity & tag_names_mask) >> tag_names_shift;
|
||
|
||
ids += child_ids;
|
||
classes += child_classes;
|
||
tag_names += child_tag_names;
|
||
};
|
||
|
||
for (auto& list : m_compound_selectors) {
|
||
for (auto& simple_selector : list.simple_selectors) {
|
||
switch (simple_selector.type) {
|
||
case SimpleSelector::Type::Id:
|
||
// count the number of ID selectors in the selector (= A)
|
||
++ids;
|
||
break;
|
||
case SimpleSelector::Type::Class:
|
||
case SimpleSelector::Type::Attribute:
|
||
// count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B)
|
||
++classes;
|
||
break;
|
||
case SimpleSelector::Type::PseudoClass: {
|
||
auto& pseudo_class = simple_selector.pseudo_class();
|
||
switch (pseudo_class.type) {
|
||
case PseudoClass::Has:
|
||
case PseudoClass::Is:
|
||
case PseudoClass::Not: {
|
||
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the
|
||
// specificity of the most specific complex selector in its selector list argument.
|
||
count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list);
|
||
break;
|
||
}
|
||
case PseudoClass::NthChild:
|
||
case PseudoClass::NthLastChild: {
|
||
// Analogously, the specificity of an :nth-child() or :nth-last-child() selector
|
||
// is the specificity of the pseudo class itself (counting as one pseudo-class selector)
|
||
// plus the specificity of the most specific complex selector in its selector list argument (if any).
|
||
++classes;
|
||
count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list);
|
||
break;
|
||
}
|
||
case PseudoClass::Where:
|
||
// The specificity of a :where() pseudo-class is replaced by zero.
|
||
break;
|
||
default:
|
||
++classes;
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
case SimpleSelector::Type::TagName:
|
||
case SimpleSelector::Type::PseudoElement:
|
||
// count the number of type selectors and pseudo-elements in the selector (= C)
|
||
++tag_names;
|
||
break;
|
||
case SimpleSelector::Type::Universal:
|
||
// ignore the universal selector
|
||
break;
|
||
case SimpleSelector::Type::Nesting:
|
||
// We should have replaced this already
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Due to storage limitations, implementations may have limitations on the size of A, B, or C.
|
||
// If so, values higher than the limit must be clamped to that limit, and not overflow.
|
||
m_specificity = (min(ids, 0xff) << ids_shift)
|
||
+ (min(classes, 0xff) << classes_shift)
|
||
+ (min(tag_names, 0xff) << tag_names_shift);
|
||
|
||
return *m_specificity;
|
||
}
|
||
|
||
// https://www.w3.org/TR/cssom/#serialize-a-simple-selector
|
||
String Selector::SimpleSelector::serialize() const
|
||
{
|
||
StringBuilder s;
|
||
switch (type) {
|
||
case Selector::SimpleSelector::Type::TagName:
|
||
case Selector::SimpleSelector::Type::Universal: {
|
||
auto qualified_name = this->qualified_name();
|
||
// 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null
|
||
// namespace (not in a namespace) append the serialization of the namespace prefix as an identifier,
|
||
// followed by a "|" (U+007C) to s.
|
||
if (qualified_name.namespace_type == QualifiedName::NamespaceType::Named) {
|
||
serialize_an_identifier(s, qualified_name.namespace_);
|
||
s.append('|');
|
||
}
|
||
|
||
// 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace)
|
||
// append "|" (U+007C) to s.
|
||
if (qualified_name.namespace_type == QualifiedName::NamespaceType::None)
|
||
s.append('|');
|
||
|
||
// 3. If this is a type selector append the serialization of the element name as an identifier to s.
|
||
if (type == Selector::SimpleSelector::Type::TagName)
|
||
serialize_an_identifier(s, qualified_name.name.name);
|
||
|
||
// 4. If this is a universal selector append "*" (U+002A) to s.
|
||
if (type == Selector::SimpleSelector::Type::Universal)
|
||
s.append('*');
|
||
|
||
break;
|
||
}
|
||
case Selector::SimpleSelector::Type::Attribute: {
|
||
auto& attribute = this->attribute();
|
||
|
||
// 1. Append "[" (U+005B) to s.
|
||
s.append('[');
|
||
|
||
// 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace)
|
||
// append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
|
||
if (attribute.qualified_name.namespace_type == QualifiedName::NamespaceType::Named) {
|
||
serialize_an_identifier(s, attribute.qualified_name.namespace_);
|
||
s.append('|');
|
||
}
|
||
|
||
// 3. Append the serialization of the attribute name as an identifier to s.
|
||
serialize_an_identifier(s, attribute.qualified_name.name.name);
|
||
|
||
// 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector),
|
||
// followed by the serialization of the attribute value as a string, to s.
|
||
if (!attribute.value.is_empty()) {
|
||
switch (attribute.match_type) {
|
||
case Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
|
||
s.append("="sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
|
||
s.append("~="sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::MatchType::ContainsString:
|
||
s.append("*="sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment:
|
||
s.append("|="sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
|
||
s.append("^="sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
|
||
s.append("$="sv);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
serialize_a_string(s, attribute.value);
|
||
}
|
||
|
||
// 5. If the attribute selector has the case-insensitivity flag present, append " i" (U+0020 U+0069) to s.
|
||
// If the attribute selector has the case-insensitivity flag present, append " s" (U+0020 U+0073) to s.
|
||
// (the line just above is an addition to CSS OM to match Selectors Level 4 last draft)
|
||
switch (attribute.case_type) {
|
||
case Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch:
|
||
s.append(" i"sv);
|
||
break;
|
||
case Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch:
|
||
s.append(" s"sv);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// 6. Append "]" (U+005D) to s.
|
||
s.append(']');
|
||
break;
|
||
}
|
||
|
||
case Selector::SimpleSelector::Type::Class:
|
||
// Append a "." (U+002E), followed by the serialization of the class name as an identifier to s.
|
||
s.append('.');
|
||
serialize_an_identifier(s, name());
|
||
break;
|
||
|
||
case Selector::SimpleSelector::Type::Id:
|
||
// Append a "#" (U+0023), followed by the serialization of the ID as an identifier to s.
|
||
s.append('#');
|
||
serialize_an_identifier(s, name());
|
||
break;
|
||
|
||
case Selector::SimpleSelector::Type::PseudoClass: {
|
||
auto& pseudo_class = this->pseudo_class();
|
||
|
||
auto metadata = pseudo_class_metadata(pseudo_class.type);
|
||
// HACK: `:host()` has both a function and a non-function form, so handle that first.
|
||
// It's also not in the spec.
|
||
if (pseudo_class.type == PseudoClass::Host) {
|
||
if (pseudo_class.argument_selector_list.is_empty()) {
|
||
s.append(':');
|
||
s.append(pseudo_class_name(pseudo_class.type));
|
||
} else {
|
||
s.append(':');
|
||
s.append(pseudo_class_name(pseudo_class.type));
|
||
s.append('(');
|
||
s.append(serialize_a_group_of_selectors(pseudo_class.argument_selector_list));
|
||
s.append(')');
|
||
}
|
||
}
|
||
// If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s.
|
||
else if (metadata.is_valid_as_identifier) {
|
||
s.append(':');
|
||
s.append(pseudo_class_name(pseudo_class.type));
|
||
}
|
||
// Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028),
|
||
// followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s.
|
||
else {
|
||
s.append(':');
|
||
s.append(pseudo_class_name(pseudo_class.type));
|
||
s.append('(');
|
||
if (pseudo_class.type == PseudoClass::NthChild
|
||
|| pseudo_class.type == PseudoClass::NthLastChild
|
||
|| pseudo_class.type == PseudoClass::NthOfType
|
||
|| pseudo_class.type == PseudoClass::NthLastOfType) {
|
||
// The result of serializing the value using the rules to serialize an <an+b> value.
|
||
s.append(pseudo_class.nth_child_pattern.serialize());
|
||
} else if (pseudo_class.type == PseudoClass::Not
|
||
|| pseudo_class.type == PseudoClass::Is
|
||
|| pseudo_class.type == PseudoClass::Where) {
|
||
// The result of serializing the value using the rules for serializing a group of selectors.
|
||
// NOTE: `:is()` and `:where()` aren't in the spec for this yet, but it should be!
|
||
s.append(serialize_a_group_of_selectors(pseudo_class.argument_selector_list));
|
||
} else if (pseudo_class.type == PseudoClass::Lang) {
|
||
// The serialization of a comma-separated list of each argument’s serialization as a string, preserving relative order.
|
||
s.join(", "sv, pseudo_class.languages);
|
||
}
|
||
s.append(')');
|
||
}
|
||
break;
|
||
}
|
||
case Selector::SimpleSelector::Type::PseudoElement:
|
||
// Note: Pseudo-elements are dealt with in Selector::serialize()
|
||
break;
|
||
case Type::Nesting:
|
||
// AD-HOC: Not in spec yet.
|
||
s.append('&');
|
||
break;
|
||
}
|
||
return MUST(s.to_string());
|
||
}
|
||
|
||
// https://www.w3.org/TR/cssom/#serialize-a-selector
|
||
String Selector::serialize() const
|
||
{
|
||
StringBuilder s;
|
||
|
||
// To serialize a selector let s be the empty string, run the steps below for each part of the chain of the selector, and finally return s:
|
||
for (size_t i = 0; i < compound_selectors().size(); ++i) {
|
||
auto const& compound_selector = compound_selectors()[i];
|
||
// 1. If there is only one simple selector in the compound selectors which is a universal selector, append the result of serializing the universal selector to s.
|
||
if (compound_selector.simple_selectors.size() == 1
|
||
&& compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::Universal) {
|
||
s.append(compound_selector.simple_selectors.first().serialize());
|
||
}
|
||
// 2. Otherwise, for each simple selector in the compound selectors that is not a universal selector
|
||
// of which the namespace prefix maps to a namespace that is not the default namespace
|
||
// serialize the simple selector and append the result to s.
|
||
else {
|
||
for (auto& simple_selector : compound_selector.simple_selectors) {
|
||
if (simple_selector.type == SimpleSelector::Type::Universal) {
|
||
auto qualified_name = simple_selector.qualified_name();
|
||
if (qualified_name.namespace_type == SimpleSelector::QualifiedName::NamespaceType::Default)
|
||
continue;
|
||
// FIXME: I *think* if we have a namespace prefix that happens to equal the same as the default namespace,
|
||
// we also should skip it. But we don't have access to that here. eg:
|
||
// <style>
|
||
// @namespace "http://example";
|
||
// @namespace foo "http://example";
|
||
// foo|*.bar { } /* This would skip the `foo|*` when serializing. */
|
||
// </style>
|
||
}
|
||
s.append(simple_selector.serialize());
|
||
}
|
||
}
|
||
|
||
// 3. If this is not the last part of the chain of the selector append a single SPACE (U+0020),
|
||
// followed by the combinator ">", "+", "~", ">>", "||", as appropriate, followed by another
|
||
// single SPACE (U+0020) if the combinator was not whitespace, to s.
|
||
if (i != compound_selectors().size() - 1) {
|
||
s.append(' ');
|
||
// Note: The combinator that appears between parts `i` and `i+1` appears with the `i+1` selector,
|
||
// so we have to check that one.
|
||
switch (compound_selectors()[i + 1].combinator) {
|
||
case Selector::Combinator::ImmediateChild:
|
||
s.append("> "sv);
|
||
break;
|
||
case Selector::Combinator::NextSibling:
|
||
s.append("+ "sv);
|
||
break;
|
||
case Selector::Combinator::SubsequentSibling:
|
||
s.append("~ "sv);
|
||
break;
|
||
case Selector::Combinator::Column:
|
||
s.append("|| "sv);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
} else {
|
||
// 4. If this is the last part of the chain of the selector and there is a pseudo-element,
|
||
// append "::" followed by the name of the pseudo-element, to s.
|
||
if (compound_selector.simple_selectors.last().type == Selector::SimpleSelector::Type::PseudoElement) {
|
||
s.append("::"sv);
|
||
s.append(compound_selector.simple_selectors.last().pseudo_element().name());
|
||
}
|
||
}
|
||
}
|
||
|
||
return MUST(s.to_string());
|
||
}
|
||
|
||
// https://www.w3.org/TR/cssom/#serialize-a-group-of-selectors
|
||
String serialize_a_group_of_selectors(SelectorList const& selectors)
|
||
{
|
||
// To serialize a group of selectors serialize each selector in the group of selectors and then serialize a comma-separated list of these serializations.
|
||
return MUST(String::join(", "sv, selectors));
|
||
}
|
||
|
||
StringView Selector::PseudoElement::name(Selector::PseudoElement::Type pseudo_element)
|
||
{
|
||
switch (pseudo_element) {
|
||
case Selector::PseudoElement::Type::Before:
|
||
return "before"sv;
|
||
case Selector::PseudoElement::Type::After:
|
||
return "after"sv;
|
||
case Selector::PseudoElement::Type::FirstLine:
|
||
return "first-line"sv;
|
||
case Selector::PseudoElement::Type::FirstLetter:
|
||
return "first-letter"sv;
|
||
case Selector::PseudoElement::Type::Marker:
|
||
return "marker"sv;
|
||
case Selector::PseudoElement::Type::MeterBar:
|
||
return "-webkit-meter-bar"sv;
|
||
case Selector::PseudoElement::Type::MeterEvenLessGoodValue:
|
||
return "-webkit-meter-even-less-good-value"sv;
|
||
case Selector::PseudoElement::Type::MeterOptimumValue:
|
||
return "-webkit-meter-optimum-value"sv;
|
||
case Selector::PseudoElement::Type::MeterSuboptimumValue:
|
||
return "-webkit-meter-suboptimum-value"sv;
|
||
case Selector::PseudoElement::Type::ProgressBar:
|
||
return "-webkit-progress-bar"sv;
|
||
case Selector::PseudoElement::Type::ProgressValue:
|
||
return "-webkit-progress-value"sv;
|
||
case Selector::PseudoElement::Type::Placeholder:
|
||
return "placeholder"sv;
|
||
case Selector::PseudoElement::Type::Selection:
|
||
return "selection"sv;
|
||
case Selector::PseudoElement::Type::SliderRunnableTrack:
|
||
return "-webkit-slider-runnable-track"sv;
|
||
case Selector::PseudoElement::Type::SliderThumb:
|
||
return "-webkit-slider-thumb"sv;
|
||
case Selector::PseudoElement::Type::Backdrop:
|
||
return "backdrop"sv;
|
||
case Selector::PseudoElement::Type::KnownPseudoElementCount:
|
||
break;
|
||
case Selector::PseudoElement::Type::UnknownWebKit:
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
|
||
Optional<Selector::PseudoElement> Selector::PseudoElement::from_string(FlyString const& name)
|
||
{
|
||
if (name.equals_ignoring_ascii_case("after"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::After };
|
||
} else if (name.equals_ignoring_ascii_case("before"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::Before };
|
||
} else if (name.equals_ignoring_ascii_case("first-letter"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::FirstLetter };
|
||
} else if (name.equals_ignoring_ascii_case("first-line"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::FirstLine };
|
||
} else if (name.equals_ignoring_ascii_case("marker"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::Marker };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-meter-bar"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::MeterBar };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-meter-even-less-good-value"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::MeterEvenLessGoodValue };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-meter-optimum-value"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::MeterOptimumValue };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-meter-suboptimum-value"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::MeterSuboptimumValue };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-progress-bar"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::ProgressBar };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-progress-value"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::ProgressValue };
|
||
} else if (name.equals_ignoring_ascii_case("placeholder"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::Placeholder };
|
||
} else if (name.equals_ignoring_ascii_case("selection"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::Selection };
|
||
} else if (name.equals_ignoring_ascii_case("backdrop"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::Backdrop };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-slider-runnable-track"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::SliderRunnableTrack };
|
||
} else if (name.equals_ignoring_ascii_case("-webkit-slider-thumb"sv)) {
|
||
return Selector::PseudoElement { Selector::PseudoElement::Type::SliderThumb };
|
||
}
|
||
return {};
|
||
}
|
||
|
||
NonnullRefPtr<Selector> Selector::relative_to(SimpleSelector const& parent) const
|
||
{
|
||
// To make us relative to the parent, prepend it to the list of compound selectors,
|
||
// and ensure the next compound selector starts with a combinator.
|
||
Vector<CompoundSelector> copied_compound_selectors;
|
||
copied_compound_selectors.ensure_capacity(compound_selectors().size() + 1);
|
||
copied_compound_selectors.empend(CompoundSelector { .simple_selectors = { parent } });
|
||
|
||
bool first = true;
|
||
for (auto compound_selector : compound_selectors()) {
|
||
if (first) {
|
||
if (compound_selector.combinator == Combinator::None)
|
||
compound_selector.combinator = Combinator::Descendant;
|
||
first = false;
|
||
}
|
||
|
||
copied_compound_selectors.append(move(compound_selector));
|
||
}
|
||
|
||
return Selector::create(move(copied_compound_selectors));
|
||
}
|
||
|
||
NonnullRefPtr<Selector> Selector::absolutized(Selector::SimpleSelector const& selector_for_nesting) const
|
||
{
|
||
if (!contains_the_nesting_selector())
|
||
return *this;
|
||
|
||
Vector<CompoundSelector> absolutized_compound_selectors;
|
||
absolutized_compound_selectors.ensure_capacity(m_compound_selectors.size());
|
||
for (auto const& compound_selector : m_compound_selectors)
|
||
absolutized_compound_selectors.append(compound_selector.absolutized(selector_for_nesting));
|
||
|
||
return Selector::create(move(absolutized_compound_selectors));
|
||
}
|
||
|
||
Selector::CompoundSelector Selector::CompoundSelector::absolutized(Selector::SimpleSelector const& selector_for_nesting) const
|
||
{
|
||
// TODO: Cache if it contains the nesting selector?
|
||
|
||
Vector<SimpleSelector> absolutized_simple_selectors;
|
||
absolutized_simple_selectors.ensure_capacity(simple_selectors.size());
|
||
for (auto const& simple_selector : simple_selectors)
|
||
absolutized_simple_selectors.append(simple_selector.absolutized(selector_for_nesting));
|
||
|
||
return CompoundSelector {
|
||
.combinator = this->combinator,
|
||
.simple_selectors = absolutized_simple_selectors,
|
||
};
|
||
}
|
||
|
||
Selector::SimpleSelector Selector::SimpleSelector::absolutized(Selector::SimpleSelector const& selector_for_nesting) const
|
||
{
|
||
switch (type) {
|
||
case Type::Nesting:
|
||
// Nesting selectors get replaced directly.
|
||
return selector_for_nesting;
|
||
|
||
case Type::PseudoClass: {
|
||
// Pseudo-classes may contain other selectors, so we need to absolutize them.
|
||
// Copy the PseudoClassSelector, and then replace its argument selector list.
|
||
auto pseudo_class = this->pseudo_class();
|
||
if (!pseudo_class.argument_selector_list.is_empty()) {
|
||
SelectorList new_selector_list;
|
||
new_selector_list.ensure_capacity(pseudo_class.argument_selector_list.size());
|
||
for (auto const& argument_selector : pseudo_class.argument_selector_list)
|
||
new_selector_list.append(argument_selector->absolutized(selector_for_nesting));
|
||
pseudo_class.argument_selector_list = move(new_selector_list);
|
||
}
|
||
return SimpleSelector {
|
||
.type = Type::PseudoClass,
|
||
.value = move(pseudo_class),
|
||
};
|
||
}
|
||
|
||
case Type::Universal:
|
||
case Type::TagName:
|
||
case Type::Id:
|
||
case Type::Class:
|
||
case Type::Attribute:
|
||
case Type::PseudoElement:
|
||
// Everything else isn't affected
|
||
return *this;
|
||
}
|
||
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
|
||
}
|