Serenity Operating System
at master 368 lines 18 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "Selector.h" 9#include <LibWeb/CSS/Serialize.h> 10 11namespace Web::CSS { 12 13Selector::Selector(Vector<CompoundSelector>&& compound_selectors) 14 : m_compound_selectors(move(compound_selectors)) 15{ 16 // FIXME: This assumes that only one pseudo-element is allowed in a selector, and that it appears at the end. 17 // This is not true in Selectors-4! 18 if (!m_compound_selectors.is_empty()) { 19 for (auto const& simple_selector : m_compound_selectors.last().simple_selectors) { 20 if (simple_selector.type == SimpleSelector::Type::PseudoElement) { 21 m_pseudo_element = simple_selector.pseudo_element(); 22 break; 23 } 24 } 25 } 26} 27 28// https://www.w3.org/TR/selectors-4/#specificity-rules 29u32 Selector::specificity() const 30{ 31 if (m_specificity.has_value()) 32 return *m_specificity; 33 34 constexpr u32 ids_shift = 16; 35 constexpr u32 classes_shift = 8; 36 constexpr u32 tag_names_shift = 0; 37 constexpr u32 ids_mask = 0xff << ids_shift; 38 constexpr u32 classes_mask = 0xff << classes_shift; 39 constexpr u32 tag_names_mask = 0xff << tag_names_shift; 40 41 u32 ids = 0; 42 u32 classes = 0; 43 u32 tag_names = 0; 44 45 auto count_specificity_of_most_complex_selector = [&](auto& selector_list) { 46 u32 max_selector_list_argument_specificity = 0; 47 for (auto const& complex_selector : selector_list) { 48 max_selector_list_argument_specificity = max(max_selector_list_argument_specificity, complex_selector->specificity()); 49 } 50 51 u32 child_ids = (max_selector_list_argument_specificity & ids_mask) >> ids_shift; 52 u32 child_classes = (max_selector_list_argument_specificity & classes_mask) >> classes_shift; 53 u32 child_tag_names = (max_selector_list_argument_specificity & tag_names_mask) >> tag_names_shift; 54 55 ids += child_ids; 56 classes += child_classes; 57 tag_names += child_tag_names; 58 }; 59 60 for (auto& list : m_compound_selectors) { 61 for (auto& simple_selector : list.simple_selectors) { 62 switch (simple_selector.type) { 63 case SimpleSelector::Type::Id: 64 // count the number of ID selectors in the selector (= A) 65 ++ids; 66 break; 67 case SimpleSelector::Type::Class: 68 case SimpleSelector::Type::Attribute: 69 // count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B) 70 ++classes; 71 break; 72 case SimpleSelector::Type::PseudoClass: { 73 auto& pseudo_class = simple_selector.pseudo_class(); 74 switch (pseudo_class.type) { 75 case SimpleSelector::PseudoClass::Type::Is: 76 case SimpleSelector::PseudoClass::Type::Not: { 77 // The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the 78 // specificity of the most specific complex selector in its selector list argument. 79 count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list); 80 break; 81 } 82 case SimpleSelector::PseudoClass::Type::NthChild: 83 case SimpleSelector::PseudoClass::Type::NthLastChild: { 84 // Analogously, the specificity of an :nth-child() or :nth-last-child() selector 85 // is the specificity of the pseudo class itself (counting as one pseudo-class selector) 86 // plus the specificity of the most specific complex selector in its selector list argument (if any). 87 ++classes; 88 count_specificity_of_most_complex_selector(pseudo_class.argument_selector_list); 89 break; 90 } 91 case SimpleSelector::PseudoClass::Type::Where: 92 // The specificity of a :where() pseudo-class is replaced by zero. 93 break; 94 default: 95 ++classes; 96 break; 97 } 98 break; 99 } 100 case SimpleSelector::Type::TagName: 101 case SimpleSelector::Type::PseudoElement: 102 // count the number of type selectors and pseudo-elements in the selector (= C) 103 ++tag_names; 104 break; 105 case SimpleSelector::Type::Universal: 106 // ignore the universal selector 107 break; 108 } 109 } 110 } 111 112 // Due to storage limitations, implementations may have limitations on the size of A, B, or C. 113 // If so, values higher than the limit must be clamped to that limit, and not overflow. 114 m_specificity = (min(ids, 0xff) << ids_shift) 115 + (min(classes, 0xff) << classes_shift) 116 + (min(tag_names, 0xff) << tag_names_shift); 117 118 return *m_specificity; 119} 120 121// https://www.w3.org/TR/cssom/#serialize-a-simple-selector 122ErrorOr<String> Selector::SimpleSelector::serialize() const 123{ 124 StringBuilder s; 125 switch (type) { 126 case Selector::SimpleSelector::Type::TagName: 127 case Selector::SimpleSelector::Type::Universal: 128 // FIXME: 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. 129 // FIXME: 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace) append "|" (U+007C) to s. 130 // 3. If this is a type selector append the serialization of the element name as an identifier to s. 131 if (type == Selector::SimpleSelector::Type::TagName) { 132 TRY(serialize_an_identifier(s, name())); 133 } 134 // 4. If this is a universal selector append "*" (U+002A) to s. 135 if (type == Selector::SimpleSelector::Type::Universal) 136 TRY(s.try_append('*')); 137 break; 138 case Selector::SimpleSelector::Type::Attribute: { 139 auto& attribute = this->attribute(); 140 141 // 1. Append "[" (U+005B) to s. 142 TRY(s.try_append('[')); 143 144 // FIXME: 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. 145 146 // 3. Append the serialization of the attribute name as an identifier to s. 147 TRY(serialize_an_identifier(s, attribute.name)); 148 149 // 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector), 150 // followed by the serialization of the attribute value as a string, to s. 151 if (!attribute.value.is_empty()) { 152 switch (attribute.match_type) { 153 case Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: 154 TRY(s.try_append("="sv)); 155 break; 156 case Selector::SimpleSelector::Attribute::MatchType::ContainsWord: 157 TRY(s.try_append("~="sv)); 158 break; 159 case Selector::SimpleSelector::Attribute::MatchType::ContainsString: 160 TRY(s.try_append("*="sv)); 161 break; 162 case Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: 163 TRY(s.try_append("|="sv)); 164 break; 165 case Selector::SimpleSelector::Attribute::MatchType::StartsWithString: 166 TRY(s.try_append("^="sv)); 167 break; 168 case Selector::SimpleSelector::Attribute::MatchType::EndsWithString: 169 TRY(s.try_append("$="sv)); 170 break; 171 default: 172 break; 173 } 174 175 TRY(serialize_a_string(s, attribute.value)); 176 } 177 178 // 5. If the attribute selector has the case-insensitivity flag present, append " i" (U+0020 U+0069) to s. 179 // If the attribute selector has the case-insensitivity flag present, append " s" (U+0020 U+0073) to s. 180 // (the line just above is an addition to CSS OM to match Selectors Level 4 last draft) 181 switch (attribute.case_type) { 182 case Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch: 183 TRY(s.try_append(" i"sv)); 184 break; 185 case Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch: 186 TRY(s.try_append(" s"sv)); 187 break; 188 default: 189 break; 190 } 191 192 // 6. Append "]" (U+005D) to s. 193 TRY(s.try_append(']')); 194 break; 195 } 196 197 case Selector::SimpleSelector::Type::Class: 198 // Append a "." (U+002E), followed by the serialization of the class name as an identifier to s. 199 TRY(s.try_append('.')); 200 TRY(serialize_an_identifier(s, name())); 201 break; 202 203 case Selector::SimpleSelector::Type::Id: 204 // Append a "#" (U+0023), followed by the serialization of the ID as an identifier to s. 205 TRY(s.try_append('#')); 206 TRY(serialize_an_identifier(s, name())); 207 break; 208 209 case Selector::SimpleSelector::Type::PseudoClass: { 210 auto& pseudo_class = this->pseudo_class(); 211 212 switch (pseudo_class.type) { 213 case Selector::SimpleSelector::PseudoClass::Type::Link: 214 case Selector::SimpleSelector::PseudoClass::Type::Visited: 215 case Selector::SimpleSelector::PseudoClass::Type::Hover: 216 case Selector::SimpleSelector::PseudoClass::Type::Focus: 217 case Selector::SimpleSelector::PseudoClass::Type::FocusWithin: 218 case Selector::SimpleSelector::PseudoClass::Type::FirstChild: 219 case Selector::SimpleSelector::PseudoClass::Type::LastChild: 220 case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: 221 case Selector::SimpleSelector::PseudoClass::Type::Empty: 222 case Selector::SimpleSelector::PseudoClass::Type::Root: 223 case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: 224 case Selector::SimpleSelector::PseudoClass::Type::LastOfType: 225 case Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: 226 case Selector::SimpleSelector::PseudoClass::Type::Disabled: 227 case Selector::SimpleSelector::PseudoClass::Type::Enabled: 228 case Selector::SimpleSelector::PseudoClass::Type::Checked: 229 case Selector::SimpleSelector::PseudoClass::Type::Active: 230 // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. 231 TRY(s.try_append(':')); 232 TRY(s.try_append(pseudo_class_name(pseudo_class.type))); 233 break; 234 case Selector::SimpleSelector::PseudoClass::Type::NthChild: 235 case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: 236 case Selector::SimpleSelector::PseudoClass::Type::NthOfType: 237 case Selector::SimpleSelector::PseudoClass::Type::NthLastOfType: 238 case Selector::SimpleSelector::PseudoClass::Type::Not: 239 case Selector::SimpleSelector::PseudoClass::Type::Is: 240 case Selector::SimpleSelector::PseudoClass::Type::Where: 241 case Selector::SimpleSelector::PseudoClass::Type::Lang: 242 // Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028), 243 // followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s. 244 TRY(s.try_append(':')); 245 TRY(s.try_append(pseudo_class_name(pseudo_class.type))); 246 TRY(s.try_append('(')); 247 if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthChild 248 || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild 249 || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthOfType 250 || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastOfType) { 251 // The result of serializing the value using the rules to serialize an <an+b> value. 252 TRY(s.try_append(TRY(pseudo_class.nth_child_pattern.serialize()))); 253 } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not 254 || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Is 255 || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Where) { 256 // The result of serializing the value using the rules for serializing a group of selectors. 257 // NOTE: `:is()` and `:where()` aren't in the spec for this yet, but it should be! 258 TRY(s.try_append(TRY(serialize_a_group_of_selectors(pseudo_class.argument_selector_list)))); 259 } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Lang) { 260 // The serialization of a comma-separated list of each argument’s serialization as a string, preserving relative order. 261 s.join(", "sv, pseudo_class.languages); 262 } 263 TRY(s.try_append(')')); 264 break; 265 default: 266 dbgln("FIXME: Unknown pseudo class type for serialization: {}", to_underlying(pseudo_class.type)); 267 VERIFY_NOT_REACHED(); 268 } 269 break; 270 } 271 case Selector::SimpleSelector::Type::PseudoElement: 272 // Note: Pseudo-elements are dealt with in Selector::serialize() 273 break; 274 default: 275 dbgln("FIXME: Unsupported simple selector serialization for type {}", to_underlying(type)); 276 break; 277 } 278 return s.to_string(); 279} 280 281// https://www.w3.org/TR/cssom/#serialize-a-selector 282ErrorOr<String> Selector::serialize() const 283{ 284 StringBuilder s; 285 286 // 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: 287 for (size_t i = 0; i < compound_selectors().size(); ++i) { 288 auto const& compound_selector = compound_selectors()[i]; 289 // 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. 290 if (compound_selector.simple_selectors.size() == 1 291 && compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::Universal) { 292 TRY(s.try_append(TRY(compound_selector.simple_selectors.first().serialize()))); 293 } 294 // 2. Otherwise, for each simple selector in the compound selectors... 295 // FIXME: ...that is not a universal selector of which the namespace prefix maps to a namespace that is not the default namespace... 296 // ...serialize the simple selector and append the result to s. 297 else { 298 for (auto& simple_selector : compound_selector.simple_selectors) { 299 TRY(s.try_append(TRY(simple_selector.serialize()))); 300 } 301 } 302 303 // 3. If this is not the last part of the chain of the selector append a single SPACE (U+0020), 304 // followed by the combinator ">", "+", "~", ">>", "||", as appropriate, followed by another 305 // single SPACE (U+0020) if the combinator was not whitespace, to s. 306 if (i != compound_selectors().size() - 1) { 307 TRY(s.try_append(' ')); 308 // Note: The combinator that appears between parts `i` and `i+1` appears with the `i+1` selector, 309 // so we have to check that one. 310 switch (compound_selectors()[i + 1].combinator) { 311 case Selector::Combinator::ImmediateChild: 312 TRY(s.try_append("> "sv)); 313 break; 314 case Selector::Combinator::NextSibling: 315 TRY(s.try_append("+ "sv)); 316 break; 317 case Selector::Combinator::SubsequentSibling: 318 TRY(s.try_append("~ "sv)); 319 break; 320 case Selector::Combinator::Column: 321 TRY(s.try_append("|| "sv)); 322 break; 323 default: 324 break; 325 } 326 } else { 327 // 4. If this is the last part of the chain of the selector and there is a pseudo-element, 328 // append "::" followed by the name of the pseudo-element, to s. 329 if (compound_selector.simple_selectors.last().type == Selector::SimpleSelector::Type::PseudoElement) { 330 TRY(s.try_append("::"sv)); 331 TRY(s.try_append(pseudo_element_name(compound_selector.simple_selectors.last().pseudo_element()))); 332 } 333 } 334 } 335 336 return s.to_string(); 337} 338 339// https://www.w3.org/TR/cssom/#serialize-a-group-of-selectors 340ErrorOr<String> serialize_a_group_of_selectors(Vector<NonnullRefPtr<Selector>> const& selectors) 341{ 342 // To serialize a group of selectors serialize each selector in the group of selectors and then serialize a comma-separated list of these serializations. 343 return String::join(", "sv, selectors); 344} 345 346Optional<Selector::PseudoElement> pseudo_element_from_string(StringView name) 347{ 348 if (name.equals_ignoring_ascii_case("after"sv)) { 349 return Selector::PseudoElement::After; 350 } else if (name.equals_ignoring_ascii_case("before"sv)) { 351 return Selector::PseudoElement::Before; 352 } else if (name.equals_ignoring_ascii_case("first-letter"sv)) { 353 return Selector::PseudoElement::FirstLetter; 354 } else if (name.equals_ignoring_ascii_case("first-line"sv)) { 355 return Selector::PseudoElement::FirstLine; 356 } else if (name.equals_ignoring_ascii_case("marker"sv)) { 357 return Selector::PseudoElement::Marker; 358 } else if (name.equals_ignoring_ascii_case("-webkit-progress-bar"sv)) { 359 return Selector::PseudoElement::ProgressBar; 360 } else if (name.equals_ignoring_ascii_case("-webkit-progress-value"sv)) { 361 return Selector::PseudoElement::ProgressValue; 362 } else if (name.equals_ignoring_ascii_case("placeholder"sv)) { 363 return Selector::PseudoElement::Placeholder; 364 } 365 return {}; 366} 367 368}