diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index 80ae1ae204a..66fa7649b7b 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -53,6 +53,67 @@ static inline GC::Ptr traverse_up(GC::Ptr node return node->parent(); } +// https://www.rfc-editor.org/rfc/rfc4647.html#section-3.3.2 +// NB: Language tags only use ASCII characters, so we can get away with using StringView. +static bool language_range_matches_tag(StringView language_range, StringView language_tag) +{ + // 1. Split both the extended language range and the language tag being compared into a list of subtags by + // dividing on the hyphen (%x2D) character. + auto range_subtags = language_range.split_view('-', SplitBehavior::KeepEmpty); + auto tag_subtags = language_tag.split_view('-', SplitBehavior::KeepEmpty); + + // Two subtags match if either they are the same when compared case-insensitively or the language range's subtag + // is the wildcard '*'. + auto subtags_match = [](StringView language_range_subtag, StringView language_subtag) { + return language_range_subtag == "*"sv + || Infra::is_ascii_case_insensitive_match(language_range_subtag, language_subtag); + }; + + // 2. Begin with the first subtag in each list. If the first subtag in the range does not match the first + // subtag in the tag, the overall match fails. Otherwise, move to the next subtag in both the range and the + // tag. + auto tag_subtag = tag_subtags.begin(); + auto range_subtag = range_subtags.begin(); + if (!subtags_match(*range_subtag, *tag_subtag)) + return false; + ++tag_subtag; + ++range_subtag; + + // 3. While there are more subtags left in the language range's list: + while (!range_subtag.is_end()) { + // A. If the subtag currently being examined in the range is the wildcard ('*'), move to the next subtag in + // the range and continue with the loop. + if (*range_subtag == "*"sv) { + ++range_subtag; + continue; + } + + // B. Else, if there are no more subtags in the language tag's list, the match fails. + if (tag_subtag.is_end()) + return false; + + // C. Else, if the current subtag in the range's list matches the current subtag in the language tag's + // list, move to the next subtag in both lists and continue with the loop. + if (subtags_match(*range_subtag, *tag_subtag)) { + ++range_subtag; + ++tag_subtag; + continue; + } + + // D. Else, if the language tag's subtag is a "singleton" (a single letter or digit, which includes the + // private-use subtag 'x') the match fails. + if (tag_subtag->length() == 1 && is_ascii_alphanumeric((*tag_subtag)[0])) { + return false; + } + + // E. Else, move to the next subtag in the language tag's list and continue with the loop. + ++tag_subtag; + } + + // 4. When the language range's list has no more subtags, the match succeeds. + return true; +} + // https://drafts.csswg.org/selectors-4/#the-lang-pseudo static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector const& languages) { @@ -62,21 +123,17 @@ static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector auto element_language = maybe_element_language.release_value(); - // FIXME: This is ad-hoc. Implement a proper language range matching algorithm as recommended by BCP47. - for (auto const& language : languages) { - if (language.is_empty()) - continue; - if (language == "*"sv) + // The element’s content language matches a language range if its content language, as represented in BCP 47 + // syntax, matches the given language range in an extended filtering operation per [RFC4647] Matching of Language + // Tags (section 3.3.2). Both the content language and the language range must be canonicalized and converted to + // extlang form as per section 4.5 of [RFC5646] prior to the extended filtering operation. The matching is + // performed case-insensitively within the ASCII range. + + // FIXME: Canonicalize both as extlang. + + for (auto const& language_range : languages) { + if (language_range_matches_tag(language_range, element_language)) return true; - auto element_language_length = element_language.bytes_as_string_view().length(); - auto language_length = language.bytes_as_string_view().length(); - if (element_language_length == language_length) { - if (Infra::is_ascii_case_insensitive_match(element_language, language)) - return true; - } else if (element_language_length > language_length) { - if (element_language.starts_with_bytes(language, CaseSensitivity::CaseInsensitive) && element_language.bytes_as_string_view()[language_length] == '-') - return true; - } } return false; } diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/selectors/selectors-4/lang-000-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/selectors/selectors-4/lang-000-ref.html new file mode 100644 index 00000000000..466e9f464b9 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/selectors/selectors-4/lang-000-ref.html @@ -0,0 +1,11 @@ + + + +CSS Selectors 4 - :lang matching reference + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-000.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-000.html new file mode 100644 index 00000000000..994ef4ae23e --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-000.html @@ -0,0 +1,13 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-001.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-001.html new file mode 100644 index 00000000000..3f8686e5907 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-001.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-002.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-002.html new file mode 100644 index 00000000000..769f924ce61 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-002.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-003.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-003.html new file mode 100644 index 00000000000..55fc15fa2bc --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-003.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-004.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-004.html new file mode 100644 index 00000000000..e0e916e799b --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-004.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-005.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-005.html new file mode 100644 index 00000000000..a1d0b037292 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-005.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-006.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-006.html new file mode 100644 index 00000000000..92d0199a505 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-006.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-007.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-007.html new file mode 100644 index 00000000000..9e4e1b8dfd1 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-007.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-008.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-008.html new file mode 100644 index 00000000000..01e1379dfda --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-008.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-009.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-009.html new file mode 100644 index 00000000000..53d1d5d4588 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-009.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-010.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-010.html new file mode 100644 index 00000000000..c0775a2204f --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-010.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-011.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-011.html new file mode 100644 index 00000000000..7288043f39b --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-011.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-012.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-012.html new file mode 100644 index 00000000000..3c2d08c9021 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-012.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-013.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-013.html new file mode 100644 index 00000000000..72174ff3cdb --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-013.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-014.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-014.html new file mode 100644 index 00000000000..490785fbf65 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-014.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-015.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-015.html new file mode 100644 index 00000000000..01bc0b6df2a --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-015.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-016.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-016.html new file mode 100644 index 00000000000..ec90c73126a --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-016.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-017.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-017.html new file mode 100644 index 00000000000..f0c19666ef6 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-017.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-018.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-018.html new file mode 100644 index 00000000000..d050461b3ab --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-018.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-019.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-019.html new file mode 100644 index 00000000000..b0d03aaf188 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-019.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-020.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-020.html new file mode 100644 index 00000000000..dd90ab1fc55 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-020.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-021.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-021.html new file mode 100644 index 00000000000..bde7152233a --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-021.html @@ -0,0 +1,15 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-022.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-022.html new file mode 100644 index 00000000000..e0490a827f6 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-022.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-023.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-023.html new file mode 100644 index 00000000000..f2b9e1ecab6 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-023.html @@ -0,0 +1,15 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-024.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-024.html new file mode 100644 index 00000000000..48ed98c8948 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-024.html @@ -0,0 +1,14 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + +
This should be green
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-025.html b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-025.html new file mode 100644 index 00000000000..8a1f6e15669 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/selectors/selectors-4/lang-025.html @@ -0,0 +1,16 @@ + + + +CSS Selectors 4 - :lang matching + + + + + + + +
This should be green